snarkvm_console_program/data/dynamic/record/
mod.rs1mod bytes;
17mod equal;
18mod find;
19mod parse;
20mod to_bits;
21mod to_fields;
22
23use crate::{
24 Access,
25 Address,
26 Boolean,
27 Entry,
28 Field,
29 Group,
30 Identifier,
31 Literal,
32 Network,
33 Owner,
34 Plaintext,
35 Record,
36 Result,
37 ToField,
38 ToFields,
39 U8,
40 Value,
41};
42
43use snarkvm_console_algorithms::{Poseidon2, Poseidon8};
44use snarkvm_console_collections::merkle_tree::MerkleTree;
45use snarkvm_console_network::*;
46
47use indexmap::IndexMap;
48
49pub const RECORD_DATA_TREE_DEPTH: u8 = 5;
51
52pub type RecordDataTree<E> = MerkleTree<E, Poseidon8<E>, Poseidon2<E>, RECORD_DATA_TREE_DEPTH>;
54pub type RecordData<N> = IndexMap<Identifier<N>, Entry<N, Plaintext<N>>>;
56
57#[derive(Clone)]
88pub struct DynamicRecord<N: Network> {
89 owner: Address<N>,
91 root: Field<N>,
93 nonce: Group<N>,
95 version: U8<N>,
97 data: Option<RecordData<N>>,
99}
100
101impl<N: Network> DynamicRecord<N> {
102 pub const fn new_unchecked(
104 owner: Address<N>,
105 root: Field<N>,
106 nonce: Group<N>,
107 version: U8<N>,
108 data: Option<RecordData<N>>,
109 ) -> Self {
110 Self { owner, root, nonce, version, data }
111 }
112}
113
114impl<N: Network> DynamicRecord<N> {
115 pub const fn owner(&self) -> &Address<N> {
117 &self.owner
118 }
119
120 pub const fn root(&self) -> &Field<N> {
122 &self.root
123 }
124
125 pub const fn nonce(&self) -> &Group<N> {
127 &self.nonce
128 }
129
130 pub const fn version(&self) -> &U8<N> {
132 &self.version
133 }
134
135 pub const fn data(&self) -> &Option<RecordData<N>> {
137 &self.data
138 }
139
140 pub fn is_hiding(&self) -> bool {
142 !self.version.is_zero()
143 }
144}
145
146impl<N: Network> DynamicRecord<N> {
147 pub fn from_record(record: &Record<N, Plaintext<N>>) -> Result<Self> {
149 let owner = *record.owner().clone();
151 let data = record.data().clone();
153 let nonce = *record.nonce();
155 let version = *record.version();
157
158 let tree = Self::merkleize_data(&data)?;
160
161 let root = *tree.root();
163
164 Ok(Self::new_unchecked(owner, root, nonce, version, Some(data)))
165 }
166
167 pub fn to_record(&self, owner_is_private: bool) -> Result<Record<N, Plaintext<N>>> {
169 let Some(data) = &self.data else {
171 bail!("Cannot convert a dynamic record to static record without the underlying data");
172 };
173 let owner = match owner_is_private {
175 false => Owner::<N, Plaintext<N>>::Public(self.owner),
176 true => Owner::<N, Plaintext<N>>::Private(Plaintext::from(Literal::Address(self.owner))),
177 };
178 Record::<N, Plaintext<N>>::from_plaintext(owner, data.clone(), self.nonce, self.version)
180 }
181
182 pub fn merkleize_data(data: &IndexMap<Identifier<N>, Entry<N, Plaintext<N>>>) -> Result<RecordDataTree<N>> {
186 let leaves = data
188 .iter()
189 .map(|(name, entry)| {
190 let fields = entry.to_fields()?;
192 let mut leaf = Vec::with_capacity(1 + fields.len());
194 leaf.push(name.to_field()?);
196 leaf.extend(fields);
198
199 Ok(leaf)
200 })
201 .collect::<Result<Vec<_>>>()?;
202
203 let (leaf_hasher, path_hasher) = Self::initialize_hashers();
205
206 RecordDataTree::new(leaf_hasher, path_hasher, &leaves)
208 }
209
210 pub fn initialize_hashers() -> (&'static Poseidon8<N>, &'static Poseidon2<N>) {
212 (N::dynamic_record_leaf_hasher(), N::dynamic_record_path_hasher())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use snarkvm_console_network::MainnetV0;
220
221 use crate::{Entry, Literal, Owner, Record};
222 use snarkvm_console_types::{Address, Group, U8, U64};
223 use snarkvm_utilities::{TestRng, Uniform};
224
225 use core::str::FromStr;
226
227 type CurrentNetwork = MainnetV0;
228
229 #[test]
230 fn test_data_depth() {
231 assert_eq!(CurrentNetwork::MAX_DATA_ENTRIES.ilog2(), RECORD_DATA_TREE_DEPTH as u32);
232 }
233
234 fn create_test_record(
235 rng: &mut TestRng,
236 data: RecordData<CurrentNetwork>,
237 owner_is_private: bool,
238 ) -> Record<CurrentNetwork, Plaintext<CurrentNetwork>> {
239 let owner = match owner_is_private {
240 true => Owner::Private(Plaintext::from(Literal::Address(Address::rand(rng)))),
241 false => Owner::Public(Address::rand(rng)),
242 };
243 Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_plaintext(owner, data, Group::rand(rng), U8::new(0))
244 .unwrap()
245 }
246
247 fn assert_round_trip(record: &Record<CurrentNetwork, Plaintext<CurrentNetwork>>, owner_is_private: bool) {
248 let dynamic = DynamicRecord::from_record(record).unwrap();
249 let recovered = dynamic.to_record(owner_is_private).unwrap();
250 assert_eq!(record.nonce(), recovered.nonce());
251 assert_eq!(record.data(), recovered.data());
252 }
253
254 #[test]
255 fn test_round_trip_various_records() {
256 let rng = &mut TestRng::default();
257
258 let record = create_test_record(rng, indexmap::IndexMap::new(), false);
260 assert_round_trip(&record, false);
261
262 let data = indexmap::indexmap! {
264 Identifier::from_str("a").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
265 Identifier::from_str("b").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
266 };
267 let record = create_test_record(rng, data, true);
268 assert_round_trip(&record, true);
269
270 let data = indexmap::indexmap! {
272 Identifier::from_str("x").unwrap() => Entry::Public(Plaintext::from(Literal::U64(U64::rand(rng)))),
273 };
274 let record = create_test_record(rng, data, false);
275 assert_round_trip(&record, false);
276
277 let data = indexmap::indexmap! {
279 Identifier::from_str("pub").unwrap() => Entry::Public(Plaintext::from(Literal::U64(U64::rand(rng)))),
280 Identifier::from_str("priv").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
281 Identifier::from_str("const").unwrap() => Entry::Constant(Plaintext::from(Literal::U64(U64::rand(rng)))),
282 };
283 let record = create_test_record(rng, data, true);
284 assert_round_trip(&record, true);
285
286 let inner = Plaintext::Struct(
288 indexmap::indexmap! {
289 Identifier::from_str("x").unwrap() => Plaintext::from(Literal::U64(U64::rand(rng))),
290 Identifier::from_str("y").unwrap() => Plaintext::from(Literal::U64(U64::rand(rng))),
291 },
292 Default::default(),
293 );
294 let data = indexmap::indexmap! {
295 Identifier::from_str("point").unwrap() => Entry::Private(inner),
296 };
297 let record = create_test_record(rng, data, false);
298 assert_round_trip(&record, false);
299 }
300
301 #[test]
302 fn test_root_determinism() {
303 let rng = &mut TestRng::default();
304
305 let data1 = indexmap::indexmap! {
306 Identifier::from_str("a").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::new(100)))),
307 };
308 let data2 = indexmap::indexmap! {
309 Identifier::from_str("a").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::new(100)))),
310 };
311 let data3 = indexmap::indexmap! {
312 Identifier::from_str("a").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::new(200)))),
313 };
314
315 let r1 = DynamicRecord::from_record(&create_test_record(rng, data1, false)).unwrap();
316 let r2 = DynamicRecord::from_record(&create_test_record(rng, data2, false)).unwrap();
317 let r3 = DynamicRecord::from_record(&create_test_record(rng, data3, false)).unwrap();
318
319 assert_eq!(r1.root(), r2.root(), "Same data should produce same root");
320 assert_ne!(r1.root(), r3.root(), "Different data should produce different roots");
321 }
322
323 #[test]
324 fn test_membership_proofs() {
325 let rng = &mut TestRng::default();
326
327 let data = indexmap::indexmap! {
328 Identifier::from_str("a").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
329 Identifier::from_str("b").unwrap() => Entry::Public(Plaintext::from(Literal::U64(U64::rand(rng)))),
330 Identifier::from_str("c").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
331 };
332
333 let tree = DynamicRecord::<CurrentNetwork>::merkleize_data(&data).unwrap();
335 let leaves: Vec<_> = data
336 .iter()
337 .map(|(name, entry)| {
338 let mut leaf = vec![name.to_field().unwrap()];
339 leaf.extend(entry.to_fields().unwrap());
340 leaf
341 })
342 .collect();
343
344 for (i, leaf) in leaves.iter().enumerate() {
346 let path = tree.prove(i, leaf).unwrap();
347 assert!(tree.verify(&path, tree.root(), leaf));
348 }
349
350 let path = tree.prove(0, &leaves[0]).unwrap();
352 assert!(!tree.verify(&path, tree.root(), &leaves[1])); assert!(!tree.verify(&path, &Field::from_u64(12345), &leaves[0])); }
355
356 #[test]
357 fn test_find_owner() {
358 let rng = &mut TestRng::default();
359 let owner_addr = Address::rand(rng);
360 let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_plaintext(
361 Owner::Public(owner_addr),
362 indexmap::IndexMap::new(),
363 Group::rand(rng),
364 U8::new(0),
365 )
366 .unwrap();
367 let dynamic = DynamicRecord::from_record(&record).unwrap();
368
369 let path = [Access::Member(Identifier::from_str("owner").unwrap())];
371 let value = dynamic.find(&path).unwrap();
372 assert_eq!(value, Value::Plaintext(Plaintext::from(Literal::Address(owner_addr))));
373 }
374
375 #[test]
376 fn test_find_rejects_non_owner_paths() {
377 let rng = &mut TestRng::default();
378 let record = create_test_record(rng, indexmap::IndexMap::new(), false);
379 let dynamic = DynamicRecord::from_record(&record).unwrap();
380
381 let path = [Access::Member(Identifier::from_str("data").unwrap())];
383 assert!(dynamic.find(&path).is_err());
384
385 let empty: &[Access<CurrentNetwork>] = &[];
387 assert!(dynamic.find(empty).is_err());
388
389 let long_path = [
391 Access::Member(Identifier::from_str("owner").unwrap()),
392 Access::Member(Identifier::from_str("nested").unwrap()),
393 ];
394 assert!(dynamic.find(&long_path).is_err());
395 }
396}