From cbcd31b54b6bfa955a0a91a46ea6dd0bbea905d2 Mon Sep 17 00:00:00 2001 From: Jeanmichel7 Date: Tue, 17 Sep 2024 23:44:17 +0200 Subject: [PATCH] review --- packages/consensus/src/types/utreexo.cairo | 71 +++++++- packages/consensus/src/types/utxo_set.cairo | 182 +++++++++++++++++++- scripts/data/client.sh | 2 +- scripts/data/requirements.txt | 3 +- scripts/data/utreexo.py | 147 ++++++++++++++++ 5 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 scripts/data/utreexo.py diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 5ae1468e..521c11cd 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -28,7 +28,10 @@ //! Read more about utreexo: https://eprint.iacr.org/2019/611.pdf use super::transaction::OutPoint; +use core::poseidon::PoseidonTrait; +use core::hash::{HashStateTrait, HashStateExTrait}; use core::fmt::{Display, Formatter, Error}; +use utils::hash::Digest; /// Accumulator representation of the state aka "Compact State Node". /// Part of the chain state. @@ -50,7 +53,7 @@ pub trait UtreexoAccumulator { /// /// Note that this call also pushes old UTXOs "to the left", to a larger subtree. /// This mechanism ensures that short-lived outputs have small inclusion proofs. - fn add(ref self: UtreexoState, output: OutPoint); + fn add(ref self: UtreexoState, outpoint_hash: felt252); /// Verifies inclusion proof for a single output. fn verify( @@ -72,6 +75,70 @@ pub trait UtreexoAccumulator { fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof); } +// https://eprint.iacr.org/2019/611.pdf Algorithm 1 AddOne +// p18 +// To prevent such an attack, we require that the data inserted into the +// accumulator be not just the hash of a TXO, which is controllable by the +// attacker, but instead the concatenation of the TXO data with the block +// hash in which the TXO is confirmed. The attacker does not know the block +// hash before the TXO is confirmed, and it is not alterable by the attacker +// after confirmation (without significant cost). Verifiers, when inserting into +// the accumulator, perform this concatenation themselves after checking the +// proof of work of the block. Inclusion proofs contain this block hash data so +// that the leaf hash value can be correctly computed. +fn parent_hash(left: felt252, right: felt252, _block_hash: Digest) -> felt252 { + let parent_data = (left, right); + PoseidonTrait::new().update_with(parent_data).finalize() +} + +pub impl UtreexoAccumulatorImpl of UtreexoAccumulator { + // https://eprint.iacr.org/2019/611.pdf Algorithm 1 AddOne + fn add(ref self: UtreexoState, outpoint_hash: felt252) { + let mut new_roots: Array> = Default::default(); + let mut n: felt252 = outpoint_hash; + let mut first_none_found: bool = false; + + for root in self + .roots { + if (!first_none_found) { + if (root.is_none()) { + first_none_found = true; + new_roots.append(Option::Some(n)); + } else { + n = parent_hash((*root).unwrap(), n, 0x0_u256.into()); + new_roots.append(Option::None); + } + } else { + new_roots.append(*root); + } + }; + + //check if end with Option::None + if (new_roots[new_roots.len() - 1].is_some()) { + new_roots.append(Option::None); + } + + self.roots = new_roots.span(); + self.num_leaves += 1_u64; + } + + fn verify( + self: @UtreexoState, output: @OutPoint, proof: @UtreexoProof + ) -> Result<(), UtreexoError> { + Result::Ok(()) + } + + fn delete(ref self: UtreexoState, proof: @UtreexoProof) {} + + fn verify_batch( + self: @UtreexoState, outputs: Span, proof: @UtreexoBatchProof + ) -> Result<(), UtreexoError> { + Result::Ok(()) + } + + fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof) {} +} + #[derive(Drop, Copy, PartialEq)] pub enum UtreexoError {} @@ -97,7 +164,7 @@ pub struct UtreexoBatchProof { pub impl UtreexoStateDefault of Default { fn default() -> UtreexoState { - UtreexoState { roots: array![].span(), num_leaves: 0, } + UtreexoState { roots: array![Option::None].span(), num_leaves: 0, } } } diff --git a/packages/consensus/src/types/utxo_set.cairo b/packages/consensus/src/types/utxo_set.cairo index ad7b2db4..b5bfb12f 100644 --- a/packages/consensus/src/types/utxo_set.cairo +++ b/packages/consensus/src/types/utxo_set.cairo @@ -9,11 +9,12 @@ //! In order to prove that the UTXOs provided actually belong to the set we use either //! Utreexo accumulator or local cache. +//input contain outpoint contain output use core::dict::Felt252Dict; use core::hash::{HashStateTrait, HashStateExTrait}; use core::poseidon::PoseidonTrait; -use super::utreexo::UtreexoState; use super::transaction::OutPoint; +use super::utreexo::{UtreexoState, UtreexoAccumulator}; #[derive(Default, Destruct)] pub struct UtxoSet { @@ -32,10 +33,18 @@ pub impl UtxoSetImpl of UtxoSetTrait { } fn add(ref self: UtxoSet, output: OutPoint) { + let outpoint_hash = PoseidonTrait::new().update_with(output).finalize(); if output.data.cached { - let outpoint_hash = PoseidonTrait::new().update_with(output).finalize(); self.cache.insert(outpoint_hash, true); - } else { // TODO: update utreexo roots + } else { + self.utreexo_state.add(outpoint_hash); + } + } + + // coexistant ? + fn leaves_to_add(ref self: UtxoSet, leaves_hash: Array) { + for leave in leaves_hash { + self.utreexo_state.add(leave); } } @@ -50,3 +59,170 @@ pub impl UtxoSetImpl of UtxoSetTrait { } } } + +#[cfg(test)] +mod tests { + use super::{UtxoSet, UtxoSetTrait, OutPoint}; + use consensus::types::transaction::TxOut; + + use core::hash::{HashStateTrait, HashStateExTrait}; + use core::poseidon::PoseidonTrait; + + #[test] + /// To check the validity of expected fields, there is a python program from ZeroSync + /// https://github.com/ZeroSync/ZeroSync/blob/main/src/utxo_set/bridge_node.py + /// $ python scripts/data/utreexo.py + fn test_utreexo_add1() { + let mut utxo_set: UtxoSet = UtxoSetTrait::new(Default::default()); + + // https://learnmeabitcoin.com/explorer/tx/b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082#input-0 + // coinbase outpoint + let outpoint1 = OutPoint { + txid: 0x0000000000000000000000000000000000000000000000000000000000000000_u256.into(), + vout: 4294967295, + data: TxOut { value: 0, pk_script: @"0x", cached: false }, + block_height: 0, + block_time: 0, + is_coinbase: false + }; + let outpoint1: felt252 = PoseidonTrait::new().update_with(outpoint1).finalize(); + + // add first leave to empty utreexo + utxo_set.leaves_to_add(array![outpoint1]); + let expected: Span> = array![ + Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add first leave"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 1); + + // add second leave + utxo_set.leaves_to_add(array![outpoint1]); + let expected: Span> = array![ + Option::None, + Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add second leave"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 2); + + // add thirdth leave + utxo_set.leaves_to_add(array![outpoint1]); + let expected: Span> = array![ + Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), + Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add thirdth leave"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 3); + + // add fourth leave + utxo_set.leaves_to_add(array![outpoint1]); + let expected: Span> = array![ + Option::None, + Option::None, + Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add fourth leave"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 4); + + // add fifth leave + utxo_set.leaves_to_add(array![outpoint1]); + let expected: Span> = array![ + Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), + Option::None, + Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add fifth leave"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 5); + + // add 3 leaves + utxo_set.leaves_to_add(array![outpoint1, outpoint1, outpoint1]); + let expected: Span> = array![ + Option::None, + Option::None, + Option::None, + Option::Some(0x708EB39E30B035376EC871F8F17CD3BADAE6A68406B13C3BB671009D56F5AD), + Option::None + ] + .span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add 3 leaves"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 8); + + // add 22 leaves + utxo_set + .leaves_to_add( + array![ + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1, + outpoint1 + ] + ); + let expected: Span> = [ + Option::None(()), + Option::Some(0x738A7C495E564574993BBCB6A62D65C3C570BB81C63801066AF8934649F66F6), + Option::Some(0x25D0DE35DD446E3D35504866FD7A04D4245E01B5908E19EAA70ABA84DD5A1F1), + Option::Some(0x708EB39E30B035376EC871F8F17CD3BADAE6A68406B13C3BB671009D56F5AD), + Option::Some(0x58D6BEF6CFC28638FB4C8271355961F50922BCC1577DD2B6D04E11B7A911702), + Option::None(()) + ].span(); + assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add 22 leaves"); + assert_eq!(utxo_set.utreexo_state.num_leaves, 30); + } + /// +/// python scripts/data/utreexo.py +/// +/// Roots: +/// ['0x0291f8f5fc449d42c715b529e542f24a80136d18f4a85de28829cd3dcaac1b9c', '', '', '', '', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: +/// ['', '0x0738a7c495e564574993bbcb6a62d65c3c570bb81c63801066af8934649f66f6', '', '', '', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: ['0x0291f8f5fc449d42c715b529e542f24a80136d18f4a85de28829cd3dcaac1b9c', +/// '0x0738a7c495e564574993bbcb6a62d65c3c570bb81c63801066af8934649f66f6', '', '', '', '', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: ['', '', '0x025d0de35dd446e3d35504866fd7a04d4245e01b5908e19eaa70aba84dd5a1f1', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: ['0x0291f8f5fc449d42c715b529e542f24a80136d18f4a85de28829cd3dcaac1b9c', '', +/// '0x025d0de35dd446e3d35504866fd7a04d4245e01b5908e19eaa70aba84dd5a1f1', '', '', '', '', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: ['', '', '', '0x00708eb39e30b035376ec871f8f17cd3badae6a68406b13c3bb671009d56f5ad', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +/// +/// Roots: ['', '0x0738a7c495e564574993bbcb6a62d65c3c570bb81c63801066af8934649f66f6', +/// '0x025d0de35dd446e3d35504866fd7a04d4245e01b5908e19eaa70aba84dd5a1f1', +/// '0x00708eb39e30b035376ec871f8f17cd3badae6a68406b13c3bb671009d56f5ad', +/// '0x058d6bef6cfc28638fb4c8271355961f50922bcc1577dd2b6d04e11b7a911702', '', '', '', '', '', +/// '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] +} diff --git a/scripts/data/client.sh b/scripts/data/client.sh index 5b39d1a2..f807ad7f 100644 --- a/scripts/data/client.sh +++ b/scripts/data/client.sh @@ -4,7 +4,7 @@ #set -o pipefail; base_dir=".client_cache" - +echo "WHOUAAAAASH" start=${1:-0} end=${2:-100} step=${3:-1} diff --git a/scripts/data/requirements.txt b/scripts/data/requirements.txt index 4ce787d1..174816cf 100644 --- a/scripts/data/requirements.txt +++ b/scripts/data/requirements.txt @@ -1,4 +1,5 @@ requests==2.32.3 black==24.8.0 flake8==7.1.1 -flake8-black==0.3.6 \ No newline at end of file +flake8-black==0.3.6 +poseidon_py==0.1.5 \ No newline at end of file diff --git a/scripts/data/utreexo.py b/scripts/data/utreexo.py new file mode 100644 index 00000000..77465e7f --- /dev/null +++ b/scripts/data/utreexo.py @@ -0,0 +1,147 @@ +# SPDX-FileCopyrightText: 2022 ZeroSync +# +# SPDX-License-Identifier: MIT + +from poseidon_py.poseidon_hash import poseidon_hash_many + + +# The array of trees in the forest +# [T_1, T_2, T_4, T_8, ... ] +root_nodes = [None] * 27 + +# The set of leaf nodes in the forest +leaf_nodes = dict() + +class Node: + def __init__(self, key, left=None, right=None): + self.val = key + self.left = left + self.right = right + self.parent = None + + +def parent_node(root1, root2): + # Convert hexadecimal strings to integers + val1 = int(root1.val, 16) + val2 = int(root2.val, 16) + # Perform Poseidon hash + root = poseidon_hash_many([val1, val2]) + # Convert the result back to a left-padded hexadecimal string + root_hex = f"0x{root:064x}" + root_node = Node(root_hex, root1, root2) + root1.parent = root_node + root2.parent = root_node + return root_node + + +def utreexo_add(leaf): + # if leaf in leaf_nodes: + # raise Exception("Leaf exists already") + + n = Node(f"0x{leaf:064x}") + leaf_nodes[leaf] = n + h = 0 + r = root_nodes[h] + while r is not None: + n = parent_node(r, n) + root_nodes[h] = None + h = h + 1 + r = root_nodes[h] + + root_nodes[h] = n + return root_nodes + + +def utreexo_delete(leaf): + leaf_node = leaf_nodes[leaf] + del leaf_nodes[leaf] + + proof, leaf_index = inclusion_proof(leaf_node) + + n = None + h = 0 + while h < len(proof): + p = proof[h] # Iterate over each proof element + if n is not None: + n = parent_node(p, n) + elif root_nodes[h] is None: + p.parent = None + root_nodes[h] = p + else: + n = parent_node(p, root_nodes[h]) + root_nodes[h] = None + h = h + 1 + + if n is not None: + n.parent = None + + root_nodes[h] = n + + proof = list(map(lambda node: node.val, proof)) + return proof, leaf_index + + +def inclusion_proof(node): + if node.parent is None: + return [], 0 + + parent = node.parent + path, leaf_index = inclusion_proof(parent) + + if node == parent.left: + path.insert(0, parent.right) + leaf_index = leaf_index * 2 + else: + path.insert(0, parent.left) + leaf_index = leaf_index * 2 + 1 + + return path, leaf_index + + +def reset_utreexo(): + global root_nodes, leaf_nodes + root_nodes = [None] * 27 + leaf_nodes = dict() + + +def print_roots(): + print( + "Roots:", + list(map(lambda node: node.val if node is not None else "", root_nodes)), + ) + +def test_utreexo_add(): + # Add first leaf to the empty Utreexo + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add second leaf + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add third leaf + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add fourth leaf + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add fifth leaf + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add 3 leaves + for i in range(3): + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + + # Add 22 leaves + for i in range(22): + utreexo_add(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C) + print_roots() + +# Example usage +if __name__ == "__main__": + # Add some elements + test_utreexo_add()