-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
be2ea99
commit bfdf99b
Showing
4 changed files
with
467 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
//! Utreexo is an accumulator for the Bitcoin unspent transaction set. | ||
//! | ||
//! It allows to verify that a certain transaction output exists | ||
//! and still unspent at a particular block while maintaining only | ||
//! a very compact state. | ||
//! | ||
//! It is also useful for transaction validation (our case) since it | ||
//! allows to "fetch" the output spent by a particular input in the | ||
//! validated transaction. This is typically required to calculate | ||
//! transaction fee and also to check that script execution succeeds. | ||
//! | ||
//! The expected workflow is the following: | ||
//! - For coinbase and inputs spending TXOs created in the same block | ||
//! utreexo accumulator is not updated (local cache is used instead); | ||
//! - For all other inputs we provide respective TXOs (extended) plus | ||
//! plus inclusion proof (can be individual, batched, or hybrid); | ||
//! - The client has to verify the inclusion proof and then remove all | ||
//! the TXOs from the utreexo set, that way updating the state; | ||
//! - For every output that is not spent in the same block we add the | ||
//! extended (additionally contains txid and output index aka vout) output | ||
//! to the accumulator (i.e. update the utreexo state). | ||
//! | ||
//! Note that utreexo data and proofs are provided via program input so | ||
//! it is not part of prover/verifier arguments. Utreexo state (which | ||
//! is part of the chain state) is what allows us to constrain | ||
//! these inputs and ensure integrity. | ||
//! | ||
//! 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; | ||
use core::nullable::{NullableTrait}; | ||
use core::dict::Felt252Dict; | ||
|
||
|
||
#[derive(Destruct, Default)] | ||
pub struct UtxoSetDictTest { | ||
/// Utreexo state. | ||
pub utreexo_state: UtreexoStateDict, | ||
/// Hashes of UTXOs created within the current block(s). | ||
/// Note that to preserve the ordering, cache has to be updated right after a | ||
/// particular output is created or spent. | ||
cache: Felt252Dict<Nullable<OutPoint>>, | ||
} | ||
|
||
#[generate_trait] | ||
pub impl UtxoSetDictTestImpl of UtxoSetDictTestTrait { | ||
fn new(utreexo_state: UtreexoStateDict) -> UtxoSetDictTest { | ||
UtxoSetDictTest { utreexo_state, ..Default::default() } | ||
} | ||
|
||
fn add(ref self: UtxoSetDictTest, outpoint: OutPoint) { | ||
if outpoint.data.cached { | ||
self.cache.insert(outpoint.block_height.into(), NullableTrait::new(outpoint)); | ||
} else { | ||
self.utreexo_state.add(outpoint); | ||
} | ||
} | ||
|
||
fn delete(ref self: UtxoSetDictTest, outpoint: @OutPoint) { | ||
if *outpoint.data.cached { // TODO: remove from cache (+ verify inclusion) | ||
} else { // TODO: update utreexo roots (+ verify inclusion) | ||
// If batched proofs are used then do nothing | ||
} | ||
} | ||
} | ||
|
||
|
||
/// Accumulator representation of the state aka "Compact State Node". | ||
/// Part of the chain state. | ||
#[derive(Destruct)] | ||
pub struct UtreexoStateDict { | ||
/// Roots of the Merkle tree forest. | ||
/// Index is the root height, None means a gap. | ||
pub roots: Felt252Dict<Nullable<felt252>>, | ||
/// Total number of leaves (in the bottom-most row). | ||
/// Required to calculate the number of nodes in a particular row. | ||
/// Can be reconstructed from the roots, but cached for convenience. | ||
pub num_leaves: u64, | ||
} | ||
|
||
/// Accumulator interface | ||
pub trait UtreexoAccumulatorDict { | ||
/// Adds single output to the accumulator. | ||
/// The order *is important*: adding A,B and B,A would result in different states. | ||
/// | ||
/// 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: UtreexoStateDict, outpoint: OutPoint); | ||
|
||
/// Verifies inclusion proof for a single output. | ||
fn verify( | ||
self: @UtreexoStateDict, output: @OutPoint, proof: @UtreexoProofDict | ||
) -> Result<(), UtreexoError>; | ||
|
||
/// Removes single output from the accumlator (order is important). | ||
/// | ||
/// Note that once verified, the output itself is not required for deletion, | ||
/// the leaf index plus inclusion proof is enough. | ||
fn delete(ref self: UtreexoStateDict, proof: @UtreexoProofDict); | ||
|
||
/// Verifies batch proof for multiple outputs (e.g. all outputs in a block). | ||
fn verify_batch( | ||
self: @UtreexoStateDict, outputs: Span<OutPoint>, proof: @UtreexoBatchProofDict | ||
) -> Result<(), UtreexoError>; | ||
|
||
/// Removes multiple outputs from the accumulator. | ||
fn delete_batch(ref self: UtreexoStateDict, proof: @UtreexoBatchProofDict); | ||
} | ||
|
||
pub fn parent_hash(left: felt252, right: felt252, _block_hash: Digest) -> felt252 { | ||
let parent_data = (left, right); // gas 1316516 | ||
|
||
// PoseidonTrait::new().update(left).update(right).update(block_hash).finalize() | ||
PoseidonTrait::new().update_with(parent_data).finalize() | ||
} | ||
|
||
struct LeafHash { | ||
value: u64, | ||
pk_script: @ByteArray | ||
} | ||
|
||
pub impl UtreexoAccumulatorDictImpl of UtreexoAccumulatorDict { | ||
// n ← leaf . n is initially the leaf to add | ||
// h ← 0 . height is initially 0 | ||
// r ← acc[h] . r is the lowest root | ||
// while r != ∅ do . loop until an empty root | ||
// n ← Parent(r, n) | ||
// acc[h] ← ∅ | ||
// h ← h + 1 | ||
// r ← acc[h] | ||
// acc[h] ← n | ||
|
||
fn add(ref self: UtreexoStateDict, outpoint: OutPoint) { | ||
//TODO impl TxOutHash of Hash<TxOut> {} to remove cache | ||
let mut n: felt252 = PoseidonTrait::new().update_with(outpoint.data).finalize(); | ||
|
||
let mut h = 0; | ||
let mut r = self.roots.get(h); | ||
|
||
loop { | ||
if (r.is_null()) { | ||
break; | ||
} | ||
n = parent_hash(r.deref(), n, 0x01_u256.into()); | ||
self.roots.insert(h, Default::default()); | ||
h += 1; | ||
r = self.roots.get(h); | ||
}; | ||
|
||
self.roots.insert(h, NullableTrait::new(n)); | ||
// println!("add leaf to dict: {h}: {n}"); | ||
self.num_leaves += 1_u64; | ||
} | ||
|
||
fn verify( | ||
self: @UtreexoStateDict, output: @OutPoint, proof: @UtreexoProofDict | ||
) -> Result<(), UtreexoError> { | ||
Result::Ok(()) | ||
} | ||
|
||
fn delete(ref self: UtreexoStateDict, proof: @UtreexoProofDict) {} | ||
|
||
fn verify_batch( | ||
self: @UtreexoStateDict, outputs: Span<OutPoint>, proof: @UtreexoBatchProofDict | ||
) -> Result<(), UtreexoError> { | ||
Result::Ok(()) | ||
} | ||
|
||
fn delete_batch(ref self: UtreexoStateDict, proof: @UtreexoBatchProofDict) {} | ||
} | ||
|
||
// #[derive(Drop, Copy, PartialEq)] | ||
pub enum UtreexoError {} | ||
|
||
/// Utreexo inclusion proof for a single transaction output. | ||
#[derive(Drop, Copy)] | ||
pub struct UtreexoProofDict { | ||
/// Index of the leaf in the forest, but also an encoded binary path, | ||
/// specifying which sibling node is left and which is right. | ||
pub leaf_index: u64, | ||
/// List of sibling nodes required to calculate the root. | ||
pub proof: Span<felt252>, | ||
} | ||
|
||
/// Utreexo inclusion proof for multiple outputs. | ||
/// Compatible with https://github.com/utreexo/utreexo | ||
#[derive(Drop, Copy)] | ||
pub struct UtreexoBatchProofDict { | ||
/// Indices of leaves to be deleted (ordered starting from 0, left to right). | ||
pub targets: Span<u64>, | ||
/// List of sibling nodes required to calculate the root. | ||
pub proof: Span<felt252>, | ||
} | ||
|
||
pub impl UtreexoStateDictDefaultDict of Default<UtreexoStateDict> { | ||
fn default() -> UtreexoStateDict { | ||
UtreexoStateDict { roots: Default::default(), num_leaves: 0, } | ||
} | ||
} | ||
|
||
// impl UtreexoStateDictDisplayDict of Display<UtreexoStateDict> { | ||
// fn fmt(self: @UtreexoStateDict, ref f: Formatter) -> Result<(), Error> { | ||
// let str: ByteArray = format!( | ||
// "UtreexoStateDict {{ roots: {}, num_leaves: {}, }}", | ||
// (*self.roots).len(), | ||
// *self.num_leaves | ||
// ); | ||
// f.buffer.append(@str); | ||
// Result::Ok(()) | ||
// } | ||
// } | ||
|
||
impl UtreexoProofDictDisplayDict of Display<UtreexoProofDict> { | ||
fn fmt(self: @UtreexoProofDict, ref f: Formatter) -> Result<(), Error> { | ||
let mut proofs: ByteArray = Default::default(); | ||
for proof in *self.proof { | ||
proofs.append(@format!("{},", proof)); | ||
}; | ||
let str: ByteArray = format!( | ||
"UtreexoProofDict {{ leaf_index: {}, proof: {}, }}", *self.leaf_index, @proofs | ||
); | ||
f.buffer.append(@str); | ||
Result::Ok(()) | ||
} | ||
} | ||
|
||
impl UtreexoBatchProofDictDisplayDict of Display<UtreexoBatchProofDict> { | ||
fn fmt(self: @UtreexoBatchProofDict, ref f: Formatter) -> Result<(), Error> { | ||
let mut targets: ByteArray = Default::default(); | ||
let mut proofs: ByteArray = Default::default(); | ||
for target in *self.targets { | ||
targets.append(@format!("{},", target)); | ||
}; | ||
for proof in *self.proof { | ||
proofs.append(@format!("{},", proof)); | ||
}; | ||
let str: ByteArray = format!( | ||
"UtreexoBatchProofDict {{ leaf_index: [{}], proof: [{}] }}", @targets, @proofs | ||
); | ||
f.buffer.append(@str); | ||
Result::Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.