diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 5ae1468e..759d3235 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: OutPoint); /// Verifies inclusion proof for a single output. fn verify( @@ -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> = Default::default(); + let mut n: felt252 = PoseidonTrait::new().update_with(outpoint).finalize(); + let mut h = 0_usize; + let mut r: Option = *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, proof: @UtreexoBatchProof + ) -> Result<(), UtreexoError> { + Result::Ok(()) + } + + fn delete_batch(ref self: UtreexoState, proof: @UtreexoBatchProof) {} +} + #[derive(Drop, Copy, PartialEq)] pub enum UtreexoError {} @@ -97,7 +169,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..f652964c 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 { @@ -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); } } @@ -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> = 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); + } +} +