From 77d56aac93eb188bda7feec31301ed54bbf26543 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 23 Feb 2026 10:56:53 +0800 Subject: [PATCH 01/13] init --- Cargo.lock | 23 -- apps/web/package.json | 2 +- crates/bmt/benches/tree_construction.rs | 4 +- crates/bmt/src/lib.rs | 12 +- crates/calendar/src/routes/ots.rs | 4 +- crates/cli/src/commands/stamp.rs | 4 +- crates/core-wasm/Cargo.toml | 23 -- crates/core-wasm/src/lib.rs | 163 --------- crates/stamper/src/lib.rs | 10 +- package.json | 1 - packages/bmt/package.json | 34 ++ packages/bmt/src/index.ts | 268 ++++++++++++++ packages/bmt/test/merkle.test.ts | 73 ++++ packages/{uts-sdk => bmt}/tsconfig.json | 0 packages/bmt/vitest.config.ts | 11 + packages/{uts-sdk => sdk}/package.json | 6 +- packages/sdk/src/index.ts | 35 ++ packages/sdk/tsconfig.json | 11 + packages/uts-sdk/src/index.ts | 71 ---- pnpm-lock.yaml | 460 +++++++++++++++++++++++- pnpm-workspace.yaml | 1 - tsconfig.json | 2 +- 22 files changed, 904 insertions(+), 314 deletions(-) delete mode 100644 crates/core-wasm/Cargo.toml delete mode 100644 crates/core-wasm/src/lib.rs create mode 100644 packages/bmt/package.json create mode 100644 packages/bmt/src/index.ts create mode 100644 packages/bmt/test/merkle.test.ts rename packages/{uts-sdk => bmt}/tsconfig.json (100%) create mode 100644 packages/bmt/vitest.config.ts rename packages/{uts-sdk => sdk}/package.json (83%) create mode 100644 packages/sdk/src/index.ts create mode 100644 packages/sdk/tsconfig.json delete mode 100644 packages/uts-sdk/src/index.ts diff --git a/Cargo.lock b/Cargo.lock index 097f267..1d32aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5311,17 +5311,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -6384,18 +6373,6 @@ dependencies = [ "uts-contracts", ] -[[package]] -name = "uts-core-wasm" -version = "0.1.0" -dependencies = [ - "serde", - "serde-wasm-bindgen", - "serde_json", - "serde_with", - "uts-core", - "wasm-bindgen", -] - [[package]] name = "uts-journal" version = "0.1.0" diff --git a/apps/web/package.json b/apps/web/package.json index 8ea4f41..7ef2755 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "vue": "^3.5.24", - "uts-sdk": "workspace:*" + "@uts/sdk": "workspace:*" }, "devDependencies": { "@types/node": "^24.10.1", diff --git a/crates/bmt/benches/tree_construction.rs b/crates/bmt/benches/tree_construction.rs index 7d06e67..dadc595 100644 --- a/crates/bmt/benches/tree_construction.rs +++ b/crates/bmt/benches/tree_construction.rs @@ -8,7 +8,7 @@ use digest::{Digest, FixedOutputReset, Output}; use sha2::Sha256; use sha3::Keccak256; use std::hint::black_box; -use uts_bmt::UnorderdMerkleTree; +use uts_bmt::UnorderedMerkleTree; const INPUT_SIZES: &[usize] = &[8, 1024, 65536, 1_048_576]; @@ -34,7 +34,7 @@ where group.bench_function(BenchmarkId::new(id, size), move |b| { // Tree construction is the operation under test. b.iter(|| { - let tree = UnorderdMerkleTree::::new(black_box(leaves.as_slice())); + let tree = UnorderedMerkleTree::::new(black_box(leaves.as_slice())); black_box(tree); }); }); diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index c8d5b27..81dba88 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -15,7 +15,7 @@ pub const INNER_NODE_PREFIX: u8 = 0x01; /// /// Leaves are **sorted** starting at index `len`. #[derive(Debug, Clone, Default)] -pub struct UnorderdMerkleTree { +pub struct UnorderedMerkleTree { /// Index 0 is not used, leaves start at index `len`. nodes: Box<[Output]>, len: usize, @@ -28,7 +28,7 @@ pub struct UnhashedFlatMerkleTree { len: usize, } -impl UnorderdMerkleTree +impl UnorderedMerkleTree where Output: Pod + Copy, { @@ -128,7 +128,7 @@ where Output: Pod + Copy, { /// Finalizes the Merkle tree by hashing internal nodes - pub fn finalize(self) -> UnorderdMerkleTree { + pub fn finalize(self) -> UnorderedMerkleTree { let mut nodes = self.buffer; let len = self.len; unsafe { @@ -152,7 +152,7 @@ where // SAFETY: initialized all elements. nodes.set_len(2 * len); } - UnorderdMerkleTree { + UnorderedMerkleTree { nodes: nodes.into_boxed_slice(), len, } @@ -234,7 +234,7 @@ mod tests { ]; leaves.sort_unstable(); - let tree = UnorderdMerkleTree::::new(&leaves); + let tree = UnorderedMerkleTree::::new(&leaves); // Manually compute the expected root let mut hasher = D::new(); @@ -265,7 +265,7 @@ mod tests { ]; leaves.sort_unstable(); - let tree = UnorderdMerkleTree::::new(&leaves); + let tree = UnorderedMerkleTree::::new(&leaves); for leaf in &leaves { let mut iter = tree diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 0587e7d..4a2ae98 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -12,7 +12,7 @@ use bytes::BytesMut; use digest::Digest; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; -use uts_bmt::UnorderdMerkleTree; +use uts_bmt::UnorderedMerkleTree; use uts_core::{ codec::{ Encode, @@ -154,7 +154,7 @@ pub async fn get_timestamp( .load_entry(root) .expect("DB error") .expect("bug: entry not found"); - let trie: UnorderdMerkleTree = entry.trie(); + let trie: UnorderedMerkleTree = entry.trie(); let proof = trie .get_proof_iter(bytemuck::cast_ref(&*commitment)) diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs index cdb2980..e747ced 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -6,7 +6,7 @@ use futures::TryFutureExt; use std::{collections::HashMap, future::ready, io, path::PathBuf, sync::LazyLock, time::Duration}; use tokio::{fs, io::AsyncWriteExt}; use url::Url; -use uts_bmt::UnorderdMerkleTree; +use uts_bmt::UnorderedMerkleTree; use uts_core::{ codec::{ Decode, Encode, VersionedProof, @@ -97,7 +97,7 @@ impl Stamp { }) .collect::>(); - let internal_tire = UnorderdMerkleTree::::new(&nonced_digest); + let internal_tire = UnorderedMerkleTree::::new(&nonced_digest); let root = internal_tire.root(); eprintln!("Internal Merkle root: {}", Hexed(root)); diff --git a/crates/core-wasm/Cargo.toml b/crates/core-wasm/Cargo.toml deleted file mode 100644 index 39ed9cb..0000000 --- a/crates/core-wasm/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -authors.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -name = "uts-core-wasm" -repository.workspace = true -version.workspace = true - -[lib] -crate-type = ["cdylib"] - -[dependencies] -serde = { workspace = true, features = ["derive"] } -serde-wasm-bindgen = { workspace = true } -serde_json = { workspace = true } -serde_with = { workspace = true, features = ["hex"] } -uts-core = { workspace = true, features = ["serde"] } -wasm-bindgen = { workspace = true } - -[lints] -workspace = true diff --git a/crates/core-wasm/src/lib.rs b/crates/core-wasm/src/lib.rs deleted file mode 100644 index 73d97b3..0000000 --- a/crates/core-wasm/src/lib.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! WASM bindings for UTS Core library. - -use serde::{Deserialize, Serialize}; -use serde_json::{Value, json}; -use serde_with::{hex::Hex, serde_as}; -use uts_core::codec::{ - Decode, Encode, - v1::{ - Attestation, BitcoinAttestation, DetachedTimestamp, DigestHeader, EthereumUTSAttestation, - MayHaveInput, PendingAttestation, Timestamp, opcode::OpCode, - }, -}; -use wasm_bindgen::prelude::*; - -/// This assumes `timestamps` is an array of serialized Timestamp byte arrays. -#[wasm_bindgen] -pub fn merge_timestamps(timestamps: JsValue) -> Result, JsError> { - let timestamps: Vec> = serde_wasm_bindgen::from_value(timestamps)?; - let timestamps: Vec = timestamps - .into_iter() - .map(|data| { - let mut decoder = &data[..]; - Timestamp::decode(&mut decoder) - .map_err(|e| JsError::new(&format!("Decode error: {}", e))) - }) - .collect::>()?; - - let merged = - Timestamp::try_merge(timestamps).map_err(|e| JsError::new(&format!("Error: {}", e)))?; - - let mut encoded = Vec::new(); - merged - .encode(&mut encoded) - .map_err(|e| JsError::new(&format!("Encode error: {}", e)))?; - Ok(encoded) -} - -/// Pack a detached timestamp with the given digest header. -#[wasm_bindgen] -pub fn pack_detached_timestamp(digest: JsValue, timestamp: Vec) -> Result, JsError> { - let digest: DigestHeader = serde_wasm_bindgen::from_value(digest)?; - let mut decoder = ×tamp[..]; - let timestamp = Timestamp::decode(&mut decoder) - .map_err(|e| JsError::new(&format!("Decode error: {}", e)))?; - let detached = DetachedTimestamp::try_from_parts(digest, timestamp) - .map_err(|e| JsError::new(&format!("Error: {}", e)))?; - - let mut encoded = Vec::new(); - detached - .encode(&mut encoded) - .map_err(|e| JsError::new(&format!("Encode error: {}", e)))?; - Ok(encoded) -} - -/// Trace the execution steps of a finalized timestamp. -#[wasm_bindgen] -pub fn trace_timestamp(timestamp: Vec) -> Result { - let mut decoder = ×tamp[..]; - let timestamp = Timestamp::decode(&mut decoder) - .map_err(|e| JsError::new(&format!("Decode error: {}", e)))?; - if !timestamp.is_finalized() { - return Err(JsError::new("Can only trace finalized timestamps")); - } - serde_wasm_bindgen::to_value(&serialize_chain(×tamp)) - .map_err(|e| JsError::new(&format!("Serialization error: {}", e))) -} - -fn serialize_chain(mut current_node: &Timestamp) -> Value { - #[serde_as] - #[derive(Serialize, Deserialize)] - struct ExecutionStep { - op: OpCode, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] - data: Option>, - #[serde_as(as = "Hex")] - input: Vec, - #[serde_as(as = "Hex")] - output: Vec, - } - - #[serde_as] - #[derive(Serialize, Deserialize)] - #[serde(tag = "kind", rename_all = "camelCase")] - enum AttestationStep { - Pending { - url: String, - }, - Bitcoin { - height: u32, - }, - EthereumUTS { - chain: u64, - height: u64, - }, - Unknown { - #[serde_as(as = "Hex")] - tag: Vec, - #[serde_as(as = "Hex")] - data: Vec, - }, - } - let mut chain = Vec::new(); - loop { - match current_node { - Timestamp::Attestation(raw) => { - if raw.tag == PendingAttestation::TAG { - let pending = PendingAttestation::from_raw(raw).unwrap(); - chain.push(json!(AttestationStep::Pending { - url: pending.uri.to_string(), - })); - } else if raw.tag == BitcoinAttestation::TAG { - let btc = BitcoinAttestation::from_raw(raw).unwrap(); - chain.push(json!(AttestationStep::Bitcoin { height: btc.height })); - } else if raw.tag == EthereumUTSAttestation::TAG { - let eth = EthereumUTSAttestation::from_raw(raw).unwrap(); - chain.push(json!(AttestationStep::EthereumUTS { - chain: eth.chain.id(), - height: eth.height, - })); - } else { - chain.push(json!(AttestationStep::Unknown { - tag: raw.tag.to_vec(), - data: raw.data.to_vec(), - })); - } - break; - } - Timestamp::Step(step) => { - let op = step.op(); - let input = step.input().unwrap().to_vec(); - let output = op.execute(&input, step.data()); - chain.push( - serde_json::to_value(&ExecutionStep { - op, - data: if op.has_immediate() { - None - } else { - Some(step.data().to_vec()) - }, - input, - output, - }) - .unwrap(), - ); - - let next = step.next(); - match next.len() { - 0 => break, - 1 => current_node = &next[0], - _ => { - let forks: Vec = - next.iter().map(|child| serialize_chain(child)).collect(); - chain.push(Value::Array(forks)); - break; - } - } - } - } - } - - Value::Array(chain) -} diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index 6211801..c66f0d2 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -20,7 +20,7 @@ use std::{ time::Duration, }; use tokio::time::{Interval, MissedTickBehavior}; -use uts_bmt::UnorderdMerkleTree; +use uts_bmt::UnorderedMerkleTree; use uts_contracts::uts::UniversalTimestamps; use uts_core::utils::Hexed; use uts_journal::reader::JournalReader; @@ -42,7 +42,7 @@ pub struct Stamper { /// Storage for merkle trees and leaf->root mappings storage: Arc, /// FIFO cache of recent merkle trees - cache: VecDeque>, + cache: VecDeque>, /// FIFO cache index of recent merkle trees cache_index: HashMap, /// The contract @@ -81,13 +81,13 @@ pub struct MerkleEntry<'a> { impl MerkleEntry<'_> { /// Get the Merkle tree from the entry - pub fn trie(&self) -> UnorderdMerkleTree + pub fn trie(&self) -> UnorderedMerkleTree where D: Digest + FixedOutputReset, Output: Pod + Copy, { // SAFETY: We trust that the data in the database is valid, and that the trie was serialized correctly. - unsafe { UnorderdMerkleTree::from_raw_bytes(&self.trie) } + unsafe { UnorderedMerkleTree::from_raw_bytes(&self.trie) } } } @@ -237,7 +237,7 @@ where } debug_assert_eq!(buffer.len(), target_size); - let merkle_tree = UnorderdMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); + let merkle_tree = UnorderedMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); let storage = self.storage.clone(); let merkle_tree = tokio::task::spawn_blocking(move || { diff --git a/package.json b/package.json index fb13c2f..80a5998 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "uts-monorepo", "private": true, "scripts": { - "build:wasm": "wasm-pack build crates/core-wasm --target web" }, "devDependencies": { "pnpm": "^10.26.2", diff --git a/packages/bmt/package.json b/packages/bmt/package.json new file mode 100644 index 0000000..4231d35 --- /dev/null +++ b/packages/bmt/package.json @@ -0,0 +1,34 @@ +{ + "name": "@uts/bmt", + "version": "0.1.0", + "description": "", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "test": "vitest", + "test:run": "vitest run", + "build": "rimraf dist && tsc" + }, + "keywords": [], + "author": "Akase Haruka ", + "license": "(MIT OR Apache-2.0)", + "devDependencies": { + "@vitest/browser": "^4.0.18", + "@vitest/browser-playwright": "^4.0.18", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } +} diff --git a/packages/bmt/src/index.ts b/packages/bmt/src/index.ts new file mode 100644 index 0000000..059b611 --- /dev/null +++ b/packages/bmt/src/index.ts @@ -0,0 +1,268 @@ +export const INNER_NODE_PREFIX: number = 0x01; +const prefixBuffer = new Uint8Array([INNER_NODE_PREFIX]); + +export enum NodePosition { + /** The sibling is a right child, `APPEND` its hash when computing the parent */ + Left, + /** The sibling is a left child, `PREPEND` its hash when computing the parent */ + Right, +} + +export interface Hasher { + readonly outputSize: number; + + update(data: Uint8Array): void; + + digest(): Uint8Array; +} + +export interface HasherFactory { + readonly outputSize: number; + + create(): Hasher; +} + +export class UnhashedFlatMerkleTree { + constructor( + public readonly buffer: Uint8Array[], + public readonly len: number + ) {} + + /** + * Finalizes the Merkle tree by hashing internal nodes + */ + public finalize(factory: HasherFactory): UnorderedMerkleTree { + const nodes = this.buffer; + const len = this.len; + + // Build the tree (from bottom to top) + for (let i = len - 1; i >= 1; i--) { + const left = nodes[2 * i]; + const right = nodes[2 * i + 1]; + + const hasher = factory.create(); + hasher.update(prefixBuffer); + hasher.update(left); + hasher.update(right); + nodes[i] = hasher.digest(); + } + + return new UnorderedMerkleTree(nodes, len); + } +} + +/** + * Flat, Fixed-Size, Read only Merkle Tree + * * Expects the length of leaves to be equal or near(less) to a power of two. + * Leaves are **sorted** starting at index `len`. + */ +export class UnorderedMerkleTree { + /** Index 0 is not used, leaves start at index `len` */ + protected nodes: Uint8Array[]; + protected len: number; + + constructor(nodes: Uint8Array[], len: number) { + this.nodes = nodes; + this.len = len; + } + + /** + * Constructs a new Merkle tree from the given hash leaves. + */ + public static new(data: Uint8Array[], factory: HasherFactory): UnorderedMerkleTree { + return UnorderedMerkleTree.newUnhashed(data, factory).finalize(factory); + } + + /** + * Constructs a new Merkle tree from the given hash leaves, without hashing internal nodes. + */ + public static newUnhashed(prehashedLeaves: Uint8Array[], factory: HasherFactory): UnhashedFlatMerkleTree { + const rawLen = prehashedLeaves.length; + if (rawLen === 0) { + throw new Error("Cannot create Merkle tree with zero leaves"); + } + + const len = nextPowerOfTwo(rawLen); + const nodes = new Array(2 * len); + + // index 0, we will never use it + nodes[0] = new Uint8Array(factory.outputSize); + + // Prepare leaves block + const leavesBlock = new Array(len); + for (let i = 0; i < len; i++) { + if (i < rawLen) { + if (prehashedLeaves[i].length !== factory.outputSize) { + throw new Error(`Invalid leaf at index ${i}: expected length ${factory.outputSize}, got ${prehashedLeaves[i].length}`); + } + leavesBlock[i] = prehashedLeaves[i]; + } else { + // Pad with default (zeroed) hash + leavesBlock[i] = new Uint8Array(factory.outputSize); + } + } + + // Sort leaves unstable (lexicographical) + leavesBlock.sort(compareBytes); + + // Copy back to tree nodes + for (let i = 0; i < len; i++) { + nodes[len + i] = leavesBlock[i]; + } + + return new UnhashedFlatMerkleTree(nodes, len); + } + + /** + * Returns the root hash of the Merkle tree + */ + public root(): Uint8Array { + return this.nodes[1]; + } + + /** + * Returns the leaves of the Merkle tree + */ + public leaves(): Uint8Array[] { + return this.nodes.slice(this.len, this.len * 2); + } + + /** + * Checks if the given leaf is contained in the Merkle tree + */ + public contains(leaf: Uint8Array): boolean { + return binarySearch(this.leaves(), leaf) !== -1; + } + + /** + * Get proof for a given leaf + */ + public getProofIter(leaf: Uint8Array): SiblingIter | null { + const leafIndexInSlice = binarySearch(this.leaves(), leaf); + if (leafIndexInSlice === -1) { + return null; + } + + return new SiblingIter(this.nodes, this.len + leafIndexInSlice); + } + + /** + * Returns the raw bytes of the Merkle tree nodes (mimicking bytemuck::cast_slice) + */ + public asRawBytes(): Uint8Array { + const hashLength = this.nodes[1].length; + const totalSize = this.nodes.length * hashLength; + const bytes = new Uint8Array(totalSize); + + for (let i = 0; i < this.nodes.length; i++) { + bytes.set(this.nodes[i], i * hashLength); + } + + return bytes; + } + + /** + * From raw bytes, reconstruct the Merkle tree + */ + public static fromRawBytes(bytes: Uint8Array, hashLength: number): UnorderedMerkleTree { + if (bytes.length % hashLength !== 0) { + throw new Error("Bytes length must be a multiple of hashLength"); + } + const totalNodes = bytes.length / hashLength; + if (totalNodes % 2 !== 0) { + throw new Error("Invalid tree structure: node count is not even"); + } + + const len = totalNodes / 2; + const nodes = new Array(totalNodes); + + for (let i = 0; i < totalNodes; i++) { + nodes[i] = bytes.slice(i * hashLength, (i + 1) * hashLength); + } + + return new UnorderedMerkleTree(nodes, len); + } +} + +/** + * Iterator over the sibling nodes of a leaf in the Merkle tree + */ +export class SiblingIter implements IterableIterator<{ position: NodePosition; sibling: Uint8Array }> { + private readonly nodes: Uint8Array[]; + private current: number; + + constructor(nodes: Uint8Array[], current: number) { + this.nodes = nodes; + this.current = current; + } + + public next(): IteratorResult<{ position: NodePosition; sibling: Uint8Array }> { + if (this.current <= 1) { + return { done: true, value: undefined }; + } + + const isLeft = (this.current & 1) === 0; + const position = isLeft ? NodePosition.Left : NodePosition.Right; + + const siblingIndex = this.current ^ 1; + const sibling = this.nodes[siblingIndex]; + + this.current >>= 1; + + return { + done: false, + value: { position, sibling }, + }; + } + + public [Symbol.iterator](): IterableIterator<{ position: NodePosition; sibling: Uint8Array }> { + return this; + } + + /** Returns the exact remaining size of the iterator */ + public get length(): number { + if (this.current <= 1) return 0; + return 31 - Math.clz32(this.current); + } +} + + + +// --- Helper Functions --- + +function nextPowerOfTwo(n: number): number { + if (n <= 1) return 1; + let p = 1; + while (p < n) p *= 2; + return p; +} + +function compareBytes(a: Uint8Array, b: Uint8Array): number { + const len = Math.min(a.length, b.length); + for (let i = 0; i < len; i++) { + if (a[i] !== b[i]) { + return a[i] - b[i]; + } + } + return a.length - b.length; +} + +function binarySearch(arr: Uint8Array[], target: Uint8Array): number { + let left = 0; + let right = arr.length - 1; + + while (left <= right) { + const mid = (left + right) >>> 1; + const cmp = compareBytes(arr[mid], target); + + if (cmp === 0) { + return mid; + } else if (cmp < 0) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return -1; +} diff --git a/packages/bmt/test/merkle.test.ts b/packages/bmt/test/merkle.test.ts new file mode 100644 index 0000000..79cbd7d --- /dev/null +++ b/packages/bmt/test/merkle.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect } from 'vitest'; +import { createHash } from 'crypto'; +import {Hasher, UnorderedMerkleTree} from "../src"; + +class Sha256Hasher implements Hasher { + private hash = createHash('sha256'); + readonly outputSize = 32; + + update(data: Uint8Array): void { + this.hash.update(data); + } + + digest(): Uint8Array { + const result = this.hash.digest(); + return new Uint8Array(result); + } +} + +const factory = { + outputSize: 32, + create: () => new Sha256Hasher() +}; + +describe('UnorderedMerkleTree', () => { + it('should create a tree and compute root', () => { + const leaves = [ + new Uint8Array(Array(32).fill(1)), + new Uint8Array(Array(32).fill(2)), + new Uint8Array(Array(32).fill(3)), + ]; + + const tree = UnorderedMerkleTree.new(leaves, factory); + const root = tree.root(); + + expect(root).toBeDefined(); + expect(root.length).toBe(32); // SHA256 output size + }); + + it('should verify leaf existence', () => { + const leaves = [ + new Uint8Array(Array(32).fill(1)), + new Uint8Array(Array(32).fill(2)), + ]; + + const tree = UnorderedMerkleTree.new(leaves, factory); + + expect(tree.contains(new Uint8Array(Array(32).fill(1)))).toBe(true); + expect(tree.contains(new Uint8Array(Array(32).fill(9)))).toBe(false); + }); + + it('should generate valid proof iteration', () => { + const leaves = [ + new Uint8Array(Array(32).fill(1)), + new Uint8Array(Array(32).fill(2)), + new Uint8Array(Array(32).fill(3)), + new Uint8Array(Array(32).fill(4)), + ]; + + const tree = UnorderedMerkleTree.new(leaves, factory); + const targetLeaf = new Uint8Array(Array(32).fill(4)); + + const proofIter = tree.getProofIter(targetLeaf); + expect(proofIter).not.toBeNull(); + + const steps = Array.from(proofIter!); + expect(steps.length).toBe(2); + + steps.forEach(step => { + expect(step.position).toBeDefined(); + expect(step.sibling).toBeInstanceOf(Uint8Array); + }); + }); +}); diff --git a/packages/uts-sdk/tsconfig.json b/packages/bmt/tsconfig.json similarity index 100% rename from packages/uts-sdk/tsconfig.json rename to packages/bmt/tsconfig.json diff --git a/packages/bmt/vitest.config.ts b/packages/bmt/vitest.config.ts new file mode 100644 index 0000000..c4fe27b --- /dev/null +++ b/packages/bmt/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + + include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + testTimeout: 10000, + }, +}); diff --git a/packages/uts-sdk/package.json b/packages/sdk/package.json similarity index 83% rename from packages/uts-sdk/package.json rename to packages/sdk/package.json index 604f999..a0d21e6 100644 --- a/packages/uts-sdk/package.json +++ b/packages/sdk/package.json @@ -1,5 +1,5 @@ { - "name": "uts-sdk", + "name": "@uts/sdk", "version": "0.1.0", "description": "", "type": "module", @@ -16,9 +16,7 @@ "keywords": [], "author": "Akase Haruka ", "license": "(MIT OR Apache-2.0)", - "dependencies": { - "uts-core-wasm": "workspace:*" - }, + "dependencies": {}, "devDependencies": { "typescript": "^5.9.3" } diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts new file mode 100644 index 0000000..f7349be --- /dev/null +++ b/packages/sdk/src/index.ts @@ -0,0 +1,35 @@ + +export type HexString = string; + +export type DigestOp = "SHA1" | "SHA512" | "RIPEMD160" | "KECCAK256"; + +export interface DigestHeader { + kind: DigestOp; + digest: HexString; +} + + +export interface BaseExecutionStep { + input: HexString; + output: HexString; +} + +export interface DataExecutionStep extends BaseExecutionStep { + op: "APPEND" | "PREPEND"; + data: HexString; +} + +export interface UnaryExecutionStep extends BaseExecutionStep { + op: DigestOp | "REVERSE" | "HEXLIFY"; +} + +export type ExecutionStep = DataExecutionStep | UnaryExecutionStep; + +export type AttestationStep = + | { kind: "pending"; url: string } + | { kind: "bitcoin"; height: number } + | { kind: "unknown"; tag: HexString; data: HexString }; + +export type TraceNode = ExecutionStep | AttestationStep | TraceNode[]; + +export type TraceResult = TraceNode[]; diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json new file mode 100644 index 0000000..865556f --- /dev/null +++ b/packages/sdk/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/uts-sdk/src/index.ts b/packages/uts-sdk/src/index.ts deleted file mode 100644 index 8624a56..0000000 --- a/packages/uts-sdk/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import init, { - merge_timestamps, - pack_detached_timestamp, - trace_timestamp -} from "uts-core-wasm"; - -export type HexString = string; - -export type DigestOp = "SHA1" | "SHA512" | "RIPEMD160" | "KECCAK256"; - -export interface DigestHeader { - kind: DigestOp; - digest: HexString; -} - - -export interface BaseExecutionStep { - input: HexString; - output: HexString; -} - -export interface DataExecutionStep extends BaseExecutionStep { - op: "APPEND" | "PREPEND"; - data: HexString; -} - -export interface UnaryExecutionStep extends BaseExecutionStep { - op: DigestOp | "REVERSE" | "HEXLIFY"; -} - -export type ExecutionStep = DataExecutionStep | UnaryExecutionStep; - -export type AttestationStep = - | { kind: "pending"; url: string } - | { kind: "bitcoin"; height: number } - | { kind: "unknown"; tag: HexString; data: HexString }; - -export type TraceNode = ExecutionStep | AttestationStep | TraceNode[]; - -export type TraceResult = TraceNode[]; - -export class UtsSDK { - private initialized = false; - - async ensureInit() { - if (!this.initialized) { - await init(); - this.initialized = true; - } - } - - mergeTimestamps(timestamps: Uint8Array[]): Uint8Array { - return merge_timestamps(timestamps) - } - - packDetachedTimestamp(digest: DigestHeader, timestamp: Uint8Array): Uint8Array { - this.checkInit(); - return pack_detached_timestamp(digest, timestamp); - } - - traceTimestamp(timestamp: Uint8Array): TraceResult { - this.checkInit(); - return trace_timestamp(timestamp) as TraceResult; - } - - private checkInit() { - if (!this.initialized) { - throw new Error("UtsSDK not initialized. Call ensureInit() first."); - } - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26c3660..f0f6954 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,9 @@ importers: apps/web: dependencies: - uts-sdk: + '@uts/sdk': specifier: workspace:* - version: link:../../packages/uts-sdk + version: link:../../packages/sdk vue: specifier: ^3.5.24 version: 3.5.26(typescript@5.9.3) @@ -43,13 +43,25 @@ importers: specifier: ^3.1.4 version: 3.2.1(typescript@5.9.3) - crates/core-wasm/pkg: {} + packages/bmt: + devDependencies: + '@vitest/browser': + specifier: ^4.0.18 + version: 4.0.18(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) + '@vitest/browser-playwright': + specifier: ^4.0.18 + version: 4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) - packages/uts-sdk: - dependencies: - uts-core-wasm: - specifier: workspace:* - version: link:../../crates/core-wasm/pkg + packages/sdk: devDependencies: typescript: specifier: ^5.9.3 @@ -233,6 +245,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -346,6 +361,15 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -359,6 +383,46 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 vue: ^3.2.25 + '@vitest/browser-playwright@4.0.18': + resolution: {integrity: sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==} + peerDependencies: + playwright: '*' + vitest: 4.0.18 + + '@vitest/browser@4.0.18': + resolution: {integrity: sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==} + peerDependencies: + vitest: 4.0.18 + + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@volar/language-core@2.4.27': resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} @@ -414,12 +478,20 @@ packages: alien-signals@3.1.2: resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + binary-install@1.1.2: resolution: {integrity: sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==} engines: {node: '>=10'} @@ -428,6 +500,14 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -442,6 +522,9 @@ packages: resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} engines: {node: '>=0.12'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -450,6 +533,13 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -475,14 +565,23 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -491,9 +590,17 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + minimatch@10.2.2: + resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -505,6 +612,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -514,6 +625,10 @@ packages: engines: {node: '>=10'} hasBin: true + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -522,9 +637,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -532,6 +653,13 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -539,6 +667,24 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + pnpm@10.26.2: resolution: {integrity: sha512-DjCP8gBfx0EDZvFU9iX2YxqysWsdLnAjhETdaunWMKhILZKkURRN68SSQWiW7Rb3sRSobsaLhASyRDhp5o/9pg==} engines: {node: '>=18.12'} @@ -553,23 +699,56 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + rollup@4.54.0: resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -618,6 +797,40 @@ packages: yaml: optional: true + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -639,9 +852,26 @@ packages: resolution: {integrity: sha512-P9exD4YkjpDbw68xUhF3MDm/CC/3eTmmthyG5bHJ56kalxOTewOunxTke4SyF8MTXV6jUtNjXggPgrGmMtczGg==} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -740,6 +970,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@polka/url@1.0.0-next.29': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/rollup-android-arm-eabi@4.54.0': @@ -808,6 +1040,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/node@24.10.4': @@ -820,6 +1061,75 @@ snapshots: vite: 7.3.0(@types/node@24.10.4) vue: 3.5.26(typescript@5.9.3) + '@vitest/browser-playwright@4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18)': + dependencies: + '@vitest/browser': 4.0.18(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + playwright: 1.58.2 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.0.18(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18)': + dependencies: + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + '@vitest/utils': 4.0.18 + magic-string: 0.30.21 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@24.10.4))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.0(@types/node@24.10.4) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + '@volar/language-core@2.4.27': dependencies: '@volar/source-map': 2.4.27 @@ -903,6 +1213,8 @@ snapshots: alien-signals@3.1.2: {} + assertion-error@2.0.1: {} + axios@0.26.1: dependencies: follow-redirects: 1.15.11 @@ -911,6 +1223,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + binary-install@1.1.2: dependencies: axios: 0.26.1 @@ -924,6 +1238,12 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@5.0.3: + dependencies: + balanced-match: 4.0.4 + + chai@6.2.2: {} + chownr@2.0.0: {} concat-map@0.0.1: {} @@ -932,6 +1252,8 @@ snapshots: entities@7.0.0: {} + es-module-lexer@1.7.0: {} + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -963,6 +1285,12 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -975,9 +1303,18 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true + glob@13.0.6: + dependencies: + minimatch: 10.2.2 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -994,10 +1331,16 @@ snapshots: inherits@2.0.4: {} + lru-cache@11.2.6: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + minimatch@10.2.2: + dependencies: + brace-expansion: 5.0.3 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -1008,6 +1351,8 @@ snapshots: minipass@5.0.0: {} + minipass@7.1.3: {} + minizlib@2.1.2: dependencies: minipass: 3.3.6 @@ -1015,22 +1360,49 @@ snapshots: mkdirp@1.0.4: {} + mrmime@2.0.1: {} + muggle-string@0.4.1: {} nanoid@3.3.11: {} + obug@2.1.1: {} + once@1.4.0: dependencies: wrappy: 1.0.2 + package-json-from-dist@1.0.1: {} + path-browserify@1.0.1: {} path-is-absolute@1.0.1: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.6 + minipass: 7.1.3 + + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + pnpm@10.26.2: {} postcss@8.5.6: @@ -1043,6 +1415,11 @@ snapshots: dependencies: glob: 7.2.3 + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 @@ -1071,8 +1448,20 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 + siginfo@2.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -1082,11 +1471,19 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyrainbow@3.0.3: {} + + totalist@3.0.1: {} + typescript@5.9.3: {} undici-types@7.16.0: {} @@ -1103,6 +1500,44 @@ snapshots: '@types/node': 24.10.4 fsevents: 2.3.3 + vitest@4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.0(@types/node@24.10.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.4 + '@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vscode-uri@3.1.0: {} vue-tsc@3.2.1(typescript@5.9.3): @@ -1127,6 +1562,13 @@ snapshots: transitivePeerDependencies: - debug + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrappy@1.0.2: {} + ws@8.19.0: {} + yallist@4.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 33f4b8b..c34b5d5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,6 @@ packages: - apps/* - packages/* - - crates/core-wasm/pkg onlyBuiltDependencies: - esbuild diff --git a/tsconfig.json b/tsconfig.json index 2f0c714..c7b0f89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "composite": true }, "references": [ - { "path": "./packages/uts-sdk" }, + { "path": "./packages/sdk"}, { "path": "./apps/web" } ] } From 3362de61fcb6c6f0a25eff9594b3b14ec14ccbaa Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 23 Feb 2026 13:20:25 +0800 Subject: [PATCH 02/13] use noble --- .pre-commit-config.yaml | 6 + .prettierignore | 6 + .prettierrc.toml | 2 + .vscode/settings.json | 2 +- apps/web/src/components/HelloWorld.vue | 6 +- apps/web/tsconfig.json | 5 +- dev-docs/e2e.md | 17 +- dev-docs/milestones/M1-mvp-server.md | 2 +- dev-docs/ots-api.md | 19 +- package.json | 5 +- packages/bmt/package.json | 4 + packages/bmt/src/index.ts | 431 +++++----- packages/bmt/test/merkle.test.ts | 113 ++- packages/bmt/vitest.browser.config.ts | 13 + packages/bmt/vitest.config.ts | 6 +- packages/sdk/src/index.ts | 32 +- pnpm-lock.yaml | 1011 ++++++++++++++++++------ tsconfig.json | 3 +- 18 files changed, 1130 insertions(+), 553 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.toml create mode 100644 packages/bmt/vitest.browser.config.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcfa3b3..71fd36a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,9 @@ repos: entry: forge fmt language: system files: \.sol$ + - id: prettier + name: prettier + entry: prettier --write + language: system + files: \.(js|jsx|ts|tsx|json|css|scss|md|html|toml)$ + pass_filenames: true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d0a6976 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +**/*.toml +**/*.sol + +crates/* +lib/* +target/* \ No newline at end of file diff --git a/.prettierrc.toml b/.prettierrc.toml new file mode 100644 index 0000000..3346a57 --- /dev/null +++ b/.prettierrc.toml @@ -0,0 +1,2 @@ +semi = false +singleQuote = true diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b7d77d..91e9922 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,4 @@ "solidity.packageDefaultDependenciesContractsDirectory": "contracts", "solidity.packageDefaultDependenciesDirectory": "lib", "solidity.exclude": ["lib/**"] -} \ No newline at end of file +} diff --git a/apps/web/src/components/HelloWorld.vue b/apps/web/src/components/HelloWorld.vue index 0510718..b43e309 100644 --- a/apps/web/src/components/HelloWorld.vue +++ b/apps/web/src/components/HelloWorld.vue @@ -6,10 +6,10 @@ defineProps<{ msg: string }>() const count = ref(0) -const sdk = new UtsSDK(); -await sdk.ensureInit(); +const sdk = new UtsSDK() +await sdk.ensureInit() -sdk.mergeTimestamps([[]]); +sdk.mergeTimestamps([[]])