Introduce SmtProof
(#270)
* add conversion for `SmtLeaf` * introduce `SmtProof` scaffolding * implement `verify_membership()` * SmtLeaf: knows its index * `SmtLeaf::index` * `SmtLeaf::get_value()` returns an Option * fix `verify_membership()` * impl `SmtProof::get` * impl `into_parts()` * `SmtProof::compute_root` * use `SmtProof` in `Smt::open` * `SmtLeaf` constructors * Vec * impl `Error` for `SmtLeafError` * fix std Error * move Word/Digest conversions to LeafIndex * `SmtProof::new()` returns an error * `SparseMerkleTree::path_and_leaf_to_opening` * `SmtLeaf`: serializable/deserializable * `SmtProof`: serializable/deserializable * add tests for SmtLeaf serialization * move `SmtLeaf` to submodule * use constructors internally * fix docs * Add `Vec` * add `Vec` to tests * no_std use statements * fmt * `Errors`: make heading * use `SMT_DEPTH` * SmtLeaf single case: check leaf index * Multiple case: check consistency with leaf index * use `pub(super)` instead of `pub(crate)` * use `pub(super)` * `SmtLeaf`: add `num_entries()` accessor * Fix `SmtLeaf` serialization * improve leaf serialization tests
This commit is contained in:
parent
61a0764a61
commit
e55b3ed2ce
8 changed files with 680 additions and 218 deletions
|
@ -4,6 +4,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
use super::smt::SmtLeafError;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MerkleError {
|
pub enum MerkleError {
|
||||||
ConflictingRoots(Vec<RpoDigest>),
|
ConflictingRoots(Vec<RpoDigest>),
|
||||||
|
@ -20,6 +22,7 @@ pub enum MerkleError {
|
||||||
NodeNotInStore(RpoDigest, NodeIndex),
|
NodeNotInStore(RpoDigest, NodeIndex),
|
||||||
NumLeavesNotPowerOfTwo(usize),
|
NumLeavesNotPowerOfTwo(usize),
|
||||||
RootNotInStore(RpoDigest),
|
RootNotInStore(RpoDigest),
|
||||||
|
SmtLeaf(SmtLeafError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MerkleError {
|
impl fmt::Display for MerkleError {
|
||||||
|
@ -50,9 +53,16 @@ impl fmt::Display for MerkleError {
|
||||||
write!(f, "the leaves count {leaves} is not a power of 2")
|
write!(f, "the leaves count {leaves} is not a power of 2")
|
||||||
}
|
}
|
||||||
RootNotInStore(root) => write!(f, "the root {:?} is not in the store", root),
|
RootNotInStore(root) => write!(f, "the root {:?} is not in the store", root),
|
||||||
|
SmtLeaf(smt_leaf_error) => write!(f, "smt leaf error: {smt_leaf_error}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl std::error::Error for MerkleError {}
|
impl std::error::Error for MerkleError {}
|
||||||
|
|
||||||
|
impl From<SmtLeafError> for MerkleError {
|
||||||
|
fn from(value: SmtLeafError) -> Self {
|
||||||
|
Self::SmtLeaf(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
86
src/merkle/smt/full/error.rs
Normal file
86
src/merkle/smt/full/error.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hash::rpo::RpoDigest,
|
||||||
|
merkle::{LeafIndex, SMT_DEPTH},
|
||||||
|
utils::collections::Vec,
|
||||||
|
Word,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SMT LEAF ERROR
|
||||||
|
// =================================================================================================
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SmtLeafError {
|
||||||
|
InconsistentKeys {
|
||||||
|
entries: Vec<(RpoDigest, Word)>,
|
||||||
|
key_1: RpoDigest,
|
||||||
|
key_2: RpoDigest,
|
||||||
|
},
|
||||||
|
InvalidNumEntriesForMultiple(usize),
|
||||||
|
SingleKeyInconsistentWithLeafIndex {
|
||||||
|
key: RpoDigest,
|
||||||
|
leaf_index: LeafIndex<SMT_DEPTH>,
|
||||||
|
},
|
||||||
|
MultipleKeysInconsistentWithLeafIndex {
|
||||||
|
leaf_index_from_keys: LeafIndex<SMT_DEPTH>,
|
||||||
|
leaf_index_supplied: LeafIndex<SMT_DEPTH>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for SmtLeafError {}
|
||||||
|
|
||||||
|
impl fmt::Display for SmtLeafError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use SmtLeafError::*;
|
||||||
|
match self {
|
||||||
|
InvalidNumEntriesForMultiple(num_entries) => {
|
||||||
|
write!(f, "Multiple leaf requires 2 or more entries. Got: {num_entries}")
|
||||||
|
}
|
||||||
|
InconsistentKeys { entries, key_1, key_2 } => {
|
||||||
|
write!(f, "Multiple leaf requires all keys to map to the same leaf index. Offending keys: {key_1} and {key_2}. Entries: {entries:?}.")
|
||||||
|
}
|
||||||
|
SingleKeyInconsistentWithLeafIndex { key, leaf_index } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Single key in leaf inconsistent with leaf index. Key: {key}, leaf index: {}",
|
||||||
|
leaf_index.value()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MultipleKeysInconsistentWithLeafIndex {
|
||||||
|
leaf_index_from_keys,
|
||||||
|
leaf_index_supplied,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Keys in entries map to leaf index {}, but leaf index {} was supplied",
|
||||||
|
leaf_index_from_keys.value(),
|
||||||
|
leaf_index_supplied.value()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMT PROOF ERROR
|
||||||
|
// =================================================================================================
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SmtProofError {
|
||||||
|
InvalidPathLength(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for SmtProofError {}
|
||||||
|
|
||||||
|
impl fmt::Display for SmtProofError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use SmtProofError::*;
|
||||||
|
match self {
|
||||||
|
InvalidPathLength(path_length) => {
|
||||||
|
write!(f, "Invalid Merkle path length. Expected {SMT_DEPTH}, got {path_length}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
373
src/merkle/smt/full/leaf.rs
Normal file
373
src/merkle/smt/full/leaf.rs
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
|
use crate::utils::{collections::Vec, string::ToString, vec};
|
||||||
|
use winter_math::StarkField;
|
||||||
|
use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
|
||||||
|
|
||||||
|
use super::{Felt, LeafIndex, Rpo256, RpoDigest, SmtLeafError, Word, EMPTY_WORD, SMT_DEPTH};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub enum SmtLeaf {
|
||||||
|
Empty(LeafIndex<SMT_DEPTH>),
|
||||||
|
Single((RpoDigest, Word)),
|
||||||
|
Multiple(Vec<(RpoDigest, Word)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmtLeaf {
|
||||||
|
// CONSTRUCTORS
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns a new leaf with the specified entries
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - Returns an error if 2 keys in `entries` map to a different leaf index
|
||||||
|
/// - Returns an error if 1 or more keys in `entries` map to a leaf index
|
||||||
|
/// different from `leaf_index`
|
||||||
|
pub fn new(
|
||||||
|
entries: Vec<(RpoDigest, Word)>,
|
||||||
|
leaf_index: LeafIndex<SMT_DEPTH>,
|
||||||
|
) -> Result<Self, SmtLeafError> {
|
||||||
|
match entries.len() {
|
||||||
|
0 => Ok(Self::new_empty(leaf_index)),
|
||||||
|
1 => {
|
||||||
|
let (key, value) = entries[0];
|
||||||
|
|
||||||
|
if LeafIndex::<SMT_DEPTH>::from(key) != leaf_index {
|
||||||
|
return Err(SmtLeafError::SingleKeyInconsistentWithLeafIndex {
|
||||||
|
key,
|
||||||
|
leaf_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::new_single(key, value))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let leaf = Self::new_multiple(entries)?;
|
||||||
|
|
||||||
|
// `new_multiple()` checked that all keys map to the same leaf index. We still need
|
||||||
|
// to ensure that that leaf index is `leaf_index`.
|
||||||
|
if leaf.index() != leaf_index {
|
||||||
|
Err(SmtLeafError::MultipleKeysInconsistentWithLeafIndex {
|
||||||
|
leaf_index_from_keys: leaf.index(),
|
||||||
|
leaf_index_supplied: leaf_index,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(leaf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new empty leaf with the specified leaf index
|
||||||
|
pub fn new_empty(leaf_index: LeafIndex<SMT_DEPTH>) -> Self {
|
||||||
|
Self::Empty(leaf_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new single leaf with the specified entry. The leaf index is derived from the
|
||||||
|
/// entry's key.
|
||||||
|
pub fn new_single(key: RpoDigest, value: Word) -> Self {
|
||||||
|
Self::Single((key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new single leaf with the specified entry. The leaf index is derived from the
|
||||||
|
/// entries' keys.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - Returns an error if 2 keys in `entries` map to a different leaf index
|
||||||
|
pub fn new_multiple(entries: Vec<(RpoDigest, Word)>) -> Result<Self, SmtLeafError> {
|
||||||
|
if entries.len() < 2 {
|
||||||
|
return Err(SmtLeafError::InvalidNumEntriesForMultiple(entries.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all keys map to the same leaf index
|
||||||
|
{
|
||||||
|
let mut keys = entries.iter().map(|(key, _)| key);
|
||||||
|
|
||||||
|
let first_key = *keys.next().expect("ensured at least 2 entries");
|
||||||
|
let first_leaf_index: LeafIndex<SMT_DEPTH> = first_key.into();
|
||||||
|
|
||||||
|
for &next_key in keys {
|
||||||
|
let next_leaf_index: LeafIndex<SMT_DEPTH> = next_key.into();
|
||||||
|
|
||||||
|
if next_leaf_index != first_leaf_index {
|
||||||
|
return Err(SmtLeafError::InconsistentKeys {
|
||||||
|
entries,
|
||||||
|
key_1: first_key,
|
||||||
|
key_2: next_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::Multiple(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUBLIC ACCESSORS
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns true if the leaf is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
matches!(self, Self::Empty(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the leaf's index in the [`super::Smt`]
|
||||||
|
pub fn index(&self) -> LeafIndex<SMT_DEPTH> {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(leaf_index) => *leaf_index,
|
||||||
|
SmtLeaf::Single((key, _)) => key.into(),
|
||||||
|
SmtLeaf::Multiple(entries) => {
|
||||||
|
// Note: All keys are guaranteed to have the same leaf index
|
||||||
|
let (first_key, _) = entries[0];
|
||||||
|
first_key.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of entries stored in the leaf
|
||||||
|
pub fn num_entries(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => 0,
|
||||||
|
SmtLeaf::Single(_) => 1,
|
||||||
|
SmtLeaf::Multiple(entries) => {
|
||||||
|
entries.len().try_into().expect("shouldn't have more than 2^64 entries")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the hash of the leaf
|
||||||
|
pub fn hash(&self) -> RpoDigest {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => EMPTY_WORD.into(),
|
||||||
|
SmtLeaf::Single((key, value)) => Rpo256::merge(&[*key, value.into()]),
|
||||||
|
SmtLeaf::Multiple(kvs) => {
|
||||||
|
let elements: Vec<Felt> = kvs.iter().copied().flat_map(kv_to_elements).collect();
|
||||||
|
Rpo256::hash_elements(&elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITERATORS
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns the key-value pairs in the leaf
|
||||||
|
pub fn entries(&self) -> Vec<&(RpoDigest, Word)> {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => Vec::new(),
|
||||||
|
SmtLeaf::Single(kv_pair) => vec![kv_pair],
|
||||||
|
SmtLeaf::Multiple(kv_pairs) => kv_pairs.iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONVERSIONS
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Converts a leaf to a list of field elements
|
||||||
|
pub fn to_elements(&self) -> Vec<Felt> {
|
||||||
|
self.clone().into_elements()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a leaf to a list of field elements
|
||||||
|
pub fn into_elements(self) -> Vec<Felt> {
|
||||||
|
self.into_entries().into_iter().flat_map(kv_to_elements).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a leaf the key-value pairs in the leaf
|
||||||
|
pub fn into_entries(self) -> Vec<(RpoDigest, Word)> {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => Vec::new(),
|
||||||
|
SmtLeaf::Single(kv_pair) => vec![kv_pair],
|
||||||
|
SmtLeaf::Multiple(kv_pairs) => kv_pairs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPERS
|
||||||
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns the value associated with `key` in the leaf, or `None` if `key` maps to another leaf.
|
||||||
|
pub(super) fn get_value(&self, key: &RpoDigest) -> Option<Word> {
|
||||||
|
// Ensure that `key` maps to this leaf
|
||||||
|
if self.index() != key.into() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => Some(EMPTY_WORD),
|
||||||
|
SmtLeaf::Single((key_in_leaf, value_in_leaf)) => {
|
||||||
|
if key == key_in_leaf {
|
||||||
|
Some(*value_in_leaf)
|
||||||
|
} else {
|
||||||
|
Some(EMPTY_WORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmtLeaf::Multiple(kv_pairs) => {
|
||||||
|
for (key_in_leaf, value_in_leaf) in kv_pairs {
|
||||||
|
if key == key_in_leaf {
|
||||||
|
return Some(*value_in_leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(EMPTY_WORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts key-value pair into the leaf; returns the previous value associated with `key`, if
|
||||||
|
/// any.
|
||||||
|
///
|
||||||
|
/// The caller needs to ensure that `key` has the same leaf index as all other keys in the leaf
|
||||||
|
pub(super) fn insert(&mut self, key: RpoDigest, value: Word) -> Option<Word> {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => {
|
||||||
|
*self = SmtLeaf::new_single(key, value);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
SmtLeaf::Single(kv_pair) => {
|
||||||
|
if kv_pair.0 == key {
|
||||||
|
// the key is already in this leaf. Update the value and return the previous
|
||||||
|
// value
|
||||||
|
let old_value = kv_pair.1;
|
||||||
|
kv_pair.1 = value;
|
||||||
|
Some(old_value)
|
||||||
|
} else {
|
||||||
|
// Another entry is present in this leaf. Transform the entry into a list
|
||||||
|
// entry, and make sure the key-value pairs are sorted by key
|
||||||
|
let mut pairs = vec![*kv_pair, (key, value)];
|
||||||
|
pairs.sort_by(|(key_1, _), (key_2, _)| cmp_keys(*key_1, *key_2));
|
||||||
|
|
||||||
|
*self = SmtLeaf::Multiple(pairs);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmtLeaf::Multiple(kv_pairs) => {
|
||||||
|
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
||||||
|
Ok(pos) => {
|
||||||
|
let old_value = kv_pairs[pos].1;
|
||||||
|
kv_pairs[pos].1 = value;
|
||||||
|
|
||||||
|
Some(old_value)
|
||||||
|
}
|
||||||
|
Err(pos) => {
|
||||||
|
kv_pairs.insert(pos, (key, value));
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes key-value pair from the leaf stored at key; returns the previous value associated
|
||||||
|
/// with `key`, if any. Also returns an `is_empty` flag, indicating whether the leaf became
|
||||||
|
/// empty, and must be removed from the data structure it is contained in.
|
||||||
|
pub(super) fn remove(&mut self, key: RpoDigest) -> (Option<Word>, bool) {
|
||||||
|
match self {
|
||||||
|
SmtLeaf::Empty(_) => (None, false),
|
||||||
|
SmtLeaf::Single((key_at_leaf, value_at_leaf)) => {
|
||||||
|
if *key_at_leaf == key {
|
||||||
|
// our key was indeed stored in the leaf, so we return the value that was stored
|
||||||
|
// in it, and indicate that the leaf should be removed
|
||||||
|
let old_value = *value_at_leaf;
|
||||||
|
|
||||||
|
// Note: this is not strictly needed, since the caller is expected to drop this
|
||||||
|
// `SmtLeaf` object.
|
||||||
|
*self = SmtLeaf::new_empty(key.into());
|
||||||
|
|
||||||
|
(Some(old_value), true)
|
||||||
|
} else {
|
||||||
|
// another key is stored at leaf; nothing to update
|
||||||
|
(None, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SmtLeaf::Multiple(kv_pairs) => {
|
||||||
|
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
||||||
|
Ok(pos) => {
|
||||||
|
let old_value = kv_pairs[pos].1;
|
||||||
|
|
||||||
|
kv_pairs.remove(pos);
|
||||||
|
debug_assert!(!kv_pairs.is_empty());
|
||||||
|
|
||||||
|
if kv_pairs.len() == 1 {
|
||||||
|
// convert the leaf into `Single`
|
||||||
|
*self = SmtLeaf::Single(kv_pairs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
(Some(old_value), false)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// other keys are stored at leaf; nothing to update
|
||||||
|
(None, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for SmtLeaf {
|
||||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||||
|
// Write: num entries
|
||||||
|
self.num_entries().write_into(target);
|
||||||
|
|
||||||
|
// Write: leaf index
|
||||||
|
let leaf_index: u64 = self.index().value();
|
||||||
|
leaf_index.write_into(target);
|
||||||
|
|
||||||
|
// Write: entries
|
||||||
|
for (key, value) in self.entries() {
|
||||||
|
key.write_into(target);
|
||||||
|
value.write_into(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserializable for SmtLeaf {
|
||||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||||
|
// Read: num entries
|
||||||
|
let num_entries = source.read_u64()?;
|
||||||
|
|
||||||
|
// Read: leaf index
|
||||||
|
let leaf_index: LeafIndex<SMT_DEPTH> = {
|
||||||
|
let value = source.read_u64()?;
|
||||||
|
LeafIndex::new_max_depth(value)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read: entries
|
||||||
|
let mut entries: Vec<(RpoDigest, Word)> = Vec::new();
|
||||||
|
for _ in 0..num_entries {
|
||||||
|
let key: RpoDigest = source.read()?;
|
||||||
|
let value: Word = source.read()?;
|
||||||
|
|
||||||
|
entries.push((key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new(entries, leaf_index)
|
||||||
|
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPER FUNCTIONS
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
/// Converts a key-value tuple to an iterator of `Felt`s
|
||||||
|
fn kv_to_elements((key, value): (RpoDigest, Word)) -> impl Iterator<Item = Felt> {
|
||||||
|
let key_elements = key.into_iter();
|
||||||
|
let value_elements = value.into_iter();
|
||||||
|
|
||||||
|
key_elements.chain(value_elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two keys, compared element-by-element using their integer representations starting with
|
||||||
|
/// the most significant element.
|
||||||
|
fn cmp_keys(key_1: RpoDigest, key_2: RpoDigest) -> Ordering {
|
||||||
|
for (v1, v2) in key_1.iter().zip(key_2.iter()).rev() {
|
||||||
|
let v1 = v1.as_int();
|
||||||
|
let v2 = v2.as_int();
|
||||||
|
if v1 != v2 {
|
||||||
|
return v1.cmp(&v2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
|
@ -1,19 +1,23 @@
|
||||||
use core::cmp::Ordering;
|
|
||||||
|
|
||||||
use winter_math::StarkField;
|
use winter_math::StarkField;
|
||||||
|
|
||||||
use crate::hash::rpo::Rpo256;
|
use crate::hash::rpo::Rpo256;
|
||||||
use crate::merkle::{EmptySubtreeRoots, InnerNodeInfo};
|
use crate::merkle::{EmptySubtreeRoots, InnerNodeInfo};
|
||||||
use crate::utils::{
|
use crate::utils::collections::{BTreeMap, BTreeSet};
|
||||||
collections::{BTreeMap, BTreeSet, Vec},
|
|
||||||
vec,
|
|
||||||
};
|
|
||||||
use crate::{Felt, EMPTY_WORD};
|
use crate::{Felt, EMPTY_WORD};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
InnerNode, LeafIndex, MerkleError, MerklePath, NodeIndex, RpoDigest, SparseMerkleTree, Word,
|
InnerNode, LeafIndex, MerkleError, MerklePath, NodeIndex, RpoDigest, SparseMerkleTree, Word,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::{SmtLeafError, SmtProofError};
|
||||||
|
|
||||||
|
mod leaf;
|
||||||
|
pub use leaf::SmtLeaf;
|
||||||
|
|
||||||
|
mod proof;
|
||||||
|
pub use proof::SmtProof;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -119,14 +123,14 @@ impl Smt {
|
||||||
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();
|
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();
|
||||||
|
|
||||||
match self.leaves.get(&leaf_pos) {
|
match self.leaves.get(&leaf_pos) {
|
||||||
Some(leaf) => leaf.get_value(key),
|
Some(leaf) => leaf.get_value(key).unwrap_or_default(),
|
||||||
None => EMPTY_WORD,
|
None => EMPTY_WORD,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
|
/// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
|
||||||
/// path to the leaf, as well as the leaf itself.
|
/// path to the leaf, as well as the leaf itself.
|
||||||
pub fn open(&self, key: &RpoDigest) -> (MerklePath, SmtLeaf) {
|
pub fn open(&self, key: &RpoDigest) -> SmtProof {
|
||||||
<Self as SparseMerkleTree<SMT_DEPTH>>::open(self, key)
|
<Self as SparseMerkleTree<SMT_DEPTH>>::open(self, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +212,7 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||||
type Key = RpoDigest;
|
type Key = RpoDigest;
|
||||||
type Value = Word;
|
type Value = Word;
|
||||||
type Leaf = SmtLeaf;
|
type Leaf = SmtLeaf;
|
||||||
type Opening = (MerklePath, SmtLeaf);
|
type Opening = SmtProof;
|
||||||
|
|
||||||
const EMPTY_VALUE: Self::Value = EMPTY_WORD;
|
const EMPTY_VALUE: Self::Value = EMPTY_WORD;
|
||||||
|
|
||||||
|
@ -250,7 +254,7 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||||
|
|
||||||
match self.leaves.get(&leaf_pos) {
|
match self.leaves.get(&leaf_pos) {
|
||||||
Some(leaf) => leaf.clone(),
|
Some(leaf) => leaf.clone(),
|
||||||
None => SmtLeaf::Empty,
|
None => SmtLeaf::new_empty(key.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +266,10 @@ impl SparseMerkleTree<SMT_DEPTH> for Smt {
|
||||||
let most_significant_felt = key[3];
|
let most_significant_felt = key[3];
|
||||||
LeafIndex::new_max_depth(most_significant_felt.as_int())
|
LeafIndex::new_max_depth(most_significant_felt.as_int())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_and_leaf_to_opening(path: MerklePath, leaf: SmtLeaf) -> SmtProof {
|
||||||
|
SmtProof::new_unchecked(path, leaf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Smt {
|
impl Default for Smt {
|
||||||
|
@ -270,196 +278,24 @@ impl Default for Smt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LEAF
|
// CONVERSIONS
|
||||||
// ================================================================================================
|
// ================================================================================================
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
impl From<Word> for LeafIndex<SMT_DEPTH> {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
fn from(value: Word) -> Self {
|
||||||
pub enum SmtLeaf {
|
// We use the most significant `Felt` of a `Word` as the leaf index.
|
||||||
Empty,
|
Self::new_max_depth(value[3].as_int())
|
||||||
Single((RpoDigest, Word)),
|
|
||||||
Multiple(Vec<(RpoDigest, Word)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmtLeaf {
|
|
||||||
/// Converts a leaf to a list of field elements
|
|
||||||
pub fn to_elements(&self) -> Vec<Felt> {
|
|
||||||
self.clone().into_elements()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a leaf to a list of field elements
|
|
||||||
pub fn into_elements(self) -> Vec<Felt> {
|
|
||||||
self.into_entries().into_iter().flat_map(kv_to_elements).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the key-value pairs in the leaf
|
|
||||||
pub fn entries(&self) -> Vec<&(RpoDigest, Word)> {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => Vec::new(),
|
|
||||||
SmtLeaf::Single(kv_pair) => vec![kv_pair],
|
|
||||||
SmtLeaf::Multiple(kv_pairs) => kv_pairs.iter().collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a leaf the key-value pairs in the leaf
|
|
||||||
pub fn into_entries(self) -> Vec<(RpoDigest, Word)> {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => Vec::new(),
|
|
||||||
SmtLeaf::Single(kv_pair) => vec![kv_pair],
|
|
||||||
SmtLeaf::Multiple(kv_pairs) => kv_pairs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes the hash of the leaf
|
|
||||||
pub fn hash(&self) -> RpoDigest {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => EMPTY_WORD.into(),
|
|
||||||
SmtLeaf::Single((key, value)) => Rpo256::merge(&[*key, value.into()]),
|
|
||||||
SmtLeaf::Multiple(kvs) => {
|
|
||||||
let elements: Vec<Felt> = kvs.iter().copied().flat_map(kv_to_elements).collect();
|
|
||||||
Rpo256::hash_elements(&elements)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPERS
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Returns the value associated with `key` in the leaf
|
|
||||||
fn get_value(&self, key: &RpoDigest) -> Word {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => EMPTY_WORD,
|
|
||||||
SmtLeaf::Single((key_in_leaf, value_in_leaf)) => {
|
|
||||||
if key == key_in_leaf {
|
|
||||||
*value_in_leaf
|
|
||||||
} else {
|
|
||||||
EMPTY_WORD
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SmtLeaf::Multiple(kv_pairs) => {
|
|
||||||
for (key_in_leaf, value_in_leaf) in kv_pairs {
|
|
||||||
if key == key_in_leaf {
|
|
||||||
return *value_in_leaf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EMPTY_WORD
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts key-value pair into the leaf; returns the previous value associated with `key`, if
|
|
||||||
/// any.
|
|
||||||
fn insert(&mut self, key: RpoDigest, value: Word) -> Option<Word> {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => {
|
|
||||||
*self = SmtLeaf::Single((key, value));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
SmtLeaf::Single(kv_pair) => {
|
|
||||||
if kv_pair.0 == key {
|
|
||||||
// the key is already in this leaf. Update the value and return the previous
|
|
||||||
// value
|
|
||||||
let old_value = kv_pair.1;
|
|
||||||
kv_pair.1 = value;
|
|
||||||
Some(old_value)
|
|
||||||
} else {
|
|
||||||
// Another entry is present in this leaf. Transform the entry into a list
|
|
||||||
// entry, and make sure the key-value pairs are sorted by key
|
|
||||||
let mut pairs = vec![*kv_pair, (key, value)];
|
|
||||||
pairs.sort_by(|(key_1, _), (key_2, _)| cmp_keys(*key_1, *key_2));
|
|
||||||
|
|
||||||
*self = SmtLeaf::Multiple(pairs);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SmtLeaf::Multiple(kv_pairs) => {
|
|
||||||
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
|
||||||
Ok(pos) => {
|
|
||||||
let old_value = kv_pairs[pos].1;
|
|
||||||
kv_pairs[pos].1 = value;
|
|
||||||
|
|
||||||
Some(old_value)
|
|
||||||
}
|
|
||||||
Err(pos) => {
|
|
||||||
kv_pairs.insert(pos, (key, value));
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes key-value pair from the leaf stored at key; returns the previous value associated
|
|
||||||
/// with `key`, if any. Also returns an `is_empty` flag, indicating whether the leaf became
|
|
||||||
/// empty, and must be removed from the data structure it is contained in.
|
|
||||||
fn remove(&mut self, key: RpoDigest) -> (Option<Word>, bool) {
|
|
||||||
match self {
|
|
||||||
SmtLeaf::Empty => (None, false),
|
|
||||||
SmtLeaf::Single((key_at_leaf, value_at_leaf)) => {
|
|
||||||
if *key_at_leaf == key {
|
|
||||||
// our key was indeed stored in the leaf, so we return the value that was stored
|
|
||||||
// in it, and indicate that the leaf should be removed
|
|
||||||
let old_value = *value_at_leaf;
|
|
||||||
|
|
||||||
// Note: this is not strictly needed, since the caller is expected to drop this
|
|
||||||
// `SmtLeaf` object.
|
|
||||||
*self = SmtLeaf::Empty;
|
|
||||||
|
|
||||||
(Some(old_value), true)
|
|
||||||
} else {
|
|
||||||
// another key is stored at leaf; nothing to update
|
|
||||||
(None, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SmtLeaf::Multiple(kv_pairs) => {
|
|
||||||
match kv_pairs.binary_search_by(|kv_pair| cmp_keys(kv_pair.0, key)) {
|
|
||||||
Ok(pos) => {
|
|
||||||
let old_value = kv_pairs[pos].1;
|
|
||||||
|
|
||||||
kv_pairs.remove(pos);
|
|
||||||
debug_assert!(!kv_pairs.is_empty());
|
|
||||||
|
|
||||||
if kv_pairs.len() == 1 {
|
|
||||||
// convert the leaf into `Single`
|
|
||||||
*self = SmtLeaf::Single(kv_pairs[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
(Some(old_value), false)
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// other keys are stored at leaf; nothing to update
|
|
||||||
(None, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELPER FUNCTIONS
|
impl From<RpoDigest> for LeafIndex<SMT_DEPTH> {
|
||||||
// ================================================================================================
|
fn from(value: RpoDigest) -> Self {
|
||||||
|
Word::from(value).into()
|
||||||
/// Converts a key-value tuple to an iterator of `Felt`s
|
}
|
||||||
fn kv_to_elements((key, value): (RpoDigest, Word)) -> impl Iterator<Item = Felt> {
|
}
|
||||||
let key_elements = key.into_iter();
|
|
||||||
let value_elements = value.into_iter();
|
impl From<&RpoDigest> for LeafIndex<SMT_DEPTH> {
|
||||||
|
fn from(value: &RpoDigest) -> Self {
|
||||||
key_elements.chain(value_elements)
|
Word::from(value).into()
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares two keys, compared element-by-element using their integer representations starting with
|
|
||||||
/// the most significant element.
|
|
||||||
fn cmp_keys(key_1: RpoDigest, key_2: RpoDigest) -> Ordering {
|
|
||||||
for (v1, v2) in key_1.iter().zip(key_2.iter()).rev() {
|
|
||||||
let v1 = v1.as_int();
|
|
||||||
let v2 = v2.as_int();
|
|
||||||
if v1 != v2 {
|
|
||||||
return v1.cmp(&v2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ordering::Equal
|
|
||||||
}
|
}
|
||||||
|
|
104
src/merkle/smt/full/proof.rs
Normal file
104
src/merkle/smt/full/proof.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use crate::utils::string::ToString;
|
||||||
|
use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
|
||||||
|
|
||||||
|
use super::{MerklePath, RpoDigest, SmtLeaf, SmtProofError, Word, SMT_DEPTH};
|
||||||
|
|
||||||
|
/// A proof which can be used to assert membership (or non-membership) of key-value pairs in a
|
||||||
|
/// [`super::Smt`].
|
||||||
|
///
|
||||||
|
/// The proof consists of a Merkle path and leaf which describes the node located at the base of the
|
||||||
|
/// path.
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct SmtProof {
|
||||||
|
path: MerklePath,
|
||||||
|
leaf: SmtLeaf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmtProof {
|
||||||
|
// CONSTRUCTOR
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns a new instance of [`SmtProof`] instantiated from the specified path and leaf.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the path length is not [`SMT_DEPTH`].
|
||||||
|
pub fn new(path: MerklePath, leaf: SmtLeaf) -> Result<Self, SmtProofError> {
|
||||||
|
if path.len() != SMT_DEPTH.into() {
|
||||||
|
return Err(SmtProofError::InvalidPathLength(path.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { path, leaf })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new instance of [`SmtProof`] instantiated from the specified path and leaf.
|
||||||
|
///
|
||||||
|
/// The length of the path is not checked. Reserved for internal use.
|
||||||
|
pub(super) fn new_unchecked(path: MerklePath, leaf: SmtLeaf) -> Self {
|
||||||
|
Self { path, leaf }
|
||||||
|
}
|
||||||
|
|
||||||
|
// PROOF VERIFIER
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns true if a [`super::Smt`] with the specified root contains the provided
|
||||||
|
/// key-value pair.
|
||||||
|
///
|
||||||
|
/// Note: this method cannot be used to assert non-membership. That is, if false is returned,
|
||||||
|
/// it does not mean that the provided key-value pair is not in the tree.
|
||||||
|
pub fn verify_membership(&self, key: &RpoDigest, value: &Word, root: &RpoDigest) -> bool {
|
||||||
|
let maybe_value_in_leaf = self.leaf.get_value(key);
|
||||||
|
|
||||||
|
match maybe_value_in_leaf {
|
||||||
|
Some(value_in_leaf) => {
|
||||||
|
// The value must match for the proof to be valid
|
||||||
|
if value_in_leaf != *value {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the Merkle path resolves to the correct root
|
||||||
|
self.compute_root() == *root
|
||||||
|
}
|
||||||
|
// If the key maps to a different leaf, the proof cannot verify membership of `value`
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUBLIC ACCESSORS
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns the value associated with the specific key according to this proof, or None if
|
||||||
|
/// this proof does not contain a value for the specified key.
|
||||||
|
///
|
||||||
|
/// A key-value pair generated by using this method should pass the `verify_membership()` check.
|
||||||
|
pub fn get(&self, key: &RpoDigest) -> Option<Word> {
|
||||||
|
self.leaf.get_value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the root of a [`super::Smt`] to which this proof resolves.
|
||||||
|
pub fn compute_root(&self) -> RpoDigest {
|
||||||
|
self.path
|
||||||
|
.compute_root(self.leaf.index().value(), self.leaf.hash())
|
||||||
|
.expect("failed to compute Merkle path root")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the proof and returns its parts.
|
||||||
|
pub fn into_parts(self) -> (MerklePath, SmtLeaf) {
|
||||||
|
(self.path, self.leaf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for SmtProof {
|
||||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||||
|
self.path.write_into(target);
|
||||||
|
self.leaf.write_into(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserializable for SmtProof {
|
||||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||||
|
let path = MerklePath::read_from(source)?;
|
||||||
|
let leaf = SmtLeaf::read_from(source)?;
|
||||||
|
|
||||||
|
Self::new(path, leaf).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,15 @@
|
||||||
|
use winter_utils::{Deserializable, Serializable};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle::{EmptySubtreeRoots, MerkleStore},
|
merkle::{EmptySubtreeRoots, MerkleStore},
|
||||||
|
utils::collections::Vec,
|
||||||
ONE, WORD_SIZE,
|
ONE, WORD_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SMT
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/// This test checks that inserting twice at the same key functions as expected. The test covers
|
/// This test checks that inserting twice at the same key functions as expected. The test covers
|
||||||
/// only the case where the key is alone in its leaf
|
/// only the case where the key is alone in its leaf
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -129,7 +135,7 @@ fn test_smt_insert_and_remove_multiple_values() {
|
||||||
assert_eq!(smt.root(), tree_root);
|
assert_eq!(smt.root(), tree_root);
|
||||||
|
|
||||||
let expected_path = store.get_path(tree_root, key_index).unwrap();
|
let expected_path = store.get_path(tree_root, key_index).unwrap();
|
||||||
assert_eq!(smt.open(&key).0, expected_path.path);
|
assert_eq!(smt.open(&key).into_parts().0, expected_path.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut smt = Smt::default();
|
let mut smt = Smt::default();
|
||||||
|
@ -248,7 +254,7 @@ fn test_smt_removal() {
|
||||||
let old_value_1 = smt.insert(key_1, EMPTY_WORD);
|
let old_value_1 = smt.insert(key_1, EMPTY_WORD);
|
||||||
assert_eq!(old_value_1, value_1);
|
assert_eq!(old_value_1, value_1);
|
||||||
|
|
||||||
assert_eq!(smt.get_leaf(&key_1), SmtLeaf::Empty);
|
assert_eq!(smt.get_leaf(&key_1), SmtLeaf::new_empty(key_1.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,12 +329,65 @@ fn test_smt_entries() {
|
||||||
assert_eq!(&(key_2, value_2), entries.next().unwrap());
|
assert_eq!(&(key_2, value_2), entries.next().unwrap());
|
||||||
assert!(entries.next().is_none());
|
assert!(entries.next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMT LEAF
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_smt_leaf_serialization() {
|
||||||
|
let empty_leaf = SmtLeaf::new_empty(LeafIndex::new_max_depth(42));
|
||||||
|
|
||||||
|
let mut serialized = empty_leaf.to_bytes();
|
||||||
|
// extend buffer with random bytes
|
||||||
|
serialized.extend([1, 2, 3, 4, 5]);
|
||||||
|
let deserialized = SmtLeaf::read_from_bytes(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(empty_leaf, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_smt_leaf_serialization() {
|
||||||
|
let single_leaf = SmtLeaf::new_single(
|
||||||
|
RpoDigest::from([10_u64.into(), 11_u64.into(), 12_u64.into(), 13_u64.into()]),
|
||||||
|
[1_u64.into(), 2_u64.into(), 3_u64.into(), 4_u64.into()],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut serialized = single_leaf.to_bytes();
|
||||||
|
// extend buffer with random bytes
|
||||||
|
serialized.extend([1, 2, 3, 4, 5]);
|
||||||
|
let deserialized = SmtLeaf::read_from_bytes(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(single_leaf, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_smt_leaf_serialization_success() {
|
||||||
|
let multiple_leaf = SmtLeaf::new_multiple(vec![
|
||||||
|
(
|
||||||
|
RpoDigest::from([10_u64.into(), 11_u64.into(), 12_u64.into(), 13_u64.into()]),
|
||||||
|
[1_u64.into(), 2_u64.into(), 3_u64.into(), 4_u64.into()],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
RpoDigest::from([100_u64.into(), 101_u64.into(), 102_u64.into(), 13_u64.into()]),
|
||||||
|
[11_u64.into(), 12_u64.into(), 13_u64.into(), 14_u64.into()],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut serialized = multiple_leaf.to_bytes();
|
||||||
|
// extend buffer with random bytes
|
||||||
|
serialized.extend([1, 2, 3, 4, 5]);
|
||||||
|
let deserialized = SmtLeaf::read_from_bytes(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(multiple_leaf, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
fn build_empty_or_single_leaf_node(key: RpoDigest, value: Word) -> RpoDigest {
|
fn build_empty_or_single_leaf_node(key: RpoDigest, value: Word) -> RpoDigest {
|
||||||
if value == EMPTY_WORD {
|
if value == EMPTY_WORD {
|
||||||
SmtLeaf::Empty.hash()
|
SmtLeaf::new_empty(key.into()).hash()
|
||||||
} else {
|
} else {
|
||||||
SmtLeaf::Single((key, value)).hash()
|
SmtLeaf::Single((key, value)).hash()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use winter_math::StarkField;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash::rpo::{Rpo256, RpoDigest},
|
hash::rpo::{Rpo256, RpoDigest},
|
||||||
Word,
|
Word,
|
||||||
|
@ -8,7 +6,7 @@ use crate::{
|
||||||
use super::{EmptySubtreeRoots, MerkleError, MerklePath, NodeIndex, Vec};
|
use super::{EmptySubtreeRoots, MerkleError, MerklePath, NodeIndex, Vec};
|
||||||
|
|
||||||
mod full;
|
mod full;
|
||||||
pub use full::{Smt, SmtLeaf, SMT_DEPTH};
|
pub use full::{Smt, SmtLeaf, SmtLeafError, SMT_DEPTH};
|
||||||
|
|
||||||
mod simple;
|
mod simple;
|
||||||
pub use simple::SimpleSmt;
|
pub use simple::SimpleSmt;
|
||||||
|
@ -52,7 +50,7 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||||
/// The type for a leaf
|
/// The type for a leaf
|
||||||
type Leaf;
|
type Leaf;
|
||||||
/// The type for an opening (i.e. a "proof") of a leaf
|
/// The type for an opening (i.e. a "proof") of a leaf
|
||||||
type Opening: From<(MerklePath, Self::Leaf)>;
|
type Opening;
|
||||||
|
|
||||||
/// The default value used to compute the hash of empty leaves
|
/// The default value used to compute the hash of empty leaves
|
||||||
const EMPTY_VALUE: Self::Value;
|
const EMPTY_VALUE: Self::Value;
|
||||||
|
@ -83,7 +81,7 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||||
MerklePath::new(path)
|
MerklePath::new(path)
|
||||||
};
|
};
|
||||||
|
|
||||||
(merkle_path, leaf).into()
|
Self::path_and_leaf_to_opening(merkle_path, leaf)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a value at the specified key, returning the previous value associated with that key.
|
/// Inserts a value at the specified key, returning the previous value associated with that key.
|
||||||
|
@ -170,6 +168,11 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
|
||||||
|
|
||||||
/// Maps a key to a leaf index
|
/// Maps a key to a leaf index
|
||||||
fn key_to_leaf_index(key: &Self::Key) -> LeafIndex<DEPTH>;
|
fn key_to_leaf_index(key: &Self::Key) -> LeafIndex<DEPTH>;
|
||||||
|
|
||||||
|
/// Maps a (MerklePath, Self::Leaf) to an opening.
|
||||||
|
///
|
||||||
|
/// The length `path` is guaranteed to be equal to `DEPTH`
|
||||||
|
fn path_and_leaf_to_opening(path: MerklePath, leaf: Self::Leaf) -> Self::Opening;
|
||||||
}
|
}
|
||||||
|
|
||||||
// INNER NODE
|
// INNER NODE
|
||||||
|
@ -240,16 +243,3 @@ impl<const DEPTH: u8> TryFrom<NodeIndex> for LeafIndex<DEPTH> {
|
||||||
Self::new(node_index.value())
|
Self::new(node_index.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Word> for LeafIndex<SMT_MAX_DEPTH> {
|
|
||||||
fn from(value: Word) -> Self {
|
|
||||||
// We use the most significant `Felt` of a `Word` as the leaf index.
|
|
||||||
Self::new_max_depth(value[3].as_int())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RpoDigest> for LeafIndex<SMT_MAX_DEPTH> {
|
|
||||||
fn from(value: RpoDigest) -> Self {
|
|
||||||
Word::from(value).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle::{EmptySubtreeRoots, InnerNodeInfo, ValuePath},
|
merkle::{EmptySubtreeRoots, InnerNodeInfo, MerklePath, ValuePath},
|
||||||
EMPTY_WORD,
|
EMPTY_WORD,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -302,4 +302,8 @@ impl<const DEPTH: u8> SparseMerkleTree<DEPTH> for SimpleSmt<DEPTH> {
|
||||||
fn key_to_leaf_index(key: &LeafIndex<DEPTH>) -> LeafIndex<DEPTH> {
|
fn key_to_leaf_index(key: &LeafIndex<DEPTH>) -> LeafIndex<DEPTH> {
|
||||||
*key
|
*key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_and_leaf_to_opening(path: MerklePath, leaf: Word) -> ValuePath {
|
||||||
|
(path, leaf).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue