-
Notifications
You must be signed in to change notification settings - Fork 4
✨ SMT Storage #49
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
base: main
Are you sure you want to change the base?
✨ SMT Storage #49
Conversation
0990433 to
b693be2
Compare
9ad9025 to
cfc92e0
Compare
crates/contracts/Cargo.toml
Outdated
| bytes32-tr-storage = [] | ||
| array-tr-storage = [] | ||
| smt-storage = [] | ||
| swap-with-change = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebase to main pls
d15e987 to
b574a43
Compare
73349f6 to
6ff66b5
Compare
6ff66b5 to
250295e
Compare
| /// Lock collateral on the storage contract (Mint/Fund operation) | ||
| GetStorageAddress { | ||
| /// Collateral and fee UTXO | ||
| #[arg(long = "collateral-utxo")] | ||
| collateral_utxo: OutPoint, | ||
| /// Collateral amount in satoshis (LBTC) | ||
| #[arg(long = "collateral-amount")] | ||
| collateral_amount: u64, | ||
| /// Miner fee in satoshis (LBTC) | ||
| #[arg(long = "fee-amount")] | ||
| fee_amount: u64, | ||
|
|
||
| /// The initial 32-byte data payload to store in the tree at the specified path | ||
| #[arg(long = "storage-bytes", value_parser = parse_hex_32)] | ||
| storage_bytes: [u8; 32], | ||
| /// The path in the Merkle Tree use for the contract logic (e.g., "rrll...") | ||
| #[arg(long = "path", value_parser = parse_bit_path)] | ||
| path: [bool; DEPTH], | ||
|
|
||
| /// Account that will pay for transaction fees and that owns a tokens to send | ||
| #[arg(long = "account-index", default_value_t = 0)] | ||
| account_index: u32, | ||
| /// When set, broadcast the built transaction via Esplora and print txid | ||
| #[arg(long = "broadcast")] | ||
| broadcast: bool, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This command should not broadcast anything, just print the address to which user can send money
| /// Returns an error if: | ||
| /// - The UTXO asset or amount validation fails (expects explicit amounts). | ||
| /// - Transaction extraction or amount proof verification fails. | ||
| pub fn build_smt_mint( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| pub fn build_smt_mint( | |
| pub fn get_storage_address( |
| ) -> Result<PartiallySignedTransaction, TransactionBuildError> { | ||
| let (collateral_out_point, collateral_tx_out) = collateral_utxo; | ||
|
|
||
| // TODO Change this validation to another func |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this todo where the validate_amount is defined
| let change_recipient_script = collateral_tx_out.script_pubkey.clone(); | ||
|
|
||
| let mut pst = PartiallySignedTransaction::new_v2(); | ||
|
|
||
| let mut collateral_input = Input::from_prevout(collateral_out_point); | ||
| collateral_input.witness_utxo = Some(collateral_tx_out.clone()); | ||
| pst.add_input(collateral_input); | ||
|
|
||
| pst.add_output(Output::new_explicit( | ||
| mint_script_pubkey, | ||
| collateral_amount, | ||
| collateral_asset_id, | ||
| None, | ||
| )); | ||
|
|
||
| if change_amount > 0 { | ||
| pst.add_output(Output::new_explicit( | ||
| change_recipient_script, | ||
| change_amount, | ||
| collateral_asset_id, | ||
| None, | ||
| )); | ||
| } | ||
|
|
||
| pst.add_output(Output::from_txout(TxOut::new_fee( | ||
| fee_amount, | ||
| collateral_asset_id, | ||
| ))); | ||
|
|
||
| pst.extract_tx()? | ||
| .verify_tx_amt_proofs(secp256k1::SECP256K1, &[collateral_tx_out])?; | ||
|
|
||
| Ok(pst) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let change_recipient_script = collateral_tx_out.script_pubkey.clone(); | |
| let mut pst = PartiallySignedTransaction::new_v2(); | |
| let mut collateral_input = Input::from_prevout(collateral_out_point); | |
| collateral_input.witness_utxo = Some(collateral_tx_out.clone()); | |
| pst.add_input(collateral_input); | |
| pst.add_output(Output::new_explicit( | |
| mint_script_pubkey, | |
| collateral_amount, | |
| collateral_asset_id, | |
| None, | |
| )); | |
| if change_amount > 0 { | |
| pst.add_output(Output::new_explicit( | |
| change_recipient_script, | |
| change_amount, | |
| collateral_asset_id, | |
| None, | |
| )); | |
| } | |
| pst.add_output(Output::from_txout(TxOut::new_fee( | |
| fee_amount, | |
| collateral_asset_id, | |
| ))); | |
| pst.extract_tx()? | |
| .verify_tx_amt_proofs(secp256k1::SECP256K1, &[collateral_tx_out])?; | |
| Ok(pst) | |
| // This should be converted to address | |
| Ok(mint_script_pubkey) |
| use std::collections::HashMap; | ||
|
|
||
| use simplicityhl::num::U256; | ||
| use simplicityhl::types::{ResolvedType, TypeConstructible, UIntType}; | ||
| use simplicityhl::value::{UIntValue, ValueConstructible}; | ||
| use simplicityhl::{WitnessValues, str::WitnessName}; | ||
|
|
||
| #[allow(non_camel_case_types)] | ||
| pub type u256 = [u8; 32]; | ||
| pub const DEPTH: usize = 7; | ||
|
|
||
| #[derive(Debug, Clone, bincode::Encode, bincode::Decode, PartialEq, Eq)] | ||
| pub struct SMTWitness { | ||
| key: u256, | ||
| leaf: u256, | ||
| merkle_data: [(u256, bool); DEPTH], | ||
| } | ||
|
|
||
| impl SMTWitness { | ||
| #[must_use] | ||
| pub fn new(key: &u256, leaf: &u256, merkle_data: &[(u256, bool); DEPTH]) -> Self { | ||
| Self { | ||
| key: *key, | ||
| leaf: *leaf, | ||
| merkle_data: *merkle_data, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Default for SMTWitness { | ||
| fn default() -> Self { | ||
| Self { | ||
| key: [0u8; 32], | ||
| leaf: [0u8; 32], | ||
| merkle_data: [([0u8; 32], false); DEPTH], | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add more context (documentation) about how practically this witness is going to be used (i.e. explain rrrrrr witness argument)
Also if there are nuances, mention them
| 0, | ||
| cmr, | ||
| ControlBlock::from_slice( | ||
| &control_block(cmr, &old_spend_info).serialize(), /*&control_block.serialize()*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| &control_block(cmr, &old_spend_info).serialize(), /*&control_block.serialize()*/ | |
| &control_block(cmr, &old_spend_info).serialize(), |
| use simplicityhl::simplicity::elements::hashes::HashEngine as _; | ||
| use simplicityhl::simplicity::hashes::{Hash, sha256}; | ||
|
|
||
| use super::build_witness::{DEPTH, u256}; | ||
|
|
||
| #[derive(Debug, Clone, bincode::Encode, bincode::Decode, PartialEq, Eq)] | ||
| pub enum TreeNode { | ||
| Leaf { | ||
| leaf_hash: u256, | ||
| }, | ||
| Branch { | ||
| hash: u256, | ||
| left: Box<TreeNode>, | ||
| right: Box<TreeNode>, | ||
| }, | ||
| } | ||
|
|
||
| impl TreeNode { | ||
| pub fn get_hash(&self) -> u256 { | ||
| match self { | ||
| TreeNode::Leaf { leaf_hash } => *leaf_hash, | ||
| TreeNode::Branch { hash, .. } => *hash, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub struct SparseMerkleTree { | ||
| root: Box<TreeNode>, | ||
| precalculate_hashes: [u256; DEPTH], | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add more context and documentation (great if there are any links to external sources)
| impl SparseMerkleTree { | ||
| #[must_use] | ||
| pub fn new() -> Self { | ||
| let mut precalculate_hashes = [[0u8; 32]; DEPTH]; | ||
| let mut eng = sha256::Hash::engine(); | ||
| let zero = [0u8; 32]; | ||
| eng.input(&zero); | ||
| precalculate_hashes[0] = *sha256::Hash::from_engine(eng).as_byte_array(); | ||
|
|
||
| for i in 1..DEPTH { | ||
| let mut eng = sha256::Hash::engine(); | ||
| eng.input(&precalculate_hashes[i - 1]); | ||
| eng.input(&precalculate_hashes[i - 1]); | ||
| precalculate_hashes[i] = *sha256::Hash::from_engine(eng).as_byte_array(); | ||
| } | ||
|
|
||
| Self { | ||
| root: Box::new(TreeNode::Leaf { | ||
| leaf_hash: precalculate_hashes[0], | ||
| }), | ||
| precalculate_hashes, | ||
| } | ||
| } | ||
|
|
||
| fn calculate_hash(left: &mut TreeNode, right: &mut TreeNode) -> u256 { | ||
| let mut eng = sha256::Hash::engine(); | ||
| eng.input(&left.get_hash()); | ||
| eng.input(&right.get_hash()); | ||
| *sha256::Hash::from_engine(eng).as_byte_array() | ||
| } | ||
|
|
||
| // Return array of hashes | ||
| fn traverse( | ||
| defaults: &[u256], | ||
| leaf: &u256, | ||
| path: &[bool], | ||
| root: &mut Box<TreeNode>, | ||
| hashes: &mut [u256], | ||
| ) { | ||
| let Some((is_right, remaining_path)) = path.split_first() else { | ||
| let tag = sha256::Hash::hash(b"TapData"); | ||
| let mut eng = sha256::Hash::engine(); | ||
| eng.input(tag.as_byte_array()); | ||
| eng.input(tag.as_byte_array()); | ||
| eng.input(leaf); | ||
|
|
||
| **root = TreeNode::Leaf { | ||
| leaf_hash: *sha256::Hash::from_engine(eng).as_byte_array(), | ||
| }; | ||
| return; | ||
| }; | ||
|
|
||
| let (child_zero, remaining_defaults) = defaults | ||
| .split_last() | ||
| .expect("Defaults length must match path length"); | ||
|
|
||
| if matches!(**root, TreeNode::Leaf { .. }) { | ||
| let new_branch = Box::new(TreeNode::Branch { | ||
| hash: [0u8; 32], | ||
| left: Box::new(TreeNode::Leaf { | ||
| leaf_hash: *child_zero, | ||
| }), | ||
| right: Box::new(TreeNode::Leaf { | ||
| leaf_hash: *child_zero, | ||
| }), | ||
| }); | ||
|
|
||
| *root = new_branch; | ||
| } | ||
|
|
||
| let (current_hash_slot, remaining_hashes) = hashes | ||
| .split_first_mut() | ||
| .expect("Hashes length must match path length"); | ||
|
|
||
| if let TreeNode::Branch { | ||
| ref mut left, | ||
| ref mut right, | ||
| ref mut hash, | ||
| } = **root | ||
| { | ||
| if *is_right { | ||
| *current_hash_slot = left.get_hash(); | ||
| Self::traverse( | ||
| remaining_defaults, | ||
| leaf, | ||
| remaining_path, | ||
| right, | ||
| remaining_hashes, | ||
| ); | ||
| } else { | ||
| *current_hash_slot = right.get_hash(); | ||
| Self::traverse( | ||
| remaining_defaults, | ||
| leaf, | ||
| remaining_path, | ||
| left, | ||
| remaining_hashes, | ||
| ); | ||
| } | ||
|
|
||
| *hash = Self::calculate_hash(left, right); | ||
| } else { | ||
| unreachable!("Should be a branch at this point"); | ||
| } | ||
| } | ||
|
|
||
| // For insert change 0 to leaf. | ||
| // For delete vice versa. | ||
| // And for udpate change old value to new. | ||
| // Then, recalculate hashes | ||
| pub fn update(&mut self, leaf: &u256, path: [bool; DEPTH]) -> [u256; DEPTH] { | ||
| let mut hashes = self.precalculate_hashes; | ||
| Self::traverse( | ||
| &self.precalculate_hashes, | ||
| leaf, | ||
| &path, | ||
| &mut self.root, | ||
| &mut hashes, | ||
| ); | ||
| hashes | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the same, mention nuances, especially attacks, etc
crates/contracts/Cargo.toml
Outdated
| [dev-dependencies] | ||
| anyhow = "1" No newline at end of file | ||
| anyhow = "1" | ||
| rand = "0.9.2" No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not add rand as a independent dep, use rand from the simplicityhl::elements dep
crates/contracts/Cargo.toml
Outdated
| bytes32-tr-storage = [] | ||
| array-tr-storage = [] | ||
| smt-storage = [] | ||
| swap-with-change = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| swap-with-change = [] |
250295e to
2ca279d
Compare
8035e03 to
d19f4d5
Compare
…tion against second preimage attack
d19f4d5 to
93da027
Compare
No description provided.