Skip to content

Conversation

@LesterEvSe
Copy link
Collaborator

No description provided.

@LesterEvSe LesterEvSe self-assigned this Jan 14, 2026
@LesterEvSe LesterEvSe requested a review from KyrylR as a code owner January 14, 2026 15:15
@LesterEvSe LesterEvSe added the enhancement New feature or request label Jan 14, 2026
@LesterEvSe LesterEvSe changed the title ✨ Unlimited SMT Storage ✨ SMT Storage Jan 14, 2026
@LesterEvSe LesterEvSe force-pushed the feature/smt-storage branch 6 times, most recently from 9ad9025 to cfc92e0 Compare January 19, 2026 14:02
bytes32-tr-storage = []
array-tr-storage = []
smt-storage = []
swap-with-change = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase to main pls

@LesterEvSe LesterEvSe force-pushed the feature/smt-storage branch 2 times, most recently from d15e987 to b574a43 Compare January 20, 2026 11:26
@LesterEvSe LesterEvSe requested a review from KyrylR January 28, 2026 15:27
Comment on lines 57 to 69
/// 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,
},
Copy link
Collaborator

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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Collaborator

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

Comment on lines 55 to 87
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)

Comment on lines 1 to 72
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],
}
}
}
Copy link
Collaborator

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()*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&control_block(cmr, &old_spend_info).serialize(), /*&control_block.serialize()*/
&control_block(cmr, &old_spend_info).serialize(),

Comment on lines 1 to 85
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],
}
Copy link
Collaborator

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)

Comment on lines 32 to 230
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
}
}
Copy link
Collaborator

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

[dev-dependencies]
anyhow = "1" No newline at end of file
anyhow = "1"
rand = "0.9.2" No newline at end of file
Copy link
Collaborator

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

bytes32-tr-storage = []
array-tr-storage = []
smt-storage = []
swap-with-change = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
swap-with-change = []

@LesterEvSe LesterEvSe force-pushed the feature/smt-storage branch 3 times, most recently from 8035e03 to d19f4d5 Compare January 29, 2026 17:26
@LesterEvSe LesterEvSe requested a review from KyrylR January 29, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants