penumbra_sdk_tct/
commitment.rs1use decaf377::Fq;
2use penumbra_sdk_proto::{penumbra::crypto::tct::v1 as pb, DomainType};
3
4#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6#[serde(into = "pb::StateCommitment", try_from = "pb::StateCommitment")]
7pub struct StateCommitment(pub Fq);
8
9#[derive(Clone, Debug, thiserror::Error)]
11pub enum ParseCommitmentError {
12 #[error(transparent)]
14 InvalidHex(#[from] hex::FromHexError),
15 #[error(transparent)]
17 InvalidCommitment(#[from] InvalidStateCommitment),
18}
19
20impl StateCommitment {
21 pub fn parse_hex(str: &str) -> Result<StateCommitment, ParseCommitmentError> {
23 let bytes = hex::decode(str)?;
24 Ok(StateCommitment::try_from(&bytes[..])?)
25 }
26}
27
28impl DomainType for StateCommitment {
29 type Proto = pb::StateCommitment;
30}
31
32#[cfg(test)]
33mod test_serde {
34 use super::StateCommitment;
35
36 #[test]
37 fn roundtrip_json_zero() {
38 let commitment = StateCommitment::try_from([0; 32]).unwrap();
39 let bytes = serde_json::to_vec(&commitment).unwrap();
40 println!("{bytes:?}");
41 let deserialized: StateCommitment = serde_json::from_slice(&bytes).unwrap();
42 assert_eq!(commitment, deserialized);
43 }
44
45 }
58
59impl From<StateCommitment> for pb::StateCommitment {
60 fn from(nc: StateCommitment) -> Self {
61 Self {
62 inner: nc.0.to_bytes().to_vec(),
63 }
64 }
65}
66
67#[derive(thiserror::Error, Debug, Clone, Copy)]
69#[error("Invalid note commitment")]
70pub struct InvalidStateCommitment;
71
72impl TryFrom<pb::StateCommitment> for StateCommitment {
73 type Error = InvalidStateCommitment;
74
75 fn try_from(value: pb::StateCommitment) -> Result<Self, Self::Error> {
76 let bytes: [u8; 32] = value.inner[..]
77 .try_into()
78 .map_err(|_| InvalidStateCommitment)?;
79
80 let inner = Fq::from_bytes_checked(&bytes).map_err(|_| InvalidStateCommitment)?;
81
82 Ok(StateCommitment(inner))
83 }
84}
85
86impl std::fmt::Display for StateCommitment {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.write_str(&hex::encode(&self.0.to_bytes()[..]))
89 }
90}
91
92impl std::fmt::Debug for StateCommitment {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 f.write_fmt(format_args!(
95 "note::Commitment({})",
96 hex::encode(&self.0.to_bytes()[..])
97 ))
98 }
99}
100
101impl From<StateCommitment> for [u8; 32] {
102 fn from(commitment: StateCommitment) -> [u8; 32] {
103 commitment.0.to_bytes()
104 }
105}
106
107impl TryFrom<[u8; 32]> for StateCommitment {
108 type Error = InvalidStateCommitment;
109
110 fn try_from(bytes: [u8; 32]) -> Result<StateCommitment, Self::Error> {
111 let inner = Fq::from_bytes_checked(&bytes).map_err(|_| InvalidStateCommitment)?;
112
113 Ok(StateCommitment(inner))
114 }
115}
116
117impl TryFrom<&[u8]> for StateCommitment {
119 type Error = InvalidStateCommitment;
120
121 fn try_from(slice: &[u8]) -> Result<StateCommitment, Self::Error> {
122 let bytes: [u8; 32] = slice[..].try_into().map_err(|_| InvalidStateCommitment)?;
123
124 let inner = Fq::from_bytes_checked(&bytes).map_err(|_| InvalidStateCommitment)?;
125
126 Ok(StateCommitment(inner))
127 }
128}
129
130#[cfg(feature = "arbitrary")]
131pub use arbitrary::FqStrategy;
132
133#[cfg(feature = "arbitrary")]
134mod arbitrary {
135 use decaf377::Fq;
136 use proptest::strategy::Strategy;
137
138 use super::StateCommitment;
139
140 impl proptest::arbitrary::Arbitrary for StateCommitment {
142 type Parameters = Vec<StateCommitment>;
143
144 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
145 FqStrategy(args.into_iter().map(|commitment| commitment.0).collect())
146 .prop_map(StateCommitment)
147 }
148
149 type Strategy = proptest::strategy::Map<FqStrategy, fn(Fq) -> StateCommitment>;
150 }
151
152 #[derive(Clone, Debug, PartialEq, Eq, Default)]
154 pub struct FqStrategy(Vec<Fq>);
155
156 impl FqStrategy {
157 pub fn arbitrary() -> Self {
159 Self::one_of(vec![])
160 }
161
162 pub fn one_of(commitments: Vec<Fq>) -> Self {
166 FqStrategy(commitments)
167 }
168 }
169
170 impl proptest::strategy::Strategy for FqStrategy {
171 type Tree = proptest::strategy::Filter<proptest::strategy::Just<Fq>, fn(&Fq) -> bool>;
172
173 type Value = Fq;
174
175 fn new_tree(
176 &self,
177 runner: &mut proptest::test_runner::TestRunner,
178 ) -> proptest::strategy::NewTree<Self> {
179 use proptest::prelude::{Rng, RngCore};
180 let rng = runner.rng();
181 Ok(if !self.0.is_empty() {
182 proptest::strategy::Just(
183 *rng.sample(rand::distributions::Slice::new(&self.0).expect("empty vector")),
184 )
185 } else {
186 let mut bytes = [0u8; 32];
187 rng.fill_bytes(&mut bytes);
188 proptest::strategy::Just(decaf377::Fq::from_le_bytes_mod_order(&bytes))
189 }
190 .prop_filter("impossible", |_| true))
191 }
192 }
193}