penumbra_sdk_asset/balance/
commitment.rs1use std::ops::Deref;
2
3use ark_r1cs_std::prelude::*;
4use ark_r1cs_std::uint8::UInt8;
5use ark_relations::r1cs::SynthesisError;
6use decaf377::r1cs::ElementVar;
7use decaf377::Fq;
8use decaf377::Fr;
9use once_cell::sync::Lazy;
10use penumbra_sdk_proto::penumbra::core::asset::v1 as pb;
11use penumbra_sdk_proto::DomainType;
12
13use crate::value::ValueVar;
14use crate::Value;
15
16impl Value {
17 #[allow(non_snake_case)]
18 pub fn commit(&self, blinding: Fr) -> Commitment {
19 let G_v = self.asset_id.value_generator();
20 let H = VALUE_BLINDING_GENERATOR.deref();
21
22 let v = Fr::from(self.amount);
23 let C = v * G_v + blinding * H;
24
25 Commitment(C)
26 }
27}
28
29impl ValueVar {
30 pub fn commit(
31 &self,
32 value_blinding: Vec<UInt8<Fq>>,
33 ) -> Result<BalanceCommitmentVar, SynthesisError> {
34 let cs = self.amount().cs();
35 let value_blinding_generator = ElementVar::new_constant(cs, *VALUE_BLINDING_GENERATOR)?;
36
37 let asset_generator = self.asset_id.value_generator()?;
38 let value_amount = self.amount();
39 let commitment = asset_generator.scalar_mul_le(value_amount.to_bits_le()?.iter())?
40 + value_blinding_generator.scalar_mul_le(value_blinding.to_bits_le()?.iter())?;
41
42 Ok(BalanceCommitmentVar { inner: commitment })
43 }
44}
45
46#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
47pub struct Commitment(pub decaf377::Element);
48
49impl Commitment {
50 pub fn to_bytes(&self) -> [u8; 32] {
51 (*self).into()
52 }
53}
54
55pub static VALUE_BLINDING_GENERATOR: Lazy<decaf377::Element> = Lazy::new(|| {
56 let s = Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"decaf377-rdsa-binding").as_bytes());
57 decaf377::Element::encode_to_curve(&s)
58});
59
60pub struct BalanceCommitmentVar {
61 pub inner: ElementVar,
62}
63
64impl AllocVar<Commitment, Fq> for BalanceCommitmentVar {
65 fn new_variable<T: std::borrow::Borrow<Commitment>>(
66 cs: impl Into<ark_relations::r1cs::Namespace<Fq>>,
67 f: impl FnOnce() -> Result<T, SynthesisError>,
68 mode: ark_r1cs_std::prelude::AllocationMode,
69 ) -> Result<Self, SynthesisError> {
70 let ns = cs.into();
71 let cs = ns.cs();
72 let inner: Commitment = *f()?.borrow();
73 match mode {
74 AllocationMode::Constant => unimplemented!(),
75 AllocationMode::Input => {
76 let element_var: ElementVar = AllocVar::new_input(cs, || Ok(inner.0))?;
77 Ok(Self { inner: element_var })
78 }
79 AllocationMode::Witness => unimplemented!(),
80 }
81 }
82}
83
84impl R1CSVar<Fq> for BalanceCommitmentVar {
85 type Value = Commitment;
86
87 fn cs(&self) -> ark_relations::r1cs::ConstraintSystemRef<Fq> {
88 self.inner.cs()
89 }
90
91 fn value(&self) -> Result<Self::Value, SynthesisError> {
92 let inner = self.inner.value()?;
93 Ok(Commitment(inner))
94 }
95}
96
97impl std::ops::Add<BalanceCommitmentVar> for BalanceCommitmentVar {
98 type Output = Self;
99 fn add(self, rhs: Self) -> Self::Output {
100 Self {
101 inner: self.inner + rhs.inner,
102 }
103 }
104}
105
106impl std::ops::Sub<BalanceCommitmentVar> for BalanceCommitmentVar {
107 type Output = Self;
108 fn sub(self, rhs: Self) -> Self::Output {
109 Self {
110 inner: self.inner - rhs.inner,
111 }
112 }
113}
114
115impl EqGadget<Fq> for BalanceCommitmentVar {
116 fn is_eq(&self, other: &Self) -> Result<Boolean<Fq>, SynthesisError> {
117 self.inner.is_eq(&other.inner)
118 }
119}
120
121#[derive(thiserror::Error, Debug)]
122pub enum Error {
123 #[error("Invalid valid commitment")]
124 InvalidBalanceCommitment,
125}
126
127impl std::ops::Add<Commitment> for Commitment {
128 type Output = Commitment;
129 fn add(self, rhs: Commitment) -> Self::Output {
130 Commitment(self.0 + rhs.0)
131 }
132}
133
134impl std::ops::Sub<Commitment> for Commitment {
135 type Output = Commitment;
136 fn sub(self, rhs: Commitment) -> Self::Output {
137 Commitment(self.0 - rhs.0)
138 }
139}
140
141impl std::ops::Neg for Commitment {
142 type Output = Commitment;
143 fn neg(self) -> Self::Output {
144 Commitment(-self.0)
145 }
146}
147
148impl From<Commitment> for [u8; 32] {
149 fn from(commitment: Commitment) -> [u8; 32] {
150 commitment.0.vartime_compress().0
151 }
152}
153
154impl TryFrom<[u8; 32]> for Commitment {
155 type Error = Error;
156
157 fn try_from(bytes: [u8; 32]) -> Result<Commitment, Self::Error> {
158 let inner = decaf377::Encoding(bytes)
159 .vartime_decompress()
160 .map_err(|_| Error::InvalidBalanceCommitment)?;
161
162 Ok(Commitment(inner))
163 }
164}
165
166impl TryFrom<&[u8]> for Commitment {
167 type Error = Error;
168
169 fn try_from(slice: &[u8]) -> Result<Commitment, Self::Error> {
170 let bytes = slice[..]
171 .try_into()
172 .map_err(|_| Error::InvalidBalanceCommitment)?;
173
174 let inner = decaf377::Encoding(bytes)
175 .vartime_decompress()
176 .map_err(|_| Error::InvalidBalanceCommitment)?;
177
178 Ok(Commitment(inner))
179 }
180}
181
182impl DomainType for Commitment {
183 type Proto = pb::BalanceCommitment;
184}
185
186impl From<Commitment> for pb::BalanceCommitment {
187 fn from(cv: Commitment) -> Self {
188 Self {
189 inner: cv.to_bytes().to_vec(),
190 }
191 }
192}
193
194impl TryFrom<pb::BalanceCommitment> for Commitment {
195 type Error = anyhow::Error;
196 fn try_from(value: pb::BalanceCommitment) -> Result<Self, Self::Error> {
197 value.inner.as_slice().try_into().map_err(Into::into)
198 }
199}