From 760f15cc1e8b664127a9e7077915eeb15cef8b61 Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 19 Dec 2025 19:45:07 +0800 Subject: [PATCH 01/74] add submit_digest poc impl --- Cargo.lock | 1795 ++++++++++++++++- Cargo.toml | 9 + crates/bmt/src/lib.rs | 2 +- crates/calendar/Cargo.toml | 28 + crates/calendar/src/main.rs | 62 + crates/calendar/src/routes.rs | 1 + crates/calendar/src/routes/ots.rs | 111 + crates/core/src/bin/uts_info.rs | 18 +- crates/core/src/codec/v1.rs | 4 +- .../core/src/codec/v1/detached_timestamp.rs | 22 +- crates/core/src/codec/v1/timestamp.rs | 1 - crates/core/src/codec/v1/timestamp/fmt.rs | 10 +- crates/core/src/lib.rs | 2 +- 13 files changed, 2000 insertions(+), 65 deletions(-) create mode 100644 crates/calendar/Cargo.toml create mode 100644 crates/calendar/src/main.rs create mode 100644 crates/calendar/src/routes.rs create mode 100644 crates/calendar/src/routes/ots.rs diff --git a/Cargo.lock b/Cargo.lock index 4ae7a74..fffc147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,405 @@ dependencies = [ "cc", ] +[[package]] +name = "alloy-consensus" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eips" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "serde", + "serde_with", + "sha2 0.10.9", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9914c147bb9b25f440eca68a31dc29f5c22298bfa7754aa802965695384122b0" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db950a29746be9e2f2c6288c8bd7a6202a81f999ce109a2933d2379970ec0fa" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash", + "hashbrown 0.16.1", + "indexmap 2.12.1", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "serde", + "sha3 0.10.8", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.13.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-serde" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-signer-local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72cfe0be3ec5a8c1a46b2e5a7047ed41121d360d97f4405bb7c1c784880c86cb" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b96d5f5890605ba9907ce1e2158e2701587631dc005bfa582cf92dd6f21147" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8247b7cca5cde556e93f8b3882b01dbd272f527836049083d240c57bf7b4c15" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.12.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd54f38512ac7bae10bbc38480eefb1b9b398ca2ce25db9cc0c048c6411c4f1" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.111", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b09815b44899564566d4d56613d14fa9a274b1043a021f00468568752f449" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1038284171df8bfd48befc0c7b78f667a7e2be162f45f07bd1c378078ebe58" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-trie" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -48,6 +447,195 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -60,6 +648,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ + "serde", "zeroize", ] @@ -115,6 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ "axum-core", + "axum-macros", "bytes", "form_urlencoded", "futures-util", @@ -160,6 +750,38 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfe9f610fe4e99cf0cfcd03ccf8c63c28c616fe714d80475ef731f3b13dd21b" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -178,6 +800,27 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -193,12 +836,34 @@ dependencies = [ "bitcoin-private", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.8.2" @@ -252,12 +917,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "byteorder" version = "1.5.0" @@ -269,6 +963,24 @@ name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] [[package]] name = "cast" @@ -294,6 +1006,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -318,6 +1036,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -408,9 +1138,9 @@ dependencies = [ "ed25519-consensus", "getrandom 0.2.16", "p256", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "rayon", "sha2 0.10.9", "thiserror 2.0.17", @@ -454,7 +1184,7 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry_sdk", "prometheus-client", - "rand", + "rand 0.8.5", "rayon", "sha2 0.10.9", "sysinfo", @@ -503,7 +1233,7 @@ dependencies = [ "num-integer", "num-rational", "num-traits", - "rand", + "rand 0.8.5", "thiserror 2.0.17", ] @@ -516,6 +1246,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -528,12 +1270,41 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -549,6 +1320,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -668,7 +1454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -680,7 +1466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -703,7 +1489,7 @@ dependencies = [ "cpufeatures", "curve25519-dalek-derive", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -727,11 +1513,47 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.111", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.111", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -755,6 +1577,50 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.111", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -804,6 +1670,18 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -814,6 +1692,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", + "serdect", "signature", "spki", ] @@ -826,17 +1705,32 @@ checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core", + "rand_core 0.6.4", "sha2 0.9.9", "thiserror 1.0.69", "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -851,12 +1745,33 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -876,6 +1791,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -888,13 +1813,51 @@ dependencies = [ ] [[package]] -name = "event-listener-strategy" -version = "0.5.4" +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" dependencies = [ - "event-listener", - "pin-project-lite", + "arrayvec", + "auto_impl", + "bytes", ] [[package]] @@ -903,7 +1866,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -919,6 +1882,30 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -928,6 +1915,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1023,6 +2016,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "generic-array" version = "0.14.7" @@ -1080,7 +2079,7 @@ dependencies = [ "parking_lot", "portable-atomic", "quanta", - "rand", + "rand 0.8.5", "smallvec", "spinning_top", ] @@ -1092,7 +2091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1107,6 +2106,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1118,6 +2123,17 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1131,6 +2147,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1246,6 +2271,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -1327,6 +2376,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1348,6 +2403,43 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -1356,6 +2448,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1438,6 +2532,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.9", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "keccak" version = "0.2.0-rc.0" @@ -1447,6 +2564,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1459,6 +2586,18 @@ version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.1" @@ -1480,6 +2619,17 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1558,6 +2708,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -1585,6 +2741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1597,6 +2754,20 @@ dependencies = [ "libc", ] +[[package]] +name = "nybbles" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4b5ecbd0beec843101bffe848217f770e8b8da81d8355b7d6e226f2199b3dc" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1687,7 +2858,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand", + "rand 0.8.5", "serde_json", "thiserror 2.0.17", "tokio", @@ -1700,7 +2871,7 @@ name = "opentimestamps" version = "0.2.0" source = "git+https://github.com/opentimestamps/rust-opentimestamps#c87e3e18284fd1bb9456e50721aa4e2796aed057" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "env_logger", "log", ] @@ -1727,6 +2898,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "parking" version = "2.2.1" @@ -1768,6 +2967,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +dependencies = [ + "memchr", + "ucd-trie", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1870,6 +3079,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1888,6 +3103,17 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -1897,6 +3123,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -1929,6 +3177,25 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.13.5" @@ -1967,6 +3234,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.42" @@ -1982,6 +3255,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1989,8 +3268,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "serde", ] [[package]] @@ -2000,16 +3291,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rapidhash" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "d8e65c75143ce5d47c55b510297eeb1182f3c739b6043c537670e9fc18612dae" dependencies = [ - "getrandom 0.2.16", + "rustversion", ] [[package]] @@ -2050,6 +3379,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "regex" version = "1.12.2" @@ -2132,13 +3481,91 @@ dependencies = [ "digest 0.11.0-rc.4", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2147,6 +3574,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -2162,6 +3601,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2178,16 +3641,56 @@ dependencies = [ "der", "generic-array", "pkcs8", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.1", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -2254,6 +3757,47 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" version = "0.11.0-rc.3" @@ -2300,6 +3844,16 @@ dependencies = [ "digest 0.11.0-rc.4", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.5", +] + [[package]] name = "sha3" version = "0.11.0-rc.3" @@ -2307,7 +3861,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" dependencies = [ "digest 0.11.0-rc.4", - "keccak", + "keccak 0.2.0-rc.0", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", ] [[package]] @@ -2341,7 +3905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2355,6 +3919,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2391,6 +3958,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2425,6 +4004,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b1d2e2059056b66fec4a6bb2b79511d5e8d76196ef49c38996f4b48db7662f" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -2459,6 +4050,25 @@ dependencies = [ "windows", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2526,6 +4136,46 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -2600,7 +4250,7 @@ version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ - "indexmap", + "indexmap 2.12.1", "toml_datetime", "toml_parser", "winnow", @@ -2663,6 +4313,7 @@ dependencies = [ "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", "tower", @@ -2787,12 +4438,48 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2830,7 +4517,27 @@ dependencies = [ "criterion 0.8.1", "digest 0.11.0-rc.4", "sha2 0.11.0-rc.3", - "sha3", + "sha3 0.11.0-rc.3", +] + +[[package]] +name = "uts-calendar" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "axum", + "axum-extra", + "bytes", + "eyre", + "sha3 0.11.0-rc.3", + "smallvec", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "uts-core", ] [[package]] @@ -2846,7 +4553,7 @@ dependencies = [ "ripemd", "sha1", "sha2 0.11.0-rc.3", - "sha3", + "sha3 0.11.0-rc.3", "smallvec", "thiserror 2.0.17", "tracing", @@ -2864,6 +4571,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -3234,6 +4950,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -3241,7 +4966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 3c2fa86..b3bb4ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,12 @@ unnecessary-box-returns = "warn" unnecessary-debug-formatting = "warn" [workspace.dependencies] +alloy-primitives = "1.5" +alloy-signer = "1.1" +alloy-signer-local = "1.1" auto_impl = "1.3" +axum = "0.8" +axum-extra = "0.12" bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } @@ -48,6 +53,7 @@ strum = "0.27" thiserror = "2" tokio = { version = "1", features = ["rt"] } toml = "0.9" +tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } @@ -56,3 +62,6 @@ ripemd = "0.2.0-rc.3" sha1 = "0.11.0-rc.3" sha2 = "0.11.0-rc.3" sha3 = "0.11.0-rc.3" + +uts-bmt = { path = "crates/bmt" } +uts-core = { path = "crates/core" } diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index 9aae905..eb70e96 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -3,8 +3,8 @@ #![feature(likely_unlikely)] //! High performance binary Merkle tree implementation in Rust. -use std::hint::unlikely; use digest::{Digest, FixedOutputReset, Output}; +use std::hint::unlikely; /// Flat, Fixed-Size, Read only Merkle Tree /// diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml new file mode 100644 index 0000000..ea29bad --- /dev/null +++ b/crates/calendar/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-calendar" +repository.workspace = true +version.workspace = true + +[dependencies] +alloy-primitives = { workspace = true } +alloy-signer = { workspace = true } +alloy-signer-local = { workspace = true } +axum = { workspace = true, features = ["macros"] } +axum-extra = { workspace = true } +bytes = { workspace = true } +eyre = { workspace = true } +sha3 = { workspace = true } +smallvec = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tower-http = { workspace = true, features = ["limit"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +uts-core = { workspace = true } + +[lints] +workspace = true diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs new file mode 100644 index 0000000..f968da4 --- /dev/null +++ b/crates/calendar/src/main.rs @@ -0,0 +1,62 @@ +//! Calendar server + +#[macro_use] +extern crate tracing; + +use axum::{ + Router, + extract::DefaultBodyLimit, + routing::{get, post}, +}; + +mod routes; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + tracing_subscriber::fmt::init(); + + let app = Router::new() + .route( + "/digest", + post(routes::ots::submit_digest) + .layer(DefaultBodyLimit::max(routes::ots::MAX_DIGEST_SIZE)), + ) + .route( + "/timestamp/{hex_commitment}", + get(routes::ots::get_timestamp), + ); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await?; + + Ok(()) +} + +async fn shutdown_signal() { + use tokio::signal; + + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} diff --git a/crates/calendar/src/routes.rs b/crates/calendar/src/routes.rs new file mode 100644 index 0000000..5797280 --- /dev/null +++ b/crates/calendar/src/routes.rs @@ -0,0 +1 @@ +pub mod ots; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs new file mode 100644 index 0000000..5513e7a --- /dev/null +++ b/crates/calendar/src/routes/ots.rs @@ -0,0 +1,111 @@ +use alloy_primitives::{Keccak256, b256}; +use alloy_signer::SignerSync; +use alloy_signer_local::LocalSigner; +use axum::body::Bytes; +use bytes::{BufMut, BytesMut}; +use smallvec::SmallVec; +use std::time::SystemTime; +use tracing::Level; +use uts_core::{ + codec::{ + Encode, Encoder, + v1::{Attestation, opcode::OpCode}, + }, + utils::Hexed, +}; + +pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 +const ERC2098_SIGNATURE_SIZE: usize = 64; + +// Test this with official ots client: +// ots stamp -c "http://localhost:3000/" -m 1 +// cargo run --bin uts-info -- .ots +// Sample: +// ``` +// OTS Detached Timestamp found: +// Version 1 Proof digest of SHA256 877c470874fa92e5609a1396b1188ffa3e539d83ec2748a7cb6fb2d4430d45a2 +// execute APPEND ec04517482d3be52b6123ca37f683285 +// result 877c470874fa92e5609a1396b1188ffa3e539d83ec2748a7cb6fb2d4430d45a2ec04517482d3be52b6123ca37f683285 +// execute SHA256 +// result 2edc60a195a879bd446c5473921c46db14c4b1974516682ecae2b406121a5732 +// execute PREPEND 5137456900000000 +// result 51374569000000002edc60a195a879bd446c5473921c46db14c4b1974516682ecae2b406121a5732 +// execute APPEND 9f947a5cf576ba4f68593ac5e350204cc8b38bf0fd5f6f2d4436820d3164dfeaf7405188dfc4bad66e8f42e6fd0a6ffdcceebda548d01224113baab1a568a2b8 +// result 51374569000000002edc60a195a879bd446c5473921c46db14c4b1974516682ecae2b406121a57329f947a5cf576ba4f68593ac5e350204cc8b38bf0fd5f6f2d4436820d3164dfeaf7405188dfc4bad66e8f42e6fd0a6ffdcceebda548d01224113baab1a568a2b8 +// execute KECCAK256 +// result c15b4e8b93e9aaee5b8c736f5b73e5f313062e389925a0b1fc6495053f99d352 +// result attested by Pending: update URI https://localhost:3000 +// ``` +#[instrument(level = Level::TRACE, skip_all)] +pub async fn submit_digest(digest: Bytes) -> Bytes { + const MAX_MESSAGE_SIZE: usize = MAX_DIGEST_SIZE + size_of::() + ERC2098_SIGNATURE_SIZE; + + let uri = "https://localhost:3000".to_string(); + + let buf_size = 1 // OpCode::PREPEND + + 1 // length of u64 length in leb128 + + 8 // u64 timestamp + + 1 // OpCode::APPEND + + 1 // length of signature length in leb128 + + ERC2098_SIGNATURE_SIZE // signature + + 1 // FIXME: TBD: OpCode::KECCAK256 + + 1 // OpCode::ATTESTATION + + 8 // Pending tag + + 1 // length of packed ATTESTATION data length in leb128 + + (1 + uri.len()); // length of uri in leb128 + uri bytes + let attestation = Attestation::Pending { uri }; + + let mut timestamp = BytesMut::with_capacity(buf_size).writer(); // TODO: replace this with a builder + + let mut pending_attestation = SmallVec::<[u8; MAX_MESSAGE_SIZE]>::new(); + + // ots uses 32-bit unix time, but we use u64 here for future proofing, as it's not part of the ots spec. + let recv_timestamp: u64 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Clock MUST not go backwards") + .as_secs(); + trace!(recv_timestamp); + let recv_timestamp = recv_timestamp.to_le_bytes(); + OpCode::PREPEND.encode(&mut timestamp).unwrap(); + timestamp.encode_bytes(&recv_timestamp).unwrap(); + pending_attestation.extend(recv_timestamp); + + trace!(digest = ?Hexed(&digest)); + pending_attestation.extend_from_slice(&digest); + + let signer = LocalSigner::from_bytes(&b256!( + "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" + )) + .unwrap(); // TODO: load from app state + let undeniable_sig = signer.sign_message_sync(&digest).unwrap(); + let undeniable_sig = undeniable_sig.as_erc2098(); + trace!(undeniable_sig = ?Hexed(&undeniable_sig)); + OpCode::APPEND.encode(&mut timestamp).unwrap(); + timestamp.encode_bytes(&undeniable_sig).unwrap(); + pending_attestation.extend(undeniable_sig); + + trace!(pending_attestation = ?Hexed(&pending_attestation)); + + // FIXME: + // discussion: return the hash or the raw timestamp message? + // if using hash, client will request upgrade timestamp by hash (256 bits, 64 hex chars) + // + // if using raw timestamp message, client will request timestamp by whole message (variable size, 208 hex chars if request is 32 bytes), + // but we will have info about the receiving time of the request, + // which can narrow down the search space + let mut hasher = Keccak256::new(); + hasher.update(&pending_attestation); + hasher.finalize_into(&mut pending_attestation[0..32]); + OpCode::KECCAK256.encode(&mut timestamp).unwrap(); + + OpCode::ATTESTATION.encode(&mut timestamp).unwrap(); + attestation.encode(&mut timestamp).unwrap(); + + // TODO: store the pending_attestation into journal + + let timestamp = timestamp.into_inner(); + debug_assert_eq!(timestamp.len(), buf_size, "buffer size mismatch"); + timestamp.freeze() +} + +pub async fn get_timestamp() {} diff --git a/crates/core/src/bin/uts_info.rs b/crates/core/src/bin/uts_info.rs index 536ceca..837c1a9 100644 --- a/crates/core/src/bin/uts_info.rs +++ b/crates/core/src/bin/uts_info.rs @@ -7,10 +7,15 @@ //! Simple application to open an OTS info file and dump its contents //! to stdout in a human-readable format -use std::{env, fs, io, io::BufReader, process}; -use std::io::Seek; -use uts_core::codec::{Decode, VersionedProof}; -use uts_core::codec::v1::{DetachedTimestamp, Timestamp}; +use std::{ + env, fs, io, + io::{BufReader, Seek}, + process, +}; +use uts_core::codec::{ + Decode, VersionedProof, + v1::{DetachedTimestamp, Timestamp}, +}; fn main() { let args: Vec = env::args().collect(); @@ -33,7 +38,10 @@ fn main() { println!("{ots}"); } Err(e) => { - println!("Not a valid Detached Timestamp OTS file (trying raw timestamp): {}\n", e); + println!( + "Not a valid Detached Timestamp OTS file (trying raw timestamp): {}\n", + e + ); } }; diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index f5f367a..aa0b6b6 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -1,12 +1,12 @@ //! Components for the version 1 OpenTimestamps serialization format. mod attestation; +mod detached_timestamp; mod digest; pub mod opcode; mod timestamp; -mod detached_timestamp; pub use attestation::{Attestation, AttestationTag}; +pub use detached_timestamp::DetachedTimestamp; pub use digest::DigestHeader; pub use timestamp::Timestamp; -pub use detached_timestamp::DetachedTimestamp; diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index dcc33b8..1fe5732 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -1,8 +1,9 @@ -use std::fmt; -use std::fmt::Formatter; +use crate::codec::{ + Decode, Encode, Proof, Version, + v1::{DigestHeader, Timestamp, timestamp}, +}; use smallvec::ToSmallVec; -use crate::codec::{Decode, Encode, Proof, Version}; -use crate::codec::v1::{timestamp, DigestHeader, Timestamp}; +use std::{fmt, fmt::Formatter}; /// A file containing a timestamp for another file /// Contains a timestamp, along with a header and the digest of the file. @@ -29,7 +30,10 @@ impl Decode for DetachedTimestamp { } impl Encode for DetachedTimestamp { - fn encode(&self, mut writer: impl crate::codec::Encoder) -> Result<(), crate::error::EncodeError> { + fn encode( + &self, + mut writer: impl crate::codec::Encoder, + ) -> Result<(), crate::error::EncodeError> { self.header.encode(&mut writer)?; self.timestamp.encode(&mut writer)?; Ok(()) @@ -38,16 +42,12 @@ impl Encode for DetachedTimestamp { impl fmt::Display for DetachedTimestamp { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!( - f, - "digest of {}", - self.header - )?; + writeln!(f, "digest of {}", self.header)?; timestamp::fmt::fmt( &self.timestamp, Some(&self.header.digest().to_smallvec()), - f + f, ) } } diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 8c1cd51..bcabda6 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -31,7 +31,6 @@ const MAX_OP_LENGTH: usize = 4096; /// execute APPEND 0ef41e45bb5534b3 /// result attested by Pending: update URI https://alice.btc.calendar.opentimestamps.org /// ``` -/// #[derive(Clone, PartialEq, Eq, Debug)] pub struct Timestamp { steps: Vec, diff --git a/crates/core/src/codec/v1/timestamp/fmt.rs b/crates/core/src/codec/v1/timestamp/fmt.rs index e87733e..5ded172 100644 --- a/crates/core/src/codec/v1/timestamp/fmt.rs +++ b/crates/core/src/codec/v1/timestamp/fmt.rs @@ -79,7 +79,6 @@ fn fmt_recurse( } else { None }; - if let Some(step_idx) = resolve_ptr(step.first_child) { let step = ×tamp.steps[step_idx]; @@ -92,13 +91,6 @@ fn fmt_recurse( impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_recurse( - self, - None, - &self.steps.last().unwrap(), - f, - 0, - true, - ) + fmt_recurse(self, None, &self.steps.last().unwrap(), f, 0, true) } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index db5a5d1..1ac7265 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -10,4 +10,4 @@ pub mod fixtures; pub mod codec; /// Error types raised by codec operations. pub mod error; -mod utils; +pub mod utils; From c8a73a7b7a4793b52a77cd774a86ef3d7f874d5b Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Tue, 23 Dec 2025 16:02:06 +0800 Subject: [PATCH 02/74] refactor: timestamp rewrite (#2) * add builder * add builder * rewrite * rewrite Attestation * clippy * return when ok * fix * full integration with allocator API * fmt * apply review * apply review --- Cargo.lock | 3 +- Cargo.toml | 1 + crates/calendar/Cargo.toml | 2 +- crates/calendar/src/routes/ots.rs | 26 +- crates/core/Cargo.toml | 11 +- crates/core/src/bin/uts_info.rs | 7 +- crates/core/src/codec.rs | 66 ++--- crates/core/src/codec/imp.rs | 43 +++ crates/core/src/codec/imp/bytes.rs | 14 + crates/core/src/codec/{ => imp}/primitives.rs | 31 +-- crates/core/src/codec/imp/std_io.rs | 35 +++ crates/core/src/codec/proof.rs | 40 +-- crates/core/src/codec/v1.rs | 4 +- crates/core/src/codec/v1/attestation.rs | 254 ++++++++++++------ .../core/src/codec/v1/detached_timestamp.rs | 80 +++--- crates/core/src/codec/v1/digest.rs | 37 +-- crates/core/src/codec/v1/opcode.rs | 94 ++++--- crates/core/src/codec/v1/timestamp.rs | 228 +++++++++------- crates/core/src/codec/v1/timestamp/builder.rs | 1 + crates/core/src/codec/v1/timestamp/decode.rs | 178 ++++-------- crates/core/src/codec/v1/timestamp/encode.rs | 79 ++---- crates/core/src/codec/v1/timestamp/fmt.rs | 146 +++++----- crates/core/src/error.rs | 29 +- crates/core/src/fixtures.rs | 4 +- crates/core/src/lib.rs | 5 + crates/core/src/utils.rs | 22 +- crates/core/src/utils/hex.rs | 19 ++ crates/core/src/utils/sync.rs | 9 + crates/core/src/utils/sync/race.rs | 20 ++ crates/core/src/utils/sync/std.rs | 20 ++ 30 files changed, 870 insertions(+), 638 deletions(-) create mode 100644 crates/core/src/codec/imp.rs create mode 100644 crates/core/src/codec/imp/bytes.rs rename crates/core/src/codec/{ => imp}/primitives.rs (69%) create mode 100644 crates/core/src/codec/imp/std_io.rs create mode 100644 crates/core/src/codec/v1/timestamp/builder.rs create mode 100644 crates/core/src/utils/hex.rs create mode 100644 crates/core/src/utils/sync.rs create mode 100644 crates/core/src/utils/sync/race.rs create mode 100644 crates/core/src/utils/sync/std.rs diff --git a/Cargo.lock b/Cargo.lock index fffc147..fad715a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4545,16 +4545,17 @@ name = "uts-core" version = "0.1.0" dependencies = [ "auto_impl", + "bytes", "criterion 0.5.1", "digest 0.11.0-rc.4", "hex", + "once_cell", "opentimestamps", "paste", "ripemd", "sha1", "sha2 0.11.0-rc.3", "sha3 0.11.0-rc.3", - "smallvec", "thiserror 2.0.17", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index b3bb4ed..4887329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ const_format = "0.2" criterion = { version = "0.8", features = ["html_reports"] } eyre = "0.6" hex = "0.4" +once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" serde = "1.0" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index ea29bad..e8ca796 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -22,7 +22,7 @@ tokio = { workspace = true, features = ["full"] } tower-http = { workspace = true, features = ["limit"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } -uts-core = { workspace = true } +uts-core = { workspace = true, features = ["bytes"] } [lints] workspace = true diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 5513e7a..4b847b0 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -2,14 +2,14 @@ use alloy_primitives::{Keccak256, b256}; use alloy_signer::SignerSync; use alloy_signer_local::LocalSigner; use axum::body::Bytes; -use bytes::{BufMut, BytesMut}; +use bytes::BytesMut; use smallvec::SmallVec; use std::time::SystemTime; use tracing::Level; use uts_core::{ codec::{ - Encode, Encoder, - v1::{Attestation, opcode::OpCode}, + Encoder, + v1::{Attestation, PendingAttestation, opcode::OpCode}, }, utils::Hexed, }; @@ -53,9 +53,9 @@ pub async fn submit_digest(digest: Bytes) -> Bytes { + 8 // Pending tag + 1 // length of packed ATTESTATION data length in leb128 + (1 + uri.len()); // length of uri in leb128 + uri bytes - let attestation = Attestation::Pending { uri }; + let attestation = PendingAttestation { uri: uri.into() }; - let mut timestamp = BytesMut::with_capacity(buf_size).writer(); // TODO: replace this with a builder + let mut timestamp = BytesMut::with_capacity(buf_size); let mut pending_attestation = SmallVec::<[u8; MAX_MESSAGE_SIZE]>::new(); @@ -66,8 +66,8 @@ pub async fn submit_digest(digest: Bytes) -> Bytes { .as_secs(); trace!(recv_timestamp); let recv_timestamp = recv_timestamp.to_le_bytes(); - OpCode::PREPEND.encode(&mut timestamp).unwrap(); - timestamp.encode_bytes(&recv_timestamp).unwrap(); + timestamp.encode(OpCode::PREPEND).unwrap(); + timestamp.encode_bytes(recv_timestamp).unwrap(); pending_attestation.extend(recv_timestamp); trace!(digest = ?Hexed(&digest)); @@ -80,8 +80,8 @@ pub async fn submit_digest(digest: Bytes) -> Bytes { let undeniable_sig = signer.sign_message_sync(&digest).unwrap(); let undeniable_sig = undeniable_sig.as_erc2098(); trace!(undeniable_sig = ?Hexed(&undeniable_sig)); - OpCode::APPEND.encode(&mut timestamp).unwrap(); - timestamp.encode_bytes(&undeniable_sig).unwrap(); + timestamp.encode(OpCode::APPEND).unwrap(); + timestamp.encode_bytes(undeniable_sig).unwrap(); pending_attestation.extend(undeniable_sig); trace!(pending_attestation = ?Hexed(&pending_attestation)); @@ -96,14 +96,12 @@ pub async fn submit_digest(digest: Bytes) -> Bytes { let mut hasher = Keccak256::new(); hasher.update(&pending_attestation); hasher.finalize_into(&mut pending_attestation[0..32]); - OpCode::KECCAK256.encode(&mut timestamp).unwrap(); + timestamp.encode(OpCode::KECCAK256).unwrap(); - OpCode::ATTESTATION.encode(&mut timestamp).unwrap(); - attestation.encode(&mut timestamp).unwrap(); + timestamp.encode(OpCode::ATTESTATION).unwrap(); + timestamp.encode(attestation.to_raw().unwrap()).unwrap(); // TODO: store the pending_attestation into journal - - let timestamp = timestamp.into_inner(); debug_assert_eq!(timestamp.len(), buf_size, "buffer size mismatch"); timestamp.freeze() } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c6fba23..c6c9391 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -12,25 +12,26 @@ version.workspace = true [[bin]] name = "uts-info" path = "src/bin/uts_info.rs" +required-features = ["std"] [dependencies] auto_impl.workspace = true +bytes = { workspace = true, optional = true } digest.workspace = true hex.workspace = true +once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true ripemd.workspace = true sha1.workspace = true sha2.workspace = true sha3.workspace = true -smallvec = { workspace = true, features = ["write"] } thiserror.workspace = true tracing = { workspace = true, optional = true } [features] -nightly = [ - "smallvec/specialization", - "smallvec/may_dangle", -] +bytes = ["dep:bytes"] +default = ["std"] +std = [] tracing = ["dep:tracing"] [dev-dependencies] diff --git a/crates/core/src/bin/uts_info.rs b/crates/core/src/bin/uts_info.rs index 837c1a9..c7d11ea 100644 --- a/crates/core/src/bin/uts_info.rs +++ b/crates/core/src/bin/uts_info.rs @@ -13,7 +13,7 @@ use std::{ process, }; use uts_core::codec::{ - Decode, VersionedProof, + Decode, Reader, VersionedProof, v1::{DetachedTimestamp, Timestamp}, }; @@ -32,10 +32,11 @@ fn main() { } }; - match VersionedProof::::decode(&mut fh) { + match VersionedProof::::decode(&mut Reader(&mut fh)) { Ok(ots) => { println!("OTS Detached Timestamp found:"); println!("{ots}"); + return; } Err(e) => { println!( @@ -47,7 +48,7 @@ fn main() { fh.seek(io::SeekFrom::Start(0)).unwrap(); - match Timestamp::decode(fh) { + match Timestamp::decode(&mut Reader(&mut fh)) { Ok(ots) => { println!("Raw Timestamp found:"); println!("{ots}"); diff --git a/crates/core/src/codec.rs b/crates/core/src/codec.rs index e51cc05..ebc2ff5 100644 --- a/crates/core/src/codec.rs +++ b/crates/core/src/codec.rs @@ -1,14 +1,14 @@ use crate::error::{DecodeError, EncodeError}; +use alloc::alloc::Global; use auto_impl::auto_impl; -use std::{ - io::{BufRead, Write}, - ops::RangeBounds, -}; +use core::{alloc::Allocator, ops::RangeBounds}; mod proof; pub use proof::{Proof, Version, VersionedProof}; -mod primitives; +mod imp; +#[cfg(feature = "std")] +pub use imp::{Reader, Writer}; /// Types and helpers for the version 1 serialization format. pub mod v1; @@ -17,15 +17,13 @@ pub mod v1; pub const MAGIC: &[u8; 31] = b"\x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94"; /// Helper trait for writing OpenTimestamps primitives to a byte stream. -pub trait Encoder: Write { +pub trait Encoder: Sized { /// Encodes a single byte to the writer. - fn encode_byte(&mut self, byte: u8) -> Result<(), EncodeError> { - self.write_all(&[byte])?; - Ok(()) - } + fn encode_byte(&mut self, byte: u8) -> Result<(), EncodeError>; /// Encodes a byte slice prefixed with its length. - fn encode_bytes(&mut self, bytes: &[u8]) -> Result<(), EncodeError> { + fn encode_bytes(&mut self, bytes: impl AsRef<[u8]>) -> Result<(), EncodeError> { + let bytes = bytes.as_ref(); self.encode(bytes.len())?; self.write_all(bytes)?; Ok(()) @@ -33,27 +31,22 @@ pub trait Encoder: Write { /// Writes the OpenTimestamps magic sequence to the stream. fn encode_magic(&mut self) -> Result<(), EncodeError> { - self.write_all(MAGIC)?; - Ok(()) + self.write_all(&MAGIC[..]) } /// Encodes a value implementing the [`Encode`] trait. - #[inline] fn encode(&mut self, value: impl Encode) -> Result<(), EncodeError> { value.encode(self) } -} -impl Encoder for W {} + // --- no_std feature compatibility --- + fn write_all(&mut self, buf: impl AsRef<[u8]>) -> Result<(), EncodeError>; +} /// Helper trait for reading OpenTimestamps primitives from a byte stream. -pub trait Decoder: BufRead { +pub trait Decoder: Sized { /// Decodes a single byte from the reader. - fn decode_byte(&mut self) -> Result { - let mut byte = [0]; - self.read_exact(&mut byte)?; - Ok(byte[0]) - } + fn decode_byte(&mut self) -> Result; /// Decodes a value and ensures it falls within the supplied range. fn decode_ranged( @@ -80,26 +73,37 @@ pub trait Decoder: BufRead { } /// Decodes a value implementing the [`Decode`] trait. - #[inline] fn decode(&mut self) -> Result { T::decode(self) } -} -impl Decoder for R {} - -/// Marker trait for types supporting both [`Encode`] and [`Decode`]. -pub trait Codec: Encode + Decode {} + /// Decodes a value implementing the [`Decode`] trait. + fn decode_in, A: Allocator>(&mut self, alloc: A) -> Result { + T::decode_in(self, alloc) + } -impl Codec for T {} + // --- no_std feature compatibility --- + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), DecodeError>; +} /// Serializes a value into an OpenTimestamps-compatible byte stream. #[auto_impl(&, &mut, Box, Rc, Arc)] pub trait Encode { - fn encode(&self, writer: impl Encoder) -> Result<(), EncodeError>; + fn encode(&self, writer: &mut impl Encoder) -> Result<(), EncodeError>; } /// Deserializes a value from an OpenTimestamps-compatible byte stream. pub trait Decode: Sized { - fn decode(reader: impl Decoder) -> Result; + fn decode(decoder: &mut impl Decoder) -> Result; +} + +/// Deserializes a value from an OpenTimestamps-compatible byte stream. +pub trait DecodeIn: Sized { + fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result; +} + +impl> Decode for T { + fn decode(decoder: &mut impl Decoder) -> Result { + T::decode_in(decoder, Global) + } } diff --git a/crates/core/src/codec/imp.rs b/crates/core/src/codec/imp.rs new file mode 100644 index 0000000..397da10 --- /dev/null +++ b/crates/core/src/codec/imp.rs @@ -0,0 +1,43 @@ +use crate::codec::*; +use alloc::vec::Vec; + +#[cfg(feature = "bytes")] +mod bytes; +mod primitives; +#[cfg(feature = "std")] +mod std_io; + +#[cfg(feature = "std")] +pub use std_io::{Reader, Writer}; + +impl Encoder for Vec { + fn encode_byte(&mut self, byte: u8) -> Result<(), EncodeError> { + self.push(byte); + Ok(()) + } + + fn write_all(&mut self, buf: impl AsRef<[u8]>) -> Result<(), EncodeError> { + self.extend_from_slice(buf.as_ref()); + Ok(()) + } +} + +impl Decoder for &[u8] { + fn decode_byte(&mut self) -> Result { + let Some((a, b)) = self.split_at_checked(1) else { + return Err(DecodeError::UnexpectedEof); + }; + *self = b; + Ok(a[0]) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), DecodeError> { + let len = buf.len(); + let Some((a, b)) = self.split_at_checked(len) else { + return Err(DecodeError::UnexpectedEof); + }; + buf.copy_from_slice(a); + *self = b; + Ok(()) + } +} diff --git a/crates/core/src/codec/imp/bytes.rs b/crates/core/src/codec/imp/bytes.rs new file mode 100644 index 0000000..f4d0158 --- /dev/null +++ b/crates/core/src/codec/imp/bytes.rs @@ -0,0 +1,14 @@ +use crate::codec::*; +use bytes::{BufMut, BytesMut}; + +impl Encoder for BytesMut { + fn encode_byte(&mut self, byte: u8) -> Result<(), EncodeError> { + self.put_u8(byte); + Ok(()) + } + + fn write_all(&mut self, buf: impl AsRef<[u8]>) -> Result<(), EncodeError> { + self.put_slice(buf.as_ref()); + Ok(()) + } +} diff --git a/crates/core/src/codec/primitives.rs b/crates/core/src/codec/imp/primitives.rs similarity index 69% rename from crates/core/src/codec/primitives.rs rename to crates/core/src/codec/imp/primitives.rs index 8655876..afd129d 100644 --- a/crates/core/src/codec/primitives.rs +++ b/crates/core/src/codec/imp/primitives.rs @@ -1,14 +1,11 @@ -use crate::{ - codec::{Decode, Encode}, - error::EncodeError, -}; +use crate::codec::*; macro_rules! leb128 { ($ty:ty) => { paste::paste! { - impl super::Encode for $ty { + impl crate::codec::Encode for $ty { #[inline] - fn encode(&self, mut encoder: impl crate::codec::Encoder) -> Result<(), $crate::error::EncodeError> { + fn encode(&self, encoder: &mut impl crate::codec::Encoder) -> Result<(), $crate::error::EncodeError> { let mut n = *self; let mut buf = [0u8; <$ty>::BITS.div_ceil(7) as usize]; let mut i = 0; @@ -34,9 +31,9 @@ macro_rules! leb128 { } } - impl super::Decode for $ty { + impl crate::codec::DecodeIn for $ty { #[inline] - fn decode(mut decoder: impl crate::codec::Decoder) -> Result { + fn decode_in(decoder: &mut impl crate::codec::Decoder, _alloc: A) -> Result { let mut ret: $ty = 0; let mut shift: u32 = 0; @@ -69,15 +66,15 @@ leb128!(u16, u32, u64, u128); impl Encode for u8 { #[inline] - fn encode(&self, mut encoder: impl crate::codec::Encoder) -> Result<(), EncodeError> { - encoder.write_all(&[*self])?; + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.write_all([*self])?; Ok(()) } } -impl Decode for u8 { +impl DecodeIn for u8 { #[inline] - fn decode(mut decoder: impl crate::codec::Decoder) -> Result { + fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result { let mut byte = [0u8; 1]; decoder.read_exact(&mut byte)?; Ok(byte[0]) @@ -86,17 +83,17 @@ impl Decode for u8 { impl Encode for usize { #[inline] - fn encode(&self, mut encoder: impl crate::codec::Encoder) -> Result<(), EncodeError> { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { let val: u32 = (*self).try_into().map_err(|_| EncodeError::UsizeOverflow)?; - val.encode(&mut encoder)?; + val.encode(encoder)?; Ok(()) } } -impl Decode for usize { +impl DecodeIn for usize { #[inline] - fn decode(mut decoder: impl crate::codec::Decoder) -> Result { - let val = u32::decode(&mut decoder)?; + fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result { + let val = u32::decode(decoder)?; Ok(val as usize) } } diff --git a/crates/core/src/codec/imp/std_io.rs b/crates/core/src/codec/imp/std_io.rs new file mode 100644 index 0000000..2c10093 --- /dev/null +++ b/crates/core/src/codec/imp/std_io.rs @@ -0,0 +1,35 @@ +use crate::codec::*; +use std::{ + io::{Read, Write}, + slice, +}; + +pub struct Writer(pub W); +pub struct Reader(pub R); + +impl Encoder for Writer { + fn encode_byte(&mut self, byte: u8) -> Result<(), EncodeError> { + self.write_all(slice::from_ref(&byte))?; + Ok(()) + } + + #[inline] + fn write_all(&mut self, buf: impl AsRef<[u8]>) -> Result<(), EncodeError> { + self.0.write_all(buf.as_ref())?; + Ok(()) + } +} + +impl Decoder for Reader { + fn decode_byte(&mut self) -> Result { + let mut byte = [0]; + self.read_exact(&mut byte)?; + Ok(byte[0]) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), DecodeError> { + self.0.read_exact(buf)?; + Ok(()) + } +} diff --git a/crates/core/src/codec/proof.rs b/crates/core/src/codec/proof.rs index 179f2a6..7716712 100644 --- a/crates/core/src/codec/proof.rs +++ b/crates/core/src/codec/proof.rs @@ -1,44 +1,50 @@ use crate::{ - codec::{Codec, Decode, Decoder, Encode, Encoder}, + codec::{DecodeIn, Decoder, Encode, Encoder}, error::{DecodeError, EncodeError}, }; -use std::fmt; +use alloc::alloc::{Allocator, Global}; +use core::fmt; /// Version number of the serialization format. pub type Version = u32; /// Trait implemented by proof payloads for a specific serialization version. -pub trait Proof: Codec { +pub trait Proof: Encode + DecodeIn { /// Version identifier that must match the encoded proof. const VERSION: Version; } /// Wrapper that prefixes a proof with its version and magic bytes. #[derive(Clone, PartialEq, Eq, Debug)] -#[repr(transparent)] -pub struct VersionedProof(pub T); +pub struct VersionedProof, A: Allocator = Global> { + pub proof: T, + allocator: A, +} -impl Decode for VersionedProof { - fn decode(mut reader: impl Decoder) -> Result { - reader.assert_magic()?; - let version: Version = reader.decode()?; +impl, A: Allocator + Clone> DecodeIn for VersionedProof { + fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result { + decoder.assert_magic()?; + let version: Version = decoder.decode()?; if version != T::VERSION { return Err(DecodeError::BadVersion); } - Ok(VersionedProof(T::decode(&mut reader)?)) + Ok(VersionedProof { + proof: T::decode_in(decoder, alloc.clone())?, + allocator: alloc, + }) } } -impl Encode for VersionedProof { - fn encode(&self, mut writer: impl Encoder) -> Result<(), EncodeError> { - writer.encode_magic()?; - writer.encode(T::VERSION)?; - self.0.encode(writer) +impl, A: Allocator> Encode for VersionedProof { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.encode_magic()?; + encoder.encode(T::VERSION)?; + self.proof.encode(encoder) } } -impl fmt::Display for VersionedProof { +impl + fmt::Display, A: Allocator> fmt::Display for VersionedProof { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Version {} Proof {}", T::VERSION, self.0) + write!(f, "Version {} Proof {}", T::VERSION, self.proof) } } diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index aa0b6b6..4815fc6 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -6,7 +6,9 @@ mod digest; pub mod opcode; mod timestamp; -pub use attestation::{Attestation, AttestationTag}; +pub use attestation::{ + Attestation, AttestationTag, BitcoinAttestation, PendingAttestation, RawAttestation, +}; pub use detached_timestamp::DetachedTimestamp; pub use digest::DigestHeader; pub use timestamp::Timestamp; diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index 85d313c..ce86e19 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -1,27 +1,22 @@ -// Copyright (C) The OpenTimestamps developers -// Copyright (C) The ots-rs developers -// SPDX-License-Identifier: MIT OR Apache-2.0 - //! # Attestations //! //! An attestation is a claim that some data existed at some time. It //! comes from some server or from a blockchain. use crate::{ - codec::{Decoder, Encoder}, + codec::{Decode, DecodeIn, Decoder, Encode, Encoder}, error::{DecodeError, EncodeError}, - utils::Hexed, + utils::{Hexed, OnceLock}, }; -use smallvec::SmallVec; -use std::{ - fmt, - io::{BufRead, Write}, +use alloc::{ + alloc::{Allocator, Global}, + borrow::Cow, + vec::Vec, }; +use core::fmt; /// Size in bytes of the tag identifying the attestation type. const TAG_SIZE: usize = 8; -/// Maximum length of a URI in a "pending" attestation. -const MAX_URI_LEN: usize = 1000; /// Tag indicating a Bitcoin attestation. const BITCOIN_TAG: &[u8; 8] = b"\x05\x88\x96\x0d\x73\xd7\x19\x01"; @@ -31,92 +26,179 @@ const PENDING_TAG: &[u8; 8] = b"\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e"; /// Tag identifying the attestation kind. pub type AttestationTag = [u8; TAG_SIZE]; -/// Proof that some data existed at a given time. -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum Attestation { - /// Attestation derived from a Bitcoin block header. - /// - /// This consists solely of a block height and asserts that the - /// current hash matches the Merkle root of the block at that height. - Bitcoin { height: u32 }, - /// Attestation delivered by an OpenTimestamps calendar server. - /// - /// Only a restricted URI is stored locally so that the server can be - /// queried later for the full proof material. - Pending { uri: String }, - /// Opaque attestation stored verbatim. - Unknown { tag: AttestationTag, data: Vec }, +/// Raw Proof that some data existed at a given time. +#[derive(Clone, Debug)] +pub struct RawAttestation { + pub tag: AttestationTag, + pub data: Vec, + /// Cached value for verifying the attestation. + pub(crate) value: OnceLock>, } -impl Attestation { - /// Decodes an attestation payload from the reader. - pub fn decode(mut reader: R) -> Result { +impl DecodeIn for RawAttestation { + fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result { let mut tag = [0u8; TAG_SIZE]; - reader.read_exact(&mut tag)?; - let len = reader.decode()?; - - if tag == *BITCOIN_TAG { - let height = reader.decode()?; - Ok(Attestation::Bitcoin { height }) - } else if tag == *PENDING_TAG { - // This validation logic copied from python-opentimestamps. Peter comments - // that he is deliberately avoiding ?, &, @, etc., to "keep us out of trouble" - let length = reader.decode_ranged(0..=MAX_URI_LEN)?; - let mut uri_bytes = Vec::with_capacity(len); - uri_bytes.resize(length, 0); - reader.read_exact(&mut uri_bytes)?; - let uri_string = - String::from_utf8(uri_bytes).map_err(|_| DecodeError::InvalidUriChar)?; - if !uri_string.chars().all( - |ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '-' | '_' | '/' | ':'), - ) { - return Err(DecodeError::InvalidUriChar); - } + decoder.read_exact(&mut tag)?; + + let len = decoder.decode()?; + let mut data = Vec::with_capacity_in(len, alloc); + data.resize(len, 0); + decoder.read_exact(&mut data)?; + + Ok(RawAttestation { + tag, + data, + value: OnceLock::new(), + }) + } +} + +impl Encode for RawAttestation { + #[inline] + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.write_all(self.tag)?; + encoder.encode_bytes(&self.data) + } +} + +impl PartialEq for RawAttestation { + fn eq(&self, other: &Self) -> bool { + self.tag == other.tag && self.data.as_slice() == other.data.as_slice() + } +} + +impl Eq for RawAttestation {} + +impl RawAttestation { + /// Returns the allocator used by this raw attestation. + #[inline] + pub fn allocator(&self) -> &A { + self.data.allocator() + } +} - Ok(Attestation::Pending { uri: uri_string }) - } else { - let mut data = Vec::with_capacity(len); - data.resize(len, 0); - reader.read_exact(&mut data)?; +pub trait Attestation<'a>: Sized { + const TAG: AttestationTag; - Ok(Attestation::Unknown { tag, data }) + fn from_raw(raw: &'a RawAttestation) -> Result { + if raw.tag != Self::TAG { + return Err(DecodeError::BadAttestationTag); } + + Self::from_raw_data(&raw.data) } - /// Encodes the attestation to the writer. - pub fn encode(&self, mut writer: W) -> Result<(), EncodeError> { - match *self { - Attestation::Bitcoin { height } => { - writer.write_all(BITCOIN_TAG)?; - let mut buffer = SmallVec::<[u8; u32::BITS.div_ceil(7) as usize]>::new(); - buffer.encode(height)?; - writer.encode_bytes(&buffer) - } - Attestation::Pending { ref uri } => { - writer.write_all(PENDING_TAG)?; - let mut buffer = Vec::new(); - buffer.encode_bytes(uri.as_bytes())?; - writer.encode_bytes(&buffer) - } - Attestation::Unknown { ref tag, ref data } => { - writer.write_all(tag)?; - writer.encode_bytes(data) - } + fn to_raw(&self) -> Result { + self.to_raw_in(Global) + } + + fn to_raw_in(&self, alloc: A) -> Result, EncodeError> { + Ok(RawAttestation { + tag: Self::TAG, + data: self.to_raw_data_in(alloc)?, + value: OnceLock::new(), + }) + } + + fn from_raw_data(data: &'a [u8]) -> Result; + fn to_raw_data_in(&self, alloc: A) -> Result, EncodeError>; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BitcoinAttestation { + pub height: u32, +} + +impl fmt::Display for BitcoinAttestation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Bitcoin at height {}", self.height) + } +} + +impl Attestation<'_> for BitcoinAttestation { + const TAG: AttestationTag = *BITCOIN_TAG; + + fn from_raw_data(data: &[u8]) -> Result { + let height = u32::decode(&mut &*data)?; + Ok(BitcoinAttestation { height }) + } + + fn to_raw_data_in(&self, alloc: A) -> Result, EncodeError> { + let mut buffer = Vec::with_capacity_in(u32::BITS.div_ceil(7) as usize, alloc); + buffer.encode(self.height)?; + Ok(buffer) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PendingAttestation<'a> { + pub uri: Cow<'a, str>, +} + +impl PendingAttestation<'_> { + /// Maximum length of a URI in a "pending" attestation. + pub const MAX_URI_LEN: usize = 1000; + + #[inline] + pub fn validate_uri(uri: &str) -> bool { + uri.chars() + .all(|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '-' | '_' | '/' | ':')) + } +} + +impl fmt::Display for PendingAttestation<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Pending at {}", self.uri) + } +} + +impl<'a> Attestation<'a> for PendingAttestation<'a> { + const TAG: AttestationTag = *PENDING_TAG; + + fn from_raw_data(data: &'a [u8]) -> Result { + let data = &mut &data[..]; + let length = u32::decode(data)? as usize; // length prefix + if length > Self::MAX_URI_LEN { + return Err(DecodeError::UriTooLong); + } + if data.len() < length { + return Err(DecodeError::UnexpectedEof); + } + let uri = core::str::from_utf8(&data[..length]).map_err(|_| DecodeError::InvalidUriChar)?; + if !Self::validate_uri(uri) { + return Err(DecodeError::InvalidUriChar); } + Ok(PendingAttestation { + uri: Cow::Borrowed(uri), + }) + } + + fn to_raw_data_in(&self, alloc: A) -> Result, EncodeError> { + if self.uri.len() > Self::MAX_URI_LEN { + return Err(EncodeError::UriTooLong); + } + if !Self::validate_uri(&self.uri) { + return Err(EncodeError::InvalidUriChar); + } + let mut buffer = + Vec::with_capacity_in(self.uri.len() + u32::BITS.div_ceil(7) as usize, alloc); + buffer.encode_bytes(self.uri.as_bytes())?; + Ok(buffer) } } -impl fmt::Display for Attestation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Attestation::Bitcoin { height } => write!(f, "Bitcoin block {}", height), - Attestation::Pending { ref uri } => write!(f, "Pending: update URI {}", uri), - Attestation::Unknown { ref tag, ref data } => write!( - f, - "unknown attestation type {}: {}", - Hexed(tag), - Hexed(data) - ), +impl fmt::Display for RawAttestation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.tag { + tag if *tag == *BITCOIN_TAG => { + let att = BitcoinAttestation::from_raw(self).expect("Valid Bitcoin attestation"); + write!(f, "{}", att) + } + tag if *tag == *PENDING_TAG => { + let att = PendingAttestation::from_raw(self).expect("Valid Pending attestation"); + write!(f, "{}", att) + } + _ => write!(f, "Unknown Attestation with tag {}", Hexed(&self.tag)), } } } diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 627a1f7..7626851 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -1,9 +1,9 @@ use crate::codec::{ - Decode, Encode, Proof, Version, - v1::{DigestHeader, Timestamp, timestamp}, + Decode, DecodeIn, Encode, Encoder, Proof, Version, + v1::{DigestHeader, Timestamp}, }; -use smallvec::ToSmallVec; -use std::{fmt, fmt::Formatter}; +use alloc::alloc::{Allocator, Global}; +use core::{fmt, fmt::Formatter}; /// A file containing a timestamp for another file /// Contains a timestamp, along with a header and the digest of the file. @@ -12,43 +12,57 @@ use std::{fmt, fmt::Formatter}; /// which don't encode/decode the magic and version. /// The Python version is equivalent to `VersionedProof`. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct DetachedTimestamp { +pub struct DetachedTimestamp { header: DigestHeader, - timestamp: Timestamp, + timestamp: Timestamp, } -impl Proof for DetachedTimestamp { +impl Proof for DetachedTimestamp { const VERSION: Version = 1; } -impl Decode for DetachedTimestamp { - fn decode(mut reader: impl crate::codec::Decoder) -> Result { - let header = DigestHeader::decode(&mut reader)?; - let timestamp = Timestamp::decode(&mut reader)?; +impl DecodeIn for DetachedTimestamp { + fn decode_in( + decoder: &mut impl crate::codec::Decoder, + alloc: A, + ) -> Result { + let header = DigestHeader::decode(decoder)?; + let timestamp = Timestamp::decode_in(decoder, alloc)?; Ok(DetachedTimestamp { header, timestamp }) } } -impl Encode for DetachedTimestamp { - fn encode( - &self, - mut writer: impl crate::codec::Encoder, - ) -> Result<(), crate::error::EncodeError> { - self.header.encode(&mut writer)?; - self.timestamp.encode(&mut writer)?; +impl Encode for DetachedTimestamp { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), crate::error::EncodeError> { + self.header.encode(encoder)?; + self.timestamp.encode(encoder)?; Ok(()) } } -impl fmt::Display for DetachedTimestamp { +impl fmt::Display for DetachedTimestamp { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln!(f, "digest of {}", self.header)?; - timestamp::fmt::fmt( - &self.timestamp, - Some(&self.header.digest().to_smallvec()), - f, - ) + self.timestamp.fmt(Some(self.header.digest()), f) + } +} + +impl DetachedTimestamp { + /// Returns the digest header. + pub fn header(&self) -> &DigestHeader { + &self.header + } + + /// Returns the timestamp. + pub fn timestamp(&self) -> &Timestamp { + &self.timestamp + } + + /// Returns the allocator used by this detached timestamp. + #[inline] + pub fn allocator(&self) -> &A { + self.timestamp.allocator() } } @@ -56,7 +70,7 @@ impl fmt::Display for DetachedTimestamp { mod tests { use super::*; use crate::{ - codec::{Decode, Encode, proof::VersionedProof}, + codec::{Decode, Encoder, proof::VersionedProof}, fixtures, }; @@ -65,14 +79,18 @@ mod tests { let mut encoded_small = vec![]; let mut encoded_large = vec![]; - let ots = VersionedProof::::decode(fixtures::SMALL_DETACHED_TIMESTAMP); - assert!(ots.is_ok()); - assert!(ots.unwrap().encode(&mut encoded_small).is_ok()); + let ots = + VersionedProof::::decode(&mut &*fixtures::SMALL_DETACHED_TIMESTAMP) + .unwrap(); + println!("{:#?}", ots); + println!("{}", ots); + assert!(encoded_small.encode(&ots).is_ok()); assert_eq!(encoded_small, fixtures::SMALL_DETACHED_TIMESTAMP); - let ots = VersionedProof::::decode(fixtures::LARGE_DETACHED_TIMESTAMP); - assert!(ots.is_ok()); - assert!(ots.unwrap().encode(&mut encoded_large).is_ok()); + let ots = + VersionedProof::::decode(&mut &*fixtures::LARGE_DETACHED_TIMESTAMP) + .unwrap(); + assert!(encoded_large.encode(&ots).is_ok()); assert_eq!(encoded_large, fixtures::LARGE_DETACHED_TIMESTAMP); } } diff --git a/crates/core/src/codec/v1/digest.rs b/crates/core/src/codec/v1/digest.rs index 8ba71ff..5eeb85c 100644 --- a/crates/core/src/codec/v1/digest.rs +++ b/crates/core/src/codec/v1/digest.rs @@ -1,25 +1,30 @@ use crate::{ - codec::{Decode, Decoder, Encode, Encoder, v1::opcode::DigestOp}, + codec::{DecodeIn, Decoder, Encode, Encoder, v1::opcode::DigestOp}, error::{DecodeError, EncodeError}, utils::Hexed, }; -use std::fmt; +use alloc::alloc::Allocator; +use core::fmt; /// Header describing the digest that anchors a timestamp. -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DigestHeader { kind: DigestOp, digest: [u8; 32], } +impl fmt::Debug for DigestHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DigestHeader") + .field("kind", &self.kind) + .field("digest", &Hexed(self.digest())) + .finish() + } +} + impl fmt::Display for DigestHeader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} {}", - self.kind, - Hexed(&self.digest[..self.kind.output_size()]) - ) + write!(f, "{} {}", self.kind, Hexed(self.digest())) } } @@ -38,20 +43,20 @@ impl DigestHeader { impl Encode for DigestHeader { #[cfg_attr(feature = "tracing", tracing::instrument(skip(writer), err))] #[inline] - fn encode(&self, mut writer: impl Encoder) -> Result<(), EncodeError> { - writer.encode(&self.kind)?; - writer.write_all(&self.digest[..self.kind.output_size()])?; + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.encode(self.kind)?; + encoder.write_all(&self.digest[..self.kind.output_size()])?; Ok(()) } } -impl Decode for DigestHeader { +impl DecodeIn for DigestHeader { #[cfg_attr(feature = "tracing", tracing::instrument(skip(reader), ret, err))] #[inline] - fn decode(mut reader: impl Decoder) -> Result { - let kind = reader.decode()?; + fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result { + let kind = decoder.decode()?; let mut digest = [0u8; 32]; - reader.read_exact(&mut digest)?; + decoder.read_exact(&mut digest)?; Ok(DigestHeader { kind, digest }) } diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index ed8d2d5..f9772f4 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -3,26 +3,30 @@ //! It contains opcode information and utilities to work with opcodes. use crate::{ - codec::{Decode, Decoder, Encode, Encoder}, + codec::{Decode, DecodeIn, Decoder, Encode, Encoder}, error::{DecodeError, EncodeError}, }; +use alloc::{alloc::Allocator, vec::Vec}; +use core::{fmt, hint::unreachable_unchecked}; use digest::{Digest, OutputSizeUser, typenum::Unsigned}; use ripemd::Ripemd160; use sha1::Sha1; use sha2::Sha256; use sha3::Keccak256; -use smallvec::ToSmallVec; -use std::{fmt, hint::unreachable_unchecked}; - -pub(crate) type OperationBuffer = smallvec::SmallVec<[u8; 64]>; /// An OpenTimestamps opcode. /// /// This is always a valid opcode. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct OpCode(u8); +impl fmt::Debug for OpCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} + impl fmt::Display for OpCode { /// Formats the opcode as a string. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -32,15 +36,15 @@ impl fmt::Display for OpCode { impl Encode for OpCode { #[inline] - fn encode(&self, mut writer: impl Encoder) -> Result<(), EncodeError> { - writer.encode_byte(self.tag()) + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.encode_byte(self.tag()) } } impl Decode for OpCode { #[inline] - fn decode(mut reader: impl Decoder) -> Result { - let byte = reader.decode_byte()?; + fn decode(decoder: &mut impl Decoder) -> Result { + let byte = decoder.decode_byte()?; OpCode::new(byte).ok_or(DecodeError::BadOpCode(byte)) } } @@ -55,19 +59,13 @@ impl OpCode { /// Returns `true` when the opcode requires an immediate operand. #[inline] pub const fn has_immediate(&self) -> bool { - match *self { - Self::APPEND | Self::PREPEND => true, - _ => false, - } + matches!(*self, Self::APPEND | Self::PREPEND) } /// Returns `true` for control opcodes. #[inline] pub const fn is_control(&self) -> bool { - match *self { - Self::ATTESTATION | Self::FORK => true, - _ => false, - } + matches!(*self, Self::ATTESTATION | Self::FORK) } /// Returns `true` for digest opcodes. @@ -91,26 +89,45 @@ impl OpCode { /// /// Panics if the opcode is a control opcode. #[inline] - pub fn execute(&self, input: impl AsRef<[u8]>, immediate: impl AsRef<[u8]>) -> OperationBuffer { + pub fn execute(&self, input: impl AsRef<[u8]>, immediate: impl AsRef<[u8]>) -> Vec { + self.execute_in(input, immediate, alloc::alloc::Global) + } + + /// Executes the opcode on the given input data, with an optional immediate value. + /// + /// # Panics + /// + /// Panics if the opcode is a control opcode. + #[inline] + pub fn execute_in( + &self, + input: impl AsRef<[u8]>, + immediate: impl AsRef<[u8]>, + alloc: A, + ) -> Vec { if let Some(digest_op) = self.as_digest() { - return digest_op.execute(input); + return digest_op.execute_in(input, alloc); } let input = input.as_ref(); match *self { Self::APPEND => { - let mut out = OperationBuffer::from_slice(input); - out.extend_from_slice(immediate.as_ref()); + let immediate = immediate.as_ref(); + let mut out = Vec::with_capacity_in(input.len() + immediate.len(), alloc); + out.extend_from_slice(input); + out.extend_from_slice(immediate); out } Self::PREPEND => { - let mut out = OperationBuffer::from_slice(immediate.as_ref()); + let immediate = immediate.as_ref(); + let mut out = Vec::with_capacity_in(input.len() + immediate.len(), alloc); + out.extend_from_slice(immediate); out.extend_from_slice(input); out } Self::REVERSE => { let len = input.len(); - let mut out = OperationBuffer::with_capacity(len); + let mut out = Vec::::with_capacity_in(len, alloc); unsafe { // SAFETY: The vector capacity is set to len, so setting the length to len is valid. @@ -128,7 +145,7 @@ impl OpCode { } Self::HEXLIFY => { let hex_len = input.len() * 2; - let mut out = OperationBuffer::with_capacity(hex_len); + let mut out = Vec::::with_capacity_in(hex_len, alloc); // SAFETY: that the vector is actually the specified size. unsafe { out.set_len(hex_len); @@ -153,10 +170,16 @@ impl PartialEq for OpCode { /// An OpenTimestamps digest opcode. /// /// This is always a valid opcode. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct DigestOp(OpCode); +impl fmt::Debug for DigestOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.0.name()) + } +} + impl fmt::Display for DigestOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) @@ -177,15 +200,15 @@ impl PartialEq for DigestOp { impl Encode for DigestOp { #[inline] - fn encode(&self, writer: impl Encoder) -> Result<(), EncodeError> { - self.0.encode(writer) + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + self.0.encode(encoder) } } -impl Decode for DigestOp { +impl DecodeIn for DigestOp { #[inline] - fn decode(mut reader: impl Decoder) -> Result { - let opcode = OpCode::decode(&mut reader)?; + fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result { + let opcode = OpCode::decode(decoder)?; opcode .as_digest() .ok_or(DecodeError::ExpectedDigestOp(opcode)) @@ -264,13 +287,18 @@ macro_rules! define_digest_opcodes { } /// Executes the digest operation on the input data. - pub fn execute(&self, input: impl AsRef<[u8]>) -> OperationBuffer { + pub fn execute(&self, input: impl AsRef<[u8]>) -> ::alloc::vec::Vec { + self.execute_in(input, ::alloc::alloc::Global) + } + + /// Executes the digest operation on the input data. + pub fn execute_in(&self, input: impl AsRef<[u8]>, alloc: A) -> ::alloc::vec::Vec { match *self { $( Self::$variant => { paste::paste! { let mut hasher = [<$variant:camel>]::new(); hasher.update(input); - hasher.finalize().to_smallvec() + hasher.finalize().to_vec_in(alloc) } }, )* // SAFETY: unreachable as all variants are covered. diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index d15dfcf..245ce6b 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -1,18 +1,16 @@ //! ** The implementation here is subject to change as this is a read-only version. ** -use crate::codec::{ - Proof, Version, - v1::{Attestation, opcode::OpCode}, -}; -use std::num::NonZeroU32; -type StepPtr = Option; +use crate::{ + codec::v1::{attestation::RawAttestation, opcode::OpCode}, + utils::{Hexed, OnceLock}, +}; +use alloc::{alloc::Global, vec::Vec}; +use core::{alloc::Allocator, fmt::Debug}; +mod builder; mod decode; mod encode; -pub(crate) mod fmt; - -const RECURSION_LIMIT: usize = 256; -const MAX_OP_LENGTH: usize = 4096; +mod fmt; /// Proof that that one or more attestations commit to a message. /// @@ -31,125 +29,147 @@ const MAX_OP_LENGTH: usize = 4096; /// execute APPEND 0ef41e45bb5534b3 /// result attested by Pending: update URI https://alice.btc.calendar.opentimestamps.org /// ``` -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Timestamp { - steps: Vec, - data: Vec, - attestations: Vec, +#[derive(Clone, Debug)] +pub enum Timestamp { + Step(Step), + Attestation(RawAttestation), } -/// An OpenTimestamps step. -#[derive(Clone, PartialEq, Eq, Debug)] -#[repr(C)] -struct Step { - opcode: OpCode, - _padding: u8, - data_len: u16, - data_offset: u32, - // LCRS tree structure - first_child: StepPtr, - next_sibling: StepPtr, +/// An execution Step. +#[derive(Clone)] +pub struct Step { + op: OpCode, + data: Vec, + input: OnceLock>, + next: Vec, A>, } -// cache line aligned -const _: () = assert!(size_of::() == 16); -impl Default for Step { - fn default() -> Self { - Step { - opcode: OpCode::ATTESTATION, - _padding: 0, - data_len: 0, - data_offset: 0, - first_child: None, - next_sibling: None, +impl PartialEq for Timestamp { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Timestamp::Step(s1), Timestamp::Step(s2)) => s1 == s2, + (Timestamp::Attestation(a1), Timestamp::Attestation(a2)) => a1 == a2, + _ => false, } } } +impl Eq for Timestamp {} -impl Proof for Timestamp { - const VERSION: Version = 1; +impl PartialEq for Step { + fn eq(&self, other: &Self) -> bool { + self.op == other.op && self.data == other.data && self.next == other.next + } +} +impl Eq for Step {} + +impl Debug for Step { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut f = f.debug_struct("Step"); + f.field("op", &self.op); + if self.op.has_immediate() { + f.field("data", &Hexed(&self.data)); + } + f.field("next", &self.next).finish() + } } -impl Timestamp { - /// Returns the data slice associated with a step. - /// - /// # Safety - /// - /// The caller must ensure that the step was constructed from this timestamp's data buffer. - #[inline] - unsafe fn get_step_data(&self, step: &Step) -> &[u8] { - if step.data_len == 0 { - return &[]; +impl Timestamp { + /// Returns the opcode of this timestamp node. + pub fn op(&self) -> OpCode { + match self { + Timestamp::Step(step) => { + debug_assert_ne!( + step.op, + OpCode::ATTESTATION, + "sanity check failed: Step with ATTESTATION opcode" + ); + step.op + } + Timestamp::Attestation(_) => OpCode::ATTESTATION, } - let start = step.data_offset as usize; - debug_assert!(start < self.data.len()); - let end = start + step.data_len as usize; - debug_assert!(end <= self.data.len()); - // SAFETY: bounds checked above - unsafe { self.data.get_unchecked(start..end) } } - /// Returns the attestation index encoded by an attestation step. - /// - /// # Safety - /// - /// The caller must ensure that the step is an attestation step and that the - /// safety requirements of [`Self::get_step_data`] also hold. + /// Returns this timestamp as a step, if it is one. #[inline] - unsafe fn get_attest_idx(&self, step: &Step) -> u32 { - debug_assert!(step.opcode == OpCode::ATTESTATION); - debug_assert_eq!(step.data_len as usize, size_of::()); - let data = unsafe { self.get_step_data(step) }; - u32::from_le_bytes(data.try_into().unwrap()) + pub fn as_step(&self) -> Option<&Step> { + match self { + Timestamp::Step(step) => Some(step), + Timestamp::Attestation(_) => None, + } } + /// Returns this timestamp as an attestation, if it is one. #[inline] - fn push_to_heap(heap: &mut Vec, data: &[u8]) -> (u32, u16) { - if data.is_empty() { - return (0, 0); + pub fn as_attestation(&self) -> Option<&RawAttestation> { + match self { + Timestamp::Attestation(attestation) => Some(attestation), + Timestamp::Step(_) => None, } - - let offset = heap.len(); - let len = data.len(); - - assert!(offset <= u32::MAX as usize, "Data heap overflow (max 4GB)"); - assert!(len <= u16::MAX as usize, "Ref data too large (max 65KB)"); - - heap.extend_from_slice(data); - - (offset as u32, len as u16) } - /// Returns a mutable buffer from the heap. - /// - /// # Safety - /// - /// The caller must write exactly `len` bytes into the returned buffer. + /// Returns the input data for this timestamp node, if finalized. #[inline] - unsafe fn get_buffer_from_heap(heap: &mut Vec, len: usize) -> (u32, u16, &mut [u8]) { - let offset = heap.len(); - assert!(offset <= u32::MAX as usize, "Data heap overflow (max 4GB)"); - assert!(len <= u16::MAX as usize, "Ref data too large (max 65KB)"); - - heap.reserve(len); - - // SAFETY: we just reserved enough space - let buffer = unsafe { - heap.set_len(offset + len); - heap.get_unchecked_mut(offset..offset + len) - }; + pub fn input(&self) -> Option<&[u8]> { + match self { + Timestamp::Step(step) => step.input.get().map(|v| v.as_slice()), + Timestamp::Attestation(attestation) => attestation.value.get().map(|v| v.as_slice()), + } + } - (offset as u32, len as u16, buffer) + /// Returns the allocator used by this timestamp node. + #[inline] + pub fn allocator(&self) -> &A { + match self { + Self::Attestation(attestation) => attestation.allocator(), + Self::Step(step) => step.allocator(), + } } } -#[inline] -fn make_ptr(idx: usize) -> StepPtr { - assert!(idx < u32::MAX as usize); - NonZeroU32::new((idx + 1) as u32) +impl Timestamp { + /// Finalizes the timestamp with the given input data. + /// + /// # Panics + /// + /// Panics if the timestamp is already finalized with different input data. + pub fn finalize(&self, input: Vec) { + match self { + Self::Attestation(attestation) => { + if let Some(already) = attestation.value.get() { + assert_eq!(&input, already, "trying to finalize with different input"); + return; + } + let _ = attestation.value.get_or_init(|| input); + } + Self::Step(step) => { + if let Some(already) = step.input.get() { + assert_eq!(&input, already, "trying to finalize with different input"); + return; + } + let input = step.input.get_or_init(|| input); + + match step.op { + OpCode::FORK => { + debug_assert!(step.next.len() >= 2, "FORK must have at least two children"); + for child in &step.next { + child.finalize(input.clone()); + } + } + OpCode::ATTESTATION => unreachable!("should not happen"), + op => { + let output = op.execute_in(input, &step.data, step.allocator().clone()); + debug_assert!(step.next.len() == 1, "non-FORK must have exactly one child"); + step.next[0].finalize(output); + } + } + } + } + } } -#[inline] -fn resolve_ptr(ptr: StepPtr) -> Option { - ptr.map(|nz| (nz.get() - 1) as usize) +impl Step { + /// Returns the allocator used by this step. + pub(crate) fn allocator(&self) -> &A { + self.data.allocator() + } } diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs new file mode 100644 index 0000000..d5ee9ab --- /dev/null +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -0,0 +1 @@ +//! Timestamp Builder diff --git a/crates/core/src/codec/v1/timestamp/decode.rs b/crates/core/src/codec/v1/timestamp/decode.rs index 277814f..c03e018 100644 --- a/crates/core/src/codec/v1/timestamp/decode.rs +++ b/crates/core/src/codec/v1/timestamp/decode.rs @@ -1,152 +1,86 @@ use super::*; use crate::{ - codec::{Decode, Decoder}, + codec::{Decode, DecodeIn, Decoder}, error::DecodeError, }; -use std::io::BufRead; -impl Decode for Timestamp { - fn decode(mut reader: impl Decoder) -> Result { - let mut steps = Vec::new(); - let mut data = Vec::new(); - let mut attestations = Vec::new(); +const RECURSION_LIMIT: usize = 256; - Self::decode_step_recurse( - &mut reader, - &mut steps, - &mut data, - &mut attestations, - None, - RECURSION_LIMIT, - )?; - - Ok(Timestamp { - steps, - data, - attestations, - }) +impl DecodeIn for Timestamp { + fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result { + Self::decode_recursive(decoder, RECURSION_LIMIT, alloc) } } -impl Timestamp { - fn decode_step_recurse( - reader: &mut R, - steps: &mut Vec, - data: &mut Vec, - attestations: &mut Vec, - op: Option, +impl Timestamp { + fn decode_recursive( + decoder: &mut impl Decoder, recursion_limit: usize, - ) -> Result { + alloc: A, + ) -> Result, DecodeError> { if recursion_limit == 0 { return Err(DecodeError::RecursionLimit); } + let op = OpCode::decode(&mut *decoder)?; - let op = match op { - Some(op) => op, - None => reader.decode()?, - }; + Self::decode_from_op(op, decoder, recursion_limit, alloc) + } - let step = match op { + fn decode_from_op( + op: OpCode, + decoder: &mut impl Decoder, + limit: usize, + alloc: A, + ) -> Result, DecodeError> { + match op { OpCode::ATTESTATION => { - let attest = Attestation::decode(reader)?; - let attest_idx = attestations.len(); - attestations.push(attest); - let (data_offset, data_len) = - Self::push_to_heap(data, &(attest_idx as u32).to_le_bytes()); - Step { - opcode: op, - data_len, - data_offset, - ..Default::default() - } + let attestation = RawAttestation::decode_in(decoder, alloc)?; + Ok(Timestamp::Attestation(attestation)) } OpCode::FORK => { - let mut first_child: StepPtr = None; - let mut prev_sibling_idx: Option = None; - + let mut children = Vec::new_in(alloc.clone()); let mut next_op = OpCode::FORK; - while next_op == OpCode::FORK { - let child_ptr = Self::decode_step_recurse( - reader, - steps, - data, - attestations, - None, - recursion_limit - 1, - )?; - - // LCRS: - // if prev sibling exist, link its next_sibling to current child - // else it's first_child - if let Some(prev) = prev_sibling_idx { - steps[prev].next_sibling = child_ptr; - } else { - first_child = child_ptr; - } - - // update prev_sibling_idx to current child - prev_sibling_idx = resolve_ptr(child_ptr); - - next_op = reader.decode()?; - } - - let child_ptr = Self::decode_step_recurse( - reader, - steps, - data, - attestations, - Some(next_op), - recursion_limit - 1, - )?; - if let Some(prev) = prev_sibling_idx { - steps[prev].next_sibling = child_ptr; - } else { - first_child = child_ptr; - } - - Step { - opcode: op, - data_len: 0, - data_offset: 0, - first_child, - ..Default::default() + let child = Self::decode_recursive(&mut *decoder, limit - 1, alloc.clone())?; + children.push(child); + next_op = OpCode::decode(&mut *decoder)?; } + children.push(Self::decode_from_op( + next_op, + decoder, + limit - 1, + alloc.clone(), + )?); + Ok(Timestamp::Step(Step { + op: OpCode::FORK, + data: Vec::new_in(alloc), + input: OnceLock::new(), + next: children, + })) } _ => { - debug_assert!(!op.is_control()); - let (data_offset, data_len) = if op.has_immediate() { - let length = reader.decode_ranged(1..=MAX_OP_LENGTH)?; - // SAFETY: We will fill the buffer right after getting it. - let (data_offset, data_len, buffer) = - unsafe { Self::get_buffer_from_heap(data, length) }; - reader.read_exact(buffer)?; - (data_offset, data_len) + let data = if op.has_immediate() { + const MAX_OP_LENGTH: usize = 4096; + let length = decoder.decode_ranged(1..=MAX_OP_LENGTH)?; + let mut data = Vec::with_capacity_in(length, alloc.clone()); + data.resize(length, 0); + decoder.read_exact(&mut data)?; + + data } else { - (0, 0) + Vec::new_in(alloc.clone()) }; - let next = Self::decode_step_recurse( - reader, - steps, - data, - attestations, - None, - recursion_limit - 1, - )?; + let mut next = Vec::with_capacity_in(1, alloc.clone()); + next.push(Self::decode_recursive(decoder, limit - 1, alloc)?); - Step { - opcode: op, - data_len, - data_offset, - first_child: next, - ..Default::default() - } + Ok(Timestamp::Step(Step { + op, + data, + input: OnceLock::new(), + next, + })) } - }; - - let step_idx = steps.len(); - steps.push(step); - Ok(make_ptr(step_idx)) + } } } diff --git a/crates/core/src/codec/v1/timestamp/encode.rs b/crates/core/src/codec/v1/timestamp/encode.rs index 67b4f4a..e9e97e2 100644 --- a/crates/core/src/codec/v1/timestamp/encode.rs +++ b/crates/core/src/codec/v1/timestamp/encode.rs @@ -3,67 +3,34 @@ use crate::{ codec::{Encode, Encoder}, error::EncodeError, }; -use std::io::Write; -impl Encode for Timestamp { - fn encode(&self, mut writer: impl Encoder) -> Result<(), EncodeError> { - self.encode_step_recurse(&mut writer, &self.steps.last().unwrap()) - } -} - -impl Timestamp { - fn encode_step_recurse( - &self, - writer: &mut W, - step: &Step, - ) -> Result<(), EncodeError> { - // 1. Write OpCode - // Note: We need a way to serialize the OpCode (e.g., as u8) - writer.encode(step.opcode)?; - - // 2. Write data - match step.opcode { - OpCode::ATTESTATION => { - // SAFETY: caller ensures step is attestation step - let attest_idx = unsafe { self.get_attest_idx(step) }; - let attest = &self.attestations[attest_idx as usize]; - attest.encode(&mut *writer)?; +impl Encode for Timestamp { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + match self { + Self::Attestation(attestation) => { + encoder.encode(self.op())?; + attestation.encode(encoder)?; } - _ if step.data_len != 0 => { - // SAFETY: caller ensures step is valid - let step_data = unsafe { self.get_step_data(step) }; - writer.encode_bytes(step_data)?; + Self::Step(step) if step.op == OpCode::FORK => { + debug_assert!(step.next.len() >= 2, "FORK must have at least two children"); + for child in step.next.iter().take(step.next.len() - 1) { + encoder.encode(self.op())?; + child.encode(encoder)?; + } + // Encode the last child + step.next.last().expect("infallible").encode(encoder)?; } - _ => {} - } - - if let Some(first_child) = resolve_ptr(step.first_child) { - let mut current = &self.steps[first_child]; - if let OpCode::FORK = step.opcode { - // Encode the first child - self.encode_step_recurse(writer, current)?; - - // Logic: Child -> FORK -> Child -> ... -> LastChild - while let Some(next_sibling_idx) = resolve_ptr(current.next_sibling) { - let next = &self.steps[next_sibling_idx]; - // Encode current child - self.encode_step_recurse(writer, next)?; - // Write Separator FORK - let continues = resolve_ptr(next.next_sibling).is_some(); - if continues { - writer.encode(OpCode::FORK)?; - } - - // Move to next - current = next; + Self::Step(step) => { + encoder.encode(self.op())?; + if !step.data.is_empty() { + debug_assert!(step.op.has_immediate()); + encoder.encode_bytes(&step.data)?; } - Ok(()) - } else { - // FIXME: tailcall optimization - self.encode_step_recurse(writer, current) + debug_assert_eq!(step.next.len(), 1); + step.next[0].encode(encoder)?; } - } else { - Ok(()) } + + Ok(()) } } diff --git a/crates/core/src/codec/v1/timestamp/fmt.rs b/crates/core/src/codec/v1/timestamp/fmt.rs index 5ded172..17c6d6a 100644 --- a/crates/core/src/codec/v1/timestamp/fmt.rs +++ b/crates/core/src/codec/v1/timestamp/fmt.rs @@ -1,96 +1,84 @@ use super::*; -use crate::{ - codec::v1::opcode::{OpCode, OperationBuffer}, - utils::Hexed, -}; -use std::fmt; +use crate::utils::Hexed; +use core::fmt; -pub(crate) fn fmt( - timestamp: &Timestamp, - input: Option<&OperationBuffer>, - f: &mut fmt::Formatter, -) -> fmt::Result { - fmt_recurse( - ×tamp, - input, - ×tamp.steps.last().unwrap(), - f, - 0, - true, - ) +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt_recurse(None, f, 0, true) + } } -fn fmt_recurse( - timestamp: &Timestamp, - input: Option<&OperationBuffer>, - step: &Step, - f: &mut fmt::Formatter, - depth: usize, - first_line: bool, -) -> fmt::Result { - fn indent(f: &mut fmt::Formatter, depth: usize, first_line: bool) -> fmt::Result { - if depth == 0 { - return Ok(()); - } - - for _ in 0..depth - 1 { - f.write_str(" ")?; - } - if first_line { - f.write_str("--->")?; - } else { - f.write_str(" ")?; - } - Ok(()) +impl Timestamp { + pub(crate) fn fmt(&self, input: Option<&[u8]>, f: &mut fmt::Formatter) -> fmt::Result { + let input = match input { + Some(input) => Some(input), + None => match self { + Self::Step(step) => step.input.get().map(|v| v.as_slice()), + Self::Attestation(_) => None, + }, + }; + self.fmt_recurse(input, f, 0, true) } - indent(f, depth, first_line)?; - match step.opcode { - OpCode::FORK => { - writeln!(f, "fork")?; - let mut child_ptr = step.first_child; - while let Some(child_idx) = resolve_ptr(child_ptr) { - let child_step = ×tamp.steps[child_idx]; - fmt_recurse(timestamp, input, child_step, f, depth + 1, true)?; - child_ptr = child_step.next_sibling; + fn fmt_recurse( + &self, + input: Option<&[u8]>, + f: &mut fmt::Formatter, + depth: usize, + first_line: bool, + ) -> fmt::Result { + fn indent(f: &mut fmt::Formatter, depth: usize, first_line: bool) -> fmt::Result { + if depth == 0 { + return Ok(()); + } + + for _ in 0..depth - 1 { + f.write_str(" ")?; + } + if first_line { + f.write_str("--->")?; + } else { + f.write_str(" ")?; } Ok(()) } - OpCode::ATTESTATION => { - // SAFETY: caller ensures step is attestation step - let attest_idx = unsafe { timestamp.get_attest_idx(step) } as usize; - let attest = ×tamp.attestations[attest_idx]; - writeln!(f, "result attested by {attest}") - } - op @ _ => { - let data = unsafe { timestamp.get_step_data(step) }; - if op.has_immediate() { - writeln!(f, "execute {op} {}", Hexed(&data))?; - } else { - writeln!(f, "execute {op}")?; + indent(f, depth, first_line)?; + match self { + Self::Step(step) if step.op == OpCode::FORK => { + writeln!(f, "fork")?; + for child in &step.next { + child.fmt_recurse(input, f, depth + 1, true)?; + } + Ok(()) } + Self::Step(step) => { + let op = step.op; + if op.has_immediate() { + writeln!(f, "execute {op} {}", Hexed(&step.data))?; + } else { + writeln!(f, "execute {op}")?; + } - let result = if let Some(input) = input { - let result = op.execute(&input, &data); - indent(f, depth, false)?; - writeln!(f, " result {}", Hexed(&result))?; - Some(result) - } else { - None - }; + let result = if let Some(value) = step.next.first().and_then(|next| next.input()) { + Some(value.to_vec_in(step.allocator().clone())) + } else if let Some(input) = input { + let result = op.execute_in(input, &step.data, step.allocator().clone()); + indent(f, depth, false)?; + writeln!(f, " result {}", Hexed(&result))?; + Some(result) + } else { + None + }; - if let Some(step_idx) = resolve_ptr(step.first_child) { - let step = ×tamp.steps[step_idx]; - fmt_recurse(timestamp, result.as_ref(), step, f, depth, false)?; + for child in &step.next { + child.fmt_recurse(result.as_deref(), f, depth, false)?; + } + Ok(()) + } + Self::Attestation(attestation) => { + writeln!(f, "result attested by {attestation}") } - Ok(()) } } } - -impl fmt::Display for Timestamp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_recurse(self, None, &self.steps.last().unwrap(), f, 0, true) - } -} diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 44c0362..e5a4946 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -9,6 +9,9 @@ pub enum DecodeError { /// File has a version we do not understand. #[error("bad version")] BadVersion, + /// Expected an attestation tag but decoded something else. + #[error("bad attestation tag")] + BadAttestationTag, /// Read an LEB128-encoded integer that overflowed the expected size. #[error("read a LEB128 value overflows {0} bits")] LEB128Overflow(u32), @@ -24,12 +27,19 @@ pub enum DecodeError { /// Encountered an invalid character in a URI. #[error("invalid character in URI")] InvalidUriChar, + /// URI is too long. + #[error("URI too long")] + UriTooLong, /// Recursed deeper than allowed while decoding the proof. #[error("recursion limit reached")] RecursionLimit, + /// Reached end-of-file unexpectedly. + #[error("unexpected end of file")] + UnexpectedEof, /// General I/O error + #[cfg(feature = "std")] #[error("I/O error: {0}")] - Io(#[from] std::io::Error), + Io(std::io::Error), } /// Errors returned while encoding proofs. @@ -38,7 +48,24 @@ pub enum EncodeError { /// Tried to encode a `usize` exceeding `u32::MAX`. #[error("tried to encode a usize exceeding u32::MAX")] UsizeOverflow, + /// Encountered an invalid character in a URI. + #[error("invalid character in URI")] + InvalidUriChar, + /// URI is too long. + #[error("URI too long")] + UriTooLong, /// General I/O error + #[cfg(feature = "std")] #[error("I/O error: {0}")] Io(#[from] std::io::Error), } + +#[cfg(feature = "std")] +impl From for DecodeError { + fn from(err: std::io::Error) -> Self { + match err.kind() { + std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEof, + _ => Self::Io(err), + } + } +} diff --git a/crates/core/src/fixtures.rs b/crates/core/src/fixtures.rs index 7b9aa3f..2eb9b7c 100644 --- a/crates/core/src/fixtures.rs +++ b/crates/core/src/fixtures.rs @@ -4,7 +4,7 @@ //! Embedded test fixtures for detached timestamp files used in tests and benchmarks. -pub const SMALL_DETACHED_TIMESTAMP: &[u8] = b"\ +pub static SMALL_DETACHED_TIMESTAMP: &[u8] = b"\ \x00\x4f\x70\x65\x6e\x54\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x00\x00\x50\x72\x6f\x6f\x66\x00\xbf\x89\xe2\xe8\x84\xe8\x92\ \x94\x01\x08\xa7\x0d\xfe\x69\xc5\xa0\xd6\x28\x16\x78\x1a\xbb\x6e\x17\x77\x85\x47\x18\x62\x4a\x0d\x19\x42\x31\xad\xb1\x4c\ \x32\xee\x54\x38\xa4\xf0\x10\x7a\x46\x05\xde\x0a\x5b\x37\xcb\x21\x17\x59\xc6\x81\x2b\xfe\x2e\x08\xff\xf0\x10\x24\x4b\x79\ @@ -15,7 +15,7 @@ pub const SMALL_DETACHED_TIMESTAMP: &[u8] = b"\ \x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e\x2e\x2d\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6c\x69\x63\x65\x2e\x62\x74\x63\x2e\x63\x61\ \x6c\x65\x6e\x64\x61\x72\x2e\x6f\x70\x65\x6e\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x2e\x6f\x72\x67"; -pub const LARGE_DETACHED_TIMESTAMP: &[u8] = b"\ +pub static LARGE_DETACHED_TIMESTAMP: &[u8] = b"\ \x00\x4f\x70\x65\x6e\x54\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x00\x00\x50\x72\x6f\x6f\x66\x00\xbf\x89\xe2\xe8\x84\xe8\x92\ \x94\x01\x08\x6f\xd9\xc1\xc4\xf0\x96\xb7\x7e\x6d\x44\x57\xba\xc1\xc7\xf5\x10\x10\xd3\x18\xdb\x48\x3f\x28\x68\xd3\x79\x58\ \x43\xf0\x98\xd3\x78\xf0\x10\xe2\xe2\x24\x43\x9e\x7f\x0f\xdd\x8c\x1e\xea\xc7\x3e\xa7\x39\xdb\x08\xf1\x20\xa5\x74\x44\x4a\ diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 1ac7265..e524fb4 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,6 +1,11 @@ #![feature(exact_bitshifts)] +#![feature(allocator_api)] +#![cfg_attr(not(feature = "std"), no_std)] //! # Universal Timestamps Core Library +extern crate alloc; +extern crate core; + mod tracing; #[cfg(test)] diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 56c4372..6863042 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -1,19 +1,5 @@ -use std::fmt; +mod hex; +pub use hex::Hexed; -/// Zero-allocation wrapper that displays byte slices as lowercase hex. -pub struct Hexed<'a, T: ?Sized>(pub &'a T); - -impl<'a, T: ?Sized + AsRef<[u8]>> fmt::Display for Hexed<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for b in self.0.as_ref() { - write!(f, "{:02x}", b)?; - } - Ok(()) - } -} - -impl<'a, T: ?Sized + AsRef<[u8]>> fmt::Debug for Hexed<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} +mod sync; +pub use sync::OnceLock; diff --git a/crates/core/src/utils/hex.rs b/crates/core/src/utils/hex.rs new file mode 100644 index 0000000..8a1948b --- /dev/null +++ b/crates/core/src/utils/hex.rs @@ -0,0 +1,19 @@ +use core::fmt; + +/// Zero-allocation wrapper that displays byte slices as lowercase hex. +pub struct Hexed<'a, T: ?Sized>(pub &'a T); + +impl<'a, T: ?Sized + AsRef<[u8]>> fmt::Display for Hexed<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for b in self.0.as_ref() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl<'a, T: ?Sized + AsRef<[u8]>> fmt::Debug for Hexed<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} diff --git a/crates/core/src/utils/sync.rs b/crates/core/src/utils/sync.rs new file mode 100644 index 0000000..660f4c9 --- /dev/null +++ b/crates/core/src/utils/sync.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "std")] +mod std; +#[cfg(feature = "std")] +pub use self::std::OnceLock; + +#[cfg(not(feature = "std"))] +mod race; +#[cfg(not(feature = "std"))] +pub use self::race::OnceLock; diff --git a/crates/core/src/utils/sync/race.rs b/crates/core/src/utils/sync/race.rs new file mode 100644 index 0000000..1ee7567 --- /dev/null +++ b/crates/core/src/utils/sync/race.rs @@ -0,0 +1,20 @@ +#[derive(Default, Debug, Clone)] +#[repr(transparent)] +pub struct OnceLock(once_cell::race::OnceBox); + +impl OnceLock { + pub const fn new() -> OnceLock { + OnceLock(once_cell::race::OnceBox::new()) + } + + pub fn get(&self) -> Option<&T> { + self.0.get() + } + + pub fn get_or_init(&self, init: F) -> &T + where + F: FnOnce() -> T, + { + self.0.get_or_init(|| alloc::boxed::Box::new(init())) + } +} diff --git a/crates/core/src/utils/sync/std.rs b/crates/core/src/utils/sync/std.rs new file mode 100644 index 0000000..3078519 --- /dev/null +++ b/crates/core/src/utils/sync/std.rs @@ -0,0 +1,20 @@ +#[derive(Default, Debug, Clone)] +#[repr(transparent)] +pub struct OnceLock(std::sync::OnceLock); + +impl OnceLock { + pub const fn new() -> OnceLock { + OnceLock(std::sync::OnceLock::new()) + } + + pub fn get(&self) -> Option<&T> { + self.0.get() + } + + pub fn get_or_init(&self, init: F) -> &T + where + F: FnOnce() -> T, + { + self.0.get_or_init(init) + } +} From 05720e9175c14d5bf09b1dcccce1a9a409f41c93 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Wed, 24 Dec 2025 12:03:56 +0800 Subject: [PATCH 03/74] feat: proof builder (#9) * complete * fmt * clippy * apply review * apply review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * typo --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Cargo.lock | 56 ++++--- Cargo.toml | 3 +- crates/calendar/Cargo.toml | 8 +- crates/calendar/src/main.rs | 21 ++- crates/calendar/src/routes/ots.rs | 143 ++++++++++-------- crates/calendar/src/time.rs | 28 ++++ crates/core/src/codec/v1/opcode.rs | 46 ++++++ crates/core/src/codec/v1/timestamp.rs | 14 +- crates/core/src/codec/v1/timestamp/builder.rs | 112 ++++++++++++++ 9 files changed, 334 insertions(+), 97 deletions(-) create mode 100644 crates/calendar/src/time.rs diff --git a/Cargo.lock b/Cargo.lock index fad715a..207bdeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,24 @@ dependencies = [ "cc", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "allocator-api2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c583acf993cf4245c4acb0a2cc2ab1f9cc097de73411bb6d3647ff6af2b1013d" + +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "alloy-consensus" version = "1.1.3" @@ -750,27 +768,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfe9f610fe4e99cf0cfcd03ccf8c63c28c616fe714d80475ef731f3b13dd21b" -dependencies = [ - "axum", - "axum-core", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-macros" version = "0.5.0" @@ -940,6 +937,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bump-scope" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a117a0e80c453511760a08c607060a778967a8dec5415c1d1a75e20630825824" +dependencies = [ + "allocator-api2 0.2.21", + "allocator-api2 0.3.1", + "allocator-api2 0.4.0", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -4313,7 +4321,6 @@ dependencies = [ "futures-util", "http", "http-body", - "http-body-util", "iri-string", "pin-project-lite", "tower", @@ -4528,13 +4535,12 @@ dependencies = [ "alloy-signer", "alloy-signer-local", "axum", - "axum-extra", + "bump-scope", "bytes", "eyre", + "itoa", "sha3 0.11.0-rc.3", - "smallvec", "tokio", - "tower-http", "tracing", "tracing-subscriber", "uts-core", diff --git a/Cargo.toml b/Cargo.toml index 4887329..a16c008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ alloy-signer-local = "1.1" auto_impl = "1.3" axum = "0.8" axum-extra = "0.12" +bump-scope = { version = "1.5", features = ["nightly"] } bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } @@ -44,12 +45,12 @@ const_format = "0.2" criterion = { version = "0.8", features = ["html_reports"] } eyre = "0.6" hex = "0.4" +itoa = "1.0" once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" serde = "1.0" serde_with = "3.16" -smallvec = { version = "1.15", features = ["union", "const_generics", "const_new"] } strum = "0.27" thiserror = "2" tokio = { version = "1", features = ["rt"] } diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index e8ca796..bf24afa 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -13,16 +13,18 @@ alloy-primitives = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } axum = { workspace = true, features = ["macros"] } -axum-extra = { workspace = true } +bump-scope.workspace = true bytes = { workspace = true } eyre = { workspace = true } +itoa = { workspace = true } sha3 = { workspace = true } -smallvec = { workspace = true } tokio = { workspace = true, features = ["full"] } -tower-http = { workspace = true, features = ["limit"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } [lints] workspace = true + +[features] +performance = ["tracing/release_max_level_info"] diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index f968da4..bf1db24 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,20 +1,38 @@ +#![feature(allocator_api)] //! Calendar server #[macro_use] extern crate tracing; +use alloy_primitives::b256; +use alloy_signer::k256::ecdsa::SigningKey; +use alloy_signer_local::LocalSigner; use axum::{ Router, extract::DefaultBodyLimit, routing::{get, post}, }; +use std::sync::Arc; mod routes; +pub mod time; + +/// Application state shared across handlers. +#[derive(Debug)] +pub struct AppState { + signer: LocalSigner, +} #[tokio::main] async fn main() -> eyre::Result<()> { tracing_subscriber::fmt::init(); + tokio::spawn(time::updater()); + + let signer = LocalSigner::from_bytes(&b256!( + "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" + ))?; + let app = Router::new() .route( "/digest", @@ -24,7 +42,8 @@ async fn main() -> eyre::Result<()> { .route( "/timestamp/{hex_commitment}", get(routes::ots::get_timestamp), - ); + ) + .with_state(Arc::new(AppState { signer })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 4b847b0..c1c6680 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -1,21 +1,19 @@ -use alloy_primitives::{Keccak256, b256}; +use crate::{AppState, time::current_time_sec}; use alloy_signer::SignerSync; -use alloy_signer_local::LocalSigner; -use axum::body::Bytes; +use axum::{body::Bytes, extract::State}; +use bump_scope::Bump; use bytes::BytesMut; -use smallvec::SmallVec; -use std::time::SystemTime; -use tracing::Level; +use sha3::{Digest, Keccak256}; +use std::{cell::RefCell, sync::Arc}; use uts_core::{ codec::{ - Encoder, - v1::{Attestation, PendingAttestation, opcode::OpCode}, + Encode, + v1::{PendingAttestation, Timestamp}, }, utils::Hexed, }; pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 -const ERC2098_SIGNATURE_SIZE: usize = 64; // Test this with official ots client: // ots stamp -c "http://localhost:3000/" -m 1 @@ -36,74 +34,87 @@ const ERC2098_SIGNATURE_SIZE: usize = 64; // result c15b4e8b93e9aaee5b8c736f5b73e5f313062e389925a0b1fc6495053f99d352 // result attested by Pending: update URI https://localhost:3000 // ``` -#[instrument(level = Level::TRACE, skip_all)] -pub async fn submit_digest(digest: Bytes) -> Bytes { - const MAX_MESSAGE_SIZE: usize = MAX_DIGEST_SIZE + size_of::() + ERC2098_SIGNATURE_SIZE; +pub async fn submit_digest(State(state): State>, digest: Bytes) -> Bytes { + let (output, _commitment) = submit_digest_inner(digest, &state.signer); + // TODO: submit commitment to journal + output +} + +// TODO: We need to benchmark this. +pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u8; 32]) { + const PRE_ALLOCATION_SIZE_HINT: usize = 4096; + thread_local! { + // We don't have `.await` in this function, so it's safe to borrow thread local. + static BUMP: RefCell = RefCell::new(Bump::with_size(PRE_ALLOCATION_SIZE_HINT)); + static HASHER: RefCell = RefCell::new(Keccak256::new()); + } - let uri = "https://localhost:3000".to_string(); + // ots uses 32-bit unix time, but we use u64 here for future proofing, as it's not part of the ots spec. + let recv_timestamp = current_time_sec().to_le_bytes(); - let buf_size = 1 // OpCode::PREPEND - + 1 // length of u64 length in leb128 - + 8 // u64 timestamp - + 1 // OpCode::APPEND - + 1 // length of signature length in leb128 - + ERC2098_SIGNATURE_SIZE // signature - + 1 // FIXME: TBD: OpCode::KECCAK256 - + 1 // OpCode::ATTESTATION - + 8 // Pending tag - + 1 // length of packed ATTESTATION data length in leb128 - + (1 + uri.len()); // length of uri in leb128 + uri bytes - let attestation = PendingAttestation { uri: uri.into() }; + let undeniable_sig = { + // sign_message_sync invokes heap allocation, so manually hash it. + const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n"; + let hash = HASHER.with(|hasher| { + let mut hasher = hasher.borrow_mut(); + hasher.update(EIP191_PREFIX.as_bytes()); + match digest.len() { + // 32 + 8 + 32 => hasher.update(b"40"), + // 64 + 8 + 64 => hasher.update(b"72"), + _ => { + let length = digest.len() + size_of::(); + let mut buffer = itoa::Buffer::new(); + let printed = buffer.format(length); + hasher.update(printed.as_bytes()); + } + } + hasher.update(recv_timestamp); + hasher.update(&digest); + hasher.finalize_reset() + }); - let mut timestamp = BytesMut::with_capacity(buf_size); + let undeniable_sig = signer.sign_hash_sync(&hash.0.into()).unwrap(); + undeniable_sig.as_erc2098() + }; - let mut pending_attestation = SmallVec::<[u8; MAX_MESSAGE_SIZE]>::new(); + #[cfg(any(debug_assertions, not(feature = "performance")))] + trace!( + recv_timestamp = ?Hexed(&recv_timestamp), + digest = ?Hexed(&digest), + undeniable_sig = ?Hexed(&undeniable_sig), + ); - // ots uses 32-bit unix time, but we use u64 here for future proofing, as it's not part of the ots spec. - let recv_timestamp: u64 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Clock MUST not go backwards") - .as_secs(); - trace!(recv_timestamp); - let recv_timestamp = recv_timestamp.to_le_bytes(); - timestamp.encode(OpCode::PREPEND).unwrap(); - timestamp.encode_bytes(recv_timestamp).unwrap(); - pending_attestation.extend(recv_timestamp); + BUMP.with(|bump| { + let mut bump = bump.borrow_mut(); + bump.reset(); - trace!(digest = ?Hexed(&digest)); - pending_attestation.extend_from_slice(&digest); + let builder = Timestamp::builder_in(&*bump) + .prepend(recv_timestamp.to_vec_in(&bump)) + .append(undeniable_sig.to_vec_in(&bump)) + .keccak256(); - let signer = LocalSigner::from_bytes(&b256!( - "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" - )) - .unwrap(); // TODO: load from app state - let undeniable_sig = signer.sign_message_sync(&digest).unwrap(); - let undeniable_sig = undeniable_sig.as_erc2098(); - trace!(undeniable_sig = ?Hexed(&undeniable_sig)); - timestamp.encode(OpCode::APPEND).unwrap(); - timestamp.encode_bytes(undeniable_sig).unwrap(); - pending_attestation.extend(undeniable_sig); + let mut commitment = [0u8; 32]; + commitment.copy_from_slice(&builder.commitment(&digest)); - trace!(pending_attestation = ?Hexed(&pending_attestation)); + let timestamp = builder + .attest(PendingAttestation { + uri: "https://localhost:3000".into(), + }) + .unwrap(); - // FIXME: - // discussion: return the hash or the raw timestamp message? - // if using hash, client will request upgrade timestamp by hash (256 bits, 64 hex chars) - // - // if using raw timestamp message, client will request timestamp by whole message (variable size, 208 hex chars if request is 32 bytes), - // but we will have info about the receiving time of the request, - // which can narrow down the search space - let mut hasher = Keccak256::new(); - hasher.update(&pending_attestation); - hasher.finalize_into(&mut pending_attestation[0..32]); - timestamp.encode(OpCode::KECCAK256).unwrap(); + // copy data out of bump + // TODO: eliminate this allocation by reusing from a pool + // TODO: wrap the buffer with a drop trait to return to pool + let mut buf = BytesMut::with_capacity(128); + timestamp.encode(&mut buf).unwrap(); - timestamp.encode(OpCode::ATTESTATION).unwrap(); - timestamp.encode(attestation.to_raw().unwrap()).unwrap(); + #[cfg(any(debug_assertions, not(feature = "performance")))] + trace!(timestamp = ?timestamp, encoded_length = buf.len()); - // TODO: store the pending_attestation into journal - debug_assert_eq!(timestamp.len(), buf_size, "buffer size mismatch"); - timestamp.freeze() + (buf.freeze(), commitment) + }) } pub async fn get_timestamp() {} diff --git a/crates/calendar/src/time.rs b/crates/calendar/src/time.rs new file mode 100644 index 0000000..d073b58 --- /dev/null +++ b/crates/calendar/src/time.rs @@ -0,0 +1,28 @@ +//! A module that maintains a globally accessible current time in seconds since the Unix epoch. +//! +//! This is for performance optimization to avoid frequent syscalls for time retrieval. +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +static CURRENT_TIME_SEC: AtomicU64 = AtomicU64::new(0); + +/// Returns the current time in seconds since the Unix epoch. +#[inline] +pub fn current_time_sec() -> u64 { + CURRENT_TIME_SEC.load(Ordering::Relaxed) +} + +/// An asynchronous task that updates the current time every second. +pub async fn updater() { + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + CURRENT_TIME_SEC.store(now, Ordering::Relaxed); + interval.tick().await; + } +} diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index f9772f4..6b97073 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -309,6 +309,38 @@ macro_rules! define_digest_opcodes { }; } +macro_rules! impl_simple_step { + ($variant:ident) => {paste::paste! { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] + pub fn [< $variant:lower >](self) -> Self { + self.push_step(OpCode::[<$variant>]) + } + } + }}; + ($($variant:ident),* $(,)?) => { + $( + impl_simple_step! { $variant } + )* + }; +} + +macro_rules! impl_step_with_data { + ($variant:ident) => {paste::paste! { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] + pub fn [< $variant:lower >](self, data: ::alloc::vec::Vec) -> Self { + self.push_immediate_step(OpCode::[<$variant>], data) + } + } + }}; + ($($variant:ident),* $(,)?) => { + $( + impl_step_with_data! { $variant } + )* + }; +} + define_opcodes! { 0x02 => SHA1, 0x03 => RIPEMD160, @@ -329,6 +361,20 @@ define_digest_opcodes! { 0x67 => KECCAK256, } +impl_simple_step! { + SHA1, + RIPEMD160, + SHA256, + KECCAK256, + REVERSE, + HEXLIFY, +} + +impl_step_with_data! { + APPEND, + PREPEND, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 245ce6b..7f604d0 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -7,7 +7,7 @@ use crate::{ use alloc::{alloc::Global, vec::Vec}; use core::{alloc::Allocator, fmt::Debug}; -mod builder; +pub(crate) mod builder; mod decode; mod encode; mod fmt; @@ -73,6 +73,13 @@ impl Debug for Step { } } +impl Timestamp { + /// Creates a new timestamp builder. + pub fn builder() -> builder::TimestampBuilder { + builder::TimestampBuilder::new_in(Global) + } +} + impl Timestamp { /// Returns the opcode of this timestamp node. pub fn op(&self) -> OpCode { @@ -127,6 +134,11 @@ impl Timestamp { } impl Timestamp { + /// Creates a new timestamp builder with the given allocator. + pub fn builder_in(alloc: A) -> builder::TimestampBuilder { + builder::TimestampBuilder::new_in(alloc) + } + /// Finalizes the timestamp with the given input data. /// /// # Panics diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index d5ee9ab..9275f37 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -1 +1,113 @@ //! Timestamp Builder + +use crate::{ + codec::v1::{Attestation, Timestamp, opcode::OpCode, timestamp::Step}, + error::EncodeError, + utils::OnceLock, +}; +use alloc::alloc::{Allocator, Global}; + +#[derive(Debug, Clone)] +pub struct TimestampBuilder { + steps: Vec, A>, +} + +#[derive(Debug, Clone)] +struct LinearStep { + op: OpCode, + data: Vec, +} + +impl TimestampBuilder { + /// Creates a new `TimestampBuilder`. + pub fn new_in(alloc: A) -> TimestampBuilder { + TimestampBuilder { + steps: Vec::new_in(alloc), + } + } + + /// Pushes a new execution step with immediate data to the timestamp. + /// + /// # Panics + /// + /// Panics if the opcode is not an opcode with immediate data. + pub(crate) fn push_immediate_step(mut self, op: OpCode, data: Vec) -> Self { + assert!(op.has_immediate()); + self.steps.push(LinearStep { op, data }); + self + } + + /// Pushes a new execution step without immediate data to the timestamp. + /// + /// # Panics + /// + /// Panics if: + /// - the opcode is control opcode + /// - the opcode is an opcode with immediate data + pub(crate) fn push_step(mut self, op: OpCode) -> Self { + self.steps.push(LinearStep { + op, + data: Vec::new_in(self.allocator().clone()), + }); + self + } + + /// Computes the commitment of the timestamp for the given input. + /// + /// In this context, the **commitment** is the deterministic result of + /// executing the timestamp's linear chain of operations over the input + /// bytes. It is computed by: + /// + /// 1. Taking the provided `input` bytes as the initial value. + /// 2. Iterating over all steps in the order they were added to the builder. + /// 3. For each step, applying its opcode to the current value together + /// with the step's immediate data via [`OpCode::execute_in`], and using + /// the result as the new current value. + /// + /// The final value after all steps have been applied is returned as the + /// commitment. + pub fn commitment(&self, input: impl AsRef<[u8]>) -> Vec { + let alloc = self.allocator().clone(); + let mut commitment = input.as_ref().to_vec_in(alloc.clone()); + for step in &self.steps { + commitment = step.op.execute_in(&commitment, &step.data, alloc.clone()); + } + commitment + } + + /// Finalizes the timestamp with the given attestation. + /// + /// # Notes + /// + /// The built timestamp does not include any input data. The input data must be + /// provided later using the `finalize` method on the `Timestamp` object. + pub fn attest<'a, T: Attestation<'a>>( + self, + attestation: T, + ) -> Result, EncodeError> { + let alloc = self.allocator().clone(); + + let mut current = Timestamp::Attestation(attestation.to_raw_in(alloc.clone())?); + + for step in self.steps.into_iter().rev() { + let step_node = Step { + op: step.op, + data: step.data, + input: OnceLock::new(), + next: { + let mut v = Vec::with_capacity_in(1, alloc.clone()); + v.push(current); + v + }, + }; + current = Timestamp::Step(step_node); + } + + Ok(current) + } + + #[inline] + fn allocator(&self) -> &A { + self.steps.allocator() + } +} From dc8d717b6f44f0ad44a7e0473e2dfe1d6c82ff58 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Wed, 24 Dec 2025 12:41:55 +0800 Subject: [PATCH 04/74] feat: add submit_digest bench (#10) * add bench * apply review --- .cargo/config.toml | 8 ++++ Cargo.lock | 1 + Cargo.toml | 14 +++++++ crates/calendar/Cargo.toml | 7 ++++ crates/calendar/benches/submit_digest.rs | 38 ++++++++++++++++++ crates/calendar/src/lib.rs | 49 ++++++++++++++++++++++++ crates/calendar/src/main.rs | 43 +-------------------- crates/calendar/src/routes.rs | 1 + crates/calendar/src/routes/ots.rs | 4 ++ crates/calendar/src/time.rs | 33 ++++++++++++++-- 10 files changed, 154 insertions(+), 44 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 crates/calendar/benches/submit_digest.rs create mode 100644 crates/calendar/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..aef462a --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +rustflags = ["-C", "target-cpu=native"] + +[target.x86_64-unknown-linux-gnu] +rustflags = [ + # (Nightly) Make the current crate share its generic instantiations + "-Zshare-generics=y", +] diff --git a/Cargo.lock b/Cargo.lock index 207bdeb..7e1f9ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4537,6 +4537,7 @@ dependencies = [ "axum", "bump-scope", "bytes", + "criterion 0.8.1", "eyre", "itoa", "sha3 0.11.0-rc.3", diff --git a/Cargo.toml b/Cargo.toml index a16c008..9be0d3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,3 +67,17 @@ sha3 = "0.11.0-rc.3" uts-bmt = { path = "crates/bmt" } uts-core = { path = "crates/core" } + +[profile.bench] +codegen-units = 1 +lto = "fat" + +[profile.release] +codegen-units = 1 +lto = "fat" + +[profile.dev] +opt-level = 1 + +[profile.dev.package."*"] +opt-level = 3 diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index bf24afa..4b3891b 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -23,6 +23,13 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } +[dev-dependencies] +criterion.workspace = true + +[[bench]] +harness = false +name = "submit_digest" + [lints] workspace = true diff --git a/crates/calendar/benches/submit_digest.rs b/crates/calendar/benches/submit_digest.rs new file mode 100644 index 0000000..760be3e --- /dev/null +++ b/crates/calendar/benches/submit_digest.rs @@ -0,0 +1,38 @@ +//! Benchmark for submitting digests of varying sizes. + +use alloy_primitives::b256; +use alloy_signer_local::LocalSigner; +use bytes::Bytes; +use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use std::hint::black_box; +use uts_calendar::routes::ots::submit_digest_inner; + +const DIGEST_SIZES: &[usize] = &[32, 64]; + +fn benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("submit_digest"); + + for &size in DIGEST_SIZES { + group.throughput(Throughput::Elements(1)); + group.bench_function(BenchmarkId::from_parameter(size), move |b| { + let signer = LocalSigner::from_bytes(&b256!( + "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" + )) + .unwrap(); + let input = Bytes::from(vec![0u8; size]); + + b.iter_batched( + || input.clone(), + |input| { + let out = submit_digest_inner(input, &signer); + black_box(out) + }, + BatchSize::SmallInput, + ); + }); + } + group.finish(); +} + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/crates/calendar/src/lib.rs b/crates/calendar/src/lib.rs new file mode 100644 index 0000000..7ed561b --- /dev/null +++ b/crates/calendar/src/lib.rs @@ -0,0 +1,49 @@ +#![feature(thread_sleep_until)] +#![feature(allocator_api)] + +//! Calendar server library. + +#[macro_use] +extern crate tracing; + +use alloy_signer::k256::ecdsa::SigningKey; +use alloy_signer_local::LocalSigner; + +/// Calendar server routes and handlers. +pub mod routes; +/// Time-related utilities and background tasks. +pub mod time; + +/// Application state shared across handlers. +#[derive(Debug)] +pub struct AppState { + /// Local signer for signing OTS timestamps. + pub signer: LocalSigner, +} + +/// Signal for graceful shutdown. +pub async fn shutdown_signal() { + use tokio::signal; + + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index bf1db24..8653571 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,11 +1,6 @@ -#![feature(allocator_api)] //! Calendar server -#[macro_use] -extern crate tracing; - use alloy_primitives::b256; -use alloy_signer::k256::ecdsa::SigningKey; use alloy_signer_local::LocalSigner; use axum::{ Router, @@ -13,21 +8,13 @@ use axum::{ routing::{get, post}, }; use std::sync::Arc; - -mod routes; -pub mod time; - -/// Application state shared across handlers. -#[derive(Debug)] -pub struct AppState { - signer: LocalSigner, -} +use uts_calendar::{AppState, routes, shutdown_signal, time}; #[tokio::main] async fn main() -> eyre::Result<()> { tracing_subscriber::fmt::init(); - tokio::spawn(time::updater()); + tokio::spawn(time::async_updater()); let signer = LocalSigner::from_bytes(&b256!( "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" @@ -53,29 +40,3 @@ async fn main() -> eyre::Result<()> { Ok(()) } - -async fn shutdown_signal() { - use tokio::signal; - - let ctrl_c = async { - signal::ctrl_c() - .await - .expect("failed to install Ctrl+C handler"); - }; - - #[cfg(unix)] - let terminate = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("failed to install signal handler") - .recv() - .await; - }; - - #[cfg(not(unix))] - let terminate = std::future::pending::<()>(); - - tokio::select! { - _ = ctrl_c => {}, - _ = terminate => {}, - } -} diff --git a/crates/calendar/src/routes.rs b/crates/calendar/src/routes.rs index 5797280..ca72c74 100644 --- a/crates/calendar/src/routes.rs +++ b/crates/calendar/src/routes.rs @@ -1 +1,2 @@ +/// ots related routes pub mod ots; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index c1c6680..5135586 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -13,6 +13,7 @@ use uts_core::{ utils::Hexed, }; +/// Maximum digest size accepted by the endpoint. pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 // Test this with official ots client: @@ -34,6 +35,7 @@ pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 // result c15b4e8b93e9aaee5b8c736f5b73e5f313062e389925a0b1fc6495053f99d352 // result attested by Pending: update URI https://localhost:3000 // ``` +/// Submit digest to calendar server and get pending timestamp in response. pub async fn submit_digest(State(state): State>, digest: Bytes) -> Bytes { let (output, _commitment) = submit_digest_inner(digest, &state.signer); // TODO: submit commitment to journal @@ -41,6 +43,7 @@ pub async fn submit_digest(State(state): State>, digest: Bytes) -> } // TODO: We need to benchmark this. +/// inner function to submit digest, returns (encoded timestamp, commitment) pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u8; 32]) { const PRE_ALLOCATION_SIZE_HINT: usize = 4096; thread_local! { @@ -117,4 +120,5 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u }) } +/// Get current timestamp from calendar server. pub async fn get_timestamp() {} diff --git a/crates/calendar/src/time.rs b/crates/calendar/src/time.rs index d073b58..86c4b52 100644 --- a/crates/calendar/src/time.rs +++ b/crates/calendar/src/time.rs @@ -3,10 +3,12 @@ //! This is for performance optimization to avoid frequent syscalls for time retrieval. use std::{ sync::atomic::{AtomicU64, Ordering}, - time::{Duration, SystemTime, UNIX_EPOCH}, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; +use tokio::time::MissedTickBehavior; static CURRENT_TIME_SEC: AtomicU64 = AtomicU64::new(0); +const UPDATE_PERIOD: Duration = Duration::from_secs(1); /// Returns the current time in seconds since the Unix epoch. #[inline] @@ -15,8 +17,9 @@ pub fn current_time_sec() -> u64 { } /// An asynchronous task that updates the current time every second. -pub async fn updater() { - let mut interval = tokio::time::interval(Duration::from_secs(1)); +pub async fn async_updater() { + let mut interval = tokio::time::interval(UPDATE_PERIOD); + interval.set_missed_tick_behavior(MissedTickBehavior::Skip); loop { let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -26,3 +29,27 @@ pub async fn updater() { interval.tick().await; } } + +/// A task that updates the current time every second. +pub fn updater() { + let mut next_tick = Instant::now(); + + loop { + next_tick += UPDATE_PERIOD; + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + CURRENT_TIME_SEC.store(now, Ordering::Relaxed); + + // Note: This behavior is different from the async version, which skips missed ticks. + let now_instant = Instant::now(); + if next_tick > now_instant { + std::thread::sleep_until(next_tick); + } else { + // If we've fallen behind, resynchronize to avoid accumulating drift. + next_tick = now_instant; + } + } +} From 9f03f3eeb782c44b99ecd057e006c128532ccea9 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Wed, 24 Dec 2025 12:49:01 +0800 Subject: [PATCH 05/74] feat: high performace journal mvp (#6) * wip * mvp * add tests * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * update README * add stamper * fix wait_at_least * fix merge * fmt --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Cargo.lock | 13 ++ Cargo.toml | 2 + crates/journal/Cargo.toml | 20 ++ crates/journal/README.md | 33 +++ crates/journal/src/error.rs | 13 ++ crates/journal/src/lib.rs | 396 +++++++++++++++++++++++++++++++++++ crates/journal/src/reader.rs | 256 ++++++++++++++++++++++ crates/journal/src/wal.rs | 115 ++++++++++ crates/stamper/Cargo.toml | 14 ++ crates/stamper/src/lib.rs | 0 10 files changed, 862 insertions(+) create mode 100644 crates/journal/Cargo.toml create mode 100644 crates/journal/README.md create mode 100644 crates/journal/src/error.rs create mode 100644 crates/journal/src/lib.rs create mode 100644 crates/journal/src/reader.rs create mode 100644 crates/journal/src/wal.rs create mode 100644 crates/stamper/Cargo.toml create mode 100644 crates/stamper/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7e1f9ed..dbaf6fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4567,6 +4567,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "uts-journal" +version = "0.1.0" +dependencies = [ + "dyn-clone", + "eyre", + "tokio", +] + +[[package]] +name = "uts-stamper" +version = "0.1.0" + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 9be0d3b..df1f79f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ compact_str = "0.9" config = "0.15" const_format = "0.2" criterion = { version = "0.8", features = ["html_reports"] } +dyn-clone = "1.0" eyre = "0.6" hex = "0.4" itoa = "1.0" @@ -67,6 +68,7 @@ sha3 = "0.11.0-rc.3" uts-bmt = { path = "crates/bmt" } uts-core = { path = "crates/core" } +uts-journal = { path = "crates/journal" } [profile.bench] codegen-units = 1 diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml new file mode 100644 index 0000000..c603867 --- /dev/null +++ b/crates/journal/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors.workspace = true +description = "High performance append-only journal for UTS" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-journal" +repository.workspace = true +version.workspace = true + +[dependencies] +dyn-clone = { workspace = true } + +[dev-dependencies] +eyre = { workspace = true } +tokio = { workspace = true, features = ["full"] } + +[lints] +workspace = true diff --git a/crates/journal/README.md b/crates/journal/README.md new file mode 100644 index 0000000..39aecea --- /dev/null +++ b/crates/journal/README.md @@ -0,0 +1,33 @@ +# uts-journal + +**High-performance, append-only journal designed for the Universal Timestamps (UTS) protocol.** + +`uts-journal` is an embedded, lock-free, ring-buffer-based Write-Ahead Log (WAL) implemented in Rust. +It is designed to handle extremely high throughput (target: 1M+ TPS) with sub-millisecond durability guarantees, +specifically optimized for the low-latency requirements of timestamping services. + +## Architecture & Design Rationale + +### Why not Kafka? + +While distributed message queues are the standard for microservices decoupling, `uts-journal` was built to satisfy a +specific set of constraints where generic solutions fall short: + +| Feature | Distributed MQ (e.g., Kafka) | uts-journal | +|------------------------|--------------------------------|--------------------------------------| +| **Topology** | External Service (Networked) | Embedded Library (In-Process) | +| **Durability Latency** | Network RTT + Disk IO (~2-5ms) | Disk IO only (<1ms possible) | +| **Throughput Control** | Limited by Network Bandwidth | Limited by PCIe/Memory Bandwidth | +| **Consistency** | Eventual / ISync Replicas | Strong Local Consistency | +| **Resource Usage** | Heavy (JVM/Broker overhead) | Minimal (Zero-copy, Zero-allocation) | + +### The "Request-Persist-Return" Loop + +UTS requires a synchronous acknowledgement model: **HTTP POST → Sequence → Persist → Return**. +To achieve 1M TPS under this constraint: + +1. **Group Commit:** `uts-journal` allows thousands of concurrent write requests to queue in the ring buffer. +2. **Batched IO:** A dedicated WAL worker thread detects pending writes and flushes them to stable storage in minimal syscalls. +3. **Wake-on-Persist:** Once the persist boundary advances, the worker efficiently wakes only the relevant `Waker`s associated with the committed slots. + +This architecture converts random IOPS into sequential throughput, allowing the system to handle massive concurrency on a single node. diff --git a/crates/journal/src/error.rs b/crates/journal/src/error.rs new file mode 100644 index 0000000..74bdbc1 --- /dev/null +++ b/crates/journal/src/error.rs @@ -0,0 +1,13 @@ +use std::fmt; + +/// Error indicating that the journal buffer is full. +#[derive(Debug)] +pub struct BufferFull; + +impl fmt::Display for BufferFull { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Journal buffer is full") + } +} + +impl std::error::Error for BufferFull {} diff --git a/crates/journal/src/lib.rs b/crates/journal/src/lib.rs new file mode 100644 index 0000000..107aef6 --- /dev/null +++ b/crates/journal/src/lib.rs @@ -0,0 +1,396 @@ +//! Journal implementation for UTS + +use crate::{ + error::BufferFull, + reader::JournalReader, + wal::{DummyWal, Wal}, +}; +use std::{ + cell::UnsafeCell, + fmt, + ops::Deref, + pin::Pin, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, AtomicU64, Ordering}, + }, + task::{Poll, Waker}, +}; + +/// Error types. +pub mod error; +/// Journal reader. +pub mod reader; +/// Write-Ahead Log backend. +pub mod wal; + +/// A journal for storing fixed-size entries in a ring buffer. +/// +/// All index here are monotonic u64, wrapping around on overflow. +/// +/// Following invariants are maintained: +/// `consumed_index` <= `persisted_index` <= `write_index` +#[derive(Clone)] +pub struct Journal { + inner: Arc>, + /// Wal backend for recovery. + wal: Box, +} + +pub(crate) struct JournalInner { + /// The ring buffer storing the entries. + /// The capacity of the ring buffer, **MUST** be power of two. + buffer: Box<[UnsafeCell<[u8; ENTRY_SIZE]>]>, + /// The co-ring buffer storing the wakers. + /// The capacity of the ring buffer, **MUST** be power of two. + waker_buffer: Box<[WakerEntry]>, + /// Mask for indexing into the ring buffer. + index_mask: u64, + /// Next Write Position, aka: + /// - Total entries written count. + /// - Position to write the next entry to. + write_index: AtomicU64, + /// WAL Committed Boundary, aka.: + /// - Total committed entries count. + /// - Position has not yet been persisted to durable storage. + persisted_index: AtomicU64, + /// Free Boundary, aka.: + /// - Total consumed entries count. + /// - Position that has not yet been consumed by readers. + consumed_index: AtomicU64, + /// Whether a reader has taken ownership of this journal. + reader_taken: AtomicBool, + /// Waker for the consumer to notify new persisted entries. + consumer_wait: Mutex>, +} + +unsafe impl Sync for JournalInner {} +unsafe impl Send for JournalInner {} + +impl fmt::Debug for Journal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Journal").finish() + } +} + +impl Journal { + /// Create a new journal with the specified capacity. + /// + /// The capacity will be rounded up to the next power of two. + pub fn with_capacity(capacity: usize) -> Self { + let capacity = capacity.next_power_of_two(); + let index_mask = capacity as u64 - 1; + + let mut buffer = Vec::with_capacity(capacity); + buffer.resize_with(capacity, || UnsafeCell::new([0u8; ENTRY_SIZE])); + let buffer = buffer.into_boxed_slice(); + + let mut waker_buffer = Vec::with_capacity(capacity); + waker_buffer.resize_with(capacity, Default::default); + let waker_buffer = waker_buffer.into_boxed_slice(); + + let inner = Arc::new(JournalInner { + buffer, + waker_buffer, + index_mask, + write_index: AtomicU64::new(0), + persisted_index: AtomicU64::new(0), + consumed_index: AtomicU64::new(0), + reader_taken: AtomicBool::new(false), + consumer_wait: Mutex::new(None), + }); + + let wal = Box::new(DummyWal::new(inner.clone())); + + Self { inner, wal } + } + + /// Get the capacity of the journal. + #[inline] + fn capacity(&self) -> usize { + self.inner.buffer.len() + } + + /// Acquires a reader for this journal. + /// + /// # Panics + /// + /// Panics if a reader is already taken. + pub fn reader(&self) -> JournalReader { + self.try_reader().expect("Journal reader already taken") + } + + /// Try acquires a reader for this journal. + /// + /// If a reader is already taken, returns None. + pub fn try_reader(&self) -> Option> { + if self.inner.reader_taken.swap(true, Ordering::AcqRel) { + return None; + } + + Some(JournalReader::new(self.inner.clone())) + } + + /// Commit a new entry to the journal. + /// + /// # Panics + /// + /// Panics if the journal is full. + pub fn commit(&self, data: &[u8; ENTRY_SIZE]) -> CommitFuture<'_, ENTRY_SIZE> { + self.try_commit(data).expect("Journal buffer is full") + } + + /// Try commit a new entry to the journal. + /// + /// Returns a future that resolves when the entry has been safely persisted. + /// Returns `BufferFull` error if the journal is full. + pub fn try_commit( + &self, + data: &[u8; ENTRY_SIZE], + ) -> Result, BufferFull> { + let mut current_written = self.inner.write_index.load(Ordering::Relaxed); + loop { + // 1. Check if there is space in the buffer. + let consumed = self.inner.consumed_index.load(Ordering::Acquire); + if current_written.wrapping_sub(consumed) >= self.capacity() as u64 { + return Err(BufferFull); + } + + // 2. Try to reserve a slot. + match self.inner.write_index.compare_exchange_weak( + current_written, + current_written.wrapping_add(1), + Ordering::AcqRel, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(actual) => current_written = actual, + } + } + + // 3. Write the data to the slot. + let slot = unsafe { &mut *self.data_slot_ptr(current_written) }; + slot.copy_from_slice(data); + + // 4. Notify WAL worker if needed. + let committed = self.inner.persisted_index.load(Ordering::Relaxed); + // Explain: Before we wrote to the slot, if there is no pending committed entry, + // There's a chance the WAL worker is sleeping, we need to wake it up. + if current_written == committed { + // Notify the WAL worker thread to persist new entries. + self.wal.unpark(); + } + + Ok(CommitFuture { + journal: self, + slot: current_written, + active_waker: None, + }) + } + + /// Get a mut ptr to the slot at the given index. + #[inline] + fn data_slot_ptr(&self, index: u64) -> *mut [u8; ENTRY_SIZE] { + self.inner.data_slot_ptr(index) + } + + /// Get a ref to the waker entry at the given index. + #[inline] + fn waker_slot(&self, index: u64) -> &WakerEntry { + self.inner.waker_slot(index) + } +} + +impl JournalInner { + /// Get a mut ptr to the slot at the given index. + #[inline] + const fn data_slot_ptr(&self, index: u64) -> *mut [u8; ENTRY_SIZE] { + let slot_idx = index & self.index_mask; + self.buffer[slot_idx as usize].get() + } + + /// Get a ref to the waker entry at the given index. + #[inline] + const fn waker_slot(&self, index: u64) -> &WakerEntry { + let slot_idx = index & self.index_mask; + &self.waker_buffer[slot_idx as usize] + } +} + +/// Future returned by `Journal::commit` representing the commit operation. +/// The future resolves when the entry has been safely persisted. +#[derive(Debug)] +pub struct CommitFuture<'a, const ENTRY_SIZE: usize> { + journal: &'a Journal, + slot: u64, + /// Whether the waker has been registered. + active_waker: Option, +} + +impl<'a, const ENTRY_SIZE: usize> Future for CommitFuture<'a, ENTRY_SIZE> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + if self.journal.inner.persisted_index.load(Ordering::Acquire) > self.slot { + return Poll::Ready(()); + } + + let should_register = match &self.active_waker { + None => true, + // waker changed, need to update, rare case + Some(w) => !w.will_wake(cx.waker()), + }; + + if should_register { + let entry = self.journal.waker_slot(self.slot); + let mut guard = entry.lock().expect("Mutex poisoned"); + + if self.journal.inner.persisted_index.load(Ordering::Acquire) > self.slot { + return Poll::Ready(()); + } + + *guard = Some(cx.waker().clone()); + self.active_waker = Some(cx.waker().clone()); + } + + if self.journal.inner.persisted_index.load(Ordering::Acquire) > self.slot { + return Poll::Ready(()); + } + + Poll::Pending + } +} + +/// A consumer wait entry. +struct ConsumerWait { + waker: Waker, + target_index: u64, +} + +/// A waker entry in the co-ring buffer. +/// +/// Aligned to cache line size to prevent false sharing. +#[derive(Default)] +#[repr(C, align(64))] +struct WakerEntry(Mutex>); + +impl Deref for WakerEntry { + type Target = Mutex>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + pub const ENTRY_SIZE: usize = 8; + pub const TEST_DATA: &[[u8; ENTRY_SIZE]] = &[ + [0u8; ENTRY_SIZE], + [1u8; ENTRY_SIZE], + [2u8; ENTRY_SIZE], + [3u8; ENTRY_SIZE], + [4u8; ENTRY_SIZE], + [5u8; ENTRY_SIZE], + [6u8; ENTRY_SIZE], + [7u8; ENTRY_SIZE], + [8u8; ENTRY_SIZE], + [9u8; ENTRY_SIZE], + ]; + pub type Journal = crate::Journal; + + #[tokio::test(flavor = "current_thread")] + async fn try_reader_is_exclusive() { + let journal = Journal::with_capacity(2); + + let reader = journal.try_reader().unwrap(); + + assert!( + journal.try_reader().is_none(), + "second reader acquisition should fail" + ); + + drop(reader); + assert!( + journal.try_reader().is_some(), + "reader acquisition should succeed after drop" + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn commit_and_read_round_trip() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + journal.commit(&TEST_DATA[0]).await; + journal.commit(&TEST_DATA[1]).await; + + { + let entries = reader.read(2); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0], TEST_DATA[0]); + assert_eq!(entries[1], TEST_DATA[1]); + } + + reader.commit(); + assert_eq!(reader.available(), 0); + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn commit_returns_error_when_full() -> eyre::Result<()> { + let journal = Journal::with_capacity(2); + + journal.commit(&TEST_DATA[1]).await; + journal.commit(&TEST_DATA[2]).await; + + let err = journal + .try_commit(&TEST_DATA[3]) + .expect_err("buffer should report full on third commit"); + assert!(matches!(err, BufferFull)); + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn reader_handles_wrap_around_reads() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + for entry in TEST_DATA.iter().take(4) { + journal.commit(entry).await; + } + + { + let entries = reader.read(2); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0], TEST_DATA[0]); + assert_eq!(entries[1], TEST_DATA[1]); + } + reader.commit(); + + for entry in TEST_DATA.iter().skip(4).take(2) { + journal.commit(entry).await; + } + + { + let entries = reader.read(4); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0], TEST_DATA[2]); + assert_eq!(entries[1], TEST_DATA[3]); + } + + { + let entries = reader.read(4); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0], TEST_DATA[4]); + assert_eq!(entries[1], TEST_DATA[5]); + } + + reader.commit(); + assert_eq!(reader.available(), 0); + Ok(()) + } +} diff --git a/crates/journal/src/reader.rs b/crates/journal/src/reader.rs new file mode 100644 index 0000000..70e77d5 --- /dev/null +++ b/crates/journal/src/reader.rs @@ -0,0 +1,256 @@ +use crate::{ConsumerWait, JournalInner}; +use std::{ + fmt, + pin::Pin, + sync::{Arc, atomic::Ordering}, + task::{Context, Poll}, +}; + +/// A reader for consuming settled entries from the journal. +/// +/// Reader **WON'T** advance the shared consumed boundary until `commit()` is called. +pub struct JournalReader { + journal: Arc>, + consumed: u64, +} + +impl fmt::Debug for JournalReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("JournalReader") + .field("consumed", &self.consumed) + .finish() + } +} + +impl Drop for JournalReader { + fn drop(&mut self) { + self.journal.reader_taken.store(false, Ordering::Release); + } +} + +impl JournalReader { + pub(super) fn new(journal: Arc>) -> Self { + let consumed = journal.consumed_index.load(Ordering::Acquire); + Self { journal, consumed } + } + + /// Returns the number of available entries that are settled but not yet consumed by this reader. + #[inline] + pub fn available(&self) -> usize { + let persisted = self.journal.persisted_index.load(Ordering::Acquire); + persisted.wrapping_sub(self.consumed) as usize + } + + /// Wait until at least `min` entries are available. + pub async fn wait_at_least(&mut self, min: usize) { + if self.available() >= min { + return; + } + + let target_index = self.consumed.wrapping_add(min as u64); + { + // panics if the target_index exceeds buffer size, otherwise we might wait forever + // this happens if: + // - asks for more entries than the buffer can hold + // - didn't commit previously read entries, then asks for more than new entries than the buffer can hold + // this is considered a misuse of the API / design flaw in the caller, so we panics + let journal_buffer_size = self.journal.buffer.len() as u64; + let current_consumed = self.journal.consumed_index.load(Ordering::Acquire); + let max_possible_target = current_consumed.wrapping_add(journal_buffer_size); + if target_index > max_possible_target { + panic!( + "requested ({target_index}) exceeds max possible ({max_possible_target}): journal.buffer.len()={journal_buffer_size}, journal.consumed_index={current_consumed}" + ); + } + } + + // Slow path + struct WaitForBatch<'a, const ENTRY_SIZE: usize> { + reader: &'a JournalReader, + target_index: u64, + } + + impl<'a, const ENTRY_SIZE: usize> Future for WaitForBatch<'a, ENTRY_SIZE> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + if self.reader.journal.persisted_index.load(Ordering::Acquire) >= self.target_index + { + return Poll::Ready(()); + } + + let mut guard = self + .reader + .journal + .consumer_wait + .lock() + .expect("Mutex poisoned"); + if self.reader.journal.persisted_index.load(Ordering::Acquire) >= self.target_index + { + return Poll::Ready(()); + } + *guard = Some(ConsumerWait { + waker: cx.waker().clone(), + target_index: self.target_index, + }); + + Poll::Pending + } + } + + WaitForBatch { + reader: self, + target_index, + } + .await; + } + + /// Read available entries, up to `max`. + /// Bumps the internal consumed index by the number of entries yielded. + /// + /// Caller is responsible for calling `commit()` after processing the entries. + pub fn read(&mut self, max: usize) -> &[[u8; ENTRY_SIZE]] { + let available = self.available(); + if available == 0 { + return &[]; + } + + let count = available.min(max); + let start_idx = self.consumed; + + // handle wrap-around + let buffer_len = self.journal.buffer.len(); + let slot_idx = (start_idx & self.journal.index_mask) as usize; + let continuous_len = count.min(buffer_len - slot_idx); + + // push local consumed index + self.consumed += continuous_len as u64; + + // return slice + let ptr = self.journal.buffer[slot_idx].get(); + // SAFETY: bounds checked above + unsafe { std::slice::from_raw_parts(ptr, continuous_len) } + } + + /// Commit current consumed index. + pub fn commit(&mut self) { + self.journal + .consumed_index + .store(self.consumed, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use crate::tests::*; + use std::sync::atomic::Ordering; + use tokio::time::{Duration, sleep, timeout}; + + #[tokio::test(flavor = "current_thread")] + async fn available_tracks_persisted_entries() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + assert_eq!(reader.available(), 0); + + journal.commit(&TEST_DATA[0]).await; + assert_eq!(reader.available(), 1); + + journal.commit(&TEST_DATA[1]).await; + assert_eq!(reader.available(), 2); + + let slice = reader.read(1); + assert_eq!(slice.len(), 1); + assert_eq!(reader.available(), 1); + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn commit_updates_shared_consumed_boundary() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + for entry in TEST_DATA.iter().take(3) { + journal.commit(entry).await; + } + + let slice = reader.read(2); + assert_eq!(slice.len(), 2); + assert_eq!(reader.available(), 1); + assert_eq!( + reader.journal.consumed_index.load(Ordering::Acquire), + 0, + "global consumed boundary should not advance before commit", + ); + + reader.commit(); + assert_eq!(reader.journal.consumed_index.load(Ordering::Acquire), 2); + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn wait_at_least_resumes_after_persistence() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + let journal_clone = journal.clone(); + let task = tokio::spawn(async move { + sleep(Duration::from_millis(5)).await; + journal_clone.commit(&TEST_DATA[0]).await; + }); + + reader.wait_at_least(1).await; + assert_eq!(reader.available(), 1); + + task.await?; + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn wait_at_least_waits_for_correct_count() -> eyre::Result<()> { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + let journal_clone = journal.clone(); + let task = tokio::spawn(async move { + for entry in TEST_DATA.iter().take(4) { + journal_clone.commit(entry).await; + sleep(Duration::from_millis(5)).await; + } + }); + + timeout(Duration::from_secs(1), reader.wait_at_least(3)).await?; + assert!(reader.available() >= 3); + + task.await?; + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + #[should_panic( + expected = "requested (5) exceeds max possible (4): journal.buffer.len()=4, journal.consumed_index=0" + )] + async fn wait_at_least_exceeds_buffer_size() { + let journal = Journal::with_capacity(4); + let mut reader = journal.reader(); + + timeout(Duration::from_secs(1), reader.wait_at_least(5)) + .await + .unwrap(); + } + + #[tokio::test(flavor = "current_thread")] + #[should_panic( + expected = "requested (5) exceeds max possible (4): journal.buffer.len()=4, journal.consumed_index=0" + )] + async fn wait_at_least_dirty_read_exceeds_available() { + let journal = Journal::with_capacity(4); + journal.commit(&TEST_DATA[0]).await; + + let mut reader = journal.reader(); + reader.read(1); + + timeout(Duration::from_secs(1), reader.wait_at_least(4)) + .await + .unwrap(); + } +} diff --git a/crates/journal/src/wal.rs b/crates/journal/src/wal.rs new file mode 100644 index 0000000..392f13a --- /dev/null +++ b/crates/journal/src/wal.rs @@ -0,0 +1,115 @@ +use crate::JournalInner; +use dyn_clone::DynClone; +use std::{ + sync::{Arc, atomic::Ordering}, + thread, + thread::JoinHandle, + time::Duration, +}; + +const MAX_SPINS: usize = 10_000; +const IO_BATCH_LIMIT: u64 = 128; + +/// Write-Ahead Log Trait +/// +/// Busy-Wait + Parking when there's no work to do. +pub trait Wal: DynClone + Send + Sync + 'static { + /// Unpark the WAL worker thread to notify new data is available. + fn unpark(&self); + /// Shutdown the WAL worker thread. + fn shutdown(&self) { + // Default implementation does nothing. + // Specific implementations can override this method to provide shutdown logic. + } +} + +dyn_clone::clone_trait_object!(Wal); + +#[derive(Clone)] +pub(crate) struct DummyWal { + worker: Arc>, +} + +struct WalWorker { + journal: Arc>, +} + +impl DummyWal { + pub(crate) fn new(journal: Arc>) -> Self { + let worker = WalWorker { journal }; + let worker = thread::spawn(move || { + worker.run(); + }); + Self { + worker: Arc::new(worker), + } + } +} + +impl Wal for DummyWal { + fn unpark(&self) { + self.worker.thread().unpark(); + } +} + +impl WalWorker { + fn run(self) { + let Self { journal } = self; + + let mut persisted = 0; + let mut spins = 0; + + loop { + let written = journal.write_index.load(Ordering::Acquire); + let available = written.wrapping_sub(persisted); + + if available > 0 { + // reset spins counter + spins = 0; + + // take as much as we can, limited by IO_BATCH_LIMIT + let batch_size = available.min(IO_BATCH_LIMIT); + let target_index = persisted + batch_size; + + // simulate IO + // TODO: replace with real IO + thread::sleep(Duration::from_millis(1)); + + // notify waiters only after data is "persisted" + for i in persisted..target_index { + let entry = journal.waker_slot(i); + if let Some(waker) = entry.lock().unwrap().take() { + waker.wake(); + } + } + + // update persisted index + persisted = target_index; + journal.persisted_index.store(persisted, Ordering::Release); + + // notify consumer if needed + let mut guard = journal.consumer_wait.lock().unwrap(); + if let Some(wait) = guard.as_ref() { + // Only wake if the target_index is reached + if persisted >= wait.target_index { + guard.take().unwrap().waker.wake(); + } + } + + continue; + } + + // busy-wait + park + if spins < MAX_SPINS { + // busy-wait + std::hint::spin_loop(); + spins += 1; + } else { + // park the thread + thread::park(); + // reset spins counter on wake + spins = 0; + } + } + } +} diff --git a/crates/stamper/Cargo.toml b/crates/stamper/Cargo.toml new file mode 100644 index 0000000..3a282a9 --- /dev/null +++ b/crates/stamper/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-stamper" +repository.workspace = true +version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs new file mode 100644 index 0000000..e69de29 From 8b299a07754baf8531bedd95d2f1d9b1e9642cee Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Wed, 24 Dec 2025 13:58:40 +0800 Subject: [PATCH 06/74] feat: link journal (#11) --- Cargo.lock | 35 +++++++++++++++++++++++++ Cargo.toml | 2 +- crates/calendar/Cargo.toml | 9 ++++++- crates/calendar/src/lib.rs | 5 ++++ crates/calendar/src/main.rs | 9 ++++++- crates/calendar/src/routes/ots.rs | 25 +++++++++++++----- crates/core/src/codec/v1/attestation.rs | 11 +++++++- crates/journal/src/lib.rs | 2 +- crates/journal/src/reader.rs | 2 +- 9 files changed, 87 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbaf6fc..3bf4232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2103,6 +2103,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -2243,6 +2262,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -4243,6 +4263,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -4538,6 +4571,7 @@ dependencies = [ "bump-scope", "bytes", "criterion 0.8.1", + "digest 0.11.0-rc.4", "eyre", "itoa", "sha3 0.11.0-rc.3", @@ -4545,6 +4579,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uts-core", + "uts-journal", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df1f79f..d92e56b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ alloy-primitives = "1.5" alloy-signer = "1.1" alloy-signer-local = "1.1" auto_impl = "1.3" -axum = "0.8" +axum = { version = "0.8", default-features = false } axum-extra = "0.12" bump-scope = { version = "1.5", features = ["nightly"] } bytes = "1.11" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index 4b3891b..b5c4aff 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -12,9 +12,14 @@ version.workspace = true alloy-primitives = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } -axum = { workspace = true, features = ["macros"] } +axum = { workspace = true, default-features = false, features = [ + "macros", + "http2", + "tokio", +] } # http2 only bump-scope.workspace = true bytes = { workspace = true } +digest = { workspace = true } eyre = { workspace = true } itoa = { workspace = true } sha3 = { workspace = true } @@ -22,6 +27,7 @@ tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } +uts-journal = { workspace = true } [dev-dependencies] criterion.workspace = true @@ -34,4 +40,5 @@ name = "submit_digest" workspace = true [features] +dev = ["axum/http1"] # for easier testing performance = ["tracing/release_max_level_info"] diff --git a/crates/calendar/src/lib.rs b/crates/calendar/src/lib.rs index 7ed561b..6202b7a 100644 --- a/crates/calendar/src/lib.rs +++ b/crates/calendar/src/lib.rs @@ -8,6 +8,9 @@ extern crate tracing; use alloy_signer::k256::ecdsa::SigningKey; use alloy_signer_local::LocalSigner; +use digest::{OutputSizeUser, typenum::Unsigned}; +use sha3::Keccak256; +use uts_journal::Journal; /// Calendar server routes and handlers. pub mod routes; @@ -19,6 +22,8 @@ pub mod time; pub struct AppState { /// Local signer for signing OTS timestamps. pub signer: LocalSigner, + /// Journal + pub journal: Journal<{ ::OutputSize::USIZE }>, } /// Signal for graceful shutdown. diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index 8653571..2ca5709 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -9,6 +9,9 @@ use axum::{ }; use std::sync::Arc; use uts_calendar::{AppState, routes, shutdown_signal, time}; +use uts_journal::Journal; + +const RING_BUFFER_CAPACITY: usize = 1 << 20; // 1 million entries #[tokio::main] async fn main() -> eyre::Result<()> { @@ -19,6 +22,10 @@ async fn main() -> eyre::Result<()> { let signer = LocalSigner::from_bytes(&b256!( "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" ))?; + let journal = Journal::with_capacity(RING_BUFFER_CAPACITY); + + let _reader = journal.reader(); + // TODO: spawn stamper task let app = Router::new() .route( @@ -30,7 +37,7 @@ async fn main() -> eyre::Result<()> { "/timestamp/{hex_commitment}", get(routes::ots::get_timestamp), ) - .with_state(Arc::new(AppState { signer })); + .with_state(Arc::new(AppState { signer, journal })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 5135586..d787186 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -1,9 +1,15 @@ use crate::{AppState, time::current_time_sec}; use alloy_signer::SignerSync; -use axum::{body::Bytes, extract::State}; +use axum::{ + body::Bytes, + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, +}; use bump_scope::Bump; use bytes::BytesMut; -use sha3::{Digest, Keccak256}; +use digest::Digest; +use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; use uts_core::{ codec::{ @@ -36,10 +42,15 @@ pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 // result attested by Pending: update URI https://localhost:3000 // ``` /// Submit digest to calendar server and get pending timestamp in response. -pub async fn submit_digest(State(state): State>, digest: Bytes) -> Bytes { - let (output, _commitment) = submit_digest_inner(digest, &state.signer); - // TODO: submit commitment to journal - output +pub async fn submit_digest(State(state): State>, digest: Bytes) -> Response { + let (output, commitment) = submit_digest_inner(digest, &state.signer); + match state.journal.try_commit(&commitment) { + Err(_) => { + return (StatusCode::SERVICE_UNAVAILABLE, r#"{"err":"server busy"}"#).into_response(); + } // journal is full + Ok(fut) => fut.await, + } + output.into_response() } // TODO: We need to benchmark this. @@ -114,7 +125,7 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u timestamp.encode(&mut buf).unwrap(); #[cfg(any(debug_assertions, not(feature = "performance")))] - trace!(timestamp = ?timestamp, encoded_length = buf.len()); + trace!(encoded_length = buf.len(), timestamp = ?timestamp); (buf.freeze(), commitment) }) diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index ce86e19..cf519b3 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -27,7 +27,7 @@ const PENDING_TAG: &[u8; 8] = b"\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e"; pub type AttestationTag = [u8; TAG_SIZE]; /// Raw Proof that some data existed at a given time. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct RawAttestation { pub tag: AttestationTag, pub data: Vec, @@ -35,6 +35,15 @@ pub struct RawAttestation { pub(crate) value: OnceLock>, } +impl fmt::Debug for RawAttestation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RawAttestation") + .field("tag", &Hexed(&self.tag)) + .field("data", &Hexed(&self.data)) + .finish() + } +} + impl DecodeIn for RawAttestation { fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result { let mut tag = [0u8; TAG_SIZE]; diff --git a/crates/journal/src/lib.rs b/crates/journal/src/lib.rs index 107aef6..be4f093 100644 --- a/crates/journal/src/lib.rs +++ b/crates/journal/src/lib.rs @@ -227,7 +227,7 @@ pub struct CommitFuture<'a, const ENTRY_SIZE: usize> { active_waker: Option, } -impl<'a, const ENTRY_SIZE: usize> Future for CommitFuture<'a, ENTRY_SIZE> { +impl Future for CommitFuture<'_, ENTRY_SIZE> { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { diff --git a/crates/journal/src/reader.rs b/crates/journal/src/reader.rs index 70e77d5..cea3f36 100644 --- a/crates/journal/src/reader.rs +++ b/crates/journal/src/reader.rs @@ -70,7 +70,7 @@ impl JournalReader { target_index: u64, } - impl<'a, const ENTRY_SIZE: usize> Future for WaitForBatch<'a, ENTRY_SIZE> { + impl Future for WaitForBatch<'_, ENTRY_SIZE> { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { if self.reader.journal.persisted_index.load(Ordering::Acquire) >= self.target_index From 308a2027fc9f6f7b40a44eefd806899bf734bbca Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Thu, 25 Dec 2025 14:16:09 +0800 Subject: [PATCH 07/74] feat: solidity contracts (#14) * init foundry workspace * add contracts crate * apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .env | 1 + .gitignore | 9 +- .gitmodules | 9 + .pre-commit-config.yaml | 6 +- .vscode/settings.json | 5 + Cargo.lock | 1132 ++++++++++++++++++++-- Cargo.toml | 4 + contract-tests/UniversalTimestamps.t.sol | 49 + contracts/IUniversalTimestamps.sol | 11 + contracts/UniversalTimestamps.sol | 72 ++ crates/contracts/Cargo.toml | 25 + crates/contracts/src/lib.rs | 86 ++ crates/stamper/src/lib.rs | 1 + foundry.lock | 20 + foundry.toml | 15 + lib/forge-std | 1 + lib/openzeppelin-contracts-upgradeable | 1 + lib/openzeppelin-foundry-upgrades | 1 + remappings.txt | 8 + 19 files changed, 1354 insertions(+), 102 deletions(-) create mode 100644 .env create mode 100644 .gitmodules create mode 100644 .vscode/settings.json create mode 100644 contract-tests/UniversalTimestamps.t.sol create mode 100644 contracts/IUniversalTimestamps.sol create mode 100644 contracts/UniversalTimestamps.sol create mode 100644 crates/contracts/Cargo.toml create mode 100644 crates/contracts/src/lib.rs create mode 100644 foundry.lock create mode 100644 foundry.toml create mode 160000 lib/forge-std create mode 160000 lib/openzeppelin-contracts-upgradeable create mode 160000 lib/openzeppelin-foundry-upgrades create mode 100644 remappings.txt diff --git a/.env b/.env new file mode 100644 index 0000000..ebc5ed4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +FOUNDRY_OUT="target/foundry" diff --git a/.gitignore b/.gitignore index f84ad73..dd887e9 100644 --- a/.gitignore +++ b/.gitignore @@ -473,4 +473,11 @@ Icon[ .AppleDesktop Network Trash Folder Temporary Items -.apdisk \ No newline at end of file +.apdisk + +# Hardhat/Foundry files +cache +cache-hardhat +artifacts +artifacts-hardhat +broadcast diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..23acfb1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6761c56..fcfa3b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,4 +13,8 @@ repos: language: system types: [toml] pass_filenames: true - + - id: forge-fmt + name: forge fmt + entry: forge fmt + language: system + files: \.sol$ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0b7d77d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "solidity.packageDefaultDependenciesContractsDirectory": "contracts", + "solidity.packageDefaultDependenciesDirectory": "lib", + "solidity.exclude": ["lib/**"] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3bf4232..6262675 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,11 +48,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" +[[package]] +name = "alloy" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f609fb6392508278b276906d6247ea44f5777e448db95444fa39e89b7aee896a" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-node-bindings", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b163ff4acf0eac29af05a911397cc418a76e153467b859398adc26cb9335a611" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum", +] + [[package]] name = "alloy-consensus" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" +checksum = "f3dcd2b4e208ce5477de90ccdcbd4bde2c8fb06af49a443974e92bb8f2c5e93f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -77,9 +115,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" +checksum = "ee5655f234985f5ab1e31bef7e02ed11f0a899468cf3300e061e1b96e9e11de0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -89,6 +127,58 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f01b6d8e5b4f3222aaf7f18613a7292e2fbc9163fe120649cd1b078ca534349" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-core" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4087016b0896051dd3d03e0bedda2f4d4d1689af8addc8450288c63a9e5f68" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow", +] + [[package]] name = "alloy-eip2124" version = "0.2.0" @@ -123,15 +213,16 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "k256", "serde", "thiserror 2.0.17", ] [[package]] name = "alloy-eips" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" +checksum = "6847d641141b92a1557094aa6c236cbe49c06fb24144d4a21fe6acb970c15888" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -150,11 +241,39 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-genesis" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3192fca2eb0b0c4b122b3c2d8254496b88a4e810558dddd3ea2f30ad9469df" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165210652f71dfc094b051602bafd691f506c54050a174b1cba18fb5ef706a3" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + [[package]] name = "alloy-json-abi" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9914c147bb9b25f440eca68a31dc29f5c22298bfa7754aa802965695384122b0" +checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -164,9 +283,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" +checksum = "d4ab3330e491053e9608b2a315f147357bb8acb9377a988c1203f2e8e2b296c9" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -179,9 +298,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" +checksum = "c1e22ff194b1e34b4defd1e257e3fe4dce0eee37451c7757a1510d6b23e7379a" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -205,9 +324,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" +checksum = "b8a6cbb9f431bdad294eebb5af9b293d6979e633bfe5468d1e87c1421a858265" dependencies = [ "alloy-consensus", "alloy-eips", @@ -216,18 +335,39 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-node-bindings" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9e5dae7d2be44904dba55bb8b538e5de89fdb9e50b3f0f163277b729285011" +dependencies = [ + "alloy-genesis", + "alloy-hardforks", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "k256", + "rand 0.8.5", + "serde_json", + "tempfile", + "thiserror 2.0.17", + "tracing", + "url", +] + [[package]] name = "alloy-primitives" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db950a29746be9e2f2c6288c8bd7a6202a81f999ce109a2933d2379970ec0fa" +checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", - "foldhash", + "foldhash 0.2.0", "hashbrown 0.16.1", "indexmap 2.12.1", "itoa", @@ -244,6 +384,75 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-provider" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f5dde1abc3d582e53d139904fcdd8b2103f0bd03e8f2acb4292edbbaeaa7e6e" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-node-bindings", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap 6.1.0", + "either", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbfe0a3c553a027f722185fb574124d205147fffb309cae52d0a2094f076887" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "auto_impl", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "wasmtimer", +] + [[package]] name = "alloy-rlp" version = "0.3.12" @@ -266,22 +475,106 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "alloy-rpc-client" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a94bdef2710322c6770be08689fee0878c2ad75615b8fc40e05d7f3c9618c0b" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "811a573c8080e1b492d488e6a240ec5dd7677d7167e91ce9cb4d0ec1fcac8027" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838ca94be532a929f27961851000ec8bbbaeb06e2a2bcca44fac7855a2fe0f6f" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-rpc-types-any" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" +checksum = "12df0b34551ca2eab8ec83b56cb709ee5da991737282180d354a659b907f00dc" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-debug" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49a3a168a5bf18f1cf7ed5723a650aebe714edf7665b53dacf5707716733d0" +dependencies = [ + "alloy-primitives", + "derive_more", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe16cd1dea6089902ec609e04261a9ae6d11ec66005ba24c1f97f0eefbc0fa9" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "rand 0.8.5", + "serde", + "strum", +] + [[package]] name = "alloy-rpc-types-eth" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" +checksum = "b7f9f130511b8632686dfe6f9909b38d7ae4c68de3ce17d28991400646a39b25" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -291,18 +584,44 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "serde_with", "thiserror 2.0.17", ] +[[package]] +name = "alloy-rpc-types-trace" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cafe859944638c5d57d1a3a0034cdb5d07c98c37de8adce5508f28834acf958f" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-rpc-types-txpool" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afaa06544e36f223b99b1415a12911230fd527994f020736c3c7950d5080208e" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-serde" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" +checksum = "067b718d2e6ac1bb889341fcc7a250cfa49bcd3ba4f23923f1c1eb1f2b10cb7c" dependencies = [ "alloy-primitives", "serde", @@ -311,9 +630,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" +checksum = "acff6b251740ef473932386d3b71657d3825daebf2217fb41a7ef676229225d4" dependencies = [ "alloy-primitives", "async-trait", @@ -326,9 +645,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cfe0be3ec5a8c1a46b2e5a7047ed41121d360d97f4405bb7c1c784880c86cb" +checksum = "c9129ef31975d987114c27c9930ee817cf3952355834d47f2fdf4596404507e8" dependencies = [ "alloy-consensus", "alloy-network", @@ -342,9 +661,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b96d5f5890605ba9907ce1e2158e2701587631dc005bfa582cf92dd6f21147" +checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -356,10 +675,11 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8247b7cca5cde556e93f8b3882b01dbd272f527836049083d240c57bf7b4c15" +checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", @@ -374,25 +694,27 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd54f38512ac7bae10bbc38480eefb1b9b398ca2ce25db9cc0c048c6411c4f1" +checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck", "macro-string", "proc-macro2", "quote", + "serde_json", "syn 2.0.111", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b09815b44899564566d4d56613d14fa9a274b1043a021f00468568752f449" +checksum = "af67a0b0dcebe14244fc92002cd8d96ecbf65db4639d479f5fcd5805755a4c27" dependencies = [ "serde", "winnow", @@ -400,9 +722,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1038284171df8bfd48befc0c7b78f667a7e2be162f45f07bd1c378078ebe58" +checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -410,11 +732,86 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-transport" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec1fb08ee484e615f24867c0b154fff5722bb00176102a16868c6532b7c3623" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b722073c76f2de7e118d546ee1921c50710f97feb32aed50db94cfa5b663e1" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdedcf401aab4b96d8b5e6638b79d04a6afb96c0bfcb50a2324fbadfe65c47b3" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942210908f0c56941097f5653a5f334546940e6fd9073495b257e52216469feb" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "alloy-trie" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +checksum = "2b77b56af09ead281337d06b1d036c88e2dc8a2e45da512a532476dbee94912b" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -428,9 +825,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" +checksum = "04950a13cc4209d8e9b78f306e87782466bad8538c94324702d061ff03e211c9" dependencies = [ "darling", "proc-macro2", @@ -672,15 +1069,37 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -692,6 +1111,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -717,9 +1147,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "axum-macros", @@ -797,6 +1227,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bit-set" version = "0.8.0" @@ -950,9 +1386,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byte-slice-cast" @@ -998,9 +1434,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -1480,9 +1916,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.5" +version = "0.2.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a" +checksum = "f7fa010a85c7440677a0f4c59cf7ebabef52d7d8b4f79051e5fa60d3f0dd87d0" dependencies = [ "hybrid-array", ] @@ -1575,6 +2011,26 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "der" version = "0.7.10" @@ -1608,18 +2064,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -1658,7 +2114,7 @@ checksum = "ea390c940e465846d64775e55e3115d5dc934acb953de6f6e6360bc232fe2bf7" dependencies = [ "block-buffer 0.11.0", "const-oid 0.10.1", - "crypto-common 0.2.0-rc.5", + "crypto-common 0.2.0-rc.6", ] [[package]] @@ -1672,6 +2128,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "dtoa" version = "1.0.10" @@ -1908,6 +2370,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -2061,9 +2529,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -2079,7 +2549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ "cfg-if", - "dashmap", + "dashmap 5.5.3", "futures", "futures-timer", "no-std-compat", @@ -2145,13 +2615,24 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2 0.2.21", + "equivalent", + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash", + "foldhash 0.2.0", "serde", "serde_core", ] @@ -2275,6 +2756,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.4", +] + [[package]] name = "hyper-util" version = "0.1.19" @@ -2311,7 +2809,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2489,6 +2987,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interprocess" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2534,11 +3047,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jobserver" @@ -2647,6 +3169,21 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "macro-string" version = "0.1.4" @@ -2710,9 +3247,9 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2782,6 +3319,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "nybbles" version = "0.4.6" @@ -2999,10 +3557,20 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" name = "pest" version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ - "memchr", - "ucd-trie", + "futures", + "rustc_version 0.4.1", ] [[package]] @@ -3094,9 +3662,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "potential_utf" @@ -3241,7 +3809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.111", @@ -3268,6 +3836,61 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -3362,9 +3985,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.1.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e65c75143ce5d47c55b510297eeb1182f3c739b6043c537670e9fc18612dae" +checksum = "2988730ee014541157f48ce4dcc603940e00915edc3c7f9a8d78092256bb2493" dependencies = [ "rustversion", ] @@ -3398,6 +4021,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3458,9 +4087,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -3471,16 +4100,21 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -3488,6 +4122,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 1.0.4", ] [[package]] @@ -3500,6 +4135,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ripemd" version = "0.2.0-rc.3" @@ -3585,9 +4234,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -3596,6 +4245,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3616,9 +4300,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -3719,6 +4403,12 @@ dependencies = [ "pest", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.228" @@ -3751,15 +4441,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -3826,6 +4516,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0-rc.3" @@ -3998,6 +4699,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4034,9 +4756,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b1d2e2059056b66fec4a6bb2b79511d5e8d76196ef49c38996f4b48db7662f" +checksum = "5f92d01b5de07eaf324f7fca61cc6bd3d82bbc1de5b6c963e6fe79e86f36580d" dependencies = [ "paste", "proc-macro2", @@ -4086,9 +4808,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -4224,6 +4946,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.48.0" @@ -4252,6 +4989,16 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -4261,6 +5008,23 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", ] [[package]] @@ -4278,18 +5042,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.12.1", "toml_datetime", @@ -4299,9 +5063,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -4375,9 +5139,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4398,9 +5162,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4472,6 +5236,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1 0.10.6", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" @@ -4530,6 +5313,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -4542,6 +5331,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4582,6 +5377,18 @@ dependencies = [ "uts-journal", ] +[[package]] +name = "uts-contracts" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-contract", + "alloy-sol-types", + "eyre", + "futures", + "tokio", +] + [[package]] name = "uts-core" version = "0.1.0" @@ -4595,7 +5402,7 @@ dependencies = [ "opentimestamps", "paste", "ripemd", - "sha1", + "sha1 0.11.0-rc.3", "sha2 0.11.0-rc.3", "sha3 0.11.0-rc.3", "thiserror 2.0.17", @@ -4728,6 +5535,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.83" @@ -4748,6 +5569,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -4785,7 +5630,7 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core", + "windows-core 0.57.0", "windows-targets 0.52.6", ] @@ -4795,12 +5640,25 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + [[package]] name = "windows-implement" version = "0.57.0" @@ -4812,6 +5670,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "windows-interface" version = "0.57.0" @@ -4823,6 +5692,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -4838,6 +5718,33 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -5006,6 +5913,25 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror 2.0.17", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" @@ -5144,6 +6070,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "zmij" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dccf46b25b205e4bebe1d5258a991df1cc17801017a845cb5b3fe0269781aa" + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index d92e56b..255a5d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,12 @@ unnecessary-box-returns = "warn" unnecessary-debug-formatting = "warn" [workspace.dependencies] +alloy = "1" +alloy-contract = "1.2" alloy-primitives = "1.5" alloy-signer = "1.1" alloy-signer-local = "1.1" +alloy-sol-types = "1.5" auto_impl = "1.3" axum = { version = "0.8", default-features = false } axum-extra = "0.12" @@ -45,6 +48,7 @@ const_format = "0.2" criterion = { version = "0.8", features = ["html_reports"] } dyn-clone = "1.0" eyre = "0.6" +futures = "0.3" hex = "0.4" itoa = "1.0" once_cell = { version = "1.21", default-features = false } diff --git a/contract-tests/UniversalTimestamps.t.sol b/contract-tests/UniversalTimestamps.t.sol new file mode 100644 index 0000000..4c70b50 --- /dev/null +++ b/contract-tests/UniversalTimestamps.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import {UniversalTimestamps} from "../contracts/UniversalTimestamps.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +contract UniversalTimestampsV2 is UniversalTimestamps { + function version() public pure returns (string memory) { + return "V2"; + } +} + +contract UniversalTimestampsTest is Test { + UniversalTimestamps public proxy; + address owner = address(1); + + function setUp() public { + UniversalTimestamps implementation = new UniversalTimestamps(); + + bytes memory initData = abi.encodeWithSelector(UniversalTimestamps.initialize.selector, owner); + ERC1967Proxy proxyAddress = new ERC1967Proxy(address(implementation), initData); + + proxy = UniversalTimestamps(address(proxyAddress)); + } + + function test_StoragePersistenceAfterUpgrade() public { + bytes32 root = keccak256("test_data"); + + proxy.attest(root); + uint256 timeV1 = proxy.timestamp(root); + assertGt(timeV1, 0); + + vm.startPrank(owner); + + UniversalTimestampsV2 v2Impl = new UniversalTimestampsV2(); + + proxy.upgradeToAndCall(address(v2Impl), ""); + + vm.stopPrank(); + + UniversalTimestampsV2 proxyV2 = UniversalTimestampsV2(address(proxy)); + + assertEq(proxyV2.version(), "V2"); + + assertEq(proxyV2.timestamp(root), timeV1); + console.log("Storage persisted across upgrade at namespaced slot."); + } +} diff --git a/contracts/IUniversalTimestamps.sol b/contracts/IUniversalTimestamps.sol new file mode 100644 index 0000000..b5dfb0b --- /dev/null +++ b/contracts/IUniversalTimestamps.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +interface IUniversalTimestamps { + event Attested(bytes32 indexed root, address indexed sender, uint256 timestamp); + + function attest(bytes32 root) external; + + function timestamp(bytes32 root) external view returns (uint256); +} diff --git a/contracts/UniversalTimestamps.sol b/contracts/UniversalTimestamps.sol new file mode 100644 index 0000000..21969d1 --- /dev/null +++ b/contracts/UniversalTimestamps.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IUniversalTimestamps} from "./IUniversalTimestamps.sol"; +import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; + +/** + * @title UniversalTimestamps + * @dev Records and exposes timestamps for attested Merkle roots using ERC-7201 + * namespaced storage (`uts.storage.UniversalTimestamps`) derived via + * {SlotDerivation}, and is implemented as a UUPS upgradeable contract via + * OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable + * base contracts. Storage is kept in a dedicated namespaced struct to remain + * layout-compatible across upgrades, while upgrades are authorized by the + * contract owner through {_authorizeUpgrade}. + */ +contract UniversalTimestamps is Initializable, OwnableUpgradeable, UUPSUpgradeable, IUniversalTimestamps { + using SlotDerivation for string; + + string private constant _NAMESPACE = "uts.storage.UniversalTimestamps"; + + /// @custom:storage-location erc7201:uts.storage.UniversalTimestamps + struct UniversalTimestampsStorage { + mapping(bytes32 => uint256) timestamps; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __Ownable_init(initialOwner); + } + + function _getUniversalTimestampsStorage() private pure returns (UniversalTimestampsStorage storage $) { + bytes32 slot = _NAMESPACE.erc7201Slot(); + assembly ("memory-safe") { + $.slot := slot + } + } + + function timestamp(bytes32 root) external view returns (uint256) { + return _getUniversalTimestampsStorage().timestamps[root]; + } + + /** + * @notice Attest Merkle Root + * @param root The Merkle Root to be attested + */ + function attest(bytes32 root) external { + require(root != bytes32(0), "UTS: Root cannot be zero"); + + UniversalTimestampsStorage storage $ = _getUniversalTimestampsStorage(); + if ($.timestamps[root] == 0) { + $.timestamps[root] = block.timestamp; + emit Attested(root, msg.sender, block.timestamp); + } + } + + /** + * @dev Authorizes an upgrade to `newImplementation`. + * + * This function is restricted to the contract owner via the {onlyOwner} modifier, + * ensuring that only the owner can authorize upgrades to the implementation. + */ + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml new file mode 100644 index 0000000..74ef532 --- /dev/null +++ b/crates/contracts/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-contracts" +repository.workspace = true +version.workspace = true + +[dependencies] +alloy-contract = { workspace = true } +alloy-sol-types = { workspace = true, features = ["json"] } + +[dev-dependencies] +alloy = { workspace = true, features = ["full", "node-bindings"] } +eyre = { workspace = true } +futures = { workspace = true } +tokio = { workspace = true, features = ["full"] } + +[lints] +workspace = true + +[features] +erc1967 = [] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs new file mode 100644 index 0000000..e94f338 --- /dev/null +++ b/crates/contracts/src/lib.rs @@ -0,0 +1,86 @@ +//! Solidity contracts for UTS + +/// UniversalTimestamps contract +pub mod uts { + #[doc(hidden)] + pub mod binding { + use alloy_sol_types::sol; + + sol!( + #[sol(rpc, all_derives)] + IUniversalTimestamps, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/foundry/IUniversalTimestamps.sol/IUniversalTimestamps.json" + ) + ); + sol!( + #[sol(rpc)] + UniversalTimestamps, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/foundry/UniversalTimestamps.sol/UniversalTimestamps.json" + ) + ); + } + + pub use binding::IUniversalTimestamps::{ + Attested, IUniversalTimestampsInstance as UniversalTimestamps, + }; + + pub use binding::UniversalTimestamps::{BYTECODE, DEPLOYED_BYTECODE, deploy, deploy_builder}; + + #[cfg(test)] + mod tests { + use super::*; + use crate::erc1967::ERC1967ProxyInstance; + use alloy::{ + primitives::{B256, Bytes, U256, b256}, + providers::ProviderBuilder, + }; + use futures::StreamExt; + + const ROOT: B256 = + b256!("5cd5c6763b9f2b3fb1cd66a15fe92b7ac913eec295d9927886e175f144ce3308"); + + #[tokio::test] + async fn test() -> eyre::Result<()> { + let provider = ProviderBuilder::new().connect_anvil_with_wallet(); + let imp = deploy(&provider).await?; + let proxy = + ERC1967ProxyInstance::deploy(&provider, *imp.address(), Bytes::new()).await?; + let uts = UniversalTimestamps::new(*proxy.address(), &provider); + + let attested_log = uts.Attested_filter().watch().await?; + + let _ = uts.attest(ROOT).send().await?.watch().await?; + + let timestamp = uts.timestamp(ROOT).call().await?; + assert_ne!(timestamp, U256::ZERO); + + let (attested, _log) = attested_log.into_stream().next().await.unwrap()?; + assert_eq!(attested.root, ROOT); + + Ok(()) + } + } +} + +/// ERC-1967 Proxy contract +#[cfg(any(test, feature = "erc1967"))] +pub mod erc1967 { + mod binding { + use alloy_sol_types::sol; + + sol!( + #[sol(rpc)] + ERC1967Proxy, + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/foundry/ERC1967Proxy.sol/ERC1967Proxy.json" + ) + ); + } + + pub use binding::ERC1967Proxy::*; +} diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index e69de29..8b13789 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -0,0 +1 @@ + diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..1f56371 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,20 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.12.0", + "rev": "7117c90c8cf6c68e5acce4f09a6b24715cea4de6" + } + }, + "lib/openzeppelin-contracts-upgradeable": { + "tag": { + "name": "v5.5.0", + "rev": "aa677e9d28ed78fc427ec47ba2baef2030c58e7c" + } + }, + "lib/openzeppelin-foundry-upgrades": { + "tag": { + "name": "v0.4.0", + "rev": "cbce1e00305e943aa1661d43f41e5ac72c662b07" + } + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..46135a3 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +ast = true +build_info = true +extra_output = ["storageLayout"] +ffi = true +fs_permissions = [{ access = "read", path = "target/foundry" }] +ignored_error_codes = [ + 2018, # Function state mutability can be restricted to pure +] +libs = ["lib"] +out = "target/foundry" +script = "contract-scripts" +solc_version = "0.8.24" +src = "contracts" +test = "contract-tests" diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..7117c90 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 7117c90c8cf6c68e5acce4f09a6b24715cea4de6 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..aa677e9 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit aa677e9d28ed78fc427ec47ba2baef2030c58e7c diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..cbce1e0 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit cbce1e00305e943aa1661d43f41e5ac72c662b07 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..9b42f71 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,8 @@ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/ +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ +openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/ +openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ From 24ec05c0dc96568361a33278544d3f5bcf5079fa Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Thu, 19 Feb 2026 14:16:12 +0800 Subject: [PATCH 08/74] wip: ts lib & web (#17) * fix ignore file * pnpm init vue-ts * add poc * add APIs --- .cargo/config.toml | 4 +- .gitignore | 472 +------ Cargo.lock | 26 + Cargo.toml | 3 + README.md | 9 + apps/web/.gitignore | 24 + apps/web/.vscode/extensions.json | 3 + apps/web/README.md | 5 + apps/web/index.html | 13 + apps/web/package.json | 23 + apps/web/public/vite.svg | 1 + apps/web/src/App.vue | 30 + apps/web/src/assets/vue.svg | 1 + apps/web/src/components/HelloWorld.vue | 47 + apps/web/src/main.ts | 5 + apps/web/src/style.css | 79 ++ apps/web/tsconfig.app.json | 16 + apps/web/tsconfig.json | 7 + apps/web/tsconfig.node.json | 26 + apps/web/vite.config.ts | 7 + crates/core-wasm/Cargo.toml | 23 + crates/core-wasm/src/lib.rs | 153 +++ crates/core/Cargo.toml | 4 + crates/core/src/codec/v1.rs | 62 +- crates/core/src/codec/v1/attestation.rs | 9 +- .../core/src/codec/v1/detached_timestamp.rs | 47 +- crates/core/src/codec/v1/digest.rs | 6 + crates/core/src/codec/v1/opcode.rs | 46 + crates/core/src/codec/v1/timestamp.rs | 137 +- package.json | 11 + packages/uts-sdk/package.json | 25 + packages/uts-sdk/src/index.ts | 71 ++ packages/uts-sdk/tsconfig.json | 11 + pnpm-lock.yaml | 1132 +++++++++++++++++ pnpm-workspace.yaml | 8 + tsconfig.json | 18 + 36 files changed, 2080 insertions(+), 484 deletions(-) create mode 100644 apps/web/.gitignore create mode 100644 apps/web/.vscode/extensions.json create mode 100644 apps/web/README.md create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/public/vite.svg create mode 100644 apps/web/src/App.vue create mode 100644 apps/web/src/assets/vue.svg create mode 100644 apps/web/src/components/HelloWorld.vue create mode 100644 apps/web/src/main.ts create mode 100644 apps/web/src/style.css create mode 100644 apps/web/tsconfig.app.json create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.node.json create mode 100644 apps/web/vite.config.ts create mode 100644 crates/core-wasm/Cargo.toml create mode 100644 crates/core-wasm/src/lib.rs create mode 100644 package.json create mode 100644 packages/uts-sdk/package.json create mode 100644 packages/uts-sdk/src/index.ts create mode 100644 packages/uts-sdk/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.json diff --git a/.cargo/config.toml b/.cargo/config.toml index aef462a..39a35e3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,10 @@ -[build] +[target.aarch64-apple-darwin] rustflags = ["-C", "target-cpu=native"] [target.x86_64-unknown-linux-gnu] rustflags = [ + "-C", + "target-cpu=native", # (Nightly) Make the current crate share its generic instantiations "-Zshare-generics=y", ] diff --git a/.gitignore b/.gitignore index dd887e9..6528f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,469 +11,13 @@ target # Generated by cargo mutants # Contains mutation testing data -**/mutants.out*/ +**/mutants.out/ -# IntelliJ IDEA -.idea - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates -*.env - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ - -[Dd]ebug/x64/ -[Dd]ebugPublic/x64/ -[Rr]elease/x64/ -[Rr]eleases/x64/ -bin/x64/ -obj/x64/ - -[Dd]ebug/x86/ -[Dd]ebugPublic/x86/ -[Rr]elease/x86/ -[Rr]eleases/x86/ -bin/x86/ -obj/x86/ - -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -[Aa][Rr][Mm]64[Ee][Cc]/ -bld/ -[Oo]bj/ -[Oo]ut/ -[Ll]og/ -[Ll]ogs/ - -# Build results on 'Bin' directories -**/[Bb]in/* -# Uncomment if you have tasks that rely on *.refresh files to move binaries -# (https://github.com/github/gitignore/pull/3736) -#!**/[Bb]in/*.refresh - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* -*.trx - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Approval Tests result files -*.received.* - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.idb -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -# but not Directory.Build.rsp, as it configures directory-level build defaults -!Directory.Build.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -**/.paket/paket.exe -paket-files/ - -# FAKE - F# Make -**/.fake/ - -# CodeRush personal settings -**/.cr/personal - -# Python Tools for Visual Studio (PTVS) -**/__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -#tools/** -#!tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog -MSBuild_Logs/ - -# AWS SAM Build and Temporary Artifacts folder -.aws-sam - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -**/.mfractor/ - -# Local History for Visual Studio -**/.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -**/.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -## MacOS related ignores -## -## Get latest from https://github.com/github/gitignore/blob/main/Global/macOS.gitignore -# General +# MacOS files .DS_Store -__MACOSX/ -.AppleDouble -.LSOverride -Icon[ -] - -# Thumbnails -._* -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +# IntelliJ IDEA +.idea # Hardhat/Foundry files cache @@ -481,3 +25,11 @@ cache-hardhat artifacts artifacts-hardhat broadcast + +# Js +node_modules +dist +*.tsbuildinfo + +# Project files +.db diff --git a/Cargo.lock b/Cargo.lock index 6262675..ff08288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4419,6 +4419,17 @@ 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" @@ -5402,6 +5413,9 @@ dependencies = [ "opentimestamps", "paste", "ripemd", + "serde", + "serde_json", + "serde_with", "sha1 0.11.0-rc.3", "sha2 0.11.0-rc.3", "sha3 0.11.0-rc.3", @@ -5409,6 +5423,18 @@ dependencies = [ "tracing", ] +[[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/Cargo.toml b/Cargo.toml index 255a5d0..979895a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,8 @@ once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" serde = "1.0" +serde-wasm-bindgen = "0.6" +serde_json = "1.0" serde_with = "3.16" strum = "0.27" thiserror = "2" @@ -63,6 +65,7 @@ toml = "0.9" tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +wasm-bindgen = "0.2" digest = "0.11.0-rc.4" ripemd = "0.2.0-rc.3" diff --git a/README.md b/README.md index 5eb0ce5..86eaa5a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # Universal Timestamps (UTS) Universal Timestamps is a super set of [opentimestamps](https://opentimestamps.org/). + +## Development + +### Pre-requisites + +- Rust >= 1.94.0-nightly (e7d44143a 2025-12-24) +- Cargo >= 1.94.0-nightly (3861f60f6 2025-12-19) +- [wasm-pack](https://drager.github.io/wasm-pack/installer/) +- pnpm >= 10.26.2 diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/web/.vscode/extensions.json b/apps/web/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/apps/web/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..8ea4f41 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,23 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.24", + "uts-sdk": "workspace:*" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vue-tsc": "^3.1.4" + } +} diff --git a/apps/web/public/vite.svg b/apps/web/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/apps/web/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue new file mode 100644 index 0000000..aa54efc --- /dev/null +++ b/apps/web/src/App.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/apps/web/src/assets/vue.svg b/apps/web/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/apps/web/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/components/HelloWorld.vue b/apps/web/src/components/HelloWorld.vue new file mode 100644 index 0000000..0510718 --- /dev/null +++ b/apps/web/src/components/HelloWorld.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts new file mode 100644 index 0000000..2425c0f --- /dev/null +++ b/apps/web/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/apps/web/src/style.css b/apps/web/src/style.css new file mode 100644 index 0000000..f691315 --- /dev/null +++ b/apps/web/src/style.css @@ -0,0 +1,79 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..8d16e42 --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/crates/core-wasm/Cargo.toml b/crates/core-wasm/Cargo.toml new file mode 100644 index 0000000..39ed9cb --- /dev/null +++ b/crates/core-wasm/Cargo.toml @@ -0,0 +1,23 @@ +[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 new file mode 100644 index 0000000..6472495 --- /dev/null +++ b/crates/core-wasm/src/lib.rs @@ -0,0 +1,153 @@ +//! 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, 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, + }, + 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 { + 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/core/Cargo.toml b/crates/core/Cargo.toml index c6c9391..b57f971 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -22,6 +22,8 @@ hex.workspace = true once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true ripemd.workspace = true +serde = { workspace = true, optional = true } +serde_with = { workspace = true, optional = true } sha1.workspace = true sha2.workspace = true sha3.workspace = true @@ -31,9 +33,11 @@ tracing = { workspace = true, optional = true } [features] bytes = ["dep:bytes"] default = ["std"] +serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] std = [] tracing = ["dep:tracing"] [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } opentimestamps = { git = "https://github.com/opentimestamps/rust-opentimestamps" } +serde_json.workspace = true diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index 4815fc6..4ff5925 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -11,4 +11,64 @@ pub use attestation::{ }; pub use detached_timestamp::DetachedTimestamp; pub use digest::DigestHeader; -pub use timestamp::Timestamp; +pub use timestamp::{Step, Timestamp}; + +/// Error indicating that finalization of a timestamp failed due to conflicting inputs. +#[derive(Debug)] +pub struct FinalizationError; + +impl core::fmt::Display for FinalizationError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "failed to finalize timestamp due to conflicting inputs") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FinalizationError {} + +/// Trait for objects that may have input data. +pub trait MayHaveInput { + /// Returns the input data for this object, if finalized. + fn input(&self) -> Option<&[u8]>; +} + +trait ToInput { + fn to_input(&self) -> Option<&[u8]>; +} +impl ToInput for T { + fn to_input(&self) -> Option<&[u8]> { + self.input() + } +} +impl ToInput for [u8] { + fn to_input(&self) -> Option<&[u8]> { + Some(self) + } +} +impl ToInput for Vec { + fn to_input(&self) -> Option<&[u8]> { + Some(self) + } +} + +/// Trait for objects that can be checked for consistency with another object. +#[allow(private_bounds)] +pub trait ConsistentWith: MayHaveInput { + /// Checks if self is consistent with the given input. + /// + /// Note: Returns true if any of the inputs is not set. + fn is_consistent_with(&self, other: &T) -> bool { + self.input() + .zip(other.to_input()) + .map_or(true, |(a, b)| a == b) + } + + /// Checks if self is consistent with the given input. + /// + /// Note: Returns false if xor of the inputs is not set. + fn is_consistent_with_strict(&self, other: &T) -> bool { + self.input() == other.to_input() + } +} + +impl ConsistentWith for T {} diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index cf519b3..d4695f7 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -4,7 +4,7 @@ //! comes from some server or from a blockchain. use crate::{ - codec::{Decode, DecodeIn, Decoder, Encode, Encoder}, + codec::{Decode, DecodeIn, Decoder, Encode, Encoder, v1::MayHaveInput}, error::{DecodeError, EncodeError}, utils::{Hexed, OnceLock}, }; @@ -211,3 +211,10 @@ impl fmt::Display for RawAttestation { } } } + +impl MayHaveInput for RawAttestation { + #[inline] + fn input(&self) -> Option<&[u8]> { + self.value.get().map(|v| v.as_slice()) + } +} diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 7626851..7e243da 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -1,6 +1,6 @@ use crate::codec::{ Decode, DecodeIn, Encode, Encoder, Proof, Version, - v1::{DigestHeader, Timestamp}, + v1::{DigestHeader, FinalizationError, Timestamp}, }; use alloc::alloc::{Allocator, Global}; use core::{fmt, fmt::Formatter}; @@ -64,6 +64,51 @@ impl DetachedTimestamp { pub fn allocator(&self) -> &A { self.timestamp.allocator() } + + /// Consumes the detached timestamp and returns its parts. + pub fn into_parts(self) -> (DigestHeader, Timestamp) { + (self.header, self.timestamp) + } +} + +impl DetachedTimestamp { + /// Creates a new detached timestamp from the given header and timestamp. + /// + /// # Panics + /// + /// Panics if the timestamp cannot be finalized with the given header's digest. + pub fn from_parts(header: DigestHeader, timestamp: Timestamp) -> Self { + Self::try_from_parts(header, timestamp) + .expect("conflicting inputs when finalizing detached timestamp") + } + + /// Creates a new detached timestamp from the given header and timestamp. + /// + /// Returns an error if the timestamp cannot be finalized with the given header's digest. + pub fn try_from_parts( + header: DigestHeader, + timestamp: Timestamp, + ) -> Result { + timestamp.try_finalize(header.digest())?; + Ok(DetachedTimestamp { header, timestamp }) + } + + /// Finalize the detached timestamp's timestamp with the header's digest. + /// + /// # Panics + /// + /// Panics if the timestamp cannot be finalized. + pub fn finalize(&self) { + self.try_finalize() + .expect("conflicting inputs when finalizing detached timestamp"); + } + + /// Tries to finalize the detached timestamp's timestamp with the header's digest. + /// + /// Returns an error if the timestamp cannot be finalized. + pub fn try_finalize(&self) -> Result<(), FinalizationError> { + self.timestamp.try_finalize(self.header.digest()) + } } #[cfg(test)] diff --git a/crates/core/src/codec/v1/digest.rs b/crates/core/src/codec/v1/digest.rs index 5eeb85c..05792fa 100644 --- a/crates/core/src/codec/v1/digest.rs +++ b/crates/core/src/codec/v1/digest.rs @@ -8,8 +8,14 @@ use core::fmt; /// Header describing the digest that anchors a timestamp. #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr( + feature = "serde", + serde_with::serde_as, + derive(serde::Serialize, serde::Deserialize) +)] pub struct DigestHeader { kind: DigestOp, + #[cfg_attr(feature = "serde", serde_as(as = "serde_with::hex::Hex"))] digest: [u8; 32], } diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index 6b97073..b7b0a34 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -18,6 +18,10 @@ use sha3::Keccak256; /// /// This is always a valid opcode. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr( + feature = "serde", + derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr) +)] #[repr(transparent)] pub struct OpCode(u8); @@ -171,6 +175,7 @@ impl PartialEq for OpCode { /// /// This is always a valid opcode. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(transparent)] pub struct DigestOp(OpCode); @@ -261,6 +266,31 @@ macro_rules! define_opcodes { } } } + + /// Error returned when parsing an invalid opcode from a string. + #[derive(Debug)] + pub struct OpCodeFromStrError; + + impl core::fmt::Display for OpCodeFromStrError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "invalid opcode string") + } + } + + #[cfg(feature = "std")] + impl std::error::Error for OpCodeFromStrError {} + + impl core::str::FromStr for OpCode { + type Err = OpCodeFromStrError; + + #[inline] + fn from_str(s: &str) -> Result { + match s { + $( stringify!($variant) => Ok(Self::$variant), )* + _ => Err(OpCodeFromStrError), + } + } + } }; } @@ -386,4 +416,20 @@ mod tests { assert_eq!(DigestOp::SHA256.output_size(), 32); assert_eq!(DigestOp::KECCAK256.output_size(), 32); } + + #[cfg(feature = "serde")] + #[test] + fn serde_opcode() { + let opcode = OpCode::SHA256; + let serialized = serde_json::to_string(&opcode).unwrap(); + assert_eq!(serialized, "\"SHA256\""); + let deserialized: OpCode = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, opcode); + + let digest_op = DigestOp::SHA256; + let serialized = serde_json::to_string(&digest_op).unwrap(); + assert_eq!(serialized, "\"SHA256\""); + let deserialized: DigestOp = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, digest_op); + } } diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 7f604d0..7d3f7d7 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -1,7 +1,7 @@ //! ** The implementation here is subject to change as this is a read-only version. ** use crate::{ - codec::v1::{attestation::RawAttestation, opcode::OpCode}, + codec::v1::{FinalizationError, MayHaveInput, attestation::RawAttestation, opcode::OpCode}, utils::{Hexed, OnceLock}, }; use alloc::{alloc::Global, vec::Vec}; @@ -78,6 +78,22 @@ impl Timestamp { pub fn builder() -> builder::TimestampBuilder { builder::TimestampBuilder::new_in(Global) } + + /// Merges multiple timestamps into a single FORK timestamp. + /// + /// # Panics + /// + /// This will panic if there are conflicting inputs when finalizing unfinalized timestamps. + pub fn merge(timestamps: Vec) -> Timestamp { + Self::merge_in(timestamps, Global) + } + + /// Try to merge multiple timestamps into a single FORK timestamp. + /// + /// Returns an error if there are conflicting inputs when finalizing unfinalized timestamps. + pub fn try_merge(timestamps: Vec) -> Result { + Self::try_merge_in(timestamps, Global) + } } impl Timestamp { @@ -114,15 +130,6 @@ impl Timestamp { } } - /// Returns the input data for this timestamp node, if finalized. - #[inline] - pub fn input(&self) -> Option<&[u8]> { - match self { - Timestamp::Step(step) => step.input.get().map(|v| v.as_slice()), - Timestamp::Attestation(attestation) => attestation.value.get().map(|v| v.as_slice()), - } - } - /// Returns the allocator used by this timestamp node. #[inline] pub fn allocator(&self) -> &A { @@ -131,6 +138,12 @@ impl Timestamp { Self::Step(step) => step.allocator(), } } + + /// Returns true if this timestamp is finalized. + #[inline] + pub fn is_finalized(&self) -> bool { + self.input().is_some() + } } impl Timestamp { @@ -144,44 +157,128 @@ impl Timestamp { /// # Panics /// /// Panics if the timestamp is already finalized with different input data. - pub fn finalize(&self, input: Vec) { + #[inline] + pub fn finalize(&self, input: &[u8]) { + self.try_finalize(input) + .expect("conflicting inputs when finalizing timestamp") + } + + /// Try finalizes the timestamp with the given input data. + /// + /// Returns an error if the timestamp is already finalized with different input data. + pub fn try_finalize(&self, input: &[u8]) -> Result<(), FinalizationError> { + let init_fn = || input.to_vec_in(self.allocator().clone()); match self { Self::Attestation(attestation) => { if let Some(already) = attestation.value.get() { - assert_eq!(&input, already, "trying to finalize with different input"); - return; + return if &input != already { + Err(FinalizationError) + } else { + Ok(()) + }; } - let _ = attestation.value.get_or_init(|| input); + let _ = attestation.value.get_or_init(init_fn); } Self::Step(step) => { if let Some(already) = step.input.get() { - assert_eq!(&input, already, "trying to finalize with different input"); - return; + return if &input != already { + Err(FinalizationError) + } else { + Ok(()) + }; } - let input = step.input.get_or_init(|| input); + let input = step.input.get_or_init(init_fn); match step.op { OpCode::FORK => { debug_assert!(step.next.len() >= 2, "FORK must have at least two children"); for child in &step.next { - child.finalize(input.clone()); + child.finalize(input); } } OpCode::ATTESTATION => unreachable!("should not happen"), op => { let output = op.execute_in(input, &step.data, step.allocator().clone()); debug_assert!(step.next.len() == 1, "non-FORK must have exactly one child"); - step.next[0].finalize(output); + step.next[0].finalize(&output); } } } } + Ok(()) + } + + /// Merges multiple timestamps into a single FORK timestamp. + /// + /// # Panics + /// + /// This will panic if there are conflicting inputs when finalizing unfinalized timestamps. + pub fn merge_in(timestamps: Vec, A>, alloc: A) -> Timestamp { + Self::try_merge_in(timestamps, alloc).expect("conflicting inputs when merging timestamps") + } + + /// Merges multiple timestamps into a single FORK timestamp. + /// + /// This will attempt to finalize unfinalized timestamps if any of the input timestamps are finalized. + /// + /// Returns an error if there are conflicting inputs when finalizing unfinalized timestamps. + pub fn try_merge_in( + timestamps: Vec, A>, + alloc: A, + ) -> Result, FinalizationError> { + // if any timestamp is finalized, ensure they are with the same input, + // finalize unfinalized timestamps with that input + let finalized_input = timestamps.iter().find_map(|ts| ts.input()); + if let Some(ref input) = finalized_input { + for ts in timestamps.iter().filter(|ts| !ts.is_finalized()) { + ts.try_finalize(input)?; + } + } + + Ok(Timestamp::Step(Step { + op: OpCode::FORK, + data: Vec::new_in(alloc.clone()), + input: OnceLock::new(), + next: timestamps, + })) + } +} + +impl MayHaveInput for Timestamp { + #[inline] + fn input(&self) -> Option<&[u8]> { + match self { + Timestamp::Step(step) => step.input(), + Timestamp::Attestation(attestation) => attestation.input(), + } } } impl Step { + /// Returns the opcode of this step. + pub fn op(&self) -> OpCode { + self.op + } + + /// Returns the immediate data of this step. + pub fn data(&self) -> &[u8] { + self.data.as_slice() + } + + /// Returns the next timestamps of this step. + pub fn next(&self) -> &[Timestamp] { + self.next.as_slice() + } + /// Returns the allocator used by this step. - pub(crate) fn allocator(&self) -> &A { + pub fn allocator(&self) -> &A { self.data.allocator() } } + +impl MayHaveInput for Step { + #[inline] + fn input(&self) -> Option<&[u8]> { + self.input.get().map(|v| v.as_slice()) + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fb13c2f --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "uts-monorepo", + "private": true, + "scripts": { + "build:wasm": "wasm-pack build crates/core-wasm --target web" + }, + "devDependencies": { + "pnpm": "^10.26.2", + "wasm-pack": "^0.13.1" + } +} diff --git a/packages/uts-sdk/package.json b/packages/uts-sdk/package.json new file mode 100644 index 0000000..604f999 --- /dev/null +++ b/packages/uts-sdk/package.json @@ -0,0 +1,25 @@ +{ + "name": "uts-sdk", + "version": "0.1.0", + "description": "", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "keywords": [], + "author": "Akase Haruka ", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "uts-core-wasm": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.9.3" + } +} diff --git a/packages/uts-sdk/src/index.ts b/packages/uts-sdk/src/index.ts new file mode 100644 index 0000000..8624a56 --- /dev/null +++ b/packages/uts-sdk/src/index.ts @@ -0,0 +1,71 @@ +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/packages/uts-sdk/tsconfig.json b/packages/uts-sdk/tsconfig.json new file mode 100644 index 0000000..865556f --- /dev/null +++ b/packages/uts-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/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..26c3660 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1132 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + pnpm: + specifier: ^10.26.2 + version: 10.26.2 + wasm-pack: + specifier: ^0.13.1 + version: 0.13.1 + + apps/web: + dependencies: + uts-sdk: + specifier: workspace:* + version: link:../../packages/uts-sdk + vue: + specifier: ^3.5.24 + version: 3.5.26(typescript@5.9.3) + devDependencies: + '@types/node': + specifier: ^24.10.1 + version: 24.10.4 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.3(vite@7.3.0(@types/node@24.10.4))(vue@3.5.26(typescript@5.9.3)) + '@vue/tsconfig': + specifier: ^0.8.1 + version: 0.8.1(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: ^7.2.4 + version: 7.3.0(@types/node@24.10.4) + vue-tsc: + specifier: ^3.1.4 + version: 3.2.1(typescript@5.9.3) + + crates/core-wasm/pkg: {} + + packages/uts-sdk: + dependencies: + uts-core-wasm: + specifier: workspace:* + version: link:../../crates/core-wasm/pkg + devDependencies: + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.10.4': + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} + + '@vitejs/plugin-vue@6.0.3': + resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vue: ^3.2.25 + + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + + '@volar/typescript@2.4.27': + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==} + + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/language-core@3.2.1': + resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + + '@vue/tsconfig@0.8.1': + resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + alien-signals@3.1.2: + resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + + axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-install@1.1.2: + resolution: {integrity: sha512-ZS2cqFHPZOy4wLxvzqfQvDjCOifn+7uCPqNmYRIBM/03+yllON+4fNnsD0VJdW0p97y+E+dTRNPStWNqMBq+9g==} + engines: {node: '>=10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + 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@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pnpm@10.26.2: + resolution: {integrity: sha512-DjCP8gBfx0EDZvFU9iX2YxqysWsdLnAjhETdaunWMKhILZKkURRN68SSQWiW7Rb3sRSobsaLhASyRDhp5o/9pg==} + engines: {node: '>=18.12'} + hasBin: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-tsc@3.2.1: + resolution: {integrity: sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + wasm-pack@0.13.1: + resolution: {integrity: sha512-P9exD4YkjpDbw68xUhF3MDm/CC/3eTmmthyG5bHJ56kalxOTewOunxTke4SyF8MTXV6jUtNjXggPgrGmMtczGg==} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + + '@types/estree@1.0.8': {} + + '@types/node@24.10.4': + dependencies: + undici-types: 7.16.0 + + '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@24.10.4))(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.53 + vite: 7.3.0(@types/node@24.10.4) + vue: 3.5.26(typescript@5.9.3) + + '@volar/language-core@2.4.27': + dependencies: + '@volar/source-map': 2.4.27 + + '@volar/source-map@2.4.27': {} + + '@volar/typescript@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/language-core@3.2.1': + dependencies: + '@volar/language-core': 2.4.27 + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + alien-signals: 3.1.2 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.9.3) + + '@vue/shared@3.5.26': {} + + '@vue/tsconfig@0.8.1(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))': + optionalDependencies: + typescript: 5.9.3 + vue: 3.5.26(typescript@5.9.3) + + alien-signals@3.1.2: {} + + axios@0.26.1: + dependencies: + follow-redirects: 1.15.11 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + binary-install@1.1.2: + dependencies: + axios: 0.26.1 + rimraf: 3.0.2 + tar: 6.2.1 + transitivePeerDependencies: + - debug + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + chownr@2.0.0: {} + + concat-map@0.0.1: {} + + csstype@3.2.3: {} + + entities@7.0.0: {} + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + follow-redirects@1.15.11: {} + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + path-browserify@1.0.1: {} + + path-is-absolute@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pnpm@10.26.2: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + vite@7.3.0(@types/node@24.10.4): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.4 + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-tsc@3.2.1(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.27 + '@vue/language-core': 3.2.1 + typescript: 5.9.3 + + vue@3.5.26(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + optionalDependencies: + typescript: 5.9.3 + + wasm-pack@0.13.1: + dependencies: + binary-install: 1.1.2 + transitivePeerDependencies: + - debug + + wrappy@1.0.2: {} + + yallist@4.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..33f4b8b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +packages: + - apps/* + - packages/* + - crates/core-wasm/pkg + +onlyBuiltDependencies: + - esbuild + - wasm-pack diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2f0c714 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "declaration": true, + "skipLibCheck": true, + "esModuleInterop": true, + "isolatedModules": true, + "resolveJsonModule": true, + "composite": true + }, + "references": [ + { "path": "./packages/uts-sdk" }, + { "path": "./apps/web" } + ] +} From 735c01257a756cd605a5e030eb7979544f5d7831 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Thu, 19 Feb 2026 16:11:59 +0800 Subject: [PATCH 09/74] wip: stamper (#8) * wip * mvp * add tests * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * update README * add stamper * fix wait_at_least * fix merge * add stamper * fmt * apply review * add stamper to calendar server * add contract * update lock --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .gitignore | 3 + Cargo.lock | 408 +++++++++++++++++++++++++++++++++++- Cargo.toml | 7 + crates/bmt/Cargo.toml | 2 + crates/bmt/src/lib.rs | 101 ++++++--- crates/calendar/Cargo.toml | 7 +- crates/calendar/src/main.rs | 56 ++++- crates/contracts/Cargo.toml | 2 +- crates/contracts/src/lib.rs | 25 +++ crates/stamper/Cargo.toml | 11 + crates/stamper/src/lib.rs | 190 +++++++++++++++++ rust-toolchain.toml | 2 + 12 files changed, 770 insertions(+), 44 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.gitignore b/.gitignore index 6528f9e..b6c829e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Project-specific ignores +/.db + # Generated by Cargo # will have compiled files and executables debug diff --git a/Cargo.lock b/Cargo.lock index ff08288..fa18842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -654,9 +654,12 @@ dependencies = [ "alloy-primitives", "alloy-signer", "async-trait", + "coins-bip32", + "coins-bip39", "k256", "rand 0.8.5", "thiserror 2.0.17", + "zeroize", ] [[package]] @@ -740,7 +743,7 @@ checksum = "bec1fb08ee484e615f24867c0b154fff5722bb00176102a16868c6532b7c3623" dependencies = [ "alloy-json-rpc", "auto_impl", - "base64", + "base64 0.22.1", "derive_more", "futures", "futures-utils-wasm", @@ -1215,6 +1218,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -1227,12 +1236,36 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bimap" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.111", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1373,6 +1406,16 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2 0.10.9", + "tinyvec", +] + [[package]] name = "bump-scope" version = "1.5.1" @@ -1396,6 +1439,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" @@ -1411,6 +1460,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "c-kzg" version = "2.1.5" @@ -1444,6 +1503,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -1530,6 +1598,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.53" @@ -1555,6 +1634,57 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "coins-bip32" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2 0.10.9", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-bip39" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.8.5", + "sha2 0.10.9", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "const-hex", + "digest 0.10.7", + "generic-array", + "ripemd 0.1.3", + "serde", + "sha2 0.10.9", + "sha3 0.10.8", + "thiserror 1.0.69", +] + [[package]] name = "commonware-codec" version = "0.0.63" @@ -1749,6 +1879,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2382,6 +2522,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2730,6 +2885,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" dependencies = [ + "bytemuck", "typenum", ] @@ -2773,13 +2929,29 @@ dependencies = [ "webpki-roots 1.0.4", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -3094,6 +3266,7 @@ dependencies = [ "once_cell", "serdect", "sha2 0.10.9", + "signature", ] [[package]] @@ -3136,12 +3309,48 @@ version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "librocksdb-sys" +version = "0.17.3+10.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "libc", + "libz-sys", + "lz4-sys", + "zstd-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -3184,6 +3393,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "macro-string" version = "0.1.4" @@ -3222,6 +3441,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.1.1" @@ -3233,12 +3458,39 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonzero_ext" version = "0.3.0" @@ -3372,6 +3624,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.28.0" @@ -3547,6 +3843,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4091,7 +4397,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -4101,9 +4407,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -4114,6 +4422,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -4149,6 +4458,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "ripemd" version = "0.2.0-rc.3" @@ -4168,6 +4486,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rocksdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "ruint" version = "1.17.0" @@ -4313,6 +4641,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.9.0" @@ -4379,6 +4716,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -4492,7 +4852,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -5000,6 +5360,16 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -5088,7 +5458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "bytes", "http", "http-body", @@ -5358,10 +5728,12 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" name = "uts-bmt" version = "0.1.0" dependencies = [ + "bytemuck", "commonware-cryptography", "commonware-storage", "criterion 0.8.1", "digest 0.11.0-rc.4", + "hybrid-array", "sha2 0.11.0-rc.3", "sha3 0.11.0-rc.3", ] @@ -5371,6 +5743,7 @@ name = "uts-calendar" version = "0.1.0" dependencies = [ "alloy-primitives", + "alloy-provider", "alloy-signer", "alloy-signer-local", "axum", @@ -5379,13 +5752,17 @@ dependencies = [ "criterion 0.8.1", "digest 0.11.0-rc.4", "eyre", + "hashbrown 0.15.5", "itoa", + "rocksdb", "sha3 0.11.0-rc.3", "tokio", "tracing", "tracing-subscriber", + "uts-contracts", "uts-core", "uts-journal", + "uts-stamper", ] [[package]] @@ -5412,7 +5789,7 @@ dependencies = [ "once_cell", "opentimestamps", "paste", - "ripemd", + "ripemd 0.2.0-rc.3", "serde", "serde_json", "serde_with", @@ -5447,6 +5824,19 @@ dependencies = [ [[package]] name = "uts-stamper" version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-provider", + "bytemuck", + "digest 0.11.0-rc.4", + "rocksdb", + "tokio", + "tracing", + "uts-bmt", + "uts-contracts", + "uts-core", + "uts-journal", +] [[package]] name = "valuable" @@ -5454,6 +5844,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 979895a..66edd47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ unnecessary-debug-formatting = "warn" alloy = "1" alloy-contract = "1.2" alloy-primitives = "1.5" +alloy-provider = "1.2" alloy-signer = "1.1" alloy-signer-local = "1.1" alloy-sol-types = "1.5" @@ -38,6 +39,7 @@ auto_impl = "1.3" axum = { version = "0.8", default-features = false } axum-extra = "0.12" bump-scope = { version = "1.5", features = ["nightly"] } +bytemuck = "1" bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } @@ -54,6 +56,7 @@ itoa = "1.0" once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" +rocksdb = "0.24" serde = "1.0" serde-wasm-bindgen = "0.6" serde_json = "1.0" @@ -67,15 +70,19 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } wasm-bindgen = "0.2" +crypto-common = "0.2.0-rc.5" digest = "0.11.0-rc.4" +hybrid-array = "0.4.5" ripemd = "0.2.0-rc.3" sha1 = "0.11.0-rc.3" sha2 = "0.11.0-rc.3" sha3 = "0.11.0-rc.3" uts-bmt = { path = "crates/bmt" } +uts-contracts = { path = "crates/contracts" } uts-core = { path = "crates/core" } uts-journal = { path = "crates/journal" } +uts-stamper = { path = "crates/stamper" } [profile.bench] codegen-units = 1 diff --git a/crates/bmt/Cargo.toml b/crates/bmt/Cargo.toml index 6c6861f..565c54c 100644 --- a/crates/bmt/Cargo.toml +++ b/crates/bmt/Cargo.toml @@ -10,7 +10,9 @@ repository.workspace = true version.workspace = true [dependencies] +bytemuck = { workspace = true } digest.workspace = true +hybrid-array = { workspace = true, features = ["bytemuck"] } [dev-dependencies] commonware-cryptography = "0.0.63" # for benchmarks diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index eb70e96..76bce19 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -1,8 +1,8 @@ -#![feature(maybe_uninit_slice)] #![feature(maybe_uninit_fill)] #![feature(likely_unlikely)] //! High performance binary Merkle tree implementation in Rust. +use bytemuck::Pod; use digest::{Digest, FixedOutputReset, Output}; use std::hint::unlikely; @@ -18,19 +18,26 @@ pub struct FlatMerkleTree { len: usize, } +/// Merkle Tree without hashing the leaves +#[derive(Debug, Clone)] +pub struct UnhashedFlatMerkleTree { + buffer: Vec>, + len: usize, +} + impl FlatMerkleTree where - Output: Copy, + Output: Pod + Copy, { /// Constructs a new Merkle tree from the given hash leaves. pub fn new(data: &[Output]) -> Self { + Self::new_unhashed(data).finalize() + } + + /// Constructs a new Merkle tree from the given hash leaves, without hashing internal nodes. + pub fn new_unhashed(data: &[Output]) -> UnhashedFlatMerkleTree { let raw_len = data.len(); - if unlikely(raw_len == 0) { - return Self { - nodes: Box::new([Output::::default(); 2]), - len: 1, - }; - } + assert_ne!(raw_len, 0, "Cannot create Merkle tree with zero leaves"); let len = raw_len.next_power_of_two(); let mut nodes = Vec::>::with_capacity(2 * len); @@ -62,29 +69,9 @@ where .get_unchecked_mut(len..) .assume_init_mut() .sort_unstable(); - - // Build the tree - let mut hasher = D::new(); - for i in (1..len).rev() { - // SAFETY: in bounds due to loop range and initialization above - let left = maybe_uninit.get_unchecked(2 * i).assume_init_ref(); - let right = maybe_uninit.get_unchecked(2 * i + 1).assume_init_ref(); - - Digest::update(&mut hasher, left); - Digest::update(&mut hasher, right); - let parent_hash = hasher.finalize_reset(); - - maybe_uninit.get_unchecked_mut(i).write(parent_hash); - } - - // SAFETY: initialized all elements. - nodes.set_len(2 * len); } - Self { - nodes: nodes.into_boxed_slice(), - len, - } + UnhashedFlatMerkleTree { buffer: nodes, len } } /// Returns the root hash of the Merkle tree @@ -114,6 +101,58 @@ where current: self.len + leaf_index_in_slice, }) } + + /// Returns the raw bytes of the Merkle tree nodes + #[inline] + pub fn as_raw_bytes(&self) -> &[u8] { + bytemuck::cast_slice(&self.nodes) + } + + /// From raw bytes, reconstruct the Merkle tree + #[inline] + pub unsafe fn from_raw_bytes(bytes: &[u8]) -> Self { + let nodes: &[Output] = bytemuck::cast_slice(bytes); + let len = nodes.len() / 2; + Self { + nodes: nodes.to_vec().into_boxed_slice(), + len, + } + } +} + +impl UnhashedFlatMerkleTree +where + Output: Pod + Copy, +{ + /// Finalizes the Merkle tree by hashing internal nodes + pub fn finalize(self) -> FlatMerkleTree { + let mut nodes = self.buffer; + let len = self.len; + unsafe { + let maybe_uninit = nodes.spare_capacity_mut(); + + // Build the tree + let mut hasher = D::new(); + for i in (1..len).rev() { + // SAFETY: in bounds due to loop range and initialization above + let left = maybe_uninit.get_unchecked(2 * i).assume_init_ref(); + let right = maybe_uninit.get_unchecked(2 * i + 1).assume_init_ref(); + + Digest::update(&mut hasher, left); + Digest::update(&mut hasher, right); + let parent_hash = hasher.finalize_reset(); + + maybe_uninit.get_unchecked_mut(i).write(parent_hash); + } + + // SAFETY: initialized all elements. + nodes.set_len(2 * len); + } + FlatMerkleTree { + nodes: nodes.into_boxed_slice(), + len, + } + } } /// Iterator over the sibling nodes of a leaf in the Merkle tree @@ -181,7 +220,7 @@ mod tests { fn test_merkle_tree() where - Output: Copy, + Output: Pod + Copy, { let mut leaves = vec![ D::digest(b"leaf1"), @@ -212,7 +251,7 @@ mod tests { fn test_proof() where - Output: Copy, + Output: Pod + Copy, { let mut leaves = vec![ D::digest(b"apple"), diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index b5c4aff..003555c 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -10,8 +10,9 @@ version.workspace = true [dependencies] alloy-primitives = { workspace = true } +alloy-provider = { workspace = true } alloy-signer = { workspace = true } -alloy-signer-local = { workspace = true } +alloy-signer-local = { workspace = true, features = ["mnemonic"] } axum = { workspace = true, default-features = false, features = [ "macros", "http2", @@ -21,13 +22,17 @@ bump-scope.workspace = true bytes = { workspace = true } digest = { workspace = true } eyre = { workspace = true } +hashbrown = { version = "0.15.5", features = ["nightly"] } # why? itoa = { workspace = true } +rocksdb = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } +uts-contracts = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } uts-journal = { workspace = true } +uts-stamper = { workspace = true } [dev-dependencies] criterion.workspace = true diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index 2ca5709..a6431fb 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,15 +1,21 @@ //! Calendar server -use alloy_primitives::b256; -use alloy_signer_local::LocalSigner; +use alloy_primitives::{address, b256}; +use alloy_provider::{ProviderBuilder, network::EthereumWallet}; +use alloy_signer_local::{LocalSigner, MnemonicBuilder}; use axum::{ Router, extract::DefaultBodyLimit, routing::{get, post}, }; -use std::sync::Arc; +use digest::{OutputSizeUser, typenum::Unsigned}; +use rocksdb::DB; +use sha3::Keccak256; +use std::{env, sync::Arc}; use uts_calendar::{AppState, routes, shutdown_signal, time}; +use uts_contracts::uts::UniversalTimestamps; use uts_journal::Journal; +use uts_stamper::{Stamper, StamperConfig}; const RING_BUFFER_CAPACITY: usize = 1 << 20; // 1 million entries @@ -22,10 +28,50 @@ async fn main() -> eyre::Result<()> { let signer = LocalSigner::from_bytes(&b256!( "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" ))?; + + // journal + // TODO: graceful shutdown let journal = Journal::with_capacity(RING_BUFFER_CAPACITY); - let _reader = journal.reader(); - // TODO: spawn stamper task + // ethereum provider + // Implementation deployed at: 0xf74254bf3c40b29259ce12bd35e74f40b0fda07d + // Proxy deployed at: 0x98c857e675e472cf2fd98c478ed4ecc4caf81fae + let key = MnemonicBuilder::from_phrase(env::var("MNEMONIC")?.as_str()) + .index(0u32)? + .build()?; + let provider = ProviderBuilder::new() + .wallet(EthereumWallet::new(key)) + .connect("https://0xrpc.io/sep") + .await?; + + let contract = UniversalTimestamps::new( + address!("0x98c857e675e472cf2fd98c478ed4ecc4caf81fae"), + provider.clone(), + ); + + // stamper + let reader = journal.reader(); + let db = DB::open_default("./.db/tries")?; + let mut stamper = + Stamper::::OutputSize::USIZE }>::new( + reader, + Arc::new(db), + contract, + // TODO: tune configuration + StamperConfig { + max_interval_seconds: 10, + max_entries_per_timestamp: 1 << 10, // 1024 entries + min_leaves: 1 << 4, + max_cache_size: 256, + }, + ); + // TODO: graceful shutdown + tokio::spawn(async move { + stamper.run().await; + }); + + // TODO: maybe we can separate "/timestamp/{hex_commitment}" into another service with DB::open_as_secondary() + // TODO: write/read separate, reader can scale horizontally? would this be better? let app = Router::new() .route( diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 74ef532..67f2682 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -13,7 +13,7 @@ alloy-contract = { workspace = true } alloy-sol-types = { workspace = true, features = ["json"] } [dev-dependencies] -alloy = { workspace = true, features = ["full", "node-bindings"] } +alloy = { workspace = true, features = ["full", "node-bindings", "signer-mnemonic"] } eyre = { workspace = true } futures = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index e94f338..54e43a3 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -35,10 +35,13 @@ pub mod uts { use super::*; use crate::erc1967::ERC1967ProxyInstance; use alloy::{ + network::EthereumWallet, primitives::{B256, Bytes, U256, b256}, providers::ProviderBuilder, + signers::local::MnemonicBuilder, }; use futures::StreamExt; + use std::env; const ROOT: B256 = b256!("5cd5c6763b9f2b3fb1cd66a15fe92b7ac913eec295d9927886e175f144ce3308"); @@ -63,6 +66,28 @@ pub mod uts { Ok(()) } + + #[tokio::test] + #[ignore] + async fn deploy_to_sepolia() -> eyre::Result<()> { + let signer = MnemonicBuilder::from_phrase(env::var("MNEMONIC")?.as_str()) + .index(0u32)? + .build()?; + + let provider = ProviderBuilder::new() + .wallet(EthereumWallet::new(signer)) + .connect("https://0xrpc.io/sep") + .await?; + + let imp = deploy(&provider).await?; + println!("Implementation deployed at: {:?}", imp.address()); + + let proxy = + ERC1967ProxyInstance::deploy(&provider, *imp.address(), Bytes::new()).await?; + println!("Proxy deployed at: {:?}", proxy.address()); + + Ok(()) + } } } diff --git a/crates/stamper/Cargo.toml b/crates/stamper/Cargo.toml index 3a282a9..89736d9 100644 --- a/crates/stamper/Cargo.toml +++ b/crates/stamper/Cargo.toml @@ -9,6 +9,17 @@ repository.workspace = true version.workspace = true [dependencies] +alloy-primitives = { workspace = true } +alloy-provider = { workspace = true } +bytemuck = { workspace = true } +digest = { workspace = true } +rocksdb = { workspace = true } +tokio = { workspace = true, features = ["time", "macros"] } +tracing = { workspace = true } +uts-bmt = { workspace = true } +uts-contracts = { workspace = true } +uts-core = { workspace = true } +uts-journal = { workspace = true } [lints] workspace = true diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index 8b13789..63afa64 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -1 +1,191 @@ +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] +//! Timestamping + +#[macro_use] +extern crate tracing; + +use alloy_primitives::B256; +use alloy_provider::Provider; +use bytemuck::{NoUninit, Pod}; +use digest::{Digest, FixedOutputReset, Output, typenum::Unsigned}; +use rocksdb::{DB, WriteBatch}; +use std::{collections::VecDeque, fmt, sync::Arc, time::Duration}; +use tokio::time::{Interval, MissedTickBehavior}; +use uts_bmt::FlatMerkleTree; +use uts_contracts::uts::UniversalTimestamps; +use uts_core::utils::Hexed; +use uts_journal::reader::JournalReader; + +/// Stamper for timestamping +/// +/// A stamper will wait for, either: +/// - Timeout: `max_interval_seconds` has passed since last timestamp +/// - Max Entries: `max_entries_per_timestamp` have been collected since last timestamp +/// +/// Then it will collect entries from the journal reader, with the size of: +/// - at most `max_entries_per_timestamp` +/// - if available entries size is not power of two, it will take: +/// - the largest power of two less than available entries, if that is >= `min_leaves` +/// - else, it will take all available entries +pub struct Stamper { + /// Journal reader to read entries from + reader: JournalReader, + /// Storage for merkle trees and leaf->root mappings + storage: Arc, + /// FIFO cache of recent merkle trees + cache: VecDeque>, + /// The contract + contract: UniversalTimestamps

, + /// Stamper configuration + config: StamperConfig, +} + +/// Configuration for the Stamper +#[derive(Debug, Clone)] +pub struct StamperConfig { + /// The maximum interval (in seconds) between create new timestamps + pub max_interval_seconds: u64, + /// The maximum number of entries per timestamp. + /// It should be a power of two. + pub max_entries_per_timestamp: usize, + /// The minimum size of the Merkle tree leaves. + /// It should be a power of two. + pub min_leaves: usize, + /// The maximum number of recent Merkle trees to keep in cache. + pub max_cache_size: usize, +} + +impl fmt::Debug for Stamper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Stamper") + .field("cache_size", &self.cache.len()) + .field("config", &self.config) + .finish() + } +} + +impl Stamper +where + D: Digest + FixedOutputReset + 'static, + P: Provider, + Output: Pod + Copy, + [u8; ENTRY_SIZE]: NoUninit, +{ + const _SIZE_MATCHES: () = assert!(D::OutputSize::USIZE == ENTRY_SIZE); + + /// Create a new Stamper + pub fn new( + reader: JournalReader, + storage: Arc, + contract: UniversalTimestamps

, + config: StamperConfig, + ) -> Self { + Self { + reader, + storage, + cache: VecDeque::with_capacity(config.max_cache_size), + contract, + config, + } + } + + /// Work loop + pub async fn run(&mut self) { + let mut ticker = + tokio::time::interval(Duration::from_secs(self.config.max_interval_seconds)); + ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); + let mut leaves_buffer = Vec::with_capacity(self.config.max_entries_per_timestamp); + loop { + self.pack(&mut ticker, &mut leaves_buffer).await; + } + } + + async fn pack(&mut self, ticker: &mut Interval, buffer: &mut Vec<[u8; ENTRY_SIZE]>) { + let entries = self + .reader + .wait_at_least(self.config.max_entries_per_timestamp); + + let target_size = tokio::select! { + _ = ticker.tick() => { + // Timeout reached, create timestamp with available entries + let current_available = self.reader.available(); + if current_available == 0 { + debug!("No available entries, skipping this round..."); + return; + } + + debug!(current_available, "Timeout reached, creating timestamp"); + + // Determine the number of entries to take + let next_power_of_two = current_available.next_power_of_two(); + if next_power_of_two == current_available { + trace!("Current available is power of two, taking all"); + current_available + } else if next_power_of_two / 2 >= self.config.min_leaves { + let target = next_power_of_two / 2; + trace!(target, "Taking largest power of two less than available"); + target + } else { + trace!("Taking all available entries"); + current_available + } + } + _ = entries => { + // Max entries reached, create timestamp + debug!("Max entries reached, creating timestamp"); + self.config.max_entries_per_timestamp + } + }; + trace!(target_size); + + // Read entries, could need two reads if wrapping around + buffer.clear(); + buffer.extend_from_slice(self.reader.read(target_size)); + let remaining = target_size - buffer.len(); + if remaining > 0 { + buffer.extend_from_slice(self.reader.read(remaining)); + } + debug_assert_eq!(buffer.len(), target_size); + + let merkle_tree = FlatMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); + let storage = self.storage.clone(); + + let merkle_tree = tokio::task::spawn_blocking(move || { + let merkle_tree = merkle_tree.finalize(); // CPU intensive + let root = merkle_tree.root(); + info!(root = ?Hexed(root)); + + let mut batch = WriteBatch::default(); + batch.put(root, merkle_tree.as_raw_bytes()); + for leaf in merkle_tree.leaves() { + batch.put(leaf, root); + } + storage.write(batch).expect("Failed to write to storage"); // FIXME: handle error properly + merkle_tree + }) + .await + .expect("Failed to create Merkle tree"); // FIXME: handle error properly + + let root = B256::new(bytemuck::cast(*merkle_tree.root())); + + if self.cache.len() >= self.config.max_cache_size { + self.cache.pop_front(); + } + self.cache.push_back(merkle_tree); + + let tx_hash = self + .contract + .attest(root) + .send() + .await + .expect("failed to build transaction") + .watch() + .await + .expect("failed to send transaction"); // FIXME: handle error properly + info!(%tx_hash, %root,"Timestamp attested on-chain"); + + self.reader.commit(); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" From 90f6767cea5d3475076e754503824d1bc1ea1598 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sat, 21 Feb 2026 12:15:39 +0800 Subject: [PATCH 10/74] feat: verifier (#21) * embed abi * add Ethereum UTS tag * add Ethereum UTS tag * add verifier * refactor * deploy default instance * fmt --- Cargo.lock | 49 ++++-- Cargo.toml | 2 + crates/calendar/src/main.rs | 10 +- crates/contracts/Cargo.toml | 1 + crates/contracts/abi/ERC1967Proxy.json | 1 + .../contracts/abi/IUniversalTimestamps.json | 1 + crates/contracts/abi/UniversalTimestamps.json | 1 + crates/contracts/src/lib.rs | 20 +-- crates/core-wasm/src/lib.rs | 14 +- crates/core/Cargo.toml | 8 + crates/core/src/codec.rs | 39 +++++ crates/core/src/codec/imp.rs | 1 + crates/core/src/codec/imp/alloy.rs | 43 ++++++ crates/core/src/codec/v1.rs | 3 +- crates/core/src/codec/v1/attestation.rs | 141 +++++++++++++++++ crates/core/src/codec/v1/timestamp.rs | 4 +- crates/core/src/lib.rs | 2 + crates/core/src/verifier.rs | 65 ++++++++ crates/core/src/verifier/ethereum_uts.rs | 145 ++++++++++++++++++ foundry.toml | 11 ++ script/DeployCreate2.s.sol | 35 +++++ 21 files changed, 557 insertions(+), 39 deletions(-) create mode 100644 crates/contracts/abi/ERC1967Proxy.json create mode 100644 crates/contracts/abi/IUniversalTimestamps.json create mode 100644 crates/contracts/abi/UniversalTimestamps.json create mode 100644 crates/core/src/codec/imp/alloy.rs create mode 100644 crates/core/src/verifier.rs create mode 100644 crates/core/src/verifier/ethereum_uts.rs create mode 100644 script/DeployCreate2.s.sol diff --git a/Cargo.lock b/Cargo.lock index fa18842..d401d33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,14 +83,15 @@ checksum = "b163ff4acf0eac29af05a911397cc418a76e153467b859398adc26cb9335a611" dependencies = [ "alloy-primitives", "num_enum", + "serde", "strum", ] [[package]] name = "alloy-consensus" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3dcd2b4e208ce5477de90ccdcbd4bde2c8fb06af49a443974e92bb8f2c5e93f" +checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" dependencies = [ "alloy-eips", "alloy-primitives", @@ -115,9 +116,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee5655f234985f5ab1e31bef7e02ed11f0a899468cf3300e061e1b96e9e11de0" +checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" dependencies = [ "alloy-consensus", "alloy-eips", @@ -218,15 +219,28 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-eip7928" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3231de68d5d6e75332b7489cfcc7f4dfabeba94d990a10e4b923af0e6623540" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + [[package]] name = "alloy-eips" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6847d641141b92a1557094aa6c236cbe49c06fb24144d4a21fe6acb970c15888" +checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", @@ -324,9 +338,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a6cbb9f431bdad294eebb5af9b293d6979e633bfe5468d1e87c1421a858265" +checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" dependencies = [ "alloy-consensus", "alloy-eips", @@ -572,9 +586,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f9f130511b8632686dfe6f9909b38d7ae4c68de3ce17d28991400646a39b25" +checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -619,9 +633,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "067b718d2e6ac1bb889341fcc7a250cfa49bcd3ba4f23923f1c1eb1f2b10cb7c" +checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" dependencies = [ "alloy-primitives", "serde", @@ -828,9 +842,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.2.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04950a13cc4209d8e9b78f306e87782466bad8538c94324702d061ff03e211c9" +checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" dependencies = [ "darling", "proc-macro2", @@ -5771,6 +5785,7 @@ version = "0.1.0" dependencies = [ "alloy", "alloy-contract", + "alloy-primitives", "alloy-sol-types", "eyre", "futures", @@ -5781,6 +5796,11 @@ dependencies = [ name = "uts-core" version = "0.1.0" dependencies = [ + "alloy-chains", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", "auto_impl", "bytes", "criterion 0.5.1", @@ -5798,6 +5818,7 @@ dependencies = [ "sha3 0.11.0-rc.3", "thiserror 2.0.17", "tracing", + "uts-contracts", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 66edd47..48903c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,11 @@ unnecessary-debug-formatting = "warn" [workspace.dependencies] alloy = "1" +alloy-chains = "0.2" alloy-contract = "1.2" alloy-primitives = "1.5" alloy-provider = "1.2" +alloy-rpc-types-eth = "1.6" alloy-signer = "1.1" alloy-signer-local = "1.1" alloy-sol-types = "1.5" diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index a6431fb..0480225 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,6 +1,6 @@ //! Calendar server -use alloy_primitives::{address, b256}; +use alloy_primitives::b256; use alloy_provider::{ProviderBuilder, network::EthereumWallet}; use alloy_signer_local::{LocalSigner, MnemonicBuilder}; use axum::{ @@ -33,9 +33,6 @@ async fn main() -> eyre::Result<()> { // TODO: graceful shutdown let journal = Journal::with_capacity(RING_BUFFER_CAPACITY); - // ethereum provider - // Implementation deployed at: 0xf74254bf3c40b29259ce12bd35e74f40b0fda07d - // Proxy deployed at: 0x98c857e675e472cf2fd98c478ed4ecc4caf81fae let key = MnemonicBuilder::from_phrase(env::var("MNEMONIC")?.as_str()) .index(0u32)? .build()?; @@ -44,10 +41,7 @@ async fn main() -> eyre::Result<()> { .connect("https://0xrpc.io/sep") .await?; - let contract = UniversalTimestamps::new( - address!("0x98c857e675e472cf2fd98c478ed4ecc4caf81fae"), - provider.clone(), - ); + let contract = UniversalTimestamps::new(uts_contracts::uts::DEFAULT_ADDRESS, provider.clone()); // stamper let reader = journal.reader(); diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 67f2682..ac9d9ac 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] alloy-contract = { workspace = true } +alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true, features = ["json"] } [dev-dependencies] diff --git a/crates/contracts/abi/ERC1967Proxy.json b/crates/contracts/abi/ERC1967Proxy.json new file mode 100644 index 0000000..0650ce4 --- /dev/null +++ b/crates/contracts/abi/ERC1967Proxy.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"implementation","type":"address","internalType":"address"},{"name":"_data","type":"bytes","internalType":"bytes"}],"stateMutability":"payable"},{"type":"fallback","stateMutability":"payable"},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]}],"bytecode":{"object":"0x608060405260405161037338038061037383398101604081905261002291610219565b61002c8282610033565b50506102fa565b61003c82610091565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561008557610080828261010c565b505050565b61008d6101ad565b5050565b806001600160a01b03163b5f036100cb57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61011984846101ce565b905080801561013a57505f3d118061013a57505f846001600160a01b03163b115b1561014f576101476101e1565b9150506101a7565b801561017957604051639996b31560e01b81526001600160a01b03851660048201526024016100c2565b3d1561018c576101876101fa565b6101a5565b60405163d6bda27560e01b815260040160405180910390fd5b505b92915050565b34156101cc5760405163b398979f60e01b815260040160405180910390fd5b565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561022a575f80fd5b82516001600160a01b0381168114610240575f80fd5b602084810151919350906001600160401b038082111561025e575f80fd5b818601915086601f830112610271575f80fd5b81518181111561028357610283610205565b604051601f8201601f19908116603f011681019083821181831017156102ab576102ab610205565b8160405282815289868487010111156102c2575f80fd5b5f93505b828410156102e357848401860151818501870152928501926102c6565b5f8684830101528096505050505050509250929050565b606d806103065f395ff3fe6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;1081:133;;;;;;;;;;;;;;;;;;:::i;:::-;1155:52;1185:14;1201:5;1155:29;:52::i;:::-;1081:133;;600:1117;;2264:344:29;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;:::-;;2264:344;;:::o;2454:148::-;2573:18;:16;:18::i;:::-;2264:344;;:::o;1671:281::-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;1523:32:39;;1805:47:29;;;1505:51:39;1478:18;;1805:47:29;;;;;;;;1744:119;811:66;1872:73;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;1523:32:39;;5045:24:34;;;1505:51:39;1478:18;;5045:24:34;1359:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;6159:70;6113:122::o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:127:39;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:1208;234:6;242;295:2;283:9;274:7;270:23;266:32;263:52;;;311:1;308;301:12;263:52;337:16;;-1:-1:-1;;;;;382:31:39;;372:42;;362:70;;428:1;425;418:12;362:70;475:2;506:18;;;500:25;451:5;;-1:-1:-1;475:2:39;-1:-1:-1;;;;;574:14:39;;;571:34;;;601:1;598;591:12;571:34;639:6;628:9;624:22;614:32;;684:7;677:4;673:2;669:13;665:27;655:55;;706:1;703;696:12;655:55;735:2;729:9;757:2;753;750:10;747:36;;;763:18;;:::i;:::-;838:2;832:9;806:2;892:13;;-1:-1:-1;;888:22:39;;;912:2;884:31;880:40;868:53;;;936:18;;;956:22;;;933:46;930:72;;;982:18;;:::i;:::-;1022:10;1018:2;1011:22;1057:2;1049:6;1042:18;1097:7;1092:2;1087;1083;1079:11;1075:20;1072:33;1069:53;;;1118:1;1115;1108:12;1069:53;1140:1;1131:10;;1150:129;1164:2;1161:1;1158:9;1150:129;;;1252:10;;;1248:19;;1242:26;1221:14;;;1217:23;;1210:59;1175:10;;;;1150:129;;;1321:1;1316:2;1311;1303:6;1299:15;1295:24;1288:35;1342:6;1332:16;;;;;;;;146:1208;;;;;:::o;1359:203::-;600:1117:28;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;2676:11:30;:9;:11::i;:::-;600:1117:28;2350:83:30;2398:28;2408:17;:15;:17::i;:::-;2398:9;:28::i;:::-;2350:83::o;1583:132:28:-;1650:7;1676:32;811:66:29;1519:53;-1:-1:-1;;;;;1519:53:29;;1441:138;1676:32:28;1669:39;;1583:132;:::o;949:922:30:-;1293:14;1287:4;1281;1268:40;1513:4;1507;1491:14;1485:4;1469:14;1462:5;1449:69;1598:16;1592:4;1586;1571:44;1636:6;1703:69;;;;1824:16;1818:4;1811:30;1703:69;1741:16;1735:4;1728:30","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"}],\"devdoc\":{\"details\":\"This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an implementation address that can be changed. This address is stored in storage in the location specified by https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the implementation behind the proxy.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}]},\"events\":{\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":\"ERC1967Proxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":{\"keccak256\":\"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049\",\"dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol\":{\"keccak256\":\"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5\",\"dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"payable","type":"fallback"}],"devdoc":{"kind":"dev","methods":{"constructor":{"details":"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":"ERC1967Proxy"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":{"keccak256":"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e","urls":["bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049","dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol":{"keccak256":"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f","urls":["bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5","dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol","id":40451,"exportedSymbols":{"ERC1967Proxy":[40450],"ERC1967Utils":[40744],"Proxy":[40780]},"nodeType":"SourceUnit","src":"114:1604:28","nodes":[{"id":40414,"nodeType":"PragmaDirective","src":"114:24:28","nodes":[],"literals":["solidity","^","0.8",".22"]},{"id":40416,"nodeType":"ImportDirective","src":"140:35:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol","file":"../Proxy.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40781,"symbolAliases":[{"foreign":{"id":40415,"name":"Proxy","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40780,"src":"148:5:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40418,"nodeType":"ImportDirective","src":"176:48:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol","file":"./ERC1967Utils.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40745,"symbolAliases":[{"foreign":{"id":40417,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"184:12:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40450,"nodeType":"ContractDefinition","src":"600:1117:28","nodes":[{"id":40437,"nodeType":"FunctionDefinition","src":"1081:133:28","nodes":[],"body":{"id":40436,"nodeType":"Block","src":"1145:69:28","nodes":[],"statements":[{"expression":{"arguments":[{"id":40432,"name":"implementation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40424,"src":"1185:14:28","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":40433,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40426,"src":"1201:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":40429,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1155:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40431,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1168:16:28","memberName":"upgradeToAndCall","nodeType":"MemberAccess","referencedDeclaration":40559,"src":"1155:29:28","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory)"}},"id":40434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1155:52:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":40435,"nodeType":"ExpressionStatement","src":"1155:52:28"}]},"documentation":{"id":40422,"nodeType":"StructuredDocumentation","src":"637:439:28","text":" @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.\n If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an\n encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.\n Requirements:\n - If `data` is empty, `msg.value` must be zero."},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":40427,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40424,"mutability":"mutable","name":"implementation","nameLocation":"1101:14:28","nodeType":"VariableDeclaration","scope":40437,"src":"1093:22:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40423,"name":"address","nodeType":"ElementaryTypeName","src":"1093:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":40426,"mutability":"mutable","name":"_data","nameLocation":"1130:5:28","nodeType":"VariableDeclaration","scope":40437,"src":"1117:18:28","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":40425,"name":"bytes","nodeType":"ElementaryTypeName","src":"1117:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1092:44:28"},"returnParameters":{"id":40428,"nodeType":"ParameterList","parameters":[],"src":"1145:0:28"},"scope":40450,"stateMutability":"payable","virtual":false,"visibility":"public"},{"id":40449,"nodeType":"FunctionDefinition","src":"1583:132:28","nodes":[],"body":{"id":40448,"nodeType":"Block","src":"1659:56:28","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":40444,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1676:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40445,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1689:17:28","memberName":"getImplementation","nodeType":"MemberAccess","referencedDeclaration":40496,"src":"1676:30:28","typeDescriptions":{"typeIdentifier":"t_function_internal_view$__$returns$_t_address_$","typeString":"function () view returns (address)"}},"id":40446,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1676:32:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"functionReturnParameters":40443,"id":40447,"nodeType":"Return","src":"1669:39:28"}]},"baseFunctions":[40761],"documentation":{"id":40438,"nodeType":"StructuredDocumentation","src":"1220:358:28","text":" @dev Returns the current implementation address.\n TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using\n the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`"},"implemented":true,"kind":"function","modifiers":[],"name":"_implementation","nameLocation":"1592:15:28","overrides":{"id":40440,"nodeType":"OverrideSpecifier","overrides":[],"src":"1632:8:28"},"parameters":{"id":40439,"nodeType":"ParameterList","parameters":[],"src":"1607:2:28"},"returnParameters":{"id":40443,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40442,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":40449,"src":"1650:7:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40441,"name":"address","nodeType":"ElementaryTypeName","src":"1650:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1649:9:28"},"scope":40450,"stateMutability":"view","virtual":true,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":40420,"name":"Proxy","nameLocations":["625:5:28"],"nodeType":"IdentifierPath","referencedDeclaration":40780,"src":"625:5:28"},"id":40421,"nodeType":"InheritanceSpecifier","src":"625:5:28"}],"canonicalName":"ERC1967Proxy","contractDependencies":[],"contractKind":"contract","documentation":{"id":40419,"nodeType":"StructuredDocumentation","src":"226:373:28","text":" @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n implementation address that can be changed. This address is stored in storage in the location specified by\n https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the\n implementation behind the proxy."},"fullyImplemented":true,"linearizedBaseContracts":[40450,40780],"name":"ERC1967Proxy","nameLocation":"609:12:28","scope":40451,"usedErrors":[40470,40483,41236,41627],"usedEvents":[40389]}],"license":"MIT"},"id":28} \ No newline at end of file diff --git a/crates/contracts/abi/IUniversalTimestamps.json b/crates/contracts/abi/IUniversalTimestamps.json new file mode 100644 index 0000000..9a80366 --- /dev/null +++ b/crates/contracts/abi/IUniversalTimestamps.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"attest(bytes32)":"23c3617f","timestamp(bytes32)":"4d003070"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/IUniversalTimestamps.sol\":\"IUniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/IUniversalTimestamps.sol":"IUniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/IUniversalTimestamps.sol","id":187,"exportedSymbols":{"IUniversalTimestamps":[186]},"nodeType":"SourceUnit","src":"33:262:1","nodes":[{"id":165,"nodeType":"PragmaDirective","src":"33:24:1","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":186,"nodeType":"ContractDefinition","src":"59:235:1","nodes":[{"id":173,"nodeType":"EventDefinition","src":"96:80:1","nodes":[],"anonymous":false,"eventSelector":"61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a","name":"Attested","nameLocation":"102:8:1","parameters":{"id":172,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"indexed":true,"mutability":"mutable","name":"root","nameLocation":"127:4:1","nodeType":"VariableDeclaration","scope":173,"src":"111:20:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":166,"name":"bytes32","nodeType":"ElementaryTypeName","src":"111:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"},{"constant":false,"id":169,"indexed":true,"mutability":"mutable","name":"sender","nameLocation":"149:6:1","nodeType":"VariableDeclaration","scope":173,"src":"133:22:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":168,"name":"address","nodeType":"ElementaryTypeName","src":"133:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":171,"indexed":false,"mutability":"mutable","name":"timestamp","nameLocation":"165:9:1","nodeType":"VariableDeclaration","scope":173,"src":"157:17:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":170,"name":"uint256","nodeType":"ElementaryTypeName","src":"157:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"110:65:1"}},{"id":178,"nodeType":"FunctionDefinition","src":"182:39:1","nodes":[],"functionSelector":"23c3617f","implemented":false,"kind":"function","modifiers":[],"name":"attest","nameLocation":"191:6:1","parameters":{"id":176,"nodeType":"ParameterList","parameters":[{"constant":false,"id":175,"mutability":"mutable","name":"root","nameLocation":"206:4:1","nodeType":"VariableDeclaration","scope":178,"src":"198:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":174,"name":"bytes32","nodeType":"ElementaryTypeName","src":"198:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"197:14:1"},"returnParameters":{"id":177,"nodeType":"ParameterList","parameters":[],"src":"220:0:1"},"scope":186,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":185,"nodeType":"FunctionDefinition","src":"227:65:1","nodes":[],"functionSelector":"4d003070","implemented":false,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"236:9:1","parameters":{"id":181,"nodeType":"ParameterList","parameters":[{"constant":false,"id":180,"mutability":"mutable","name":"root","nameLocation":"254:4:1","nodeType":"VariableDeclaration","scope":185,"src":"246:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":179,"name":"bytes32","nodeType":"ElementaryTypeName","src":"246:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"245:14:1"},"returnParameters":{"id":184,"nodeType":"ParameterList","parameters":[{"constant":false,"id":183,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":185,"src":"283:7:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":182,"name":"uint256","nodeType":"ElementaryTypeName","src":"283:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"282:9:1"},"scope":186,"stateMutability":"view","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"IUniversalTimestamps","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[186],"name":"IUniversalTimestamps","nameLocation":"69:20:1","scope":187,"usedErrors":[],"usedEvents":[173]}],"license":"MIT"},"id":1} \ No newline at end of file diff --git a/crates/contracts/abi/UniversalTimestamps.json b/crates/contracts/abi/UniversalTimestamps.json new file mode 100644 index 0000000..1110a7d --- /dev/null +++ b/crates/contracts/abi/UniversalTimestamps.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"UPGRADE_INTERFACE_VERSION","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"initialize","inputs":[{"name":"initialOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"UUPSUnauthorizedCallContext","inputs":[]},{"type":"error","name":"UUPSUnsupportedProxiableUUID","inputs":[{"name":"slot","type":"bytes32","internalType":"bytes32"}]}],"bytecode":{"object":"0x60a060405230608052348015610013575f80fd5b5061001c610021565b6100d3565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff16156100715760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100d05780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b608051610b046100f95f395f81816104bb015281816104e401526106280152610b045ff3fe608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;1084:4:33;1041:48;;1489:53:2;;;;;;;;;-1:-1:-1;1513:22:2;:20;:22::i;:::-;1042:1794;;7709:422:32;3147:66;7898:15;;;;;;;7894:76;;;7936:23;;-1:-1:-1;;;7936:23:32;;;;;;;;;;;7894:76;7983:14;;-1:-1:-1;;;;;7983:14:32;;;:34;7979:146;;8033:33;;-1:-1:-1;;;;;;8033:33:32;-1:-1:-1;;;;;8033:33:32;;;;;8085:29;;158:50:39;;;8085:29:32;;146:2:39;131:18;8085:29:32;;;;;;;7979:146;7758:373;7709:422::o;14:200:39:-;1042:1794:2;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2140:354;;;;;;;;;;-1:-1:-1;2140:354:2;;;;;:::i;:::-;;:::i;:::-;;1896:138;;;;;;;;;;-1:-1:-1;1896:138:2;;;;;:::i;:::-;;:::i;:::-;;;345:25:39;;;333:2;318:18;1896:138:2;;;;;;;;3911:214:33;;;;;;:::i;:::-;;:::i;3466:126::-;;;;;;;;;;;;;:::i;3176:101:22:-;;;;;;;;;;;;;:::i;2462:144::-;;;;;;;;;;-1:-1:-1;1334:22:22;2591:8;2462:144;;-1:-1:-1;;;;;2591:8:22;;;2019:51:39;;2007:2;1992:18;2462:144:22;1873:203:39;1732:58:33;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1732:58:33;;;;;;;;;;;;:::i;1548:106:2:-;;;;;;;;;;-1:-1:-1;1548:106:2;;;;;:::i;:::-;;:::i;3426:215:22:-;;;;;;;;;;-1:-1:-1;3426:215:22;;;;;:::i;:::-;;:::i;2140:354:2:-;2197:4;2189:55;;;;-1:-1:-1;;;2189:55:2;;3027:2:39;2189:55:2;;;3009:21:39;3066:2;3046:18;;;3039:30;3105:26;3085:18;;;3078:54;3149:18;;2189:55:2;;;;;;;;;2255:36;2294:32;:30;:32::i;:::-;2340:12;:18;;;;;;;;;;;2255:71;;-1:-1:-1;2340:23:2;2336:152;;2379:12;:18;;;;;;;;;;;;2400:15;2379:36;;;;2434:43;;345:25:39;;;2449:10:2;;2379:18;;2434:43;;318:18:39;2434:43:2;;;;;;;2336:152;2179:315;2140:354;:::o;1896:138::-;1952:7;1978:32;:30;:32::i;:::-;:43;:49;;;;;-1:-1:-1;1978:49:2;;;;;1896:138::o;3911:214:33:-;2568:13;:11;:13::i;:::-;4026:36:::1;4044:17;4026;:36::i;:::-;4072:46;4094:17;4113:4;4072:21;:46::i;3466:126::-:0;3527:7;2839:20;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;;3466:126:33;:::o;3176:101:22:-;2355:13;:11;:13::i;:::-;3240:30:::1;3267:1;3240:18;:30::i;:::-;3176:101::o:0;1548:106:2:-;4158:30:32;4191:26;:24;:26::i;:::-;4302:15;;4158:59;;-1:-1:-1;4302:15:32;-1:-1:-1;;;4302:15:32;;;4301:16;;4348:14;;4279:19;4724:16;;:34;;;;;4744:14;4724:34;4704:54;;4768:17;4788:11;:16;;4803:1;4788:16;:50;;;;-1:-1:-1;4816:4:32;4808:25;:30;4788:50;4768:70;;4854:12;4853:13;:30;;;;;4871:12;4870:13;4853:30;4849:91;;;4906:23;;-1:-1:-1;;;4906:23:32;;;;;;;;;;;4849:91;4949:18;;-1:-1:-1;;4949:18:32;4966:1;4949:18;;;4977:67;;;;5011:22;;-1:-1:-1;;;;5011:22:32;-1:-1:-1;;;5011:22:32;;;4977:67;1619:28:2::1;1634:12;1619:14;:28::i;:::-;5068:14:32::0;5064:101;;;5098:23;;-1:-1:-1;;;;5098:23:32;;;5140:14;;-1:-1:-1;3331:50:39;;5140:14:32;;3319:2:39;3304:18;5140:14:32;;;;;;;5064:101;4092:1079;;;;;1548:106:2;:::o;3426:215:22:-;2355:13;:11;:13::i;:::-;-1:-1:-1;;;;;3510:22:22;::::1;3506:91;;3555:31;::::0;-1:-1:-1;;;3555:31:22;;3583:1:::1;3555:31;::::0;::::1;2019:51:39::0;1992:18;;3555:31:22::1;1873:203:39::0;3506:91:22::1;3606:28;3625:8;3606:18;:28::i;:::-;3426:215:::0;:::o;1660:230:2:-;1787:10;;;;;;;;;;;;;;;;;;1846:57:37;1724:36:2;1833:71:37;;;1724:36:2;1925:37:37;1787:24:2;1772:39;1660:230;-1:-1:-1;;1660:230:2:o;4328:312:33:-;4408:4;-1:-1:-1;;;;;4417:6:33;4400:23;;;:120;;;4514:6;-1:-1:-1;;;;;4478:42:33;:32;-1:-1:-1;;;;;;;;;;;1519:53:29;-1:-1:-1;;;;;1519:53:29;;1441:138;4478:32:33;-1:-1:-1;;;;;4478:42:33;;;4400:120;4383:251;;;4594:29;;-1:-1:-1;;;4594:29:33;;;;;;;;;;;2750:84:2;2355:13:22;:11;:13::i;5782:538:33:-;5899:17;-1:-1:-1;;;;;5881:50:33;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5881:52:33;;;;;;;;-1:-1:-1;;5881:52:33;;;;;;;;;;;;:::i;:::-;;;5877:437;;6243:60;;-1:-1:-1;;;6243:60:33;;-1:-1:-1;;;;;2037:32:39;;6243:60:33;;;2019:51:39;1992:18;;6243:60:33;1873:203:39;5877:437:33;-1:-1:-1;;;;;;;;;;;5975:40:33;;5971:120;;6042:34;;-1:-1:-1;;;6042:34:33;;;;;345:25:39;;;318:18;;6042:34:33;199:177:39;5971:120:33;6104:54;6134:17;6153:4;6104:29;:54::i;:::-;5934:235;5782:538;;:::o;4757:213::-;4831:4;-1:-1:-1;;;;;4840:6:33;4823:23;;4819:145;;4924:29;;-1:-1:-1;;;4924:29:33;;;;;;;;;;;2679:162:22;987:10:25;2738:7:22;1334:22;2591:8;-1:-1:-1;;;;;2591:8:22;;2462:144;2738:7;-1:-1:-1;;;;;2738:23:22;;2734:101;;2784:40;;-1:-1:-1;;;2784:40:22;;987:10:25;2784:40:22;;;2019:51:39;1992:18;;2784:40:22;1873:203:39;3795:248:22;1334:22;3944:8;;-1:-1:-1;;;;;;3962:19:22;;-1:-1:-1;;;;;3962:19:22;;;;;;;;3996:40;;3944:8;;;;;3996:40;;3868:24;;3996:40;3858:185;;3795:248;:::o;9071:205:32:-;9129:30;;3147:66;9186:27;8819:122;1868:127:22;6929:20:32;:18;:20::i;:::-;1950:38:22::1;1975:12;1950:24;:38::i;2264:344:29:-:0;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7082:141:32:-;7149:17;:15;:17::i;:::-;7144:73;;7189:17;;-1:-1:-1;;;7189:17:32;;;;;;;;;;;2001:235:22;6929:20:32;:18;:20::i;1671:281:29:-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;2037:32:39;;1805:47:29;;;2019:51:39;1992:18;;1805:47:29;1873:203:39;1744:119:29;-1:-1:-1;;;;;;;;;;;1872:73:29;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;2037:32:39;;5045:24:34;;;2019:51:39;1992:18;;5045:24:34;1873:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;8485:120:32;8535:4;8558:26;:24;:26::i;:::-;:40;-1:-1:-1;;;8558:40:32;;;;;;-1:-1:-1;8485:120:32:o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:180:39;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:39;;14:180;-1:-1:-1;14:180:39:o;381:173::-;449:20;;-1:-1:-1;;;;;498:31:39;;488:42;;478:70;;544:1;541;534:12;478:70;381:173;;;:::o;559:127::-;620:10;615:3;611:20;608:1;601:31;651:4;648:1;641:15;675:4;672:1;665:15;691:995;768:6;776;829:2;817:9;808:7;804:23;800:32;797:52;;;845:1;842;835:12;797:52;868:29;887:9;868:29;:::i;:::-;858:39;;948:2;937:9;933:18;920:32;971:18;1012:2;1004:6;1001:14;998:34;;;1028:1;1025;1018:12;998:34;1066:6;1055:9;1051:22;1041:32;;1111:7;1104:4;1100:2;1096:13;1092:27;1082:55;;1133:1;1130;1123:12;1082:55;1169:2;1156:16;1191:2;1187;1184:10;1181:36;;;1197:18;;:::i;:::-;1272:2;1266:9;1240:2;1326:13;;-1:-1:-1;;1322:22:39;;;1346:2;1318:31;1314:40;1302:53;;;1370:18;;;1390:22;;;1367:46;1364:72;;;1416:18;;:::i;:::-;1456:10;1452:2;1445:22;1491:2;1483:6;1476:18;1531:7;1526:2;1521;1517;1513:11;1509:20;1506:33;1503:53;;;1552:1;1549;1542:12;1503:53;1608:2;1603;1599;1595:11;1590:2;1582:6;1578:15;1565:46;1653:1;1648:2;1643;1635:6;1631:15;1627:24;1620:35;1674:6;1664:16;;;;;;;691:995;;;;;:::o;2081:548::-;2193:4;2222:2;2251;2240:9;2233:21;2283:6;2277:13;2326:6;2321:2;2310:9;2306:18;2299:34;2351:1;2361:140;2375:6;2372:1;2369:13;2361:140;;;2470:14;;;2466:23;;2460:30;2436:17;;;2455:2;2432:26;2425:66;2390:10;;2361:140;;;2365:3;2550:1;2545:2;2536:6;2525:9;2521:22;2517:31;2510:42;2620:2;2613;2609:7;2604:2;2596:6;2592:15;2588:29;2577:9;2573:45;2569:54;2561:62;;;;2081:548;;;;:::o;2634:186::-;2693:6;2746:2;2734:9;2725:7;2721:23;2717:32;2714:52;;;2762:1;2759;2752:12;2714:52;2785:29;2804:9;2785:29;:::i;:::-;2775:39;2634:186;-1:-1:-1;;;2634:186:39:o;3392:184::-;3462:6;3515:2;3503:9;3494:7;3490:23;3486:32;3483:52;;;3531:1;3528;3521:12;3483:52;-1:-1:-1;3554:16:39;;3392:184;-1:-1:-1;3392:184:39:o","linkReferences":{},"immutableReferences":{"41074":[{"start":1211,"length":32},{"start":1252,"length":32},{"start":1576,"length":32}]}},"methodIdentifiers":{"UPGRADE_INTERFACE_VERSION()":"ad3cb1cc","attest(bytes32)":"23c3617f","initialize(address)":"c4d66de8","owner()":"8da5cb5b","proxiableUUID()":"52d1902d","renounceOwnership()":"715018a6","timestamp(bytes32)":"4d003070","transferOwnership(address)":"f2fde38b","upgradeToAndCall(address,bytes)":"4f1ef286"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Records and exposes timestamps for attested Merkle roots using ERC-7201 namespaced storage (`uts.storage.UniversalTimestamps`) derived via {SlotDerivation}, and is implemented as a UUPS upgradeable contract via OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable base contracts. Storage is kept in a dedicated namespaced struct to remain layout-compatible across upgrades, while upgrades are authorized by the contract owner through {_authorizeUpgrade}.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"attest(bytes32)\":{\"params\":{\"root\":\"The Merkle Root to be attested\"}},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"}},\"title\":\"UniversalTimestamps\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"attest(bytes32)\":{\"notice\":\"Attest Merkle Root\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/UniversalTimestamps.sol\":\"UniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]},\"contracts/UniversalTimestamps.sol\":{\"keccak256\":\"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa\",\"dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed\",\"dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d\",\"dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e\",\"dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455\",\"dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422\",\"dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08\",\"dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34\",\"dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol\":{\"keccak256\":\"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337\",\"dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"OwnableInvalidOwner"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"type":"error","name":"OwnableUnauthorizedAccount"},{"inputs":[],"type":"error","name":"UUPSUnauthorizedCallContext"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"type":"error","name":"UUPSUnsupportedProxiableUUID"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"address","name":"initialOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"}],"devdoc":{"kind":"dev","methods":{"attest(bytes32)":{"params":{"root":"The Merkle Root to be attested"}},"constructor":{"custom:oz-upgrades-unsafe-allow":"constructor"},"owner()":{"details":"Returns the address of the current owner."},"proxiableUUID()":{"details":"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"renounceOwnership()":{"details":"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner."},"transferOwnership(address)":{"details":"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."}},"version":1},"userdoc":{"kind":"user","methods":{"attest(bytes32)":{"notice":"Attest Merkle Root"}},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/UniversalTimestamps.sol":"UniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"},"contracts/UniversalTimestamps.sol":{"keccak256":"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b","urls":["bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa","dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol":{"keccak256":"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14","urls":["bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed","dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16","urls":["bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d","dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf","urls":["bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e","dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d","urls":["bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455","dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b","urls":["bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422","dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol":{"keccak256":"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05","urls":["bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08","dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a","urls":["bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34","dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol":{"keccak256":"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f","urls":["bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337","dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/UniversalTimestamps.sol","id":327,"exportedSymbols":{"IUniversalTimestamps":[186],"Initializable":[41058],"OwnableUpgradeable":[40327],"SlotDerivation":[41925],"UUPSUpgradeable":[41224],"UniversalTimestamps":[326]},"nodeType":"SourceUnit","src":"33:2804:2","nodes":[{"id":188,"nodeType":"PragmaDirective","src":"33:24:2","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":190,"nodeType":"ImportDirective","src":"59:96:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40332,"symbolAliases":[{"foreign":{"id":189,"name":"Initializable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41058,"src":"67:13:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":192,"nodeType":"ImportDirective","src":"156:101:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40328,"symbolAliases":[{"foreign":{"id":191,"name":"OwnableUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40327,"src":"164:18:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":194,"nodeType":"ImportDirective","src":"258:100:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40336,"symbolAliases":[{"foreign":{"id":193,"name":"UUPSUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41224,"src":"266:15:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":196,"nodeType":"ImportDirective","src":"359:64:2","nodes":[],"absolutePath":"contracts/IUniversalTimestamps.sol","file":"./IUniversalTimestamps.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":187,"symbolAliases":[{"foreign":{"id":195,"name":"IUniversalTimestamps","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"367:20:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":198,"nodeType":"ImportDirective","src":"424:80:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol","file":"@openzeppelin/contracts/utils/SlotDerivation.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":41926,"symbolAliases":[{"foreign":{"id":197,"name":"SlotDerivation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41925,"src":"432:14:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":326,"nodeType":"ContractDefinition","src":"1042:1794:2","nodes":[{"id":210,"nodeType":"UsingForDirective","src":"1153:32:2","nodes":[],"global":false,"libraryName":{"id":208,"name":"SlotDerivation","nameLocations":["1159:14:2"],"nodeType":"IdentifierPath","referencedDeclaration":41925,"src":"1159:14:2"},"typeName":{"id":209,"name":"string","nodeType":"ElementaryTypeName","src":"1178:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}}},{"id":213,"nodeType":"VariableDeclaration","src":"1191:70:2","nodes":[],"constant":true,"mutability":"constant","name":"_NAMESPACE","nameLocation":"1215:10:2","scope":326,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":211,"name":"string","nodeType":"ElementaryTypeName","src":"1191:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"value":{"hexValue":"7574732e73746f726167652e556e6976657273616c54696d657374616d7073","id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1228:33:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517b","typeString":"literal_string \"uts.storage.UniversalTimestamps\""},"value":"uts.storage.UniversalTimestamps"},"visibility":"private"},{"id":219,"nodeType":"StructDefinition","src":"1341:89:2","nodes":[],"canonicalName":"UniversalTimestamps.UniversalTimestampsStorage","documentation":{"id":214,"nodeType":"StructuredDocumentation","src":"1268:68:2","text":"@custom:storage-location erc7201:uts.storage.UniversalTimestamps"},"members":[{"constant":false,"id":218,"mutability":"mutable","name":"timestamps","nameLocation":"1413:10:2","nodeType":"VariableDeclaration","scope":219,"src":"1385:38:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"typeName":{"id":217,"keyName":"","keyNameLocation":"-1:-1:-1","keyType":{"id":215,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1393:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"Mapping","src":"1385:27:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"valueName":"","valueNameLocation":"-1:-1:-1","valueType":{"id":216,"name":"uint256","nodeType":"ElementaryTypeName","src":"1404:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}},"visibility":"internal"}],"name":"UniversalTimestampsStorage","nameLocation":"1348:26:2","scope":326,"visibility":"public"},{"id":227,"nodeType":"FunctionDefinition","src":"1489:53:2","nodes":[],"body":{"id":226,"nodeType":"Block","src":"1503:39:2","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":223,"name":"_disableInitializers","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41012,"src":"1513:20:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$__$returns$__$","typeString":"function ()"}},"id":224,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1513:22:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":225,"nodeType":"ExpressionStatement","src":"1513:22:2"}]},"documentation":{"id":220,"nodeType":"StructuredDocumentation","src":"1436:48:2","text":"@custom:oz-upgrades-unsafe-allow constructor"},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":221,"nodeType":"ParameterList","parameters":[],"src":"1500:2:2"},"returnParameters":{"id":222,"nodeType":"ParameterList","parameters":[],"src":"1503:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":239,"nodeType":"FunctionDefinition","src":"1548:106:2","nodes":[],"body":{"id":238,"nodeType":"Block","src":"1609:45:2","nodes":[],"statements":[{"expression":{"arguments":[{"id":235,"name":"initialOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":229,"src":"1634:12:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":234,"name":"__Ownable_init","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40187,"src":"1619:14:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$returns$__$","typeString":"function (address)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1619:28:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":237,"nodeType":"ExpressionStatement","src":"1619:28:2"}]},"functionSelector":"c4d66de8","implemented":true,"kind":"function","modifiers":[{"id":232,"kind":"modifierInvocation","modifierName":{"id":231,"name":"initializer","nameLocations":["1597:11:2"],"nodeType":"IdentifierPath","referencedDeclaration":40898,"src":"1597:11:2"},"nodeType":"ModifierInvocation","src":"1597:11:2"}],"name":"initialize","nameLocation":"1557:10:2","parameters":{"id":230,"nodeType":"ParameterList","parameters":[{"constant":false,"id":229,"mutability":"mutable","name":"initialOwner","nameLocation":"1576:12:2","nodeType":"VariableDeclaration","scope":239,"src":"1568:20:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":228,"name":"address","nodeType":"ElementaryTypeName","src":"1568:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1567:22:2"},"returnParameters":{"id":233,"nodeType":"ParameterList","parameters":[],"src":"1609:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":253,"nodeType":"FunctionDefinition","src":"1660:230:2","nodes":[],"body":{"id":252,"nodeType":"Block","src":"1762:128:2","nodes":[],"statements":[{"assignments":[246],"declarations":[{"constant":false,"id":246,"mutability":"mutable","name":"slot","nameLocation":"1780:4:2","nodeType":"VariableDeclaration","scope":252,"src":"1772:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":245,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1772:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"id":250,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":247,"name":"_NAMESPACE","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"1787:10:2","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},"id":248,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1798:11:2","memberName":"erc7201Slot","nodeType":"MemberAccess","referencedDeclaration":41808,"src":"1787:22:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$_t_bytes32_$attached_to$_t_string_memory_ptr_$","typeString":"function (string memory) pure returns (bytes32)"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1787:24:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"VariableDeclarationStatement","src":"1772:39:2"},{"AST":{"nativeSrc":"1846:38:2","nodeType":"YulBlock","src":"1846:38:2","statements":[{"nativeSrc":"1860:14:2","nodeType":"YulAssignment","src":"1860:14:2","value":{"name":"slot","nativeSrc":"1870:4:2","nodeType":"YulIdentifier","src":"1870:4:2"},"variableNames":[{"name":"$.slot","nativeSrc":"1860:6:2","nodeType":"YulIdentifier","src":"1860:6:2"}]}]},"evmVersion":"cancun","externalReferences":[{"declaration":243,"isOffset":false,"isSlot":true,"src":"1860:6:2","suffix":"slot","valueSize":1},{"declaration":246,"isOffset":false,"isSlot":false,"src":"1870:4:2","valueSize":1}],"flags":["memory-safe"],"id":251,"nodeType":"InlineAssembly","src":"1821:63:2"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_getUniversalTimestampsStorage","nameLocation":"1669:30:2","parameters":{"id":240,"nodeType":"ParameterList","parameters":[],"src":"1699:2:2"},"returnParameters":{"id":244,"nodeType":"ParameterList","parameters":[{"constant":false,"id":243,"mutability":"mutable","name":"$","nameLocation":"1759:1:2","nodeType":"VariableDeclaration","scope":253,"src":"1724:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":242,"nodeType":"UserDefinedTypeName","pathNode":{"id":241,"name":"UniversalTimestampsStorage","nameLocations":["1724:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"1724:26:2"},"referencedDeclaration":219,"src":"1724:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"src":"1723:38:2"},"scope":326,"stateMutability":"pure","virtual":false,"visibility":"private"},{"id":267,"nodeType":"FunctionDefinition","src":"1896:138:2","nodes":[],"body":{"id":266,"nodeType":"Block","src":"1961:73:2","nodes":[],"statements":[{"expression":{"baseExpression":{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":260,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"1978:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":261,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1978:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":262,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2011:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"1978:43:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":264,"indexExpression":{"id":263,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":255,"src":"2022:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"1978:49:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":259,"id":265,"nodeType":"Return","src":"1971:56:2"}]},"baseFunctions":[185],"functionSelector":"4d003070","implemented":true,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"1905:9:2","parameters":{"id":256,"nodeType":"ParameterList","parameters":[{"constant":false,"id":255,"mutability":"mutable","name":"root","nameLocation":"1923:4:2","nodeType":"VariableDeclaration","scope":267,"src":"1915:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":254,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1915:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"1914:14:2"},"returnParameters":{"id":259,"nodeType":"ParameterList","parameters":[{"constant":false,"id":258,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":267,"src":"1952:7:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":257,"name":"uint256","nodeType":"ElementaryTypeName","src":"1952:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1951:9:2"},"scope":326,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":315,"nodeType":"FunctionDefinition","src":"2140:354:2","nodes":[],"body":{"id":314,"nodeType":"Block","src":"2179:315:2","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":274,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2197:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":277,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2213:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":276,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2205:7:2","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":275,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2205:7:2","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2205:10:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"src":"2197:18:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"5554533a20526f6f742063616e6e6f74206265207a65726f","id":280,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2217:26:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""},"value":"UTS: Root cannot be zero"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""}],"id":273,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"2189:7:2","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2189:55:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":282,"nodeType":"ExpressionStatement","src":"2189:55:2"},{"assignments":[285],"declarations":[{"constant":false,"id":285,"mutability":"mutable","name":"$","nameLocation":"2290:1:2","nodeType":"VariableDeclaration","scope":314,"src":"2255:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":284,"nodeType":"UserDefinedTypeName","pathNode":{"id":283,"name":"UniversalTimestampsStorage","nameLocations":["2255:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"2255:26:2"},"referencedDeclaration":219,"src":"2255:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"id":288,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"id":286,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"2294:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2294:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"nodeType":"VariableDeclarationStatement","src":"2255:71:2"},{"condition":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"baseExpression":{"expression":{"id":289,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2340:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":290,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2342:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2340:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":292,"indexExpression":{"id":291,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2353:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"2340:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":293,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2362:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"2340:23:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":313,"nodeType":"IfStatement","src":"2336:152:2","trueBody":{"id":312,"nodeType":"Block","src":"2365:123:2","statements":[{"expression":{"id":302,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"baseExpression":{"expression":{"id":295,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2379:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":298,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2381:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2379:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":299,"indexExpression":{"id":297,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2392:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"nodeType":"IndexAccess","src":"2379:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"expression":{"id":300,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2400:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2406:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2400:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"2379:36:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":303,"nodeType":"ExpressionStatement","src":"2379:36:2"},{"eventCall":{"arguments":[{"id":305,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2443:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},{"expression":{"id":306,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"2449:3:2","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2453:6:2","memberName":"sender","nodeType":"MemberAccess","src":"2449:10:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"expression":{"id":308,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2461:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":309,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2467:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2461:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":304,"name":"Attested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2434:8:2","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes32_$_t_address_$_t_uint256_$returns$__$","typeString":"function (bytes32,address,uint256)"}},"id":310,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2434:43:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":311,"nodeType":"EmitStatement","src":"2429:48:2"}]}}]},"baseFunctions":[178],"documentation":{"id":268,"nodeType":"StructuredDocumentation","src":"2040:95:2","text":" @notice Attest Merkle Root\n @param root The Merkle Root to be attested"},"functionSelector":"23c3617f","implemented":true,"kind":"function","modifiers":[],"name":"attest","nameLocation":"2149:6:2","parameters":{"id":271,"nodeType":"ParameterList","parameters":[{"constant":false,"id":270,"mutability":"mutable","name":"root","nameLocation":"2164:4:2","nodeType":"VariableDeclaration","scope":315,"src":"2156:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":269,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2156:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"2155:14:2"},"returnParameters":{"id":272,"nodeType":"ParameterList","parameters":[],"src":"2179:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":325,"nodeType":"FunctionDefinition","src":"2750:84:2","nodes":[],"body":{"id":324,"nodeType":"Block","src":"2832:2:2","nodes":[],"statements":[]},"baseFunctions":[41178],"documentation":{"id":316,"nodeType":"StructuredDocumentation","src":"2500:245:2","text":" @dev Authorizes an upgrade to `newImplementation`.\n This function is restricted to the contract owner via the {onlyOwner} modifier,\n ensuring that only the owner can authorize upgrades to the implementation."},"implemented":true,"kind":"function","modifiers":[{"id":322,"kind":"modifierInvocation","modifierName":{"id":321,"name":"onlyOwner","nameLocations":["2822:9:2"],"nodeType":"IdentifierPath","referencedDeclaration":40222,"src":"2822:9:2"},"nodeType":"ModifierInvocation","src":"2822:9:2"}],"name":"_authorizeUpgrade","nameLocation":"2759:17:2","overrides":{"id":320,"nodeType":"OverrideSpecifier","overrides":[],"src":"2813:8:2"},"parameters":{"id":319,"nodeType":"ParameterList","parameters":[{"constant":false,"id":318,"mutability":"mutable","name":"newImplementation","nameLocation":"2785:17:2","nodeType":"VariableDeclaration","scope":325,"src":"2777:25:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":317,"name":"address","nodeType":"ElementaryTypeName","src":"2777:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2776:27:2"},"returnParameters":{"id":323,"nodeType":"ParameterList","parameters":[],"src":"2832:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":200,"name":"Initializable","nameLocations":["1074:13:2"],"nodeType":"IdentifierPath","referencedDeclaration":41058,"src":"1074:13:2"},"id":201,"nodeType":"InheritanceSpecifier","src":"1074:13:2"},{"baseName":{"id":202,"name":"OwnableUpgradeable","nameLocations":["1089:18:2"],"nodeType":"IdentifierPath","referencedDeclaration":40327,"src":"1089:18:2"},"id":203,"nodeType":"InheritanceSpecifier","src":"1089:18:2"},{"baseName":{"id":204,"name":"UUPSUpgradeable","nameLocations":["1109:15:2"],"nodeType":"IdentifierPath","referencedDeclaration":41224,"src":"1109:15:2"},"id":205,"nodeType":"InheritanceSpecifier","src":"1109:15:2"},{"baseName":{"id":206,"name":"IUniversalTimestamps","nameLocations":["1126:20:2"],"nodeType":"IdentifierPath","referencedDeclaration":186,"src":"1126:20:2"},"id":207,"nodeType":"InheritanceSpecifier","src":"1126:20:2"}],"canonicalName":"UniversalTimestamps","contractDependencies":[],"contractKind":"contract","documentation":{"id":199,"nodeType":"StructuredDocumentation","src":"506:535:2","text":" @title UniversalTimestamps\n @dev Records and exposes timestamps for attested Merkle roots using ERC-7201\n namespaced storage (`uts.storage.UniversalTimestamps`) derived via\n {SlotDerivation}, and is implemented as a UUPS upgradeable contract via\n OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable\n base contracts. Storage is kept in a dedicated namespaced struct to remain\n layout-compatible across upgrades, while upgrades are authorized by the\n contract owner through {_authorizeUpgrade}."},"fullyImplemented":true,"linearizedBaseContracts":[326,186,41224,40412,40327,40381,41058],"name":"UniversalTimestamps","nameLocation":"1051:19:2","scope":327,"usedErrors":[40163,40168,40470,40483,40807,40810,41081,41086,41236,41627],"usedEvents":[173,40174,40389,40815]}],"license":"MIT"},"id":2} \ No newline at end of file diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 54e43a3..f955e44 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -2,6 +2,8 @@ /// UniversalTimestamps contract pub mod uts { + use alloy_primitives::{Address, address}; + #[doc(hidden)] pub mod binding { use alloy_sol_types::sol; @@ -9,18 +11,12 @@ pub mod uts { sol!( #[sol(rpc, all_derives)] IUniversalTimestamps, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../target/foundry/IUniversalTimestamps.sol/IUniversalTimestamps.json" - ) + "abi/IUniversalTimestamps.json" ); sol!( #[sol(rpc)] UniversalTimestamps, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../target/foundry/UniversalTimestamps.sol/UniversalTimestamps.json" - ) + "abi/UniversalTimestamps.json" ); } @@ -30,6 +26,9 @@ pub mod uts { pub use binding::UniversalTimestamps::{BYTECODE, DEPLOYED_BYTECODE, deploy, deploy_builder}; + /// Default address for the UniversalTimestamps contract. + pub const DEFAULT_ADDRESS: Address = address!("0xceB7a9E77bd00D0391349B9bC989167cAB5e35e7"); + #[cfg(test)] mod tests { use super::*; @@ -100,10 +99,7 @@ pub mod erc1967 { sol!( #[sol(rpc)] ERC1967Proxy, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../target/foundry/ERC1967Proxy.sol/ERC1967Proxy.json" - ) + "abi/ERC1967Proxy.json" ); } diff --git a/crates/core-wasm/src/lib.rs b/crates/core-wasm/src/lib.rs index 6472495..73d97b3 100644 --- a/crates/core-wasm/src/lib.rs +++ b/crates/core-wasm/src/lib.rs @@ -6,8 +6,8 @@ use serde_with::{hex::Hex, serde_as}; use uts_core::codec::{ Decode, Encode, v1::{ - Attestation, BitcoinAttestation, DetachedTimestamp, DigestHeader, MayHaveInput, - PendingAttestation, Timestamp, opcode::OpCode, + Attestation, BitcoinAttestation, DetachedTimestamp, DigestHeader, EthereumUTSAttestation, + MayHaveInput, PendingAttestation, Timestamp, opcode::OpCode, }, }; use wasm_bindgen::prelude::*; @@ -89,6 +89,10 @@ fn serialize_chain(mut current_node: &Timestamp) -> Value { Bitcoin { height: u32, }, + EthereumUTS { + chain: u64, + height: u64, + }, Unknown { #[serde_as(as = "Hex")] tag: Vec, @@ -108,6 +112,12 @@ fn serialize_chain(mut current_node: &Timestamp) -> Value { } 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(), diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index b57f971..619bdb9 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -15,6 +15,11 @@ path = "src/bin/uts_info.rs" required-features = ["std"] [dependencies] +alloy-chains = { workspace = true } +alloy-primitives = { workspace = true } +alloy-provider = { workspace = true, optional = true } +alloy-rpc-types-eth = { workspace = true, optional = true } +alloy-sol-types = { workspace = true, optional = true } auto_impl.workspace = true bytes = { workspace = true, optional = true } digest.workspace = true @@ -29,13 +34,16 @@ sha2.workspace = true sha3.workspace = true thiserror.workspace = true tracing = { workspace = true, optional = true } +uts-contracts = { workspace = true, optional = true } [features] bytes = ["dep:bytes"] default = ["std"] +ethereum-uts-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-rpc-types-eth", "dep:alloy-sol-types", "dep:uts-contracts"] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] std = [] tracing = ["dep:tracing"] +verifier = [] [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/crates/core/src/codec.rs b/crates/core/src/codec.rs index ebc2ff5..acc83ed 100644 --- a/crates/core/src/codec.rs +++ b/crates/core/src/codec.rs @@ -77,6 +77,13 @@ pub trait Decoder: Sized { T::decode(self) } + /// Decodes a trailing optional value implementing the [`Decode`] trait. + /// + /// See [`Decode::decode_trailing`] for details and caveats. + fn decode_trailing(&mut self) -> Result, DecodeError> { + T::decode_trailing(self) + } + /// Decodes a value implementing the [`Decode`] trait. fn decode_in, A: Allocator>(&mut self, alloc: A) -> Result { T::decode_in(self, alloc) @@ -95,15 +102,47 @@ pub trait Encode { /// Deserializes a value from an OpenTimestamps-compatible byte stream. pub trait Decode: Sized { fn decode(decoder: &mut impl Decoder) -> Result; + + /// Decodes a trailing optional value implementing the [`Decode`] trait. + /// + /// This treats any `UnexpectedEof` error as an indication that the value is absent, returning `Ok(None)`. + /// + /// If the implementor returns `UnexpectedEof` for any reason other than the absence of the value, + /// it should also override this method to avoid masking the error as `Ok(None)`. + fn decode_trailing(decoder: &mut impl Decoder) -> Result, DecodeError> { + match Self::decode(decoder) { + Ok(value) => Ok(Some(value)), + Err(DecodeError::UnexpectedEof) => Ok(None), + Err(e) => Err(e), + } + } } /// Deserializes a value from an OpenTimestamps-compatible byte stream. pub trait DecodeIn: Sized { + /// See [`Decode::decode`] for details. fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result; + + /// See [`Decode::decode_trailing`] for details and caveats. + fn decode_trailing(decoder: &mut impl Decoder, alloc: A) -> Result, DecodeError> { + match Self::decode_in(decoder, alloc) { + Ok(value) => Ok(Some(value)), + Err(DecodeError::UnexpectedEof) => Ok(None), + Err(e) => Err(e), + } + } } impl> Decode for T { fn decode(decoder: &mut impl Decoder) -> Result { T::decode_in(decoder, Global) } + + fn decode_trailing(decoder: &mut impl Decoder) -> Result, DecodeError> { + match Self::decode_in(decoder, Global) { + Ok(value) => Ok(Some(value)), + Err(DecodeError::UnexpectedEof) => Ok(None), + Err(e) => Err(e), + } + } } diff --git a/crates/core/src/codec/imp.rs b/crates/core/src/codec/imp.rs index 397da10..d393fd9 100644 --- a/crates/core/src/codec/imp.rs +++ b/crates/core/src/codec/imp.rs @@ -1,6 +1,7 @@ use crate::codec::*; use alloc::vec::Vec; +mod alloy; #[cfg(feature = "bytes")] mod bytes; mod primitives; diff --git a/crates/core/src/codec/imp/alloy.rs b/crates/core/src/codec/imp/alloy.rs new file mode 100644 index 0000000..e91d6b5 --- /dev/null +++ b/crates/core/src/codec/imp/alloy.rs @@ -0,0 +1,43 @@ +use crate::codec::{Decode, DecodeError, Decoder, Encode, EncodeError, Encoder}; +use alloy_chains::Chain; +use alloy_primitives::{Address, ChainId, FixedBytes}; + +impl Encode for FixedBytes { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.write_all(self) + } +} + +impl Decode for FixedBytes { + fn decode(decoder: &mut impl Decoder) -> Result { + let mut buf = [0u8; N]; + decoder.read_exact(&mut buf)?; + Ok(Self::new(buf)) + } +} + +impl Encode for Address { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + encoder.write_all(self.0) + } +} + +impl Decode for Address { + fn decode(decoder: &mut impl Decoder) -> Result { + let inner: FixedBytes<20> = decoder.decode()?; + Ok(Self::from(inner)) + } +} + +impl Encode for Chain { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + self.id().encode(encoder) + } +} + +impl Decode for Chain { + fn decode(decoder: &mut impl Decoder) -> Result { + let id: ChainId = decoder.decode()?; + Ok(Chain::from_id(id)) + } +} diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index 4ff5925..13dae63 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -7,7 +7,8 @@ pub mod opcode; mod timestamp; pub use attestation::{ - Attestation, AttestationTag, BitcoinAttestation, PendingAttestation, RawAttestation, + Attestation, AttestationTag, BitcoinAttestation, EthereumUTSAttestation, PendingAttestation, + RawAttestation, }; pub use detached_timestamp::DetachedTimestamp; pub use digest::DigestHeader; diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index d4695f7..8aae820 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -13,6 +13,8 @@ use alloc::{ borrow::Cow, vec::Vec, }; +use alloy_chains::Chain; +use alloy_primitives::{Address, BlockNumber, ChainId, TxHash}; use core::fmt; /// Size in bytes of the tag identifying the attestation type. @@ -22,6 +24,10 @@ const TAG_SIZE: usize = 8; const BITCOIN_TAG: &[u8; 8] = b"\x05\x88\x96\x0d\x73\xd7\x19\x01"; /// Tag indicating a pending attestation. const PENDING_TAG: &[u8; 8] = b"\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e"; +/// Tag indicating an Ethereum UTS contract attestation. +/// +/// TAG = keccak256("EthereumUTSAttestation")[:8] +const ETHEREUM_UTS_TAG: &[u8; 8] = b"\xea\xf2\xbc\x69\x3c\x93\x25\x1c"; /// Tag identifying the attestation kind. pub type AttestationTag = [u8; TAG_SIZE]; @@ -139,6 +145,126 @@ impl Attestation<'_> for BitcoinAttestation { } } +/// Attestation by an Ethereum UTS contract. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EthereumUTSAttestation { + pub chain: Chain, + pub height: BlockNumber, + /// Optional extra metadata about the attestation, such as the contract address and transaction hash. + pub metadata: EthereumUTSAttestationExtraMetadata, +} + +/// Extra metadata for an Ethereum UTS attestation. +/// +/// The tx field is only present if the contract field is present, +/// and should be ignored if the contract field is None. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct EthereumUTSAttestationExtraMetadata { + contract: Option

, + tx: Option, +} + +impl fmt::Display for EthereumUTSAttestation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "UTS on chain {} at block #{}({})", + self.chain, self.height, self.metadata + ) + } +} + +impl Attestation<'_> for EthereumUTSAttestation { + const TAG: AttestationTag = *ETHEREUM_UTS_TAG; + + fn from_raw_data(data: &[u8]) -> Result { + let data = &mut &data[..]; + let chain = Chain::decode(data)?; + let height = BlockNumber::decode(data)?; + let metadata = EthereumUTSAttestationExtraMetadata::decode(data)?; + Ok(EthereumUTSAttestation { + chain, + height, + metadata, + }) + } + + fn to_raw_data_in(&self, alloc: A) -> Result, EncodeError> { + // chain id + block number + optional address + optional tx hash + const SIZE: usize = size_of::() + size_of::() + 20 + 32; + let mut buffer = Vec::with_capacity_in(20 + 32 + 32, alloc); + buffer.encode(self.chain)?; + buffer.encode(self.height)?; + buffer.encode(&self.metadata)?; + Ok(buffer) + } +} + +impl Encode for EthereumUTSAttestationExtraMetadata { + fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { + if let Some(contract) = self.contract { + encoder.encode(&contract)?; + if let Some(tx) = self.tx { + encoder.encode(&tx)?; + } + } + Ok(()) + } +} + +impl Decode for EthereumUTSAttestationExtraMetadata { + fn decode(decoder: &mut impl Decoder) -> Result { + let contract = Address::decode_trailing(decoder)?; + let tx = if contract.is_some() { + TxHash::decode_trailing(decoder)? + } else { + None + }; + Ok(Self { contract, tx }) + } +} + +impl fmt::Display for EthereumUTSAttestationExtraMetadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match (self.contract, self.tx) { + (Some(contract), Some(tx)) => write!(f, "{contract} by tx: {tx}"), + (Some(contract), None) => write!(f, "{contract}"), + (None, Some(_)) => unreachable!("Tx should not be present without contract"), + (None, None) => write!(f, "no extra metadata"), + } + } +} + +impl EthereumUTSAttestationExtraMetadata { + /// Creates new extra metadata with the given contract address and no transaction hash. + pub fn new(contract: Address) -> Self { + Self { + contract: Some(contract), + tx: None, + } + } + + /// Creates new extra metadata with the given contract address and transaction hash. + pub fn new_with_tx(contract: Address, tx: TxHash) -> Self { + Self { + contract: Some(contract), + tx: Some(tx), + } + } + + /// Returns the contract address if present, or None if not. + #[inline] + pub fn contract(&self) -> Option
{ + self.contract + } + + /// Returns the transaction hash if present, or None if not. + #[inline] + pub fn tx(&self) -> Option { + self.tx + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct PendingAttestation<'a> { pub uri: Cow<'a, str>, @@ -218,3 +344,18 @@ impl MayHaveInput for RawAttestation { self.value.get().map(|v| v.as_slice()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ethereum_uts_tag() { + use sha3::{Digest, Keccak256}; + + let mut hasher = Keccak256::new(); + hasher.update(b"EthereumUTSAttestation"); + let result = hasher.finalize().to_vec(); + assert_eq!(&result[..8], ETHEREUM_UTS_TAG); + } +} diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 7d3f7d7..2e5b459 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -193,14 +193,14 @@ impl Timestamp { OpCode::FORK => { debug_assert!(step.next.len() >= 2, "FORK must have at least two children"); for child in &step.next { - child.finalize(input); + child.try_finalize(input)?; } } OpCode::ATTESTATION => unreachable!("should not happen"), op => { let output = op.execute_in(input, &step.data, step.allocator().clone()); debug_assert!(step.next.len() == 1, "non-FORK must have exactly one child"); - step.next[0].finalize(&output); + step.next[0].try_finalize(&output)?; } } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index e524fb4..a5fe5f4 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -16,3 +16,5 @@ pub mod codec; /// Error types raised by codec operations. pub mod error; pub mod utils; +#[cfg(feature = "verifier")] +pub mod verifier; diff --git a/crates/core/src/verifier.rs b/crates/core/src/verifier.rs new file mode 100644 index 0000000..500ad52 --- /dev/null +++ b/crates/core/src/verifier.rs @@ -0,0 +1,65 @@ +use crate::{ + codec::v1::{Attestation, PendingAttestation, RawAttestation}, + error::DecodeError, +}; + +#[cfg(feature = "ethereum-uts-verifier")] +mod ethereum_uts; +#[cfg(feature = "ethereum-uts-verifier")] +pub use ethereum_uts::EthereumUTSVerifier; + +#[derive(Debug, thiserror::Error)] +pub enum VerifyError { + /// The raw attestation lacks a value, so it cannot be verified. + #[error("raw attestation lacks a value")] + NoValue, + /// The attestation is still pending and cannot be verified yet. + #[error("attestation is still pending and cannot be verified yet")] + Pending, + /// The attestation is not the expected type + /// (e.g. a Bitcoin attestation was expected but an Ethereum attestation was found). + #[error("attestation is not the expected type")] + BadAttestationTag, + /// An error occurred while decoding the attestation. + #[error("error decoding attestation: {0}")] + Decode(DecodeError), + /// An error occurred while verifying the ethereum uts attestation. + #[cfg(feature = "ethereum-uts-verifier")] + #[error("error verifying ethereum uts attestation: {0}")] + EthereumUTS(#[from] ethereum_uts::EthereumUTSVerifierError), +} + +pub trait AttestationVerifier

+where + P: for<'a> Attestation<'a> + Send, + Self: Send + Sync, +{ + type Output; + + fn verify_raw( + &self, + raw: &RawAttestation, + ) -> impl Future> + Send { + async { + if raw.tag == PendingAttestation::TAG { + return Err(VerifyError::Pending); + } + + let Some(value) = raw.value.get() else { + return Err(VerifyError::NoValue); + }; + + match P::from_raw(raw) { + Ok(attestation) => self.verify(&attestation, value).await, + Err(DecodeError::BadAttestationTag) => Err(VerifyError::BadAttestationTag), + Err(e) => Err(VerifyError::Decode(e)), + } + } + } + + fn verify( + &self, + attestation: &P, + value: &[u8], + ) -> impl Future> + Send; +} diff --git a/crates/core/src/verifier/ethereum_uts.rs b/crates/core/src/verifier/ethereum_uts.rs new file mode 100644 index 0000000..ec0d6ca --- /dev/null +++ b/crates/core/src/verifier/ethereum_uts.rs @@ -0,0 +1,145 @@ +use super::{AttestationVerifier, VerifyError}; +use crate::codec::v1::EthereumUTSAttestation; +use alloy_primitives::{Address, ChainId, TxHash}; +use alloy_provider::{Provider, transport::TransportError}; +use alloy_rpc_types_eth::{Filter, Log}; +use alloy_sol_types::SolEvent; +use digest::OutputSizeUser; +use sha3::Keccak256; +use uts_contracts::uts::Attested; + +#[derive(Debug, Clone)] +pub struct EthereumUTSVerifier { + provider: P, + chain_id: ChainId, +} + +#[derive(Debug, thiserror::Error)] +pub enum EthereumUTSVerifierError { + #[error("invalid value length for Ethereum UTS attestation")] + InvalidLength, + #[error("chain ID mismatch")] + ChainIdMismatch, + #[error("root not found in attested logs")] + NotFound, + #[error("contract address mismatch, expected {expected}, found {found}")] + ContractMismatch { expected: Address, found: Address }, + #[error("transaction hash mismatch, expected {expected}, found {found}")] + TransactionMismatch { expected: TxHash, found: TxHash }, + #[error(transparent)] + Rpc(#[from] TransportError), +} + +impl EthereumUTSVerifier

{ + pub async fn new(provider: P) -> Result { + let chain_id = provider.get_chain_id().await?; + Ok(Self { provider, chain_id }) + } +} + +impl AttestationVerifier for EthereumUTSVerifier

{ + type Output = Log; + + async fn verify( + &self, + attestation: &EthereumUTSAttestation, + value: &[u8], + ) -> Result { + Ok(self.verify_attestation(attestation, value).await?) + } +} + +impl EthereumUTSVerifier

{ + async fn verify_attestation( + &self, + attestation: &EthereumUTSAttestation, + value: &[u8], + ) -> Result, EthereumUTSVerifierError> { + if value.len() != Keccak256::output_size() { + return Err(EthereumUTSVerifierError::InvalidLength); + } + if attestation.chain.id() != self.chain_id { + return Err(EthereumUTSVerifierError::ChainIdMismatch); + } + + let filter = Filter::new() + .from_block(attestation.height) + .to_block(attestation.height) + .event_signature(Attested::SIGNATURE_HASH); + let logs = self.provider.get_logs(&filter).await?; + + let Some(log) = logs + .into_iter() + .filter_map(|log| { + Attested::decode_log(&log.inner) + .map(|inner| Log { + inner, + block_hash: log.block_hash, + block_number: log.block_number, + block_timestamp: log.block_timestamp, + transaction_hash: log.transaction_hash, + transaction_index: log.transaction_index, + log_index: log.log_index, + removed: log.removed, + }) + .ok() + }) + .find(|log| log.inner.data.root == value) + else { + return Err(EthereumUTSVerifierError::NotFound); + }; + + // perform additional checks if available + if let Some(contract) = attestation.metadata.contract() { + if log.inner.address != contract { + return Err(EthereumUTSVerifierError::ContractMismatch { + expected: contract, + found: log.inner.address, + }); + } + if let Some(expect_tx) = attestation.metadata.tx() + && let Some(found_tx) = log.transaction_hash + && expect_tx != found_tx + { + return Err(EthereumUTSVerifierError::TransactionMismatch { + expected: expect_tx, + found: found_tx, + }); + } + } + Ok(log) + } +} + +impl EthereumUTSVerifierError { + /// The error indicates this attestation is invalid and cannot be verified. + #[inline] + pub fn is_fatal(&self) -> bool { + matches!( + self, + EthereumUTSVerifierError::InvalidLength | EthereumUTSVerifierError::NotFound + ) + } + + /// The error indicates this attestation is valid but not attested by the expected contract or transaction. + #[inline] + pub fn is_mismatch(&self) -> bool { + matches!( + self, + EthereumUTSVerifierError::ContractMismatch { .. } + | EthereumUTSVerifierError::TransactionMismatch { .. } + ) + } + + /// The error indicates this attestation may be valid but cannot be verified at the moment. + #[inline] + pub fn should_retry(&self) -> bool { + matches!(self, EthereumUTSVerifierError::Rpc(_)) + } + + /// The error indicates this attestation may be valid but the provider is not suitable for verifying it. + #[inline] + pub fn is_wrong_provider(&self) -> bool { + matches!(self, EthereumUTSVerifierError::ChainIdMismatch) + } +} diff --git a/foundry.toml b/foundry.toml index 46135a3..eed2c8e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,3 +13,14 @@ script = "contract-scripts" solc_version = "0.8.24" src = "contracts" test = "contract-tests" + +# Set EVM version explicitly (must match across environments) +evm_version = "cancun" + +# Disable metadata hash for deterministic bytecode +bytecode_hash = "none" +cbor_metadata = false + +# If using optimizer, keep settings consistent +optimizer = true +optimizer_runs = 200 diff --git a/script/DeployCreate2.s.sol b/script/DeployCreate2.s.sol new file mode 100644 index 0000000..07a73ab --- /dev/null +++ b/script/DeployCreate2.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {UniversalTimestamps} from "../contracts/UniversalTimestamps.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +interface ICreateX { + function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address); + + function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address); +} + +contract DeployCreate2 is Script { + // CreateX is deployed at the same address on all supported chains + ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + bytes32 constant SALT = keccak256("universal-timestamps"); + + function run() public { + address owner = vm.envAddress("OWNER_ADDRESS"); + + vm.startBroadcast(); + UniversalTimestamps implementation = new UniversalTimestamps{salt: SALT}(); + // Implementation deployed at: 0x2D806e4ae1c3FDCfecb019B192a53371CAC889A7 + console.log("Implementation deployed at:", address(implementation)); + + bytes memory initData = abi.encodeCall(UniversalTimestamps.initialize, (owner)); + + ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); + vm.stopBroadcast(); + + // Proxy deployed at: 0xceB7a9E77bd00D0391349B9bC989167cAB5e35e7 + console.log("Proxy deployed at:", address(proxy)); + } +} From c71e0f21bfe5f8300963acb92a683a07ef0f030d Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sat, 21 Feb 2026 14:16:04 +0800 Subject: [PATCH 11/74] feat: support upgrade (#22) --- Cargo.lock | 35 ++++++ Cargo.toml | 1 + crates/calendar/Cargo.toml | 2 + crates/calendar/src/lib.rs | 4 + crates/calendar/src/main.rs | 25 ++-- crates/calendar/src/routes/ots.rs | 68 ++++++++++- crates/core/src/codec/v1/attestation.rs | 26 +++- crates/stamper/Cargo.toml | 5 +- crates/stamper/src/lib.rs | 151 +++++++++++++++++++++--- 9 files changed, 278 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d401d33..aac81b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,6 +1295,30 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcode" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6ed1b54d8dc333e7be604d00fa9262f4635485ffea923647b6521a5fff045d" +dependencies = [ + "arrayvec", + "bitcode_derive", + "bytemuck", + "glam", + "serde", +] + +[[package]] +name = "bitcode_derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238b90427dfad9da4a9abd60f3ec1cdee6b80454bde49ed37f1781dd8e9dc7f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "bitcoin-io" version = "0.1.4" @@ -2705,6 +2729,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glam" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34627c5158214743a374170fed714833fdf4e4b0cbcc1ea98417866a4c5d4441" + [[package]] name = "glob" version = "0.3.3" @@ -5762,6 +5792,7 @@ dependencies = [ "alloy-signer-local", "axum", "bump-scope", + "bytemuck", "bytes", "criterion 0.8.1", "digest 0.11.0-rc.4", @@ -5773,6 +5804,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "uts-bmt", "uts-contracts", "uts-core", "uts-journal", @@ -5848,9 +5880,12 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-provider", + "bitcode", "bytemuck", "digest 0.11.0-rc.4", "rocksdb", + "serde", + "thiserror 2.0.17", "tokio", "tracing", "uts-bmt", diff --git a/Cargo.toml b/Cargo.toml index 48903c7..92eefc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ alloy-sol-types = "1.5" auto_impl = "1.3" axum = { version = "0.8", default-features = false } axum-extra = "0.12" +bitcode = "0.6" bump-scope = { version = "1.5", features = ["nightly"] } bytemuck = "1" bytes = "1.11" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index 003555c..fdac50f 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -19,6 +19,7 @@ axum = { workspace = true, default-features = false, features = [ "tokio", ] } # http2 only bump-scope.workspace = true +bytemuck = { workspace = true } bytes = { workspace = true } digest = { workspace = true } eyre = { workspace = true } @@ -29,6 +30,7 @@ sha3 = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } +uts-bmt = { workspace = true } uts-contracts = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } uts-journal = { workspace = true } diff --git a/crates/calendar/src/lib.rs b/crates/calendar/src/lib.rs index 6202b7a..fd04d11 100644 --- a/crates/calendar/src/lib.rs +++ b/crates/calendar/src/lib.rs @@ -9,7 +9,9 @@ extern crate tracing; use alloy_signer::k256::ecdsa::SigningKey; use alloy_signer_local::LocalSigner; use digest::{OutputSizeUser, typenum::Unsigned}; +use rocksdb::DB; use sha3::Keccak256; +use std::sync::Arc; use uts_journal::Journal; /// Calendar server routes and handlers. @@ -24,6 +26,8 @@ pub struct AppState { pub signer: LocalSigner, /// Journal pub journal: Journal<{ ::OutputSize::USIZE }>, + /// RocksDB + pub db: Arc, } /// Signal for graceful shutdown. diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index 0480225..73390ec 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,7 +1,7 @@ //! Calendar server use alloy_primitives::b256; -use alloy_provider::{ProviderBuilder, network::EthereumWallet}; +use alloy_provider::{Provider, ProviderBuilder, network::EthereumWallet}; use alloy_signer_local::{LocalSigner, MnemonicBuilder}; use axum::{ Router, @@ -12,6 +12,7 @@ use digest::{OutputSizeUser, typenum::Unsigned}; use rocksdb::DB; use sha3::Keccak256; use std::{env, sync::Arc}; +use tracing::info; use uts_calendar::{AppState, routes, shutdown_signal, time}; use uts_contracts::uts::UniversalTimestamps; use uts_journal::Journal; @@ -36,20 +37,22 @@ async fn main() -> eyre::Result<()> { let key = MnemonicBuilder::from_phrase(env::var("MNEMONIC")?.as_str()) .index(0u32)? .build()?; + info!("Using address: {:?}", key.address()); let provider = ProviderBuilder::new() .wallet(EthereumWallet::new(key)) - .connect("https://0xrpc.io/sep") + .connect("https://sepolia-rpc.scroll.io") .await?; + provider.get_chain_id().await?; // sanity check let contract = UniversalTimestamps::new(uts_contracts::uts::DEFAULT_ADDRESS, provider.clone()); // stamper let reader = journal.reader(); - let db = DB::open_default("./.db/tries")?; + let db = Arc::new(DB::open_default("./.db/tries")?); let mut stamper = Stamper::::OutputSize::USIZE }>::new( reader, - Arc::new(db), + db.clone(), contract, // TODO: tune configuration StamperConfig { @@ -64,20 +67,18 @@ async fn main() -> eyre::Result<()> { stamper.run().await; }); - // TODO: maybe we can separate "/timestamp/{hex_commitment}" into another service with DB::open_as_secondary() - // TODO: write/read separate, reader can scale horizontally? would this be better? - let app = Router::new() .route( "/digest", post(routes::ots::submit_digest) .layer(DefaultBodyLimit::max(routes::ots::MAX_DIGEST_SIZE)), ) - .route( - "/timestamp/{hex_commitment}", - get(routes::ots::get_timestamp), - ) - .with_state(Arc::new(AppState { signer, journal })); + .route("/timestamp/{commitment}", get(routes::ots::get_timestamp)) + .with_state(Arc::new(AppState { + signer, + journal, + db, + })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index d787186..f82a3c9 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -1,8 +1,9 @@ use crate::{AppState, time::current_time_sec}; +use alloy_primitives::{B256, hex}; use alloy_signer::SignerSync; use axum::{ body::Bytes, - extract::State, + extract::{Path, State}, http::StatusCode, response::{IntoResponse, Response}, }; @@ -11,13 +12,15 @@ use bytes::BytesMut; use digest::Digest; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; +use uts_bmt::{FlatMerkleTree, NodePosition}; use uts_core::{ codec::{ Encode, - v1::{PendingAttestation, Timestamp}, + v1::{EthereumUTSAttestation, PendingAttestation, Timestamp}, }, utils::Hexed, }; +use uts_stamper::DbExt; /// Maximum digest size accepted by the endpoint. pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 @@ -114,7 +117,7 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u let timestamp = builder .attest(PendingAttestation { - uri: "https://localhost:3000".into(), + uri: "http://localhost:3000".into(), }) .unwrap(); @@ -132,4 +135,61 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u } /// Get current timestamp from calendar server. -pub async fn get_timestamp() {} +pub async fn get_timestamp( + State(state): State>, + Path(commitment): Path, +) -> Response { + const PRE_ALLOCATION_SIZE_HINT: usize = 4096; + thread_local! { + // We don't have `.await` in this function, so it's safe to borrow thread local. + static BUMP: RefCell = RefCell::new(Bump::with_size(PRE_ALLOCATION_SIZE_HINT)); + } + + let Some(root) = state.db.get_root_for_leaf(commitment).expect("DB error") else { + return (StatusCode::NOT_FOUND, r#"{"err":"timestamp not found"}"#).into_response(); + }; + let entry = state + .db + .load_entry(root) + .expect("DB error") + .expect("bug: entry not found"); + let trie: FlatMerkleTree = entry.trie(); + + let mut proof_iter = trie + .get_proof_iter(bytemuck::cast_ref(&*commitment)) + .expect("bug: proof not found"); + let output = BUMP.with(|bump| { + let mut bump = bump.borrow_mut(); + bump.reset(); + + let mut builder = Timestamp::builder_in(&*bump); + + while let Some((side, sibling_hash)) = proof_iter.next() { + builder = match side { + NodePosition::Left => builder.append(sibling_hash.to_vec_in(&bump)), + NodePosition::Right => builder.prepend(sibling_hash.to_vec_in(&bump)), + } + .keccak256(); + } + let timestamp = builder + .attest(EthereumUTSAttestation::new( + entry.chain_id, + entry.height, + Default::default(), + )) + .unwrap(); + + // copy data out of bump + // TODO: eliminate this allocation by reusing from a pool + // TODO: wrap the buffer with a drop trait to return to pool + let mut buf = BytesMut::with_capacity(128); + timestamp.encode(&mut buf).unwrap(); + + #[cfg(any(debug_assertions, not(feature = "performance")))] + trace!(encoded_length = buf.len(), timestamp = ?timestamp); + + buf.freeze() + }); + + output.into_response() +} diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index 8aae820..4c0038c 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -164,6 +164,21 @@ pub struct EthereumUTSAttestationExtraMetadata { tx: Option, } +impl EthereumUTSAttestation { + /// Creates a new Ethereum UTS attestation with the given chain id, block number, and extra metadata. + pub fn new( + chain_id: ChainId, + height: BlockNumber, + metadata: EthereumUTSAttestationExtraMetadata, + ) -> Self { + Self { + chain: Chain::from_id(chain_id), + height, + metadata, + } + } +} + impl fmt::Display for EthereumUTSAttestation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -192,7 +207,7 @@ impl Attestation<'_> for EthereumUTSAttestation { fn to_raw_data_in(&self, alloc: A) -> Result, EncodeError> { // chain id + block number + optional address + optional tx hash const SIZE: usize = size_of::() + size_of::() + 20 + 32; - let mut buffer = Vec::with_capacity_in(20 + 32 + 32, alloc); + let mut buffer = Vec::with_capacity_in(SIZE, alloc); buffer.encode(self.chain)?; buffer.encode(self.height)?; buffer.encode(&self.metadata)?; @@ -325,12 +340,17 @@ impl<'a> Attestation<'a> for PendingAttestation<'a> { impl fmt::Display for RawAttestation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.tag { + tag if *tag == *PENDING_TAG => { + let att = PendingAttestation::from_raw(self).expect("Valid Pending attestation"); + write!(f, "{}", att) + } tag if *tag == *BITCOIN_TAG => { let att = BitcoinAttestation::from_raw(self).expect("Valid Bitcoin attestation"); write!(f, "{}", att) } - tag if *tag == *PENDING_TAG => { - let att = PendingAttestation::from_raw(self).expect("Valid Pending attestation"); + tag if *tag == *ETHEREUM_UTS_TAG => { + let att = + EthereumUTSAttestation::from_raw(self).expect("Valid Ethereum UTS attestation"); write!(f, "{}", att) } _ => write!(f, "Unknown Attestation with tag {}", Hexed(&self.tag)), diff --git a/crates/stamper/Cargo.toml b/crates/stamper/Cargo.toml index 89736d9..c408805 100644 --- a/crates/stamper/Cargo.toml +++ b/crates/stamper/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true version.workspace = true [dependencies] -alloy-primitives = { workspace = true } +alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true } +bitcode = { workspace = true, features = ["serde"] } bytemuck = { workspace = true } digest = { workspace = true } rocksdb = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } tokio = { workspace = true, features = ["time", "macros"] } tracing = { workspace = true } uts-bmt = { workspace = true } diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index 63afa64..d050bc6 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -6,12 +6,19 @@ #[macro_use] extern crate tracing; -use alloy_primitives::B256; +use alloy_primitives::{B256, BlockNumber, ChainId, TxHash}; use alloy_provider::Provider; use bytemuck::{NoUninit, Pod}; use digest::{Digest, FixedOutputReset, Output, typenum::Unsigned}; use rocksdb::{DB, WriteBatch}; -use std::{collections::VecDeque, fmt, sync::Arc, time::Duration}; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + collections::{HashMap, VecDeque}, + fmt, + sync::Arc, + time::Duration, +}; use tokio::time::{Interval, MissedTickBehavior}; use uts_bmt::FlatMerkleTree; use uts_contracts::uts::UniversalTimestamps; @@ -36,6 +43,8 @@ pub struct Stamper { storage: Arc, /// FIFO cache of recent merkle trees cache: VecDeque>, + /// FIFO cache index of recent merkle trees + cache_index: HashMap, /// The contract contract: UniversalTimestamps

, /// Stamper configuration @@ -57,6 +66,73 @@ pub struct StamperConfig { pub max_cache_size: usize, } +/// Merkle entry stored in the database +#[derive(Debug, Serialize, Deserialize)] +pub struct MerkleEntry<'a> { + /// Chain ID of the timestamp transaction + pub chain_id: ChainId, + /// Transaction hash of the timestamp transaction + pub tx_hash: TxHash, + /// Block number of the timestamp transaction + pub height: BlockNumber, + + trie: Cow<'a, [u8]>, +} + +impl MerkleEntry<'_> { + /// Get the Merkle tree from the entry + pub fn trie(&self) -> FlatMerkleTree + 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 { FlatMerkleTree::from_raw_bytes(&self.trie) } + } +} + +/// Errors that can occur during storage operations +#[derive(Debug, thiserror::Error)] +pub enum StorageError { + #[error(transparent)] + Rocks(#[from] rocksdb::Error), + #[error(transparent)] + Bitcode(#[from] bitcode::Error), + #[error("invalid data")] + InvalidData, +} + +/// Extension trait for DB to load Merkle entries and leaf->root mappings +pub trait DbExt { + /// Load a Merkle entry from the database by root hash + fn load_entry(&self, root: B256) -> Result>, StorageError>; + + /// Get the root hash for a given leaf hash, if it exists + fn get_root_for_leaf(&self, leaf: B256) -> Result, StorageError>; +} + +impl DbExt for DB { + fn load_entry(&self, root: B256) -> Result>, StorageError> { + let Some(data) = self.get(root)? else { + return Ok(None); + }; + let entry: MerkleEntry<'static> = bitcode::deserialize(&data)?; + Ok(Some(entry)) + } + + fn get_root_for_leaf(&self, leaf: B256) -> Result, StorageError> { + let Some(root) = self.get(leaf)? else { + return Ok(None); + }; + if root.len() != 32 { + let _entry: MerkleEntry<'static> = bitcode::deserialize(&root)?; + return Ok(Some(leaf)); // it's a single-leaf tree + } + let hash: [u8; 32] = root.as_slice().try_into().expect("infallible"); + Ok(Some(B256::new(hash))) + } +} + impl fmt::Debug for Stamper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Stamper") @@ -86,6 +162,7 @@ where reader, storage, cache: VecDeque::with_capacity(config.max_cache_size), + cache_index: HashMap::with_capacity(config.max_cache_size), contract, config, } @@ -93,16 +170,27 @@ where /// Work loop pub async fn run(&mut self) { + let chain_id = self + .contract + .provider() + .get_chain_id() + .await + .expect("Failed to get chain ID"); let mut ticker = tokio::time::interval(Duration::from_secs(self.config.max_interval_seconds)); ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); let mut leaves_buffer = Vec::with_capacity(self.config.max_entries_per_timestamp); loop { - self.pack(&mut ticker, &mut leaves_buffer).await; + self.pack(chain_id, &mut ticker, &mut leaves_buffer).await; } } - async fn pack(&mut self, ticker: &mut Interval, buffer: &mut Vec<[u8; ENTRY_SIZE]>) { + async fn pack( + &mut self, + chain_id: ChainId, + ticker: &mut Interval, + buffer: &mut Vec<[u8; ENTRY_SIZE]>, + ) { let entries = self .reader .wait_at_least(self.config.max_entries_per_timestamp); @@ -156,13 +244,6 @@ where let merkle_tree = merkle_tree.finalize(); // CPU intensive let root = merkle_tree.root(); info!(root = ?Hexed(root)); - - let mut batch = WriteBatch::default(); - batch.put(root, merkle_tree.as_raw_bytes()); - for leaf in merkle_tree.leaves() { - batch.put(leaf, root); - } - storage.write(batch).expect("Failed to write to storage"); // FIXME: handle error properly merkle_tree }) .await @@ -170,22 +251,54 @@ where let root = B256::new(bytemuck::cast(*merkle_tree.root())); - if self.cache.len() >= self.config.max_cache_size { - self.cache.pop_front(); - } - self.cache.push_back(merkle_tree); - - let tx_hash = self + // commit to blockchain + let receipt = self .contract .attest(root) .send() .await .expect("failed to build transaction") - .watch() + .get_receipt() .await .expect("failed to send transaction"); // FIXME: handle error properly - info!(%tx_hash, %root,"Timestamp attested on-chain"); + let block_number = receipt.block_number.expect("Transaction not yet mined"); + info!(%block_number, %receipt.transaction_hash, %root,"Timestamp attested on-chain"); + // write to storage + let mut batch = WriteBatch::default(); + // store leaf->root mappings for quick lookup + for leaf in merkle_tree.leaves() { + batch.put(leaf, root); + } + // store the Merkle tree + let entry = MerkleEntry { + chain_id, + tx_hash: receipt.transaction_hash, + height: block_number, + trie: Cow::Borrowed(merkle_tree.as_raw_bytes()), + }; + let serialized_entry = + bitcode::serialize(&entry).expect("Failed to serialize Merkle entry"); + bitcode::deserialize::(&serialized_entry) + .expect("Failed to deserialize Merkle entry"); // sanity check + // if it's a single-leaf tree, the root == the leaf, so we write mapping first. + batch.put(root, serialized_entry); + storage.write(batch).expect("Failed to write to storage"); // FIXME: handle error properly + + if self.cache.len() >= self.config.max_cache_size { + let evicted = self + .cache + .pop_front() + .expect("infallible due to check above"); + + let root = evicted.root(); + let removed = self.cache_index.remove(&B256::new(bytemuck::cast(*root))); + debug_assert_eq!(removed, Some(0)); + + self.cache_index.iter_mut().for_each(|(_, idx)| *idx -= 1); + } + self.cache.push_back(merkle_tree); + self.cache_index.insert(root, self.cache.len() - 1); self.reader.commit(); } } From 5222a5363035753774334a66a51b998a86c07caf Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sat, 21 Feb 2026 15:24:07 +0800 Subject: [PATCH 12/74] feat: add verifier (#23) --- Cargo.lock | 157 ++++++++++++++++++ Cargo.toml | 1 + crates/core/Cargo.toml | 19 +++ crates/core/src/bin/uts_verifier.rs | 138 +++++++++++++++ crates/core/src/codec/v1/attestation.rs | 6 + .../core/src/codec/v1/detached_timestamp.rs | 9 + crates/core/src/codec/v1/timestamp.rs | 29 ++++ 7 files changed, 359 insertions(+) create mode 100644 crates/core/src/bin/uts_verifier.rs diff --git a/Cargo.lock b/Cargo.lock index aac81b5..5d489f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -1226,6 +1241,21 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -1723,6 +1753,33 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "commonware-codec" version = "0.0.63" @@ -2729,6 +2786,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "glam" version = "0.32.0" @@ -3278,6 +3341,47 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +[[package]] +name = "jiff" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -3491,6 +3595,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.1.1" @@ -3650,6 +3763,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -3802,6 +3924,12 @@ dependencies = [ "log", ] +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + [[package]] name = "p256" version = "0.13.2" @@ -4016,6 +4144,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -4574,6 +4711,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -5595,6 +5738,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -5835,9 +5988,12 @@ dependencies = [ "alloy-sol-types", "auto_impl", "bytes", + "color-eyre", "criterion 0.5.1", "digest 0.11.0-rc.4", + "eyre", "hex", + "jiff", "once_cell", "opentimestamps", "paste", @@ -5849,6 +6005,7 @@ dependencies = [ "sha2 0.11.0-rc.3", "sha3 0.11.0-rc.3", "thiserror 2.0.17", + "tokio", "tracing", "uts-contracts", ] diff --git a/Cargo.toml b/Cargo.toml index 92eefc3..640844d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ eyre = "0.6" futures = "0.3" hex = "0.4" itoa = "1.0" +jiff = "0.2.20" once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 619bdb9..87103c6 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -14,6 +14,11 @@ name = "uts-info" path = "src/bin/uts_info.rs" required-features = ["std"] +[[bin]] +name = "uts-verifier" +path = "src/bin/uts_verifier.rs" +required-features = ["verifier-bin-deps"] + [dependencies] alloy-chains = { workspace = true } alloy-primitives = { workspace = true } @@ -22,8 +27,11 @@ alloy-rpc-types-eth = { workspace = true, optional = true } alloy-sol-types = { workspace = true, optional = true } auto_impl.workspace = true bytes = { workspace = true, optional = true } +color-eyre = { workspace = true, optional = true } digest.workspace = true +eyre = { workspace = true, optional = true } hex.workspace = true +jiff = { workspace = true, optional = true } once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true ripemd.workspace = true @@ -33,6 +41,7 @@ sha1.workspace = true sha2.workspace = true sha3.workspace = true thiserror.workspace = true +tokio = { workspace = true, optional = true } tracing = { workspace = true, optional = true } uts-contracts = { workspace = true, optional = true } @@ -45,6 +54,16 @@ std = [] tracing = ["dep:tracing"] verifier = [] +verifier-bin-deps = [ + "dep:eyre", + "dep:tokio", + "dep:color-eyre", + "dep:jiff", + "std", + "ethereum-uts-verifier", + "tokio/full", +] + [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } opentimestamps = { git = "https://github.com/opentimestamps/rust-opentimestamps" } diff --git a/crates/core/src/bin/uts_verifier.rs b/crates/core/src/bin/uts_verifier.rs new file mode 100644 index 0000000..6eaed49 --- /dev/null +++ b/crates/core/src/bin/uts_verifier.rs @@ -0,0 +1,138 @@ +use alloy_provider::ProviderBuilder; +use digest::{Digest, DynDigest}; +use jiff::{Timestamp, tz::TimeZone}; +use std::{ + env, fs, + io::{BufReader, Read}, + process, +}; +use uts_core::{ + codec::{ + Decode, Reader, VersionedProof, + v1::{ + Attestation, DetachedTimestamp, EthereumUTSAttestation, PendingAttestation, opcode::*, + }, + }, + utils::Hexed, + verifier::{AttestationVerifier, EthereumUTSVerifier}, +}; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + color_eyre::install()?; + + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: {} [eth provider url]", args[0]); + process::exit(1); + } + + let mut fh = BufReader::new(if args.len() >= 3 { + fs::File::open(&args[2])? + } else { + fs::File::open(format!("{}.ots", &args[1]))? + }); + let timestamp = VersionedProof::::decode(&mut Reader(&mut fh))?.proof; + + let digest_header = timestamp.header(); + let mut hasher = match digest_header.kind().tag() { + SHA1 => Box::new(sha1::Sha1::new()) as Box, + RIPEMD160 => Box::new(ripemd::Ripemd160::new()) as Box, + SHA256 => Box::new(sha2::Sha256::new()) as Box, + KECCAK256 => Box::new(sha3::Keccak256::new()) as Box, + _ => { + eprintln!("Unsupported digest type: {}", digest_header.kind()); + process::exit(1); + } + }; + + let mut file = BufReader::new(fs::File::open(&args[1])?); + let mut buffer = [0u8; 64 * 1024]; // 64KB buffer + loop { + let bytes_read = file.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + hasher.update(&buffer[..bytes_read]); + } + let expected = hasher.finalize(); + + if *expected != *digest_header.digest() { + eprintln!( + "Digest mismatch! Expected: {}, Found: {}", + Hexed(&expected), + Hexed(digest_header.digest()) + ); + process::exit(1); + } + eprintln!("Digest matches: {}", Hexed(&expected)); + + timestamp.try_finalize()?; + + for attestation in timestamp.attestations() { + if attestation.tag == PendingAttestation::TAG { + continue; // skip pending attestations + } + + if attestation.tag == EthereumUTSAttestation::TAG { + let eth_attestation = EthereumUTSAttestation::from_raw(&attestation)?; + eprintln!("Attested by {eth_attestation}"); + let provider_url = if args.len() >= 4 { + &*args[3] + } else { + match eth_attestation.chain.id() { + 1 => "https://0xrpc.io/eth", + 11155111 => "https://0xrpc.io/sep", + 534352 => "https://rpc.scroll.io", + 534351 => "https://sepolia-rpc.scroll.io", + _ => panic!("Unsupported chain: {}", eth_attestation.chain), + } + }; + let provider = ProviderBuilder::new().connect(provider_url).await?; + let verifier = EthereumUTSVerifier::new(provider).await?; + let result = verifier + .verify(ð_attestation, attestation.value().unwrap()) + .await?; + if let Some(block_number) = result.block_number { + if let Some(block_hash) = result.block_hash { + eprintln!("\tblock: #{block_number} {block_hash}"); + } else { + eprintln!("\tblock: {block_number}"); + } + } + if let Some(log_index) = result.log_index { + eprintln!("\tlog index: {log_index}"); + } + if let Some(transaction_hash) = result.transaction_hash { + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!("\ttransaction: {etherscan_url}/tx/{transaction_hash}"); + } else { + eprintln!("\ttransaction hash: {transaction_hash}"); + } + } + + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!( + "\tuts contract: {etherscan_url}/address/{}", + result.inner.address + ); + } else { + eprintln!("\tuts contract: {}", result.inner.address); + } + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!( + "\ttx sender: {etherscan_url}/address/{}", + result.inner.sender + ); + } else { + eprintln!("\ttx sender: {}", result.inner.sender); + } + let ts = Timestamp::from_second(result.inner.timestamp.to())?; + let zdt = ts.to_zoned(TimeZone::system()); + eprintln!("\ttime attested: {zdt}"); + eprintln!("\tmerkle root: {}", result.inner.root); + } + } + + Ok(()) +} diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index 4c0038c..c1f336e 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -90,6 +90,12 @@ impl RawAttestation { pub fn allocator(&self) -> &A { self.data.allocator() } + + /// Returns the cached value for verifying the attestation, if it exists. + #[inline] + pub fn value(&self) -> Option<&[u8]> { + self.value.get().map(|v| v.as_slice()) + } } pub trait Attestation<'a>: Sized { diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 7e243da..528c499 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -4,6 +4,7 @@ use crate::codec::{ }; use alloc::alloc::{Allocator, Global}; use core::{fmt, fmt::Formatter}; +use std::ops::Deref; /// A file containing a timestamp for another file /// Contains a timestamp, along with a header and the digest of the file. @@ -111,6 +112,14 @@ impl DetachedTimestamp { } } +impl Deref for DetachedTimestamp { + type Target = Timestamp; + + fn deref(&self) -> &Self::Target { + &self.timestamp + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 2e5b459..849b821 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -144,6 +144,12 @@ impl Timestamp { pub fn is_finalized(&self) -> bool { self.input().is_some() } + + /// Iterates over all attestations in this timestamp. + #[inline] + pub fn attestations(&self) -> AttestationIter<'_, A> { + AttestationIter { stack: vec![self] } + } } impl Timestamp { @@ -282,3 +288,26 @@ impl MayHaveInput for Step { self.input.get().map(|v| v.as_slice()) } } + +#[must_use = "AttestationIter is an iterator, it does nothing unless consumed"] +pub struct AttestationIter<'a, A: Allocator> { + stack: Vec<&'a Timestamp>, +} + +impl<'a, A: Allocator> Iterator for AttestationIter<'a, A> { + type Item = &'a RawAttestation; + + fn next(&mut self) -> Option { + while let Some(ts) = self.stack.pop() { + match ts { + Timestamp::Step(step) => { + for next in step.next().iter().rev() { + self.stack.push(next); + } + } + Timestamp::Attestation(attestation) => return Some(attestation), + } + } + None + } +} From a631d07045ad3336a5e82302bb671da66da8b5b4 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 21 Feb 2026 15:43:44 +0800 Subject: [PATCH 13/74] add catch --- crates/core/src/bin/uts_verifier.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/core/src/bin/uts_verifier.rs b/crates/core/src/bin/uts_verifier.rs index 6eaed49..99049b5 100644 --- a/crates/core/src/bin/uts_verifier.rs +++ b/crates/core/src/bin/uts_verifier.rs @@ -131,7 +131,10 @@ async fn main() -> eyre::Result<()> { let zdt = ts.to_zoned(TimeZone::system()); eprintln!("\ttime attested: {zdt}"); eprintln!("\tmerkle root: {}", result.inner.root); + continue; } + + eprintln!("Unverifiable attestation: {attestation}"); } Ok(()) From c2fc3b1647efee82afcda2d8577f8f618469e1b6 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sat, 21 Feb 2026 19:15:23 +0800 Subject: [PATCH 14/74] fix: domain seperation inner node hash (#24) --- crates/bmt/src/lib.rs | 4 ++++ crates/calendar/src/routes/ots.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index 76bce19..d4e4fec 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -6,6 +6,9 @@ use bytemuck::Pod; use digest::{Digest, FixedOutputReset, Output}; use std::hint::unlikely; +/// Prefix byte to distinguish internal nodes from leaves when hashing. +pub const INNER_NODE_PREFIX: u8 = 0x01; + /// Flat, Fixed-Size, Read only Merkle Tree /// /// Expects the length of leaves to be equal or near(less) to a power of two. @@ -138,6 +141,7 @@ where let left = maybe_uninit.get_unchecked(2 * i).assume_init_ref(); let right = maybe_uninit.get_unchecked(2 * i + 1).assume_init_ref(); + Digest::update(&mut hasher, [INNER_NODE_PREFIX]); Digest::update(&mut hasher, left); Digest::update(&mut hasher, right); let parent_hash = hasher.finalize_reset(); diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index f82a3c9..c25ba27 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -166,8 +166,12 @@ pub async fn get_timestamp( while let Some((side, sibling_hash)) = proof_iter.next() { builder = match side { - NodePosition::Left => builder.append(sibling_hash.to_vec_in(&bump)), - NodePosition::Right => builder.prepend(sibling_hash.to_vec_in(&bump)), + NodePosition::Left => builder + .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(&bump)) + .append(sibling_hash.to_vec_in(&bump)), + NodePosition::Right => builder + .prepend(sibling_hash.to_vec_in(&bump)) + .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(&bump)), } .keccak256(); } From 11718c030bed0dc2d0dcbd75fb66fef9d45af2ca Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sun, 22 Feb 2026 19:02:41 +0800 Subject: [PATCH 15/74] feat: cli crate (#25) * move to cli crate * add stamp command * fix * add upgrade --- Cargo.lock | 628 +++++++++++++++++- Cargo.toml | 4 + crates/bmt/benches/tree_construction.rs | 7 +- crates/bmt/src/lib.rs | 12 +- crates/calendar/src/routes/ots.rs | 23 +- crates/cli/Cargo.toml | 46 ++ crates/cli/src/client.rs | 9 + crates/cli/src/commands.rs | 29 + crates/cli/src/commands/inspect.rs | 34 + crates/cli/src/commands/stamp.rs | 214 ++++++ crates/cli/src/commands/upgrade.rs | 99 +++ crates/cli/src/commands/verify.rs | 146 ++++ crates/cli/src/main.rs | 19 + crates/core/Cargo.toml | 25 +- crates/core/src/bin/uts_info.rs | 61 -- crates/core/src/bin/uts_verifier.rs | 141 ---- crates/core/src/codec/proof.rs | 27 + crates/core/src/codec/v1.rs | 2 +- .../core/src/codec/v1/detached_timestamp.rs | 12 +- crates/core/src/codec/v1/digest.rs | 20 +- crates/core/src/codec/v1/opcode.rs | 28 +- crates/core/src/codec/v1/timestamp.rs | 46 +- crates/core/src/codec/v1/timestamp/builder.rs | 44 +- crates/core/src/utils.rs | 5 + crates/core/src/utils/hash.rs | 48 ++ crates/stamper/src/lib.rs | 14 +- 26 files changed, 1446 insertions(+), 297 deletions(-) create mode 100644 crates/cli/Cargo.toml create mode 100644 crates/cli/src/client.rs create mode 100644 crates/cli/src/commands.rs create mode 100644 crates/cli/src/commands/inspect.rs create mode 100644 crates/cli/src/commands/stamp.rs create mode 100644 crates/cli/src/commands/upgrade.rs create mode 100644 crates/cli/src/commands/verify.rs create mode 100644 crates/cli/src/main.rs delete mode 100644 crates/core/src/bin/uts_info.rs delete mode 100644 crates/core/src/bin/uts_verifier.rs create mode 100644 crates/core/src/utils/hash.rs diff --git a/Cargo.lock b/Cargo.lock index 5d489f7..097f267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.17", @@ -519,7 +519,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "tokio", @@ -795,7 +795,7 @@ checksum = "64b722073c76f2de7e118d546ee1921c50710f97feb32aed50db94cfa5b663e1" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest", + "reqwest 0.12.28", "serde_json", "tower", "tracing", @@ -882,12 +882,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -1177,6 +1221,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.8" @@ -1571,6 +1637,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -1600,7 +1672,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -1610,7 +1693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1684,6 +1767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -1692,8 +1776,22 @@ version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1702,6 +1800,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "coins-bip32" version = "0.12.0" @@ -1780,6 +1887,22 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "commonware-codec" version = "0.0.63" @@ -1922,7 +2045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -1984,6 +2107,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1999,6 +2132,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -2165,7 +2307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "fiat-crypto", "rustc_version 0.4.1", @@ -2641,6 +2783,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -2786,6 +2934,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.32.3" @@ -3181,6 +3343,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -3308,6 +3476,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -3382,6 +3556,28 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -3423,7 +3619,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -3432,7 +3628,7 @@ version = "0.2.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -3451,6 +3647,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.178" @@ -3624,10 +3826,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3778,6 +3980,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -3822,6 +4030,21 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.111" @@ -3830,6 +4053,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -3858,7 +4082,7 @@ dependencies = [ "bytes", "http", "opentelemetry", - "reqwest", + "reqwest 0.12.28", "tracing", ] @@ -3876,7 +4100,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest", + "reqwest 0.12.28", "thiserror 2.0.17", "tracing", ] @@ -4133,7 +4357,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4177,6 +4401,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.111", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -4349,6 +4583,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -4422,6 +4657,17 @@ dependencies = [ "serde", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -4461,6 +4707,12 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -4615,6 +4867,45 @@ dependencies = [ "webpki-roots 1.0.4", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -4766,6 +5057,7 @@ version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -4774,6 +5066,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pki-types" version = "1.13.2" @@ -4784,12 +5088,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4910,7 +5242,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5081,7 +5426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -5092,7 +5437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa1ae819b9870cadc959a052363de870944a1646932d274a4e270f64bf79e5ef" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.11.0-rc.4", ] @@ -5104,7 +5449,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -5116,7 +5461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -5127,7 +5472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.11.0-rc.4", ] @@ -5921,6 +6266,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uts-bmt" version = "0.1.0" @@ -5964,6 +6315,31 @@ dependencies = [ "uts-stamper", ] +[[package]] +name = "uts-cli" +version = "0.1.0" +dependencies = [ + "alloy-provider", + "bytemuck", + "clap", + "color-eyre", + "digest 0.11.0-rc.4", + "eyre", + "futures", + "jiff", + "rand 0.10.0", + "reqwest 0.13.2", + "ripemd 0.2.0-rc.3", + "sha1 0.11.0-rc.3", + "sha2 0.11.0-rc.3", + "sha3 0.11.0-rc.3", + "tokio", + "tracing", + "url", + "uts-bmt", + "uts-core", +] + [[package]] name = "uts-contracts" version = "0.1.0" @@ -5988,12 +6364,9 @@ dependencies = [ "alloy-sol-types", "auto_impl", "bytes", - "color-eyre", "criterion 0.5.1", "digest 0.11.0-rc.4", - "eyre", "hex", - "jiff", "once_cell", "opentimestamps", "paste", @@ -6007,6 +6380,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", + "uts-bmt", "uts-contracts", ] @@ -6109,7 +6483,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -6170,6 +6553,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.12.1", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.12.1", + "semver 1.0.27", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -6204,6 +6621,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -6371,6 +6797,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -6398,6 +6833,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -6431,6 +6881,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6443,6 +6899,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6455,6 +6917,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6479,6 +6947,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6491,6 +6965,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6503,6 +6983,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6515,6 +7001,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6542,6 +7034,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.12.1", + "prettyplease", + "syn 2.0.111", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.111", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.12.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.12.1", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index 640844d..3bc242a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +default-members = ["crates/cli"] members = ["crates/*"] resolver = "3" @@ -59,7 +60,9 @@ itoa = "1.0" jiff = "0.2.20" once_cell = { version = "1.21", default-features = false } paste = "1.0" +rand = "0.10" regex = "1.12" +reqwest = { version = "0.13", default-features = false } rocksdb = "0.24" serde = "1.0" serde-wasm-bindgen = "0.6" @@ -72,6 +75,7 @@ toml = "0.9" tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +url = "2.5" wasm-bindgen = "0.2" crypto-common = "0.2.0-rc.5" diff --git a/crates/bmt/benches/tree_construction.rs b/crates/bmt/benches/tree_construction.rs index caf9a11..7d06e67 100644 --- a/crates/bmt/benches/tree_construction.rs +++ b/crates/bmt/benches/tree_construction.rs @@ -1,4 +1,5 @@ //! Benchmark for Merkle tree construction. +use bytemuck::Pod; use criterion::{ BatchSize, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime, @@ -7,7 +8,7 @@ use digest::{Digest, FixedOutputReset, Output}; use sha2::Sha256; use sha3::Keccak256; use std::hint::black_box; -use uts_bmt::FlatMerkleTree; +use uts_bmt::UnorderdMerkleTree; const INPUT_SIZES: &[usize] = &[8, 1024, 65536, 1_048_576]; @@ -25,7 +26,7 @@ fn benchmark(c: &mut Criterion) { fn bench_digest(group: &mut BenchmarkGroup<'_, WallTime>, id: &str) where D: Digest + FixedOutputReset, - Output: Copy, + Output: Pod + Copy, { for &size in INPUT_SIZES { let leaves = generate_leaves::(size); @@ -33,7 +34,7 @@ where group.bench_function(BenchmarkId::new(id, size), move |b| { // Tree construction is the operation under test. b.iter(|| { - let tree = FlatMerkleTree::::new(black_box(leaves.as_slice())); + let tree = UnorderdMerkleTree::::new(black_box(leaves.as_slice())); black_box(tree); }); }); diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index d4e4fec..c8d5b27 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 FlatMerkleTree { +pub struct UnorderdMerkleTree { /// 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 FlatMerkleTree +impl UnorderdMerkleTree where Output: Pod + Copy, { @@ -128,7 +128,7 @@ where Output: Pod + Copy, { /// Finalizes the Merkle tree by hashing internal nodes - pub fn finalize(self) -> FlatMerkleTree { + pub fn finalize(self) -> UnorderdMerkleTree { let mut nodes = self.buffer; let len = self.len; unsafe { @@ -152,7 +152,7 @@ where // SAFETY: initialized all elements. nodes.set_len(2 * len); } - FlatMerkleTree { + UnorderdMerkleTree { nodes: nodes.into_boxed_slice(), len, } @@ -234,7 +234,7 @@ mod tests { ]; leaves.sort_unstable(); - let tree = FlatMerkleTree::::new(&leaves); + let tree = UnorderdMerkleTree::::new(&leaves); // Manually compute the expected root let mut hasher = D::new(); @@ -265,7 +265,7 @@ mod tests { ]; leaves.sort_unstable(); - let tree = FlatMerkleTree::::new(&leaves); + let tree = UnorderdMerkleTree::::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 c25ba27..0587e7d 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -1,5 +1,5 @@ use crate::{AppState, time::current_time_sec}; -use alloy_primitives::{B256, hex}; +use alloy_primitives::B256; use alloy_signer::SignerSync; use axum::{ body::Bytes, @@ -12,7 +12,7 @@ use bytes::BytesMut; use digest::Digest; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; -use uts_bmt::{FlatMerkleTree, NodePosition}; +use uts_bmt::UnorderdMerkleTree; use uts_core::{ codec::{ Encode, @@ -107,7 +107,8 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u let mut bump = bump.borrow_mut(); bump.reset(); - let builder = Timestamp::builder_in(&*bump) + let mut builder = Timestamp::builder_in(&*bump); + builder .prepend(recv_timestamp.to_vec_in(&bump)) .append(undeniable_sig.to_vec_in(&bump)) .keccak256(); @@ -153,9 +154,9 @@ pub async fn get_timestamp( .load_entry(root) .expect("DB error") .expect("bug: entry not found"); - let trie: FlatMerkleTree = entry.trie(); + let trie: UnorderdMerkleTree = entry.trie(); - let mut proof_iter = trie + let proof = trie .get_proof_iter(bytemuck::cast_ref(&*commitment)) .expect("bug: proof not found"); let output = BUMP.with(|bump| { @@ -163,18 +164,8 @@ pub async fn get_timestamp( bump.reset(); let mut builder = Timestamp::builder_in(&*bump); + builder.merkle_proof(proof); - while let Some((side, sibling_hash)) = proof_iter.next() { - builder = match side { - NodePosition::Left => builder - .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(&bump)) - .append(sibling_hash.to_vec_in(&bump)), - NodePosition::Right => builder - .prepend(sibling_hash.to_vec_in(&bump)) - .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(&bump)), - } - .keccak256(); - } let timestamp = builder .attest(EthereumUTSAttestation::new( entry.chain_id, diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000..4710edf --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,46 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-cli" +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[[bin]] +name = "uts" +path = "src/main.rs" + +[dependencies] +alloy-provider = { workspace = true } +bytemuck = { workspace = true } +clap = { workspace = true, features = ["derive"] } +color-eyre = { workspace = true } +digest = { workspace = true } +eyre = { workspace = true } +futures = { workspace = true } +jiff = { workspace = true } +rand = { workspace = true } +reqwest = { workspace = true, default-features = false, features = ["http2"] } +ripemd = { workspace = true } +sha1 = { workspace = true } +sha2 = { workspace = true } +sha3 = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +url = { workspace = true } +uts-bmt = { workspace = true } +uts-core = { workspace = true, features = ["std", "ethereum-uts-verifier", "io-utils"] } + +[features] +default = ["reqwest-default-tls"] +reqwest-default-tls = ["reqwest/default-tls"] +reqwest-native-tls = ["reqwest/native-tls"] +reqwest-native-tls-no-alpn = ["reqwest/native-tls-no-alpn"] +reqwest-native-tls-vendored = ["reqwest/native-tls-vendored"] +reqwest-native-tls-vendored-no-alpn = ["reqwest/native-tls-vendored-no-alpn"] +reqwest-rustls = ["reqwest/rustls"] diff --git a/crates/cli/src/client.rs b/crates/cli/src/client.rs new file mode 100644 index 0000000..f0d0e53 --- /dev/null +++ b/crates/cli/src/client.rs @@ -0,0 +1,9 @@ +use reqwest::Client; +use std::sync::LazyLock; + +pub static CLIENT: LazyLock = LazyLock::new(|| { + Client::builder() + .user_agent(concat!("uts/", env!("CARGO_PKG_VERSION"))) + .build() + .expect("Failed to build HTTP client") +}); diff --git a/crates/cli/src/commands.rs b/crates/cli/src/commands.rs new file mode 100644 index 0000000..a4f8391 --- /dev/null +++ b/crates/cli/src/commands.rs @@ -0,0 +1,29 @@ +use clap::Subcommand; + +mod inspect; +mod stamp; +mod upgrade; +mod verify; + +#[derive(Debug, Subcommand)] +pub enum Commands { + /// Inspect an ots file + Inspect(inspect::Inspect), + /// Verify an ots file against a file + Verify(verify::Verify), + /// Create timestamp + Stamp(stamp::Stamp), + /// Upgrade timestamp + Upgrade(upgrade::Upgrade), +} + +impl Commands { + pub async fn run(self) -> eyre::Result<()> { + match self { + Commands::Inspect(cmd) => cmd.run(), + Commands::Verify(cmd) => cmd.run().await, + Commands::Stamp(cmd) => cmd.run().await, + Commands::Upgrade(cmd) => cmd.run().await, + } + } +} diff --git a/crates/cli/src/commands/inspect.rs b/crates/cli/src/commands/inspect.rs new file mode 100644 index 0000000..ff47f6a --- /dev/null +++ b/crates/cli/src/commands/inspect.rs @@ -0,0 +1,34 @@ +use clap::Args; +use std::{fs, io, io::Seek, path::PathBuf}; +use uts_core::codec::{ + Decode, Reader, VersionedProof, + v1::{DetachedTimestamp, Timestamp}, +}; + +#[derive(Debug, Args)] +pub struct Inspect { + /// Path to the OTS file to inspect + ots_file: PathBuf, +} + +impl Inspect { + pub fn run(self) -> eyre::Result<()> { + let mut fh = fs::File::open(&self.ots_file)?; + + match VersionedProof::::decode(&mut Reader(&mut fh)) { + Ok(ots) => { + eprintln!("OTS Detached Timestamp found:\n{ots}"); + return Ok(()); + } + Err(e) => { + eprintln!("Not a valid Detached Timestamp OTS file (trying raw timestamp): {e}\n"); + } + }; + + fh.seek(io::SeekFrom::Start(0))?; + + let raw = Timestamp::decode(&mut Reader(&mut fh))?; + eprintln!("Raw Timestamp found:\n{raw}"); + Ok(()) + } +} diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs new file mode 100644 index 0000000..cdb2980 --- /dev/null +++ b/crates/cli/src/commands/stamp.rs @@ -0,0 +1,214 @@ +use crate::client::CLIENT; +use bytemuck::Pod; +use clap::{Args, ValueEnum}; +use digest::{Digest, FixedOutputReset, Output}; +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_core::{ + codec::{ + Decode, Encode, VersionedProof, + v1::{DetachedTimestamp, DigestHeader, Timestamp, TimestampBuilder, opcode::DigestOpExt}, + }, + utils::{HashAsyncFsExt, Hexed}, +}; + +static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { + vec![ + // Url::parse("https://a.pool.opentimestamps.org/").unwrap(), + // Url::parse("https://b.pool.opentimestamps.org/").unwrap(), + // Url::parse("https://a.pool.eternitywall.com/").unwrap(), + // Url::parse("https://ots.btc.catallaxy.com/").unwrap(), + Url::parse("http://127.0.0.1:3000/").unwrap(), + ] +}); + +#[derive(Debug, Args)] +pub struct Stamp { + /// Files to timestamp. May be specified multiple times. + #[arg(value_name = "FILE", num_args = 1..)] + files: Vec, + /// Create timestamp with the aid of a remote calendar. May be specified multiple times. + #[arg(short = 'c', long = "calendar", value_name = "URL", num_args = 0..)] + calendars: Vec, + /// Consider the timestamp complete if at least M calendars reply prior to the timeout + #[arg(short = 'm', default_value = "1")] + quorum: usize, + /// Hasher to use when digesting files. Default is Keccak256. + #[arg(short = 'H', long = "hasher", default_value = "keccak256")] + hasher: Hasher, + /// Timeout in seconds to wait for calendar responses. Default is 60 seconds. + #[arg(long = "timeout", default_value = "5")] + timeout: u64, +} + +#[derive(Default, Debug, Copy, Clone, ValueEnum)] +pub enum Hasher { + Sha1, + Ripemd160, + Sha256, + #[default] + Keccak256, +} + +impl Stamp { + pub async fn run(self) -> eyre::Result<()> { + match self.hasher { + Hasher::Sha1 => self.run_inner::().await, + Hasher::Ripemd160 => self.run_inner::().await, + Hasher::Sha256 => self.run_inner::().await, + Hasher::Keccak256 => self.run_inner::().await, + } + } + + async fn run_inner(self) -> eyre::Result<()> + where + D: Digest + FixedOutputReset + DigestOpExt + Send, + Output: Pod + Copy, + { + let digests = + futures::future::join_all(self.files.iter().map(|f| hash_file::(f.clone()))) + .await + .into_iter() + .collect::, _>>()?; + + for (header, path) in digests.iter().zip(self.files.iter()) { + eprintln!("File: {} {}", header, path.display()); + } + + let mut builders: HashMap = HashMap::from_iter( + self.files + .iter() + .map(|path| (path.clone(), Timestamp::builder())), + ); + + let nonced_digest = builders + .iter_mut() + .zip(digests.iter()) + .map(|((_, builder), digest)| { + let mut hasher = D::new(); + Digest::update(&mut hasher, digest.digest()); + let nonce: [u8; 32] = rand::random(); + Digest::update(&mut hasher, &nonce); + builder.append(nonce.to_vec()).digest::(); + hasher.finalize() + }) + .collect::>(); + + let internal_tire = UnorderdMerkleTree::::new(&nonced_digest); + let root = internal_tire.root(); + eprintln!("Internal Merkle root: {}", Hexed(root)); + + for ((_, builder), leaf) in builders.iter_mut().zip(nonced_digest) { + let proof = internal_tire.get_proof_iter(&leaf).expect("infallible"); + builder.merkle_proof(proof); + } + + let calendars = if self.calendars.is_empty() { + &*DEFAULT_CALENDARS + } else { + &*self.calendars + }; + + if self.quorum > calendars.len() { + eyre::bail!( + "Quorum of {} cannot be achieved with only {} calendars", + self.quorum, + self.calendars.len() + ); + } + + let stamps = futures::future::join_all( + calendars + .into_iter() + .map(|calendar| request_calendar(calendar.clone(), self.timeout, root)), + ) + .await + .into_iter() + .filter_map(|res| res.ok()) + .collect::>(); + if stamps.len() < self.quorum { + eyre::bail!( + "Only received {} valid responses from calendars, which does not meet the quorum of {}", + stamps.len(), + self.quorum + ); + } + let merged = if stamps.len() == 1 { + stamps.into_iter().next().unwrap() + } else { + Timestamp::merge(stamps) + }; + + let writes = + futures::future::join_all(builders.into_iter().zip(digests).map( + |((path, builder), header)| write_stamp(path, builder, merged.clone(), header), + )) + .await; + for (res, path) in writes.into_iter().zip(self.files.iter()) { + match res { + Ok(_) => eprintln!("Successfully wrote timestamp for {}", path.display()), + Err(e) => eprintln!("Failed to write timestamp for {}: {}", path.display(), e), + } + } + + Ok(()) + } +} + +async fn hash_file(path: PathBuf) -> io::Result { + let mut hasher = D::new(); + let file = fs::File::open(path).await?; + HashAsyncFsExt::update(&mut hasher, file).await?; + Ok(DigestHeader::new::(hasher.finalize())) +} + +async fn request_calendar(calendar: Url, timeout: u64, root: &[u8]) -> eyre::Result { + eprintln!("Submitting to remote calendar: {calendar}"); + let url = calendar.join(&"digest")?; + let response = CLIENT + .post(url) + .header("Accept", "application/vnd.opentimestamps.v1") + .body(root.to_vec()) + .timeout(Duration::from_secs(timeout)) + .send() + .and_then(|r| ready(r.error_for_status())) + .and_then(|r| r.bytes()) + .await + .inspect_err(|e| { + if e.is_status() { + eprintln!("Calendar {} responded with error: {}", calendar, e); + } else if e.is_timeout() { + eprintln!("Calendar {} timed out after {} seconds", calendar, timeout); + } else { + eprintln!("Failed to submit to calendar {}: {}", calendar, e); + } + })?; + + let ts = Timestamp::decode(&mut &*response).inspect_err(|e| { + eprintln!( + "Failed to decode response from calendar {}: {}", + calendar, e + ); + })?; + Ok(ts) +} + +async fn write_stamp( + mut path: PathBuf, + builder: TimestampBuilder, + merged: Timestamp, + header: DigestHeader, +) -> eyre::Result<()> { + let timestamp = builder.concat(merged.clone()); + let timestamp = DetachedTimestamp::from_parts(header, timestamp); + let timestamp = VersionedProof::::new(timestamp); + let mut buf = Vec::new(); + timestamp.encode(&mut buf)?; + path.add_extension("ots"); + let mut file = fs::File::create_new(path).await?; + file.write_all(&buf).await?; + Ok(()) +} diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs new file mode 100644 index 0000000..cc939a4 --- /dev/null +++ b/crates/cli/src/commands/upgrade.rs @@ -0,0 +1,99 @@ +use crate::client::CLIENT; +use clap::Args; +use eyre::bail; +use futures::TryFutureExt; +use reqwest::StatusCode; +use std::{fs, future::ready, path::PathBuf, time::Duration}; +use url::Url; +use uts_core::{ + codec::{ + Decode, Encode, VersionedProof, + v1::{Attestation, DetachedTimestamp, PendingAttestation, Timestamp}, + }, + utils::Hexed, +}; + +#[derive(Debug, Args)] +pub struct Upgrade { + /// Files to timestamp. May be specified multiple times. + #[arg(value_name = "FILE", num_args = 1..)] + files: Vec, + /// Timeout in seconds to wait for calendar responses. Default is 5 seconds. + #[arg(long = "timeout", default_value = "5")] + timeout: u64, +} + +impl Upgrade { + pub async fn run(self) -> eyre::Result<()> { + // timestamp files are small, so we can read them all synchronously before upgrading. + let files = self + .files + .iter() + .map(|path| fs::read(path)) + .collect::, _>>()?; + + let results = futures::future::join_all( + self.files + .iter() + .cloned() + .zip(files) + .into_iter() + .map(|(path, file)| upgrade_one(path, file, self.timeout)), + ) + .await + .into_iter() + .collect::>(); + for (path, result) in self.files.iter().zip(results) { + match result { + Ok(_) => eprintln!("Upgraded: {}", path.display()), + Err(e) => eprintln!("Failed to upgrade {}: {e}", path.display()), + } + } + Ok(()) + } +} + +async fn upgrade_one(path: PathBuf, file: Vec, timeout: u64) -> eyre::Result<()> { + let mut proof = VersionedProof::::decode(&mut &*file)?; + + for step in proof.proof.pending_attestations_mut() { + let pending_uri = { + let Timestamp::Attestation(attestation) = step else { + unreachable!("bug: PendingAttestationIterMut should only yield Attestations"); + }; + let commitment = attestation.value().expect("finalized when decode"); + let pending_uri = PendingAttestation::from_raw(&*attestation)?.uri; + Url::parse(&pending_uri)?.join(&format!("timestamp/{}", Hexed(commitment)))? + }; + + let result = CLIENT + .get(pending_uri) + .header("Accept", "application/vnd.opentimestamps.v1") + .timeout(Duration::from_secs(timeout)) + .send() + .and_then(|r| ready(r.error_for_status())) + .and_then(|r| r.bytes()) + .await; + + match result { + Ok(response) => { + let attestation = Timestamp::decode(&mut &*response)?; + *step = Timestamp::merge(vec![attestation, step.clone()]) + } + Err(e) => { + if let Some(status) = e.status() + && status == StatusCode::NOT_FOUND + { + bail!("calendar not ready yet."); + } + return Err(e.into()); + } + } + } + + let mut buf = Vec::new(); + proof.encode(&mut buf)?; + tokio::fs::write(path, buf).await?; + + Ok(()) +} diff --git a/crates/cli/src/commands/verify.rs b/crates/cli/src/commands/verify.rs new file mode 100644 index 0000000..3f817a3 --- /dev/null +++ b/crates/cli/src/commands/verify.rs @@ -0,0 +1,146 @@ +use alloy_provider::ProviderBuilder; +use clap::Args; +use digest::{Digest, DynDigest}; +use eyre::bail; +use jiff::{Timestamp, tz::TimeZone}; +use std::{ + fs::File, + io::{BufReader, Read}, + path::PathBuf, + process, +}; +use uts_core::{ + codec::{ + Decode, Reader, VersionedProof, + v1::{ + Attestation, DetachedTimestamp, EthereumUTSAttestation, PendingAttestation, opcode::*, + }, + }, + utils::Hexed, + verifier::{AttestationVerifier, EthereumUTSVerifier}, +}; + +#[derive(Debug, Args)] +pub struct Verify { + file: PathBuf, + stamp_file: Option, + /// Optional Ethereum provider URL for verifying Ethereum UTS attestations. + /// If not provided, a default provider will be used based on the chain ID. + #[arg(long)] + eth_provider: Option, +} + +impl Verify { + pub async fn run(self) -> eyre::Result<()> { + let stamp_file = self.stamp_file.unwrap_or_else(|| { + let mut default = self.file.clone(); + default.add_extension("ots"); + default + }); + let timestamp = + VersionedProof::::decode(&mut Reader(File::open(stamp_file)?))? + .proof; + + let digest_header = timestamp.header(); + let mut hasher = match digest_header.kind().tag() { + SHA1 => Box::new(sha1::Sha1::new()) as Box, + RIPEMD160 => Box::new(ripemd::Ripemd160::new()) as Box, + SHA256 => Box::new(sha2::Sha256::new()) as Box, + KECCAK256 => Box::new(sha3::Keccak256::new()) as Box, + _ => bail!("Unsupported digest type: {}", digest_header.kind()), + }; + + let mut file = BufReader::new(File::open(self.file)?); + let mut buffer = [0u8; 64 * 1024]; // 64KB buffer + loop { + let bytes_read = file.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + hasher.update(&buffer[..bytes_read]); + } + let expected = hasher.finalize(); + + if *expected != *digest_header.digest() { + eprintln!( + "Digest mismatch! Expected: {}, Found: {}", + Hexed(&expected), + Hexed(digest_header.digest()) + ); + process::exit(1); + } + eprintln!("Digest matches: {}", Hexed(&expected)); + + timestamp.try_finalize()?; + + for attestation in timestamp.attestations() { + if attestation.tag == PendingAttestation::TAG { + continue; // skip pending attestations + } + + if attestation.tag == EthereumUTSAttestation::TAG { + let eth_attestation = EthereumUTSAttestation::from_raw(&attestation)?; + eprintln!("Attested by {eth_attestation}"); + let provider_url = if let Some(url) = self.eth_provider.as_deref() { + url + } else { + match eth_attestation.chain.id() { + 1 => "https://0xrpc.io/eth", + 11155111 => "https://0xrpc.io/sep", + 534352 => "https://rpc.scroll.io", + 534351 => "https://sepolia-rpc.scroll.io", + _ => bail!("Unsupported chain: {}", eth_attestation.chain), + } + }; + let provider = ProviderBuilder::new().connect(provider_url).await?; + let verifier = EthereumUTSVerifier::new(provider).await?; + let result = verifier + .verify(ð_attestation, attestation.value().unwrap()) + .await?; + if let Some(block_number) = result.block_number { + if let Some(block_hash) = result.block_hash { + eprintln!("\tblock: #{block_number} {block_hash}"); + } else { + eprintln!("\tblock: {block_number}"); + } + } + if let Some(log_index) = result.log_index { + eprintln!("\tlog index: {log_index}"); + } + if let Some(transaction_hash) = result.transaction_hash { + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!("\ttransaction: {etherscan_url}/tx/{transaction_hash}"); + } else { + eprintln!("\ttransaction hash: {transaction_hash}"); + } + } + + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!( + "\tuts contract: {etherscan_url}/address/{}", + result.inner.address + ); + } else { + eprintln!("\tuts contract: {}", result.inner.address); + } + if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { + eprintln!( + "\ttx sender: {etherscan_url}/address/{}", + result.inner.sender + ); + } else { + eprintln!("\ttx sender: {}", result.inner.sender); + } + let ts = Timestamp::from_second(result.inner.timestamp.to())?; + let zdt = ts.to_zoned(TimeZone::system()); + eprintln!("\ttime attested: {zdt}"); + eprintln!("\tmerkle root: {}", result.inner.root); + continue; + } + + eprintln!("Unverifiable attestation: {attestation}"); + } + + Ok(()) + } +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs new file mode 100644 index 0000000..810c9a2 --- /dev/null +++ b/crates/cli/src/main.rs @@ -0,0 +1,19 @@ +//! UTS Cli +use crate::commands::Commands; +use clap::Parser; + +mod client; +mod commands; + +#[derive(Debug, Parser)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + color_eyre::install()?; + + Cli::parse().command.run().await +} diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 87103c6..1d3bb43 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -9,16 +9,6 @@ name = "uts-core" repository.workspace = true version.workspace = true -[[bin]] -name = "uts-info" -path = "src/bin/uts_info.rs" -required-features = ["std"] - -[[bin]] -name = "uts-verifier" -path = "src/bin/uts_verifier.rs" -required-features = ["verifier-bin-deps"] - [dependencies] alloy-chains = { workspace = true } alloy-primitives = { workspace = true } @@ -27,11 +17,8 @@ alloy-rpc-types-eth = { workspace = true, optional = true } alloy-sol-types = { workspace = true, optional = true } auto_impl.workspace = true bytes = { workspace = true, optional = true } -color-eyre = { workspace = true, optional = true } digest.workspace = true -eyre = { workspace = true, optional = true } hex.workspace = true -jiff = { workspace = true, optional = true } once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true ripemd.workspace = true @@ -43,27 +30,19 @@ sha3.workspace = true thiserror.workspace = true tokio = { workspace = true, optional = true } tracing = { workspace = true, optional = true } +uts-bmt = { workspace = true } uts-contracts = { workspace = true, optional = true } [features] bytes = ["dep:bytes"] default = ["std"] ethereum-uts-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-rpc-types-eth", "dep:alloy-sol-types", "dep:uts-contracts"] +io-utils = ["dep:tokio", "tokio/fs"] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] std = [] tracing = ["dep:tracing"] verifier = [] -verifier-bin-deps = [ - "dep:eyre", - "dep:tokio", - "dep:color-eyre", - "dep:jiff", - "std", - "ethereum-uts-verifier", - "tokio/full", -] - [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } opentimestamps = { git = "https://github.com/opentimestamps/rust-opentimestamps" } diff --git a/crates/core/src/bin/uts_info.rs b/crates/core/src/bin/uts_info.rs deleted file mode 100644 index c7d11ea..0000000 --- a/crates/core/src/bin/uts_info.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) The OpenTimestamps developers -// Copyright (C) The ots-rs developers -// SPDX-License-Identifier: MIT OR Apache-2.0 - -//! # OpenTimestamps Viewer -//! -//! Simple application to open an OTS info file and dump its contents -//! to stdout in a human-readable format - -use std::{ - env, fs, io, - io::{BufReader, Seek}, - process, -}; -use uts_core::codec::{ - Decode, Reader, VersionedProof, - v1::{DetachedTimestamp, Timestamp}, -}; - -fn main() { - let args: Vec = env::args().collect(); - if args.len() != 2 { - println!("Usage: {} ", args[0]); - process::exit(1); - } - - let mut fh = match fs::File::open(&args[1]) { - Ok(fh) => BufReader::new(fh), - Err(e) => { - println!("Failed to open {}: {}", args[1], e); - process::exit(1); - } - }; - - match VersionedProof::::decode(&mut Reader(&mut fh)) { - Ok(ots) => { - println!("OTS Detached Timestamp found:"); - println!("{ots}"); - return; - } - Err(e) => { - println!( - "Not a valid Detached Timestamp OTS file (trying raw timestamp): {}\n", - e - ); - } - }; - - fh.seek(io::SeekFrom::Start(0)).unwrap(); - - match Timestamp::decode(&mut Reader(&mut fh)) { - Ok(ots) => { - println!("Raw Timestamp found:"); - println!("{ots}"); - } - Err(e) => { - println!("Failed to parse {}: {}", args[1], e); - process::exit(1); - } - } -} diff --git a/crates/core/src/bin/uts_verifier.rs b/crates/core/src/bin/uts_verifier.rs deleted file mode 100644 index 99049b5..0000000 --- a/crates/core/src/bin/uts_verifier.rs +++ /dev/null @@ -1,141 +0,0 @@ -use alloy_provider::ProviderBuilder; -use digest::{Digest, DynDigest}; -use jiff::{Timestamp, tz::TimeZone}; -use std::{ - env, fs, - io::{BufReader, Read}, - process, -}; -use uts_core::{ - codec::{ - Decode, Reader, VersionedProof, - v1::{ - Attestation, DetachedTimestamp, EthereumUTSAttestation, PendingAttestation, opcode::*, - }, - }, - utils::Hexed, - verifier::{AttestationVerifier, EthereumUTSVerifier}, -}; - -#[tokio::main] -async fn main() -> eyre::Result<()> { - color_eyre::install()?; - - let args: Vec = env::args().collect(); - if args.len() < 2 { - eprintln!("Usage: {} [eth provider url]", args[0]); - process::exit(1); - } - - let mut fh = BufReader::new(if args.len() >= 3 { - fs::File::open(&args[2])? - } else { - fs::File::open(format!("{}.ots", &args[1]))? - }); - let timestamp = VersionedProof::::decode(&mut Reader(&mut fh))?.proof; - - let digest_header = timestamp.header(); - let mut hasher = match digest_header.kind().tag() { - SHA1 => Box::new(sha1::Sha1::new()) as Box, - RIPEMD160 => Box::new(ripemd::Ripemd160::new()) as Box, - SHA256 => Box::new(sha2::Sha256::new()) as Box, - KECCAK256 => Box::new(sha3::Keccak256::new()) as Box, - _ => { - eprintln!("Unsupported digest type: {}", digest_header.kind()); - process::exit(1); - } - }; - - let mut file = BufReader::new(fs::File::open(&args[1])?); - let mut buffer = [0u8; 64 * 1024]; // 64KB buffer - loop { - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - hasher.update(&buffer[..bytes_read]); - } - let expected = hasher.finalize(); - - if *expected != *digest_header.digest() { - eprintln!( - "Digest mismatch! Expected: {}, Found: {}", - Hexed(&expected), - Hexed(digest_header.digest()) - ); - process::exit(1); - } - eprintln!("Digest matches: {}", Hexed(&expected)); - - timestamp.try_finalize()?; - - for attestation in timestamp.attestations() { - if attestation.tag == PendingAttestation::TAG { - continue; // skip pending attestations - } - - if attestation.tag == EthereumUTSAttestation::TAG { - let eth_attestation = EthereumUTSAttestation::from_raw(&attestation)?; - eprintln!("Attested by {eth_attestation}"); - let provider_url = if args.len() >= 4 { - &*args[3] - } else { - match eth_attestation.chain.id() { - 1 => "https://0xrpc.io/eth", - 11155111 => "https://0xrpc.io/sep", - 534352 => "https://rpc.scroll.io", - 534351 => "https://sepolia-rpc.scroll.io", - _ => panic!("Unsupported chain: {}", eth_attestation.chain), - } - }; - let provider = ProviderBuilder::new().connect(provider_url).await?; - let verifier = EthereumUTSVerifier::new(provider).await?; - let result = verifier - .verify(ð_attestation, attestation.value().unwrap()) - .await?; - if let Some(block_number) = result.block_number { - if let Some(block_hash) = result.block_hash { - eprintln!("\tblock: #{block_number} {block_hash}"); - } else { - eprintln!("\tblock: {block_number}"); - } - } - if let Some(log_index) = result.log_index { - eprintln!("\tlog index: {log_index}"); - } - if let Some(transaction_hash) = result.transaction_hash { - if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { - eprintln!("\ttransaction: {etherscan_url}/tx/{transaction_hash}"); - } else { - eprintln!("\ttransaction hash: {transaction_hash}"); - } - } - - if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { - eprintln!( - "\tuts contract: {etherscan_url}/address/{}", - result.inner.address - ); - } else { - eprintln!("\tuts contract: {}", result.inner.address); - } - if let Some((_, etherscan_url)) = eth_attestation.chain.etherscan_urls() { - eprintln!( - "\ttx sender: {etherscan_url}/address/{}", - result.inner.sender - ); - } else { - eprintln!("\ttx sender: {}", result.inner.sender); - } - let ts = Timestamp::from_second(result.inner.timestamp.to())?; - let zdt = ts.to_zoned(TimeZone::system()); - eprintln!("\ttime attested: {zdt}"); - eprintln!("\tmerkle root: {}", result.inner.root); - continue; - } - - eprintln!("Unverifiable attestation: {attestation}"); - } - - Ok(()) -} diff --git a/crates/core/src/codec/proof.rs b/crates/core/src/codec/proof.rs index 7716712..ac0ec6a 100644 --- a/crates/core/src/codec/proof.rs +++ b/crates/core/src/codec/proof.rs @@ -21,6 +21,33 @@ pub struct VersionedProof, A: Allocator = Global> { allocator: A, } +impl> VersionedProof { + /// Creates a new versioned proof with the global allocator. + pub fn new(proof: T) -> Self { + VersionedProof { + proof, + allocator: Global, + } + } +} + +impl, A: Allocator> VersionedProof { + /// Creates a new versioned proof with the specified allocator. + pub fn new_with_allocator(proof: T, allocator: A) -> Self { + VersionedProof { proof, allocator } + } + + /// Returns a reference to the proof payload. + pub fn proof(&self) -> &T { + &self.proof + } + + /// Returns a reference to the allocator used by this proof. + pub fn allocator(&self) -> &A { + &self.allocator + } +} + impl, A: Allocator + Clone> DecodeIn for VersionedProof { fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result { decoder.assert_magic()?; diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index 13dae63..9091bee 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -12,7 +12,7 @@ pub use attestation::{ }; pub use detached_timestamp::DetachedTimestamp; pub use digest::DigestHeader; -pub use timestamp::{Step, Timestamp}; +pub use timestamp::{Step, Timestamp, builder::TimestampBuilder}; /// Error indicating that finalization of a timestamp failed due to conflicting inputs. #[derive(Debug)] diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 528c499..353ac91 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -4,7 +4,7 @@ use crate::codec::{ }; use alloc::alloc::{Allocator, Global}; use core::{fmt, fmt::Formatter}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; /// A file containing a timestamp for another file /// Contains a timestamp, along with a header and the digest of the file. @@ -29,7 +29,9 @@ impl DecodeIn for DetachedTimestamp { ) -> Result { let header = DigestHeader::decode(decoder)?; let timestamp = Timestamp::decode_in(decoder, alloc)?; - Ok(DetachedTimestamp { header, timestamp }) + let detached = DetachedTimestamp { header, timestamp }; + detached.finalize(); + Ok(detached) } } @@ -120,6 +122,12 @@ impl Deref for DetachedTimestamp { } } +impl DerefMut for DetachedTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.timestamp + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/src/codec/v1/digest.rs b/crates/core/src/codec/v1/digest.rs index 05792fa..93a6118 100644 --- a/crates/core/src/codec/v1/digest.rs +++ b/crates/core/src/codec/v1/digest.rs @@ -1,10 +1,14 @@ use crate::{ - codec::{DecodeIn, Decoder, Encode, Encoder, v1::opcode::DigestOp}, + codec::{ + DecodeIn, Decoder, Encode, Encoder, + v1::opcode::{DigestOp, DigestOpExt}, + }, error::{DecodeError, EncodeError}, utils::Hexed, }; use alloc::alloc::Allocator; use core::fmt; +use digest::{Output, typenum::Unsigned}; /// Header describing the digest that anchors a timestamp. #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -14,9 +18,9 @@ use core::fmt; derive(serde::Serialize, serde::Deserialize) )] pub struct DigestHeader { - kind: DigestOp, + pub(crate) kind: DigestOp, #[cfg_attr(feature = "serde", serde_as(as = "serde_with::hex::Hex"))] - digest: [u8; 32], + pub(crate) digest: [u8; 32], } impl fmt::Debug for DigestHeader { @@ -35,6 +39,16 @@ impl fmt::Display for DigestHeader { } impl DigestHeader { + /// Creates a new digest header from the given digest output. + pub fn new(digest: Output) -> Self { + let mut digest_bytes = [0u8; 32]; + digest_bytes[..D::OutputSize::USIZE].copy_from_slice(&digest); + DigestHeader { + kind: D::OPCODE, + digest: digest_bytes, + } + } + /// Returns the digest opcode recorded in the header. pub fn kind(&self) -> DigestOp { self.kind diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index b7b0a34..237bd34 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -8,7 +8,7 @@ use crate::{ }; use alloc::{alloc::Allocator, vec::Vec}; use core::{fmt, hint::unreachable_unchecked}; -use digest::{Digest, OutputSizeUser, typenum::Unsigned}; +use digest::Digest; use ripemd::Ripemd160; use sha1::Sha1; use sha2::Sha256; @@ -234,6 +234,13 @@ impl DigestOp { } } +/// Extension trait for `Digest` implementors to get the corresponding `DigestOp`. +pub trait DigestOpExt: Digest { + const OPCODE: DigestOp; + + fn opcode() -> DigestOp; +} + macro_rules! define_opcodes { ($($val:literal => $variant:ident),* $(,)?) => { $( @@ -307,9 +314,10 @@ macro_rules! define_digest_opcodes { /// Returns the output length of the digest in bytes. #[inline] pub const fn output_size(&self) -> usize { + use digest::typenum::Unsigned; paste::paste! { match *self { - $( Self::$variant => <[<$variant:camel>] as OutputSizeUser>::OutputSize::USIZE, )* + $( Self::$variant => <[<$variant:camel>] as ::digest::OutputSizeUser>::OutputSize::USIZE, )* // SAFETY: unreachable as all variants are covered. _ => unsafe { unreachable_unchecked() } } @@ -336,6 +344,18 @@ macro_rules! define_digest_opcodes { } } } + paste::paste! { + $( + impl DigestOpExt for [<$variant:camel>] { + const OPCODE: DigestOp = DigestOp::$variant; + + #[inline] + fn opcode() -> DigestOp { + DigestOp::$variant + } + } + )* + } }; } @@ -343,7 +363,7 @@ macro_rules! impl_simple_step { ($variant:ident) => {paste::paste! { impl $crate::codec::v1::timestamp::builder::TimestampBuilder { #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] - pub fn [< $variant:lower >](self) -> Self { + pub fn [< $variant:lower >](&mut self) -> &mut Self { self.push_step(OpCode::[<$variant>]) } } @@ -359,7 +379,7 @@ macro_rules! impl_step_with_data { ($variant:ident) => {paste::paste! { impl $crate::codec::v1::timestamp::builder::TimestampBuilder { #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] - pub fn [< $variant:lower >](self, data: ::alloc::vec::Vec) -> Self { + pub fn [< $variant:lower >](&mut self, data: ::alloc::vec::Vec) -> &mut Self { self.push_immediate_step(OpCode::[<$variant>], data) } } diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 849b821..0ca4e2a 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -1,7 +1,10 @@ //! ** The implementation here is subject to change as this is a read-only version. ** use crate::{ - codec::v1::{FinalizationError, MayHaveInput, attestation::RawAttestation, opcode::OpCode}, + codec::v1::{ + Attestation, FinalizationError, MayHaveInput, PendingAttestation, + attestation::RawAttestation, opcode::OpCode, + }, utils::{Hexed, OnceLock}, }; use alloc::{alloc::Global, vec::Vec}; @@ -150,6 +153,16 @@ impl Timestamp { pub fn attestations(&self) -> AttestationIter<'_, A> { AttestationIter { stack: vec![self] } } + + /// Iterates over all pending attestation steps in this timestamp. + /// + /// # Note + /// + /// This iterator will yield `Timestamp` instead of `RawAttestation`. + #[inline] + pub fn pending_attestations_mut(&mut self) -> PendingAttestationIterMut<'_, A> { + PendingAttestationIterMut { stack: vec![self] } + } } impl Timestamp { @@ -276,6 +289,11 @@ impl Step { self.next.as_slice() } + /// Returns the next timestamps of this step. + pub fn next_mut(&mut self) -> &mut [Timestamp] { + self.next.as_mut_slice() + } + /// Returns the allocator used by this step. pub fn allocator(&self) -> &A { self.data.allocator() @@ -311,3 +329,29 @@ impl<'a, A: Allocator> Iterator for AttestationIter<'a, A> { None } } + +pub struct PendingAttestationIterMut<'a, A: Allocator> { + stack: Vec<&'a mut Timestamp>, +} + +impl<'a, A: Allocator> Iterator for PendingAttestationIterMut<'a, A> { + type Item = &'a mut Timestamp; + + fn next(&mut self) -> Option { + while let Some(ts) = self.stack.pop() { + match ts { + Timestamp::Step(step) => { + for next in step.next_mut().iter_mut().rev() { + self.stack.push(next); + } + } + Timestamp::Attestation(attestation) => { + if attestation.tag == PendingAttestation::TAG { + return Some(ts); + } + } + } + } + None + } +} diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index 9275f37..9c36254 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -1,11 +1,16 @@ //! Timestamp Builder use crate::{ - codec::v1::{Attestation, Timestamp, opcode::OpCode, timestamp::Step}, + codec::v1::{ + Attestation, Timestamp, + opcode::{DigestOpExt, OpCode}, + timestamp::Step, + }, error::EncodeError, utils::OnceLock, }; use alloc::alloc::{Allocator, Global}; +use uts_bmt::{NodePosition, SiblingIter}; #[derive(Debug, Clone)] pub struct TimestampBuilder { @@ -31,7 +36,7 @@ impl TimestampBuilder { /// # Panics /// /// Panics if the opcode is not an opcode with immediate data. - pub(crate) fn push_immediate_step(mut self, op: OpCode, data: Vec) -> Self { + pub(crate) fn push_immediate_step(&mut self, op: OpCode, data: Vec) -> &mut Self { assert!(op.has_immediate()); self.steps.push(LinearStep { op, data }); self @@ -44,7 +49,7 @@ impl TimestampBuilder { /// Panics if: /// - the opcode is control opcode /// - the opcode is an opcode with immediate data - pub(crate) fn push_step(mut self, op: OpCode) -> Self { + pub fn push_step(&mut self, op: OpCode) -> &mut Self { self.steps.push(LinearStep { op, data: Vec::new_in(self.allocator().clone()), @@ -52,6 +57,29 @@ impl TimestampBuilder { self } + /// Pushes a new digest step to the timestamp. + pub fn digest(&mut self) -> &mut Self { + self.push_step(D::OPCODE.to_opcode()); + self + } + + /// Pushes the steps corresponding to the given Merkle proof to the timestamp. + pub fn merkle_proof(&mut self, mut proof: SiblingIter<'_, D>) -> &mut Self { + let alloc = self.allocator().clone(); + while let Some((side, sibling_hash)) = proof.next() { + match side { + NodePosition::Left => self + .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(alloc.clone())) + .append(sibling_hash.to_vec_in(alloc.clone())), + NodePosition::Right => self + .prepend(sibling_hash.to_vec_in(alloc.clone())) + .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(alloc.clone())), + } + .digest::(); + } + self + } + /// Computes the commitment of the timestamp for the given input. /// /// In this context, the **commitment** is the deterministic result of @@ -85,9 +113,15 @@ impl TimestampBuilder { self, attestation: T, ) -> Result, EncodeError> { + let current = Timestamp::Attestation(attestation.to_raw_in(self.allocator().clone())?); + Ok(self.concat(current)) + } + + /// Append the given timestamp after the steps in the builder. + pub fn concat(self, timestamp: Timestamp) -> Timestamp { let alloc = self.allocator().clone(); - let mut current = Timestamp::Attestation(attestation.to_raw_in(alloc.clone())?); + let mut current = timestamp; for step in self.steps.into_iter().rev() { let step_node = Step { @@ -103,7 +137,7 @@ impl TimestampBuilder { current = Timestamp::Step(step_node); } - Ok(current) + current } #[inline] diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 6863042..1b50d53 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -3,3 +3,8 @@ pub use hex::Hexed; mod sync; pub use sync::OnceLock; + +mod hash; +#[cfg(feature = "io-utils")] +pub use hash::HashAsyncFsExt; +pub use hash::HashFsExt; diff --git a/crates/core/src/utils/hash.rs b/crates/core/src/utils/hash.rs new file mode 100644 index 0000000..5fc5d7d --- /dev/null +++ b/crates/core/src/utils/hash.rs @@ -0,0 +1,48 @@ +use digest::Digest; +use std::io::{self, Read}; + +pub trait HashFsExt { + fn update(&mut self, reader: R) -> io::Result<()>; +} + +impl HashFsExt for D { + fn update(&mut self, mut reader: R) -> io::Result<()> { + let mut buffer = [0u8; 64 * 1024]; // 64KB buffer + loop { + let bytes_read = reader.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + self.update(&buffer[..bytes_read]); + } + Ok(()) + } +} + +#[cfg(feature = "io-utils")] +pub trait HashAsyncFsExt { + fn update( + &mut self, + reader: R, + ) -> impl Future> + Send; +} + +#[cfg(feature = "io-utils")] +impl HashAsyncFsExt for D { + async fn update( + &mut self, + mut reader: R, + ) -> io::Result<()> { + use tokio::io::AsyncReadExt; + + let mut buffer = [0u8; 64 * 1024]; // 64KB buffer + loop { + let bytes_read = reader.read(&mut buffer).await?; + if bytes_read == 0 { + break; + } + self.update(&buffer[..bytes_read]); + } + Ok(()) + } +} diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index d050bc6..6211801 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::FlatMerkleTree; +use uts_bmt::UnorderdMerkleTree; 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,25 +81,25 @@ pub struct MerkleEntry<'a> { impl MerkleEntry<'_> { /// Get the Merkle tree from the entry - pub fn trie(&self) -> FlatMerkleTree + pub fn trie(&self) -> UnorderdMerkleTree 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 { FlatMerkleTree::from_raw_bytes(&self.trie) } + unsafe { UnorderdMerkleTree::from_raw_bytes(&self.trie) } } } /// Errors that can occur during storage operations #[derive(Debug, thiserror::Error)] pub enum StorageError { + /// Errors from RocksDB #[error(transparent)] Rocks(#[from] rocksdb::Error), + /// Errors from bitcode serialization/deserialization #[error(transparent)] Bitcode(#[from] bitcode::Error), - #[error("invalid data")] - InvalidData, } /// Extension trait for DB to load Merkle entries and leaf->root mappings @@ -237,7 +237,7 @@ where } debug_assert_eq!(buffer.len(), target_size); - let merkle_tree = FlatMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); + let merkle_tree = UnorderdMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); let storage = self.storage.clone(); let merkle_tree = tokio::task::spawn_blocking(move || { From ada5d4a8115ea4a28714c106e13af35965f13f2f Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sun, 22 Feb 2026 19:11:34 +0800 Subject: [PATCH 16/74] Create README.md for UTS CLI tool Added README.md for UTS CLI tool with usage instructions and command reference. --- crates/cli/README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 crates/cli/README.md diff --git a/crates/cli/README.md b/crates/cli/README.md new file mode 100644 index 0000000..a6097dd --- /dev/null +++ b/crates/cli/README.md @@ -0,0 +1,73 @@ +# UTS Cli Tool + +## Quick Start + +### 1. Create a Timestamp (`stamp`) + +Submit files to remote calendar servers for initial attestation: + +```bash +# Timestamp one or more files +uts stamp file1.txt file2.zip + +# Specify custom calendars and a quorum requirement +uts stamp -c https://calendar.example.com -m 1 document.pdf + +# Use a specific hashing algorithm +uts stamp --hasher sha256 photo.jpg + +``` + +This will create a corresponding `.ots` proof file (e.g., `document.pdf.ots`). + +### 2. Upgrade a Proof (`upgrade`) + +Initial proofs are often "pending." Once the calendar server commits the Merkle root to a blockchain, you must upgrade the proof to include the full path to the block: + +```bash +uts upgrade document.pdf.ots + +``` + +### 3. Verify a Proof (`verify`) + +Verify that a file matches its timestamp proof and check the attestation status on the calendar or blockchain: + +```bash +# Automatically finds the matching .ots file +uts verify document.pdf + +# Specify an Ethereum RPC provider for on-chain verification +uts verify document.pdf --eth-provider https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY + +``` + +### 4. Inspect a Proof (`inspect`) + +View the internal structure, opcodes, and attestation paths of an `.ots` file in a human-readable format: + +```bash +uts inspect document.pdf.ots + +``` + +## Command Reference + +### `stamp` + +| Argument | Description | Default | +| --- | --- | --- | +| `-c, --calendar` | Remote calendar URL (can be specified multiple times) | Built-in list | +| `-m` | Minimum quorum of calendars required | 1 | +| `-H, --hasher` | Hashing algorithm (`keccak256`, `sha256`, `sha1`, `ripemd160`) | `keccak256` | +| `--timeout` | Timeout in seconds for calendar responses | 5 | + +### `verify` + +| Argument | Description | +| --- | --- | +| `file` | The target file to verify | +| `stamp_file` | (Optional) Explicit path to the `.ots` file | +| `--eth-provider` | (Optional) Ethereum RPC URL for UTS contract verification | + + From 2c000350d73e4728683079bfd8fc03928343c626 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Wed, 25 Feb 2026 12:08:31 +0800 Subject: [PATCH 17/74] feat: ts sdk (#26) * init * use noble * add encoder * add decoder * add tests * add sdk * complete sdk * fix * apply review * ignore lock file * add eslint * apply review * update readme --- .editorconfig | 7 + .gitignore | 1 + .pre-commit-config.yaml | 6 + .prettierignore | 8 + .prettierrc.toml | 2 + .vscode/settings.json | 2 +- Cargo.lock | 23 - README.md | 1 - apps/web/package.json | 2 +- apps/web/src/components/HelloWorld.vue | 6 +- apps/web/tsconfig.json | 5 +- 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 +- dev-docs/e2e.md | 17 +- dev-docs/milestones/M1-mvp-server.md | 2 +- dev-docs/ots-api.md | 19 +- eslint.config.js | 377 +++++ package.json | 10 +- packages/sdk/fixtures/test.ots | Bin 0 -> 3341 bytes packages/{uts-sdk => sdk}/package.json | 20 +- packages/sdk/src/bmt.ts | 276 ++++ packages/sdk/src/codec/constants.ts | 46 + packages/sdk/src/codec/decode.ts | 384 +++++ packages/sdk/src/codec/encode.ts | 315 ++++ packages/sdk/src/errors.ts | 87 ++ packages/sdk/src/index.ts | 44 + packages/sdk/src/rpc/btc.ts | 127 ++ packages/sdk/src/sdk.ts | 726 +++++++++ packages/sdk/src/types.ts | 120 ++ packages/sdk/test/btc.test.ts | 23 + packages/sdk/test/codec.test.ts | 50 + packages/sdk/test/merkle.test.ts | 68 + packages/sdk/test/sdk.test.ts | 78 + packages/{uts-sdk => sdk}/tsconfig.json | 2 +- packages/sdk/vitest.config.ts | 11 + packages/uts-sdk/src/index.ts | 71 - pnpm-lock.yaml | 1864 +++++++++++++++++++++-- pnpm-workspace.yaml | 2 +- tsconfig.json | 56 +- 44 files changed, 4605 insertions(+), 473 deletions(-) create mode 100644 .editorconfig create mode 100644 .prettierignore create mode 100644 .prettierrc.toml delete mode 100644 crates/core-wasm/Cargo.toml delete mode 100644 crates/core-wasm/src/lib.rs create mode 100644 eslint.config.js create mode 100644 packages/sdk/fixtures/test.ots rename packages/{uts-sdk => sdk}/package.json (50%) create mode 100644 packages/sdk/src/bmt.ts create mode 100644 packages/sdk/src/codec/constants.ts create mode 100644 packages/sdk/src/codec/decode.ts create mode 100644 packages/sdk/src/codec/encode.ts create mode 100644 packages/sdk/src/errors.ts create mode 100644 packages/sdk/src/index.ts create mode 100644 packages/sdk/src/rpc/btc.ts create mode 100644 packages/sdk/src/sdk.ts create mode 100644 packages/sdk/src/types.ts create mode 100644 packages/sdk/test/btc.test.ts create mode 100644 packages/sdk/test/codec.test.ts create mode 100644 packages/sdk/test/merkle.test.ts create mode 100644 packages/sdk/test/sdk.test.ts rename packages/{uts-sdk => sdk}/tsconfig.json (81%) create mode 100644 packages/sdk/vitest.config.ts delete mode 100644 packages/uts-sdk/src/index.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ad9775b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.{js,jsx,ts,tsx,css,scss,html,json,md}] +charset = utf-8 +insert_final_newline = true +end_of_line = lf +indent_style = space +indent_size = 2 +max_line_length = 80 \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6c829e..ab159c4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ broadcast node_modules dist *.tsbuildinfo +.eslintcache # Project files .db diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcfa3b3..04e49ab 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: npx 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..0b79f98 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +pnpm-lock.yaml + +**/*.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/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/README.md b/README.md index 86eaa5a..9bc639b 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,4 @@ Universal Timestamps is a super set of [opentimestamps](https://opentimestamps.o - Rust >= 1.94.0-nightly (e7d44143a 2025-12-24) - Cargo >= 1.94.0-nightly (3861f60f6 2025-12-19) -- [wasm-pack](https://drager.github.io/wasm-pack/installer/) - pnpm >= 10.26.2 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/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([[]])