penumbra_sdk_asset/balance/
commitment.rs

1use 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}