Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions simf/lib/storage/smt_storage.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Extends `bytes32_tr_storage` using `array_fold` for larger buffers.
* Optimized for small, fixed-size states where linear hashing is more efficient
* than Merkle Trees. By avoiding proof overhead like sibling hashes, we reduce
* witness size and simplify contract logic for small N.
*/
fn hash_array_tr_storage_with_update(elem: (u256, bool), prev_hash_ctx: (u256, Ctx8)) -> (u256, Ctx8) {
let (hash, is_right): (u256, bool) = dbg!(elem);
let (prev_hash, ctx_node): (u256, Ctx8) = prev_hash_ctx;
let ctx: Ctx8 = ctx_node;

let new_hash: Ctx8 = match is_right {
true => {
let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, hash);
jet::sha_256_ctx_8_add_32(ctx, prev_hash)
},
false => {
let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, prev_hash);
jet::sha_256_ctx_8_add_32(ctx, hash)
}
};

(jet::sha_256_ctx_8_finalize(new_hash), ctx_node)
}

// Use tag: SMT/1.0/leaf
fn tapleaf_init() -> Ctx8 {
let doubled_tag_hash: [u8; 64] = [
0xb6, 0xc5, 0x7e, 0x7a, 0xa0, 0x8c, 0xd9, 0xe8, 0xc2, 0xfb, 0x65, 0xee, 0x31, 0x6d, 0x2f, 0xd9,
0xa2, 0x4b, 0xf4, 0xf4, 0x00, 0xc8, 0x6d, 0xe5, 0x5d, 0xcd, 0x8c, 0x4d, 0x38, 0x3d, 0x99, 0xd8,
0xb6, 0xc5, 0x7e, 0x7a, 0xa0, 0x8c, 0xd9, 0xe8, 0xc2, 0xfb, 0x65, 0xee, 0x31, 0x6d, 0x2f, 0xd9,
0xa2, 0x4b, 0xf4, 0xf4, 0x00, 0xc8, 0x6d, 0xe5, 0x5d, 0xcd, 0x8c, 0x4d, 0x38, 0x3d, 0x99, 0xd8,
];

let ctx: Ctx8 = jet::sha_256_ctx_8_init();
jet::sha_256_ctx_8_add_64(ctx, doubled_tag_hash)
}

// Use tag: SMT/1.0/node
fn tapnode_init() -> Ctx8 {
let doubled_tag_hash: [u8; 64] = [
0x1f, 0xa6, 0xe8, 0x92, 0x6a, 0x5f, 0x09, 0xa8, 0xf7, 0xb2, 0xe1, 0x57, 0x86, 0x68, 0x2f, 0xaa,
0xdc, 0xac, 0xee, 0x90, 0x95, 0x2f, 0xe4, 0x61, 0x28, 0x5c, 0x77, 0x8c, 0xb9, 0x5e, 0xb7, 0xb2,
0x1f, 0xa6, 0xe8, 0x92, 0x6a, 0x5f, 0x09, 0xa8, 0xf7, 0xb2, 0xe1, 0x57, 0x86, 0x68, 0x2f, 0xaa,
0xdc, 0xac, 0xee, 0x90, 0x95, 0x2f, 0xe4, 0x61, 0x28, 0x5c, 0x77, 0x8c, 0xb9, 0x5e, 0xb7, 0xb2,
];

let ctx: Ctx8 = jet::sha_256_ctx_8_init();
jet::sha_256_ctx_8_add_64(ctx, doubled_tag_hash)
}

fn script_hash_for_input_script(key: u256, leaf: u256, path_bits: u8, merkle_data: [(u256, bool); 8]) -> u256 {
let tap_leaf: u256 = jet::tapleaf_hash();
let ctx: Ctx8 = tapleaf_init();
let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, leaf);
let ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, path_bits);

let hash_leaf: u256 = jet::sha_256_ctx_8_finalize(ctx);

// Optimize node tag calculation
let ctx_node: Ctx8 = tapnode_init();
let (computed, _): (u256, Ctx8) = array_fold::<hash_array_tr_storage_with_update, 8>(
merkle_data, (hash_leaf, ctx_node)
);
let tap_node: u256 = jet::build_tapbranch(tap_leaf, computed);

let tweaked_key: u256 = jet::build_taptweak(key, tap_node);

let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init();
let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32
let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key);
jet::sha_256_ctx_8_finalize(hash_ctx3)
}

fn main() {
let key: u256 = witness::KEY;
let leaf_data: u256 = witness::LEAF;
let path_bits: u8 = witness::PATH_BITS;

// Path and hash
let merkle_data: [(u256, bool); 8] = witness::MERKLE_DATA;
let (leaf1, leaf2, leaf3, leaf4): (u64, u64, u64, u64) = <u256>::into(leaf_data);

// Load
assert!(jet::eq_256(
script_hash_for_input_script(key, leaf_data, path_bits, merkle_data),
unwrap(jet::input_script_hash(jet::current_index()))
));

// There may be arbitrary logic here
let new_leaf4: u64 = 1;
let new_leaf: u256 = <(u64, u64, u64, u64)>::into((leaf1, leaf2, leaf3, new_leaf4));

// Store
assert!(jet::eq_256(
script_hash_for_input_script(key, new_leaf, path_bits, merkle_data),
unwrap(jet::output_script_hash(jet::current_index()))
));
}
137 changes: 137 additions & 0 deletions tests/storage/build_witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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];

/// The fixed depth of the Sparse Merkle Tree (SMT).
///
/// This is set to 8 because Simplicity currently requires fixed-length arrays
/// and cannot dynamically resolve array lengths using `param::LEN`.
pub const DEPTH: usize = 8;

/// Precomputed SHA256 midstate for the `SMT/1.0/leaf` domain separation tag.
///
/// This is the result of double-hashing `b"SMT/1.0/leaf"` into a fresh SHA256 engine,
/// stored in reversed byte order as required by [`sha256::Midstate`].
///
/// The `1.0` denotes the protocol version, allowing future SMT upgrades to safely
/// alter hashing logic without risking backward-compatibility hash collisions.
#[rustfmt::skip]
pub const LEAF_MIDSTATE: [u8; 32] = [
0x64, 0x59, 0x25, 0x75, 0x94, 0x38, 0xdd, 0x53, 0xbc, 0x61, 0x54, 0x2a, 0x12, 0x77, 0x85, 0x3e,
0x62, 0x7e, 0x14, 0x80, 0xd7, 0xad, 0xf0, 0x5b, 0x78, 0x09, 0x93, 0x62, 0x70, 0x98, 0x24, 0xbf,
];

/// Precomputed SHA256 midstate for the `SMT/1.0/node` domain separation tag.
///
/// This is the result of double-hashing `b"SMT/1.0/node"` into a fresh SHA256 engine,
/// stored in reversed byte order as required by [`sha256::Midstate`].
///
/// The `1.0` denotes the protocol version, allowing future SMT upgrades to safely
/// alter hashing logic without risking backward-compatibility hash collisions
#[rustfmt::skip]
pub const NODE_MIDSTATE: [u8; 32] = [
0xe3, 0x4c, 0x71, 0xa4, 0x67, 0x56, 0x64, 0xaa, 0x91, 0x98, 0x37, 0x94, 0xe6, 0x30, 0x0c, 0xa0,
0xe2, 0x0d, 0xbb, 0xb2, 0xe6, 0x69, 0x03, 0x3f, 0x2b, 0x10, 0x20, 0xc9, 0x68, 0x55, 0x06, 0xe6,
];

#[derive(Debug, Clone, bincode::Encode, bincode::Decode, PartialEq, Eq)]
pub struct SMTWitness {
/// The internal public key used for Taproot tweaking.
///
/// This corresponds to the `key` parameter in the Simplicity expression:
/// `let tweaked_key: u256 = jet::build_taptweak(key, tap_node);`.
key: u256,

/// The leaf node (value) being stored or verified in the tree.
leaf: u256,

/// A bitwise representation of the tree traversal path.
///
/// Since `DEPTH` is 8, the path fits into a single `u8`.
/// * `1` (or `true`) represents a move to the **Right**.
/// * `0` (or `false`) represents a move to the **Left**.
///
/// **Note:** The bits are ordered from the **leaf up to the root**.
/// This order is chosen to simplify bitwise processing within the Simplicity contract.
path_bits: u8,

/// The sibling nodes required to reconstruct the Merkle path.
///
/// Each element is a tuple containing the sibling's hash and a boolean direction.
/// Like `path_bits`, this array is ordered from the **leaf up to the root**
/// to facilitate efficient processing in the Simplicity loop.
merkle_data: [(u256, bool); DEPTH],
}

impl SMTWitness {
#[must_use]
pub const fn new(
key: &u256,
leaf: &u256,
path_bits: u8,
merkle_data: &[(u256, bool); DEPTH],
) -> Self {
Self {
key: *key,
leaf: *leaf,
path_bits,
merkle_data: *merkle_data,
}
}
}

impl Default for SMTWitness {
fn default() -> Self {
Self {
key: [0u8; 32],
leaf: [0u8; 32],
path_bits: 0,
merkle_data: [([0u8; 32], false); DEPTH],
}
}
}

#[must_use]
pub fn build_smt_storage_witness(witness: &SMTWitness) -> WitnessValues {
let values: Vec<simplicityhl::Value> = witness
.merkle_data
.iter()
.map(|(value, is_right)| {
let hash_val =
simplicityhl::Value::from(UIntValue::U256(U256::from_byte_array(*value)));
let direction_val = simplicityhl::Value::from(*is_right);

simplicityhl::Value::product(hash_val, direction_val)
})
.collect();

let element_type = simplicityhl::types::TypeConstructible::product(
UIntType::U256.into(),
ResolvedType::boolean(),
);

simplicityhl::WitnessValues::from(HashMap::from([
(
WitnessName::from_str_unchecked("KEY"),
simplicityhl::Value::from(UIntValue::U256(U256::from_byte_array(witness.key))),
),
(
WitnessName::from_str_unchecked("LEAF"),
simplicityhl::Value::from(UIntValue::U256(U256::from_byte_array(witness.leaf))),
),
(
WitnessName::from_str_unchecked("PATH_BITS"),
simplicityhl::Value::from(UIntValue::U8(witness.path_bits)),
),
(
WitnessName::from_str_unchecked("MERKLE_DATA"),
simplicityhl::Value::array(values, element_type),
),
]))
}
Loading