Skip to content

Commit

Permalink
add leaf to utreexo accumulator
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeanmichel7 committed Sep 15, 2024
1 parent a79c576 commit 540abe2
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 4 deletions.
76 changes: 74 additions & 2 deletions packages/consensus/src/types/utreexo.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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: OutPoint);

/// Verifies inclusion proof for a single output.
fn verify(
Expand All @@ -72,6 +75,75 @@ 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: OutPoint) {
let mut new_roots: Array<Option<felt252>> = Default::default();
let mut n: felt252 = PoseidonTrait::new().update_with(outpoint).finalize();
let mut h = 0_usize;
let mut r: Option<felt252> = *self.roots[h];
let len = self.roots.len();

// loop until an empty root and compute new root
while r.is_some() {
n = parent_hash(r.unwrap(), n, 0x1_u256.into());
new_roots.append(Option::None);
h += 1;
r = *self.roots[h];
};

// add new root to height h
new_roots.append(Option::Some(n));

// add None, which represents the end of the array if we add leaf to the highest one
if (h == len - 1) {
new_roots.append(Option::None);
}

// copy the rest of the table
h += 1;
while h < len {
new_roots.append(*self.roots[h]);
h += 1;
};

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<OutPoint>, proof: @UtreexoBatchProof
) -> Result<(), UtreexoError> {
Result::Ok(())
}

fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof) {}
}

#[derive(Drop, Copy, PartialEq)]
pub enum UtreexoError {}

Expand All @@ -97,7 +169,7 @@ pub struct UtreexoBatchProof {

pub impl UtreexoStateDefault of Default<UtreexoState> {
fn default() -> UtreexoState {
UtreexoState { roots: array![].span(), num_leaves: 0, }
UtreexoState { roots: array![Option::None].span(), num_leaves: 0, }
}
}

Expand Down
88 changes: 86 additions & 2 deletions packages/consensus/src/types/utxo_set.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,7 +36,8 @@ pub impl UtxoSetImpl of UtxoSetTrait {
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(output);
}
}

Expand All @@ -50,3 +52,85 @@ pub impl UtxoSetImpl of UtxoSetTrait {
}
}
}

#[cfg(test)]
mod tests {
use super::{UtxoSet, UtxoSetTrait, OutPoint};
use consensus::types::transaction::TxOut;

#[test]
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
};

// https://learnmeabitcoin.com/explorer/tx/f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16#input-0
let outpoint2 = OutPoint {
txid: 0x0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9_u256.into(),
vout: 0,
data: TxOut {
// https://learnmeabitcoin.com/explorer/tx/0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9#output-0
value: 5000000000,
pk_script: @"0x410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac",
cached: false
},
block_height: 9,
block_time: 1231473279,
is_coinbase: true
};

utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint2);
utxo_set.add(outpoint2);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);
utxo_set.add(outpoint1);

let roots_expected: Span<Option<felt252>> = array![
Option::None,
Option::Some(0x182CC7952C3A9FFC569132479DEA1353B61A3F1727FA391D21AE202BEEC5031),
Option::Some(0x3A3A05E57766C4C828AD2BAA9B072C4CBF679F06486D050F1FB8F2BA16D639D),
Option::Some(0x61FCDD49D86CFF9555F060DA467AB666A965DA7638E3F7FBC38646B93048517),
Option::Some(0x19AC187E0BA46D9CDB30E071F9C4740C1B8D62B5B0AEEFAB0FADEB9EB9C192A),
Option::None,
]
.span();

assert_eq!(utxo_set.utreexo_state.roots, roots_expected);
assert_eq!(utxo_set.utreexo_state.num_leaves, 30);
}
}

0 comments on commit 540abe2

Please sign in to comment.