1mod equal;
17mod find;
18mod to_bits;
19mod to_fields;
20
21use crate::{Access, Aleo, Entry, Equal, Identifier, Literal, Plaintext, Record, ToBits, ToFields, Value};
22
23use console::RECORD_DATA_TREE_DEPTH;
24use snarkvm_circuit_algorithms::{Poseidon2, Poseidon8};
25use snarkvm_circuit_collections::merkle_tree::MerkleTree;
26use snarkvm_circuit_types::{Address, Boolean, Field, Group, U8, environment::prelude::*};
27
28type CircuitLH<A> = Poseidon8<A>;
29type CircuitPH<A> = Poseidon2<A>;
30
31pub type RecordDataTree<A> = MerkleTree<A, CircuitLH<A>, CircuitPH<A>, RECORD_DATA_TREE_DEPTH>;
33
34#[derive(Clone)]
63pub struct DynamicRecord<A: Aleo> {
64 owner: Address<A>,
66 root: Field<A>,
68 nonce: Group<A>,
70 version: U8<A>,
72 data: Option<console::RecordData<A::Network>>,
75}
76
77impl<A: Aleo> Inject for DynamicRecord<A> {
78 type Primitive = console::DynamicRecord<A::Network>;
79
80 fn new(_: Mode, record: Self::Primitive) -> Self {
82 Self {
83 owner: Inject::new(Mode::Private, *record.owner()),
84 root: Inject::new(Mode::Private, *record.root()),
85 nonce: Inject::new(Mode::Private, *record.nonce()),
86 version: Inject::new(Mode::Private, *record.version()),
87 data: record.data().clone(),
88 }
89 }
90}
91
92impl<A: Aleo> DynamicRecord<A> {
93 pub const fn owner(&self) -> &Address<A> {
95 &self.owner
96 }
97
98 pub const fn root(&self) -> &Field<A> {
100 &self.root
101 }
102
103 pub const fn nonce(&self) -> &Group<A> {
105 &self.nonce
106 }
107
108 pub const fn version(&self) -> &U8<A> {
110 &self.version
111 }
112
113 pub const fn data(&self) -> Option<&console::RecordData<A::Network>> {
115 self.data.as_ref()
116 }
117}
118
119impl<A: Aleo> Eject for DynamicRecord<A> {
120 type Primitive = console::DynamicRecord<A::Network>;
121
122 fn eject_mode(&self) -> Mode {
124 let owner = self.owner.eject_mode();
125 let root = self.root.eject_mode();
126 let nonce = self.nonce.eject_mode();
127 let version = self.version.eject_mode();
128
129 Mode::combine(owner, [root, nonce, version])
130 }
131
132 fn eject_value(&self) -> Self::Primitive {
134 Self::Primitive::new_unchecked(
135 self.owner.eject_value(),
136 self.root.eject_value(),
137 self.nonce.eject_value(),
138 self.version.eject_value(),
139 self.data.clone(),
140 )
141 }
142}
143
144impl<A: Aleo> DynamicRecord<A> {
145 pub fn from_record(record: &Record<A, Plaintext<A>>) -> Result<Self> {
147 let owner = (**record.owner()).clone();
155 let data = record.data();
157 let nonce = record.nonce().clone();
159 let version = record.version().clone();
161
162 let tree = Self::merkleize_data(data)?;
163 let root = tree.root().clone();
164
165 let console_data =
166 data.iter().map(|(identifier, entry)| (identifier, entry).eject_value()).collect::<IndexMap<_, _>>();
167
168 Ok(Self { owner, root, nonce, version, data: Some(console_data) })
169 }
170
171 pub fn merkleize_data(data: &IndexMap<Identifier<A>, Entry<A, Plaintext<A>>>) -> Result<RecordDataTree<A>> {
175 let (console_leaf_hasher, console_path_hasher) = console::DynamicRecord::initialize_hashers();
177 let circuit_leaf_hasher = CircuitLH::<A>::constant(console_leaf_hasher.clone());
178 let circuit_path_hasher = CircuitPH::<A>::constant(console_path_hasher.clone());
179
180 let leaves = data
182 .iter()
183 .map(|(identifier, entry)| {
184 let fields = entry.to_fields();
185 let mut leaf = Vec::with_capacity(1 + fields.len());
186 leaf.push(identifier.to_field());
187 leaf.extend(fields);
188 leaf
189 })
190 .collect::<Vec<Vec<Field<A>>>>();
191
192 RecordDataTree::<A>::new(circuit_leaf_hasher, circuit_path_hasher, &leaves)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::Circuit;
201 use snarkvm_circuit_types::environment::{Inject, assert_scope};
202 use snarkvm_utilities::{TestRng, Uniform};
203
204 use core::str::FromStr;
205
206 type CurrentNetwork = <Circuit as Environment>::Network;
207 type ConsoleRecord = console::Record<CurrentNetwork, console::Plaintext<CurrentNetwork>>;
208
209 fn check_circuit_console_equivalence(
212 record_str: &str,
213 num_constants: u64,
214 num_public: u64,
215 num_private: u64,
216 num_constraints: u64,
217 ) {
218 let console_record = ConsoleRecord::from_str(record_str).unwrap();
220
221 let console_dynamic = console::DynamicRecord::from_record(&console_record).unwrap();
223
224 let circuit_record = Record::<Circuit, Plaintext<Circuit>>::new(Mode::Private, console_record);
226
227 Circuit::scope("check_circuit_console_equivalence", || {
228 let circuit_dynamic = DynamicRecord::<Circuit>::from_record(&circuit_record).unwrap();
230
231 let circuit_root = circuit_dynamic.root().eject_value();
233 let console_root = *console_dynamic.root();
234 assert_eq!(
235 circuit_root, console_root,
236 "Circuit and console DynamicRecord should produce the same Merkle root"
237 );
238
239 assert_eq!(circuit_dynamic.owner().eject_value(), *console_dynamic.owner());
241 assert_eq!(circuit_dynamic.nonce().eject_value(), *console_dynamic.nonce());
242 assert_eq!(circuit_dynamic.version().eject_value(), *console_dynamic.version());
243
244 assert_scope!(num_constants, num_public, num_private, num_constraints);
246 });
247
248 Circuit::reset();
249 }
250
251 fn create_console_record(
253 rng: &mut TestRng,
254 data: console::RecordData<CurrentNetwork>,
255 owner_is_private: bool,
256 ) -> ConsoleRecord {
257 let owner = match owner_is_private {
258 true => console::Owner::Private(console::Plaintext::from(console::Literal::Address(
259 console::Address::rand(rng),
260 ))),
261 false => console::Owner::Public(console::Address::rand(rng)),
262 };
263 ConsoleRecord::from_plaintext(owner, data, console::Group::rand(rng), console::U8::new(0)).unwrap()
264 }
265
266 #[test]
267 fn test_circuit_console_equivalence_empty_record() {
268 let record_str = r#"{
270 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
271 _nonce: 0group.public,
272 _version: 0u8.public
273 }"#;
274 check_circuit_console_equivalence(record_str, 1075, 0, 0, 0);
275 }
276
277 #[test]
278 fn test_circuit_console_equivalence_single_private_field() {
279 let record_str = r#"{
281 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
282 amount: 100u64.private,
283 _nonce: 0group.public,
284 _version: 0u8.public
285 }"#;
286 check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
287 }
288
289 #[test]
290 fn test_circuit_console_equivalence_single_public_field() {
291 let record_str = r#"{
293 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
294 amount: 100u64.public,
295 _nonce: 0group.public,
296 _version: 0u8.public
297 }"#;
298 check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
299 }
300
301 #[test]
302 fn test_circuit_console_equivalence_single_constant_field() {
303 let record_str = r#"{
305 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
306 amount: 100u64.constant,
307 _nonce: 0group.public,
308 _version: 0u8.public
309 }"#;
310 check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
311 }
312
313 #[test]
314 fn test_circuit_console_equivalence_mixed_entry_types() {
315 let rng = &mut TestRng::default();
316
317 let mut data = IndexMap::new();
319 data.insert(
320 console::Identifier::from_str("a").unwrap(),
321 console::Entry::Private(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
322 );
323 data.insert(
324 console::Identifier::from_str("b").unwrap(),
325 console::Entry::Public(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
326 );
327 data.insert(
328 console::Identifier::from_str("c").unwrap(),
329 console::Entry::Constant(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
330 );
331
332 let console_record = create_console_record(rng, data, true);
334 let record_str = console_record.to_string();
335
336 check_circuit_console_equivalence(&record_str, 1151, 0, 4665, 4665);
337 }
338
339 #[test]
340 fn test_circuit_console_equivalence_nested_struct() {
341 let rng = &mut TestRng::default();
342
343 let mut inner_map = IndexMap::new();
345 inner_map.insert(
346 console::Identifier::from_str("x").unwrap(),
347 console::Plaintext::from(console::Literal::U64(console::U64::rand(rng))),
348 );
349 inner_map.insert(
350 console::Identifier::from_str("y").unwrap(),
351 console::Plaintext::from(console::Literal::U64(console::U64::rand(rng))),
352 );
353 let inner = console::Plaintext::Struct(inner_map, Default::default());
354
355 let mut data = IndexMap::new();
356 data.insert(console::Identifier::from_str("point").unwrap(), console::Entry::Private(inner));
357
358 let console_record = create_console_record(rng, data, false);
360 let record_str = console_record.to_string();
361
362 check_circuit_console_equivalence(&record_str, 1180, 0, 3180, 3180);
363 }
364
365 #[test]
366 fn test_circuit_console_equivalence_private_owner() {
367 let record_str = r#"{
369 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
370 _nonce: 0group.public,
371 _version: 0u8.public
372 }"#;
373 check_circuit_console_equivalence(record_str, 1075, 0, 0, 0);
374 }
375
376 #[test]
377 fn test_circuit_console_equivalence_multiple_fields() {
378 let record_str = r#"{
380 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
381 x: 1u64.private,
382 y: 2u64.private,
383 z: 3u64.private,
384 _nonce: 0group.public,
385 _version: 0u8.public
386 }"#;
387 check_circuit_console_equivalence(record_str, 1151, 0, 4665, 4665);
388 }
389
390 #[test]
391 fn test_circuit_console_equivalence_boolean_field() {
392 let record_str = r#"{
394 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
395 flag: true.private,
396 _nonce: 0group.public,
397 _version: 0u8.public
398 }"#;
399 check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
400 }
401
402 #[test]
403 fn test_circuit_console_equivalence_address_field() {
404 let record_str = r#"{
406 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
407 recipient: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
408 _nonce: 0group.public,
409 _version: 0u8.public
410 }"#;
411 check_circuit_console_equivalence(record_str, 1100, 0, 3685, 3687);
412 }
413
414 #[test]
415 fn test_find_owner() {
416 let record_str = r#"{
417 owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
418 _nonce: 0group.public,
419 _version: 0u8.public
420 }"#;
421 let console_record = ConsoleRecord::from_str(record_str).unwrap();
422 let circuit_record = Record::<Circuit, Plaintext<Circuit>>::new(Mode::Private, console_record);
423 let circuit_dynamic = DynamicRecord::<Circuit>::from_record(&circuit_record).unwrap();
424
425 let path = [Access::Member(Identifier::from_str("owner").unwrap())];
427 assert!(circuit_dynamic.find(&path).is_ok());
428
429 let path_bad = [Access::Member(Identifier::from_str("data").unwrap())];
431 assert!(circuit_dynamic.find(&path_bad).is_err());
432 }
433}