masp_primitives/
convert.rs1use crate::{
2 sapling::{
3 pedersen_hash::{pedersen_hash, Personalization},
4 Node, ValueCommitment,
5 },
6 transaction::components::amount::{I128Sum, ValueSum},
7};
8use borsh::schema::add_definition;
9use borsh::schema::Declaration;
10use borsh::schema::Definition;
11use borsh::schema::Fields;
12use borsh::BorshSchema;
13use borsh::{BorshDeserialize, BorshSerialize};
14use group::{Curve, GroupEncoding};
15use std::collections::BTreeMap;
16use std::{
17 io::{self, Write},
18 iter::Sum,
19 ops::{Add, AddAssign, Neg, Sub, SubAssign},
20};
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct AllowedConversion {
24 assets: I128Sum,
26 generator: jubjub::ExtendedPoint,
28}
29
30impl AllowedConversion {
31 pub fn uncommitted() -> bls12_381::Scalar {
32 bls12_381::Scalar::one()
35 }
36
37 fn cm_full_point(&self) -> jubjub::SubgroupPoint {
39 let mut asset_generator_bytes = vec![];
41
42 asset_generator_bytes.extend_from_slice(&self.generator.to_bytes());
44
45 assert_eq!(asset_generator_bytes.len(), 32);
46
47 pedersen_hash(
49 Personalization::NoteCommitment,
50 asset_generator_bytes
51 .into_iter()
52 .flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)),
53 )
54 }
55
56 pub fn cmu(&self) -> bls12_381::Scalar {
58 jubjub::ExtendedPoint::from(self.cm_full_point())
61 .to_affine()
62 .get_u()
63 }
64
65 pub fn value_commitment(&self, value: u64, randomness: jubjub::Fr) -> ValueCommitment {
67 ValueCommitment {
68 asset_generator: self.generator,
69 value,
70 randomness,
71 }
72 }
73 pub fn commitment(&self) -> Node {
76 Node::from_scalar(self.cmu())
77 }
78}
79
80impl From<AllowedConversion> for I128Sum {
81 fn from(allowed_conversion: AllowedConversion) -> I128Sum {
82 allowed_conversion.assets
83 }
84}
85
86impl From<I128Sum> for AllowedConversion {
87 fn from(assets: I128Sum) -> Self {
89 let mut asset_generator = jubjub::ExtendedPoint::identity();
90 for (asset, value) in assets.components() {
91 let abs = match value.checked_abs() {
94 Some(a) => a as u64,
95 None => panic!("invalid conversion"),
96 };
97
98 let is_negative = value.is_negative();
100
101 let mut value_balance = asset.asset_generator() * jubjub::Fr::from(abs);
103
104 if is_negative {
106 value_balance = -value_balance;
107 }
108
109 asset_generator += value_balance;
111 }
112 AllowedConversion {
113 assets,
114 generator: asset_generator,
115 }
116 }
117}
118
119impl BorshSchema for AllowedConversion {
120 fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
121 let definition = Definition::Struct {
122 fields: Fields::NamedFields(vec![
123 ("assets".into(), I128Sum::declaration()),
124 ("generator".into(), <[u8; 32]>::declaration()),
125 ]),
126 };
127 add_definition(Self::declaration(), definition, definitions);
128 I128Sum::add_definitions_recursively(definitions);
129 <[u8; 32]>::add_definitions_recursively(definitions);
130 }
131
132 fn declaration() -> Declaration {
133 "AllowedConversion".into()
134 }
135}
136
137impl BorshSerialize for AllowedConversion {
138 fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
139 self.assets.write(writer)?;
140 writer.write_all(&self.generator.to_bytes())?;
141 Ok(())
142 }
143}
144
145impl BorshDeserialize for AllowedConversion {
146 fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
147 let unchecked_conv = UncheckedAllowedConversion::deserialize_reader(reader)?.0;
149 let safe_conv: AllowedConversion = unchecked_conv.assets.clone().into();
151 if safe_conv.generator == unchecked_conv.generator {
153 Ok(safe_conv)
154 } else {
155 Err(io::Error::from(io::ErrorKind::InvalidData))
157 }
158 }
159}
160
161impl Add for AllowedConversion {
162 type Output = Self;
163
164 fn add(self, rhs: Self) -> Self {
165 Self {
166 assets: self.assets + rhs.assets,
167 generator: self.generator + rhs.generator,
168 }
169 }
170}
171
172impl AddAssign for AllowedConversion {
173 fn add_assign(&mut self, rhs: Self) {
174 self.assets += rhs.assets;
175 self.generator += rhs.generator;
176 }
177}
178
179impl Sub for AllowedConversion {
180 type Output = Self;
181
182 fn sub(self, rhs: Self) -> Self {
183 Self {
184 assets: self.assets - rhs.assets,
185 generator: self.generator - rhs.generator,
186 }
187 }
188}
189
190impl SubAssign for AllowedConversion {
191 fn sub_assign(&mut self, rhs: Self) {
192 self.assets -= rhs.assets;
193 self.generator -= rhs.generator;
194 }
195}
196
197impl Neg for AllowedConversion {
198 type Output = Self;
199
200 fn neg(self) -> Self {
201 Self {
202 assets: -self.assets,
203 generator: -self.generator,
204 }
205 }
206}
207
208impl Sum for AllowedConversion {
209 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
210 iter.fold(AllowedConversion::from(ValueSum::zero()), Add::add)
211 }
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct UncheckedAllowedConversion(pub AllowedConversion);
217
218impl BorshDeserialize for UncheckedAllowedConversion {
219 fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
223 let assets = I128Sum::read(reader)?;
224 let gen_bytes =
225 <<jubjub::ExtendedPoint as GroupEncoding>::Repr as BorshDeserialize>::deserialize_reader(reader)?;
226 let generator = Option::from(jubjub::ExtendedPoint::from_bytes(&gen_bytes))
227 .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
228 Ok(Self(AllowedConversion { assets, generator }))
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use crate::asset_type::AssetType;
236 use crate::convert::AllowedConversion;
237 use crate::transaction::components::amount::ValueSum;
238
239 fn zec() -> AssetType {
241 AssetType::new(b"ZEC").unwrap()
242 }
243 fn btc() -> AssetType {
245 AssetType::new(b"BTC").unwrap()
246 }
247 fn xan() -> AssetType {
249 AssetType::new(b"XAN").unwrap()
250 }
251 #[test]
252 fn test_homomorphism() {
253 let a = ValueSum::from_pair(zec(), 5i128)
255 + ValueSum::from_pair(btc(), 6i128)
256 + ValueSum::from_pair(xan(), 7i128);
257 let b = ValueSum::from_pair(zec(), 2i128) + ValueSum::from_pair(xan(), 10i128);
259 assert_eq!(
261 AllowedConversion::from(a.clone() + b.clone()),
262 AllowedConversion::from(a) + AllowedConversion::from(b)
263 );
264 }
265 #[test]
266 fn test_serialization() {
267 let a: AllowedConversion = (ValueSum::from_pair(zec(), 5i128)
269 + ValueSum::from_pair(btc(), 6i128)
270 + ValueSum::from_pair(xan(), 7i128))
271 .into();
272 let mut data = Vec::new();
274 use borsh::BorshSerialize;
275 a.serialize(&mut data).unwrap();
276 let mut ptr = &data[..];
278 use borsh::BorshDeserialize;
279 let b = AllowedConversion::deserialize(&mut ptr).unwrap();
280 assert!(
282 ptr.is_empty(),
283 "AllowedConversion bytes should be exhausted"
284 );
285 assert_eq!(
287 a, b,
288 "serialization followed by deserialization changes value"
289 );
290 }
291}