Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Smt struct (replacement to TieredSmt) #254

Merged
merged 122 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
674e8c8
smt: scaffolding
plafer Jan 10, 2024
a50b1cd
implement `update_leaf_at`
plafer Jan 10, 2024
f26d4c4
docstring
plafer Jan 10, 2024
5cfd566
move `recompute_nodes_from_index_to_root` into trait
plafer Jan 10, 2024
49c7a8d
implement `get_merkle_path`
plafer Jan 10, 2024
72e5b6d
introduce `DEPTH` const generic to remove runtime errors
plafer Jan 10, 2024
2cf5d1e
leaf index at depth 64
plafer Jan 10, 2024
2f7263b
add `depth()` method to smt
plafer Jan 10, 2024
c29d722
file separators
plafer Jan 10, 2024
266fac1
remove hash_value
plafer Jan 10, 2024
0cd9a0f
use Vec
plafer Jan 10, 2024
f0b3527
use `InnerNode` in `SimpleSmt`
plafer Jan 10, 2024
cf5ff36
SimpleSmt: make DEPTH const generic
plafer Jan 10, 2024
e111a7d
LeafIndex: derives
plafer Jan 10, 2024
e90f1af
implement SparseMerkleTree for SimpleSmt
plafer Jan 10, 2024
85f7923
Remove old comment
plafer Jan 10, 2024
d962ced
Rename get_merkle_path
plafer Jan 10, 2024
f1bc852
Remove `SimpleSmt::get_path()` and `get_leaf_path()`
plafer Jan 11, 2024
720552f
add tryfrom NodeIndex for LeafIndex
plafer Jan 11, 2024
7d52935
impl get_leaf_at on its own
plafer Jan 11, 2024
0400214
impl get_node in terms of get_leaf_at
plafer Jan 11, 2024
8dcb297
Remove `get_leaf()`
plafer Jan 11, 2024
b574bf6
rename get_leaf_at
plafer Jan 11, 2024
037ceab
remove get_branch_node
plafer Jan 11, 2024
7965436
remove insert_branch_node
plafer Jan 11, 2024
fda8dc7
remove duplicate recompute_nodes_from_index_to_root
plafer Jan 11, 2024
beaf2a1
remove private get_leaf_node
plafer Jan 11, 2024
2a2bf9d
remove trivial helper
plafer Jan 11, 2024
77def41
remove extra `root()` method
plafer Jan 11, 2024
61d98e9
add vscode gitignore
plafer Jan 11, 2024
2417ea6
remove old `get_leaf()`
plafer Jan 11, 2024
9faad2e
Rename `update_leaf_at`
plafer Jan 11, 2024
ae39832
add comment
plafer Jan 11, 2024
78def63
import `Vec` for `no_std`
plafer Jan 11, 2024
2d1ce1c
fix docstring
plafer Jan 11, 2024
52a4eb5
nightly fmt
plafer Jan 11, 2024
2e2288f
new_smt scaffolding
plafer Jan 11, 2024
d26f387
implement `hash_leaf()`
plafer Jan 11, 2024
a085e81
implement `get_leaf()`
plafer Jan 11, 2024
b54ab12
use `Vec`
plafer Jan 11, 2024
821f6ed
insert_leaf_node scaffold
plafer Jan 11, 2024
316ebd8
insert on leaf with single element
plafer Jan 11, 2024
d3cbfb1
cmp just keys
plafer Jan 11, 2024
42ce148
insert leaf: multiple
plafer Jan 11, 2024
4a26c5b
reorg file
plafer Jan 11, 2024
a4c93f7
no_std vec
plafer Jan 11, 2024
4106593
rename `SMT_MAX_DEPTH`
plafer Jan 11, 2024
c4638f1
use `SMT_MAX_DEPTH`
plafer Jan 11, 2024
576ed4c
`SMT_MIN_DEPTH`
plafer Jan 11, 2024
66701fe
Enforce `SMT_MIN_DEPTH` in `LeafIndex`
plafer Jan 11, 2024
1a6a259
nightly fmt
plafer Jan 11, 2024
782217a
`as_int` instead of `inner()`
plafer Jan 11, 2024
08b5604
rename `insert_leaf_node`
plafer Jan 11, 2024
efeaa75
add tests mod
plafer Jan 11, 2024
dbb3d84
most signifcant felt: last felt of word
plafer Jan 11, 2024
edb17cb
fix comments
plafer Jan 11, 2024
9fab7d6
`NewSmt` constructor
plafer Jan 11, 2024
007d562
fix comment
plafer Jan 11, 2024
2565b22
Remove `max_num_entries` in `NewSmt`
plafer Jan 12, 2024
ede3c72
Rename `NewSmt` to `Smt`
plafer Jan 12, 2024
dc925b6
Rename `NewSmt` to `Smt`, and `smt` module to `sparse_merkle_tree`
plafer Jan 12, 2024
c254af2
Replace `Value`'s `Default` trait bound with `EMPTY_VALUE` associated…
plafer Jan 12, 2024
9c9f246
remove `SimpleSmt`'s `EMPTY_VALUE` constant
plafer Jan 12, 2024
c97e9da
ABSTRACT -> PROVIDED
plafer Jan 12, 2024
42b6b28
remove `depth()` method
plafer Jan 12, 2024
cf46c91
`SmtKey`: use `RpoDigest` internally
plafer Jan 12, 2024
293558f
add constructors to `SmtKey`
plafer Jan 12, 2024
e161bfa
optimize `SmtLeaf::hash()`
plafer Jan 12, 2024
4a33f78
impl `to_elements()` and `into_elements()` for `SmtLeaf`
plafer Jan 12, 2024
e9548fc
Make `SparseMerkleTree` pub(crate)
plafer Jan 12, 2024
11d6862
add sections to `Smt`
plafer Jan 12, 2024
a606800
remove `SimpleSmt::depth()`
plafer Jan 12, 2024
4f9aa81
fix `get_inner_node()` docstring
plafer Jan 12, 2024
5c61261
remove `get_inner_node` from public API
plafer Jan 12, 2024
eded7c5
insert_value: if no value at key, always return `None`
plafer Jan 12, 2024
a26cbc8
cover case of inserting an `EMPTY_VALUE`
plafer Jan 12, 2024
c8edefd
`NewLeaf::insert`
plafer Jan 12, 2024
97fe6ce
update comment
plafer Jan 12, 2024
646c0fb
test_smt_insert_at_same_key
plafer Jan 12, 2024
bee733a
test_smt_insert_at_same_key_2
plafer Jan 12, 2024
7619a10
fix most_significant_felt for `SmtKey`
plafer Jan 12, 2024
2c7cee5
fix test_smt_insert_at_same_key_2
plafer Jan 12, 2024
2b65a82
test inserting multiple values
plafer Jan 12, 2024
8f69d40
test_smt_insert_multiple_values
plafer Jan 12, 2024
f8643bc
simplify interface
plafer Jan 12, 2024
56cc975
core::iter instead of std
plafer Jan 12, 2024
c0dfc2c
test_smt_path_to_keys_in_same_leaf_are_equal
plafer Jan 12, 2024
8e7457d
cleanup `perform_remove()`
plafer Jan 12, 2024
2179b86
clippy
plafer Jan 12, 2024
3d509d4
fix comment
plafer Jan 16, 2024
0158abd
add `depth()` function
plafer Jan 16, 2024
609e17c
add depth() fn to simple smt
plafer Jan 16, 2024
1202ff9
`SparseMerkleTtree`: rename `update_leaf` to `insert`
plafer Jan 16, 2024
6d10f8b
`Smt`: rename `update_leaf`
plafer Jan 16, 2024
4de3fac
`SimpleSmt`: rename `update_leaf`
plafer Jan 16, 2024
bd7d65d
fmt
plafer Jan 16, 2024
25f1c64
get_leaf_path -> open
plafer Jan 16, 2024
5171cd8
`SparseMerkleTree::Opening` type
plafer Jan 16, 2024
81e5d2a
`open()` returns `Self::Opening`
plafer Jan 16, 2024
c7149d5
Introduce `key_to_leaf_index`
plafer Jan 16, 2024
a9b53c0
open() takes a reference to `key`
plafer Jan 16, 2024
6637542
new directory structure
plafer Jan 16, 2024
d39aee9
`SimpleSmtTrait`: Return possibly borrowed data
plafer Jan 16, 2024
7c66cc6
Revert "`SimpleSmtTrait`: Return possibly borrowed data"
plafer Jan 17, 2024
52d9c39
docstring
plafer Jan 17, 2024
deda8f1
fix `open()` docstring
plafer Jan 17, 2024
2685aa9
move `depth()` function up
plafer Jan 17, 2024
9088d89
fix var name
plafer Jan 17, 2024
e5bf0ce
move `open()` down
plafer Jan 17, 2024
49cdf63
fix comment
plafer Jan 17, 2024
7e9afc9
`ValuePath`: adjust constructor
plafer Jan 17, 2024
88797c3
Use `ValuePath` in simple smt
plafer Jan 17, 2024
dbbbf28
Merge branch 'next' into plafer-add-smt-struct
plafer Jan 18, 2024
7897d4e
Add `Self::EMPTY_VALUE`
plafer Jan 19, 2024
8d375d2
Use `Self::EMPTY_VALUE`
plafer Jan 19, 2024
e6563e5
fix comment
plafer Jan 19, 2024
13fc431
remove unused import
plafer Jan 19, 2024
6e0a969
adjust `remove` docstring
plafer Jan 19, 2024
b6458a4
fix docstrings
plafer Jan 19, 2024
50f653d
clean up `test_smt_insert_at_same_key_2`
plafer Jan 19, 2024
a880c88
Introduce SmtLeaf::Empty
plafer Jan 19, 2024
21214ca
add empty leaf hash test
plafer Jan 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/merkle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ mod path;
pub use path::{MerklePath, RootPath, ValuePath};

mod smt;
pub use smt::{LeafIndex, SimpleSmt, SMT_MAX_DEPTH, SMT_MIN_DEPTH};
pub use smt::{LeafIndex, SimpleSmt, Smt, SmtLeaf, SMT_DEPTH, SMT_MAX_DEPTH, SMT_MIN_DEPTH};

mod tiered_smt;
pub use tiered_smt::{TieredSmt, TieredSmtProof, TieredSmtProofError};
Expand Down
381 changes: 381 additions & 0 deletions src/merkle/smt/full/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
use core::cmp::Ordering;

use winter_math::StarkField;

use crate::hash::rpo::Rpo256;
use crate::merkle::EmptySubtreeRoots;
use crate::utils::{
collections::{BTreeMap, BTreeSet, Vec},
vec,
};
use crate::{Felt, EMPTY_WORD};

use super::{
InnerNode, LeafIndex, MerkleError, MerklePath, NodeIndex, RpoDigest, SparseMerkleTree, Word,
};

#[cfg(test)]
mod tests;

// CONSTANTS
// ================================================================================================

pub const SMT_DEPTH: u8 = 64;

// SMT
// ================================================================================================

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Smt {
root: RpoDigest,
leaves: BTreeMap<u64, SmtLeaf>,
inner_nodes: BTreeMap<NodeIndex, InnerNode>,
}

impl Smt {
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
// CONSTANTS
// --------------------------------------------------------------------------------------------
/// The default value used to compute the hash of empty leaves
pub const EMPTY_VALUE: Word = <Self as SparseMerkleTree<SMT_DEPTH>>::EMPTY_VALUE;

// CONSTRUCTORS
// --------------------------------------------------------------------------------------------

/// Returns a new [Smt].
///
/// All leaves in the returned tree are set to [Self::EMPTY_VALUE].
pub fn new() -> Self {
let root = *EmptySubtreeRoots::entry(SMT_DEPTH, 0);

Self {
root,
leaves: BTreeMap::new(),
inner_nodes: BTreeMap::new(),
}
}

/// Returns a new [Smt] instantiated with leaves set as specified by the provided entries.
///
/// All leaves omitted from the entries list are set to [Self::EMPTY_VALUE].
///
/// # Errors
/// Returns an error if the provided entries contain multiple values for the same key.
pub fn with_entries(
entries: impl IntoIterator<Item = (RpoDigest, Word)>,
) -> Result<Self, MerkleError> {
// create an empty tree
let mut tree = Self::new();

// This being a sparse data structure, the EMPTY_WORD is not assigned to the `BTreeMap`, so
// entries with the empty value need additional tracking.
let mut key_set_to_zero = BTreeSet::new();

for (key, value) in entries {
let old_value = tree.insert(key, value);

if old_value != EMPTY_WORD || key_set_to_zero.contains(&key) {
return Err(MerkleError::DuplicateValuesForIndex(
LeafIndex::<SMT_DEPTH>::from(key).value(),
));
}

if value == EMPTY_WORD {
key_set_to_zero.insert(key);
};
}
Ok(tree)
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns the depth of the tree
pub const fn depth(&self) -> u8 {
SMT_DEPTH
}

/// Returns the root of the tree
pub fn root(&self) -> RpoDigest {
<Self as SparseMerkleTree<SMT_DEPTH>>::root(self)
}

/// Returns the leaf at the specified index.
pub fn get_leaf(&self, key: &RpoDigest) -> SmtLeaf {
<Self as SparseMerkleTree<SMT_DEPTH>>::get_leaf(self, key)
}

/// 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.
pub fn open(&self, key: &RpoDigest) -> (MerklePath, SmtLeaf) {
<Self as SparseMerkleTree<SMT_DEPTH>>::open(self, key)
}

// STATE MUTATORS
// --------------------------------------------------------------------------------------------

/// Inserts a value at the specified key, returning the previous value associated with that key.
/// Recall that by definition, any key that hasn't been updated is associated with
/// [`Self::EMPTY_VALUE`].
///
/// This also recomputes all hashes between the leaf (associated with the key) and the root,
/// updating the root itself.
pub fn insert(&mut self, key: RpoDigest, value: Word) -> Word {
<Self as SparseMerkleTree<SMT_DEPTH>>::insert(self, key, value)
}

// HELPERS
// --------------------------------------------------------------------------------------------

/// Inserts `value` at leaf index pointed to by `key`. `value` is guaranteed to not be the empty
/// value, such that this is indeed an insertion.
fn perform_insert(&mut self, key: RpoDigest, value: Word) -> Option<Word> {
debug_assert_ne!(value, Self::EMPTY_VALUE);

let leaf_index: LeafIndex<SMT_DEPTH> = Self::key_to_leaf_index(&key);

match self.leaves.get_mut(&leaf_index.value()) {
Some(leaf) => leaf.insert(key, value),
None => {
self.leaves.insert(leaf_index.value(), SmtLeaf::Single((key, value)));

None
}
}
}

/// Removes key-value pair at leaf index pointed to by `key` if it exists.
fn perform_remove(&mut self, key: RpoDigest) -> Option<Word> {
let leaf_index: LeafIndex<SMT_DEPTH> = Self::key_to_leaf_index(&key);

if let Some(leaf) = self.leaves.get_mut(&leaf_index.value()) {
let (old_value, is_empty) = leaf.remove(key);
if is_empty {
self.leaves.remove(&leaf_index.value());
}
old_value
} else {
// there's nothing stored at the leaf; nothing to update
None
}
}
}

impl SparseMerkleTree<SMT_DEPTH> for Smt {
type Key = RpoDigest;
type Value = Word;
type Leaf = SmtLeaf;
type Opening = (MerklePath, SmtLeaf);

const EMPTY_VALUE: Self::Value = EMPTY_WORD;

fn root(&self) -> RpoDigest {
self.root
}

fn set_root(&mut self, root: RpoDigest) {
self.root = root;
}

fn get_inner_node(&self, index: NodeIndex) -> InnerNode {
self.inner_nodes.get(&index).cloned().unwrap_or_else(|| {
let node = EmptySubtreeRoots::entry(SMT_DEPTH, index.depth() + 1);

InnerNode { left: *node, right: *node }
})
}

fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) {
self.inner_nodes.insert(index, inner_node);
}

fn insert_value(&mut self, key: Self::Key, value: Self::Value) -> Option<Self::Value> {
// inserting an `EMPTY_VALUE` is equivalent to removing any value associated with `key`
if value != Self::EMPTY_VALUE {
self.perform_insert(key, value)
} else {
self.perform_remove(key)
}
}

fn get_leaf(&self, key: &RpoDigest) -> Self::Leaf {
let leaf_pos = LeafIndex::<SMT_DEPTH>::from(*key).value();

match self.leaves.get(&leaf_pos) {
Some(leaf) => leaf.clone(),
None => SmtLeaf::Empty,
}
}

fn hash_leaf(leaf: &Self::Leaf) -> RpoDigest {
leaf.hash()
}

fn key_to_leaf_index(key: &RpoDigest) -> LeafIndex<SMT_DEPTH> {
let most_significant_felt = key[3];
LeafIndex::new_max_depth(most_significant_felt.as_int())
}
}

impl Default for Smt {
fn default() -> Self {
Self::new()
}
}

// LEAF
// ================================================================================================

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SmtLeaf {
Empty,
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> {
match self {
SmtLeaf::Empty => Vec::new(),
SmtLeaf::Single(kv_pair) => kv_to_elements(kv_pair).collect(),
SmtLeaf::Multiple(kv_pairs) => kv_pairs.into_iter().flat_map(kv_to_elements).collect(),
}
}

/// 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
// ---------------------------------------------------------------------------------------------

/// 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
// ================================================================================================

/// 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
}
Loading
Loading