mod equal;
mod find;
mod to_bits;
mod to_fields;
use crate::{Access, Aleo, Entry, Equal, Identifier, Literal, Plaintext, Record, ToBits, ToFields, Value};
use console::RECORD_DATA_TREE_DEPTH;
use snarkvm_circuit_algorithms::{Poseidon2, Poseidon8};
use snarkvm_circuit_collections::merkle_tree::MerkleTree;
use snarkvm_circuit_types::{Address, Boolean, Field, Group, U8, environment::prelude::*};
type CircuitLH<A> = Poseidon8<A>;
type CircuitPH<A> = Poseidon2<A>;
pub type RecordDataTree<A> = MerkleTree<A, CircuitLH<A>, CircuitPH<A>, RECORD_DATA_TREE_DEPTH>;
#[derive(Clone)]
pub struct DynamicRecord<A: Aleo> {
owner: Address<A>,
root: Field<A>,
nonce: Group<A>,
version: U8<A>,
data: Option<console::RecordData<A::Network>>,
}
impl<A: Aleo> Inject for DynamicRecord<A> {
type Primitive = console::DynamicRecord<A::Network>;
fn new(_: Mode, record: Self::Primitive) -> Self {
Self {
owner: Inject::new(Mode::Private, *record.owner()),
root: Inject::new(Mode::Private, *record.root()),
nonce: Inject::new(Mode::Private, *record.nonce()),
version: Inject::new(Mode::Private, *record.version()),
data: record.data().clone(),
}
}
}
impl<A: Aleo> DynamicRecord<A> {
pub const fn owner(&self) -> &Address<A> {
&self.owner
}
pub const fn root(&self) -> &Field<A> {
&self.root
}
pub const fn nonce(&self) -> &Group<A> {
&self.nonce
}
pub const fn version(&self) -> &U8<A> {
&self.version
}
pub const fn data(&self) -> Option<&console::RecordData<A::Network>> {
self.data.as_ref()
}
}
impl<A: Aleo> Eject for DynamicRecord<A> {
type Primitive = console::DynamicRecord<A::Network>;
fn eject_mode(&self) -> Mode {
let owner = self.owner.eject_mode();
let root = self.root.eject_mode();
let nonce = self.nonce.eject_mode();
let version = self.version.eject_mode();
Mode::combine(owner, [root, nonce, version])
}
fn eject_value(&self) -> Self::Primitive {
Self::Primitive::new_unchecked(
self.owner.eject_value(),
self.root.eject_value(),
self.nonce.eject_value(),
self.version.eject_value(),
self.data.clone(),
)
}
}
impl<A: Aleo> DynamicRecord<A> {
pub fn from_record(record: &Record<A, Plaintext<A>>) -> Result<Self> {
let owner = (**record.owner()).clone();
let data = record.data();
let nonce = record.nonce().clone();
let version = record.version().clone();
let tree = Self::merkleize_data(data)?;
let root = tree.root().clone();
let console_data =
data.iter().map(|(identifier, entry)| (identifier, entry).eject_value()).collect::<IndexMap<_, _>>();
Ok(Self { owner, root, nonce, version, data: Some(console_data) })
}
pub fn merkleize_data(data: &IndexMap<Identifier<A>, Entry<A, Plaintext<A>>>) -> Result<RecordDataTree<A>> {
let (console_leaf_hasher, console_path_hasher) = console::DynamicRecord::initialize_hashers();
let circuit_leaf_hasher = CircuitLH::<A>::constant(console_leaf_hasher.clone());
let circuit_path_hasher = CircuitPH::<A>::constant(console_path_hasher.clone());
let leaves = data
.iter()
.map(|(identifier, entry)| {
let fields = entry.to_fields();
let mut leaf = Vec::with_capacity(1 + fields.len());
leaf.push(identifier.to_field());
leaf.extend(fields);
leaf
})
.collect::<Vec<Vec<Field<A>>>>();
RecordDataTree::<A>::new(circuit_leaf_hasher, circuit_path_hasher, &leaves)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Circuit;
use snarkvm_circuit_types::environment::{Inject, assert_scope};
use snarkvm_utilities::{TestRng, Uniform};
use core::str::FromStr;
type CurrentNetwork = <Circuit as Environment>::Network;
type ConsoleRecord = console::Record<CurrentNetwork, console::Plaintext<CurrentNetwork>>;
fn check_circuit_console_equivalence(
record_str: &str,
num_constants: u64,
num_public: u64,
num_private: u64,
num_constraints: u64,
) {
let console_record = ConsoleRecord::from_str(record_str).unwrap();
let console_dynamic = console::DynamicRecord::from_record(&console_record).unwrap();
let circuit_record = Record::<Circuit, Plaintext<Circuit>>::new(Mode::Private, console_record);
Circuit::scope("check_circuit_console_equivalence", || {
let circuit_dynamic = DynamicRecord::<Circuit>::from_record(&circuit_record).unwrap();
let circuit_root = circuit_dynamic.root().eject_value();
let console_root = *console_dynamic.root();
assert_eq!(
circuit_root, console_root,
"Circuit and console DynamicRecord should produce the same Merkle root"
);
assert_eq!(circuit_dynamic.owner().eject_value(), *console_dynamic.owner());
assert_eq!(circuit_dynamic.nonce().eject_value(), *console_dynamic.nonce());
assert_eq!(circuit_dynamic.version().eject_value(), *console_dynamic.version());
assert_scope!(num_constants, num_public, num_private, num_constraints);
});
Circuit::reset();
}
fn create_console_record(
rng: &mut TestRng,
data: console::RecordData<CurrentNetwork>,
owner_is_private: bool,
) -> ConsoleRecord {
let owner = match owner_is_private {
true => console::Owner::Private(console::Plaintext::from(console::Literal::Address(
console::Address::rand(rng),
))),
false => console::Owner::Public(console::Address::rand(rng)),
};
ConsoleRecord::from_plaintext(owner, data, console::Group::rand(rng), console::U8::new(0)).unwrap()
}
#[test]
fn test_circuit_console_equivalence_empty_record() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1075, 0, 0, 0);
}
#[test]
fn test_circuit_console_equivalence_single_private_field() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
amount: 100u64.private,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
}
#[test]
fn test_circuit_console_equivalence_single_public_field() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
amount: 100u64.public,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
}
#[test]
fn test_circuit_console_equivalence_single_constant_field() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
amount: 100u64.constant,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
}
#[test]
fn test_circuit_console_equivalence_mixed_entry_types() {
let rng = &mut TestRng::default();
let mut data = IndexMap::new();
data.insert(
console::Identifier::from_str("a").unwrap(),
console::Entry::Private(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
);
data.insert(
console::Identifier::from_str("b").unwrap(),
console::Entry::Public(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
);
data.insert(
console::Identifier::from_str("c").unwrap(),
console::Entry::Constant(console::Plaintext::from(console::Literal::U64(console::U64::rand(rng)))),
);
let console_record = create_console_record(rng, data, true);
let record_str = console_record.to_string();
check_circuit_console_equivalence(&record_str, 1151, 0, 4665, 4665);
}
#[test]
fn test_circuit_console_equivalence_nested_struct() {
let rng = &mut TestRng::default();
let mut inner_map = IndexMap::new();
inner_map.insert(
console::Identifier::from_str("x").unwrap(),
console::Plaintext::from(console::Literal::U64(console::U64::rand(rng))),
);
inner_map.insert(
console::Identifier::from_str("y").unwrap(),
console::Plaintext::from(console::Literal::U64(console::U64::rand(rng))),
);
let inner = console::Plaintext::Struct(inner_map, Default::default());
let mut data = IndexMap::new();
data.insert(console::Identifier::from_str("point").unwrap(), console::Entry::Private(inner));
let console_record = create_console_record(rng, data, false);
let record_str = console_record.to_string();
check_circuit_console_equivalence(&record_str, 1180, 0, 3180, 3180);
}
#[test]
fn test_circuit_console_equivalence_private_owner() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1075, 0, 0, 0);
}
#[test]
fn test_circuit_console_equivalence_multiple_fields() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
x: 1u64.private,
y: 2u64.private,
z: 3u64.private,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1151, 0, 4665, 4665);
}
#[test]
fn test_circuit_console_equivalence_boolean_field() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
flag: true.private,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1100, 0, 3175, 3175);
}
#[test]
fn test_circuit_console_equivalence_address_field() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
recipient: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
_nonce: 0group.public,
_version: 0u8.public
}"#;
check_circuit_console_equivalence(record_str, 1100, 0, 3685, 3687);
}
#[test]
fn test_find_owner() {
let record_str = r#"{
owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
_nonce: 0group.public,
_version: 0u8.public
}"#;
let console_record = ConsoleRecord::from_str(record_str).unwrap();
let circuit_record = Record::<Circuit, Plaintext<Circuit>>::new(Mode::Private, console_record);
let circuit_dynamic = DynamicRecord::<Circuit>::from_record(&circuit_record).unwrap();
let path = [Access::Member(Identifier::from_str("owner").unwrap())];
assert!(circuit_dynamic.find(&path).is_ok());
let path_bad = [Access::Member(Identifier::from_str("data").unwrap())];
assert!(circuit_dynamic.find(&path_bad).is_err());
}
}