From c458763d869b3df0f205202db50a3db6102910f9 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 16 Mar 2026 10:55:12 +0800 Subject: [PATCH 01/11] add ots default calendars --- Cargo.lock | 2 ++ crates/cli/Cargo.toml | 3 ++ crates/cli/src/commands/inspect.rs | 7 ++-- crates/cli/src/commands/stamp.rs | 52 +++++++++++++++++---------- crates/cli/src/commands/upgrade.rs | 56 +++++++++++++++++++++++------- crates/cli/src/commands/verify.rs | 17 ++++----- crates/cli/src/main.rs | 14 +++++++- packages/sdk-go/sdk.go | 7 ++++ packages/sdk-py/src/uts_sdk/sdk.py | 7 ++++ packages/sdk-ts/src/sdk.ts | 11 +++--- 10 files changed, 128 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3da33ba..0554c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6757,6 +6757,8 @@ dependencies = [ "sha2 0.11.0-rc.5", "sha3 0.11.0-rc.8", "tokio", + "tracing", + "tracing-subscriber", "url", "uts-bmt", "uts-contracts", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index be2e1b1..cf1dbf5 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -33,6 +33,8 @@ sha1 = { workspace = true } sha2 = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } url = { workspace = true } uts-bmt = { workspace = true } uts-contracts = { workspace = true } @@ -40,6 +42,7 @@ uts-core = { workspace = true, features = ["std", "eas-verifier", "io-utils"] } [features] default = ["reqwest-rustls"] +performance = ["tracing/max_level_info"] reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/reqwest-native-tls"] reqwest-native-tls-no-alpn = ["reqwest/native-tls-no-alpn"] diff --git a/crates/cli/src/commands/inspect.rs b/crates/cli/src/commands/inspect.rs index ff47f6a..f46c2bd 100644 --- a/crates/cli/src/commands/inspect.rs +++ b/crates/cli/src/commands/inspect.rs @@ -1,5 +1,6 @@ use clap::Args; use std::{fs, io, io::Seek, path::PathBuf}; +use tracing::{error, info}; use uts_core::codec::{ Decode, Reader, VersionedProof, v1::{DetachedTimestamp, Timestamp}, @@ -17,18 +18,18 @@ impl Inspect { match VersionedProof::::decode(&mut Reader(&mut fh)) { Ok(ots) => { - eprintln!("OTS Detached Timestamp found:\n{ots}"); + info!("OTS Detached Timestamp found:\n{ots}"); return Ok(()); } Err(e) => { - eprintln!("Not a valid Detached Timestamp OTS file (trying raw timestamp): {e}\n"); + error!("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}"); + info!("Raw Timestamp found:\n{raw}"); Ok(()) } } diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs index c5356b6..805db0a 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -5,6 +5,7 @@ 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 tracing::{error, info}; use url::Url; use uts_bmt::MerkleTree; use uts_core::{ @@ -15,8 +16,18 @@ use uts_core::{ utils::{HashAsyncFsExt, Hexed}, }; -static DEFAULT_CALENDARS: LazyLock> = - LazyLock::new(|| vec![Url::parse("https://lgm1.calendar.test.timestamps.now/").unwrap()]); +static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { + vec![ + Url::parse("https://lgm1.calendar.test.timestamps.now/").unwrap(), + // Run by Peter Todd + Url::parse("https://a.pool.opentimestamps.org/").unwrap(), + Url::parse("https://b.pool.opentimestamps.org/").unwrap(), + // Run by Riccardo Casatta + Url::parse("https://a.pool.eternitywall.com/").unwrap(), + // Run by Bull Bitcoin + Url::parse("https://ots.btc.catallaxy.com/").unwrap(), + ] +}); #[derive(Debug, Args)] pub struct Stamp { @@ -27,8 +38,8 @@ pub struct Stamp { #[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, + #[arg(short = 'm')] + quorum: Option, /// Hasher to use when digesting files. Default is Keccak256. #[arg(short = 'H', long = "hasher", default_value = "keccak256")] hasher: Hasher, @@ -61,6 +72,7 @@ impl Stamp { D: Digest + FixedOutputReset + DigestOpExt + Send, Output: Pod + Copy, { + info!("Hashing files..."); let digests = futures::future::join_all(self.files.iter().map(|f| hash_file::(f.clone()))) .await @@ -68,7 +80,7 @@ impl Stamp { .collect::, _>>()?; for (header, path) in digests.iter().zip(self.files.iter()) { - eprintln!("File: {} {}", header, path.display()); + info!("- [{}] {header}", path.display()); } let mut builders: HashMap = HashMap::from_iter( @@ -92,7 +104,7 @@ impl Stamp { let internal_tire = MerkleTree::::new(&nonced_digest); let root = internal_tire.root(); - eprintln!("Internal Merkle root: {}", Hexed(root)); + info!("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"); @@ -105,10 +117,13 @@ impl Stamp { &*self.calendars }; - if self.quorum > calendars.len() { + let quorum = self.quorum.unwrap_or_else(|| { + // Default quorum is 2/3 of the number of calendars, rounded up + (calendars.len() * 2).div_ceil(3) + }); + if quorum > calendars.len() { eyre::bail!( - "Quorum of {} cannot be achieved with only {} calendars", - self.quorum, + "Quorum of {quorum} cannot be achieved with only {} calendars", self.calendars.len() ); } @@ -122,11 +137,10 @@ impl Stamp { .into_iter() .filter_map(|res| res.ok()) .collect::>(); - if stamps.len() < self.quorum { + if stamps.len() < quorum { eyre::bail!( - "Only received {} valid responses from calendars, which does not meet the quorum of {}", + "Only received {} valid responses from calendars, which does not meet the quorum of {quorum}", stamps.len(), - self.quorum ); } let merged = if stamps.len() == 1 { @@ -142,8 +156,8 @@ impl Stamp { .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(_) => info!("[{}] timestamped", path.display()), + Err(e) => error!("[{}] failed to write timestamp for {e}", path.display()), } } @@ -159,7 +173,7 @@ async fn hash_file(path: PathBuf) -> io::Result eyre::Result { - eprintln!("Submitting to remote calendar: {calendar}"); + info!("Submitting to remote calendar: {calendar}"); let url = calendar.join("digest")?; let response = CLIENT .post(url) @@ -172,16 +186,16 @@ async fn request_calendar(calendar: Url, timeout: u64, root: &[u8]) -> eyre::Res .await .inspect_err(|e| { if e.is_status() { - eprintln!("Calendar {calendar} responded with error: {e}"); + error!("Calendar {calendar} responded with error: {e}"); } else if e.is_timeout() { - eprintln!("Calendar {calendar} timed out after {timeout} seconds"); + error!("Calendar {calendar} timed out after {timeout} seconds"); } else { - eprintln!("Failed to submit to calendar {calendar}: {e}"); + error!("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}"); + error!("Failed to decode response from calendar {calendar}: {e}"); })?; Ok(ts) } diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index 8ea9b20..21dd896 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -1,9 +1,9 @@ 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 tracing::{error, info, warn}; use url::Url; use uts_core::{ codec::{ @@ -18,6 +18,10 @@ pub struct Upgrade { /// Files to timestamp. May be specified multiple times. #[arg(value_name = "FILE", num_args = 1..)] files: Vec, + /// Whether to keep pending attestations in the proof. Default is false, which means pending + /// attestations will be removed from the proof once the upgrade process is complete. + #[arg(short, long, default_value_t = false)] + keep_pending: bool, /// Timeout in seconds to wait for calendar responses. Default is 5 seconds. #[arg(long = "timeout", default_value = "5")] timeout: u64, @@ -37,36 +41,44 @@ impl Upgrade { .iter() .cloned() .zip(files) - .map(|(path, file)| upgrade_one(path, file, self.timeout)), + .map(|(path, file)| upgrade_one(path, file, self.keep_pending, 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()), + if let Err(e) = result { + error!("[{}] failed to upgrade: {e}", path.display()) } } Ok(()) } } -async fn upgrade_one(path: PathBuf, file: Vec, timeout: u64) -> eyre::Result<()> { +async fn upgrade_one( + path: PathBuf, + file: Vec, + keep_pending: bool, + timeout: u64, +) -> eyre::Result<()> { let mut proof = VersionedProof::::decode(&mut &*file)?; for step in proof.proof.pending_attestations_mut() { - let pending_uri = { + let (calendar_server, retrieve_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 calendar_server = PendingAttestation::from_raw(&*attestation)?.uri; + + let retrieve_uri = + Url::parse(&calendar_server)?.join(&format!("timestamp/{}", Hexed(commitment)))?; + + (calendar_server, retrieve_uri) }; let result = CLIENT - .get(pending_uri) + .get(retrieve_uri) .header("Accept", "application/vnd.opentimestamps.v1") .timeout(Duration::from_secs(timeout)) .send() @@ -77,15 +89,33 @@ async fn upgrade_one(path: PathBuf, file: Vec, timeout: u64) -> eyre::Result match result { Ok(response) => { let attestation = Timestamp::decode(&mut &*response)?; - *step = Timestamp::merge(vec![attestation, step.clone()]) + info!( + "[{}] successfully upgraded pending attestation from {calendar_server}", + path.display() + ); + + *step = if keep_pending { + Timestamp::merge(vec![attestation, step.clone()]) + } else { + attestation + }; } Err(e) => { if let Some(status) = e.status() && status == StatusCode::NOT_FOUND { - bail!("calendar not ready yet."); + // calendar not ready yet + info!( + "[{}] attestation from {calendar_server} not ready yet, skipping", + path.display() + ); + continue; } - return Err(e.into()); + warn!( + "[{}] failed to upgrade pending attestation from {calendar_server}: {e}", + path.display() + ); + continue; } } } diff --git a/crates/cli/src/commands/verify.rs b/crates/cli/src/commands/verify.rs index 0635c8f..c4139c8 100644 --- a/crates/cli/src/commands/verify.rs +++ b/crates/cli/src/commands/verify.rs @@ -10,6 +10,7 @@ use std::{ path::PathBuf, process, }; +use tracing::{error, info, warn}; use uts_contracts::eas::EAS_ADDRESSES; use uts_core::{ codec::{ @@ -69,14 +70,14 @@ impl Verify { let expected = hasher.finalize(); if *expected != *digest_header.digest() { - eprintln!( + error!( "Digest mismatch! Expected: {}, Found: {}", Hexed(&expected), Hexed(digest_header.digest()) ); process::exit(1); } - eprintln!("Digest matches: {}", Hexed(&expected)); + info!("Digest matches: {}", Hexed(&expected)); timestamp.try_finalize()?; @@ -85,7 +86,7 @@ impl Verify { continue; // skip pending attestations } if attestation.tag != EASAttestation::TAG && attestation.tag != EASTimestamped::TAG { - eprintln!("Unknown attestation type: {}", Hexed(&attestation.tag)); + warn!("Unknown attestation type: {}", Hexed(&attestation.tag)); } let expected = attestation @@ -125,19 +126,19 @@ impl Verify { if attestation.tag == EASAttestation::TAG { let eas_attestation = EASAttestation::from_raw(attestation)?; let result = verifier.verify(&eas_attestation, expected).await?; - eprintln!("EAS Onchain Attestation: {}", result.uid); + info!("EAS Onchain Attestation: {}", result.uid); time = result.time; - eprintln!("\tattester: {}", result.attester); + info!("\tattester: {}", result.attester); } else { let timestamped = EASTimestamped::from_raw(attestation)?; time = verifier.verify(×tamped, expected).await?; - eprintln!("EAS Timestamped"); + info!("EAS Timestamped"); } let ts = Timestamp::from_second(time.try_into().context("i64 overflow")?)?; let zdt = ts.to_zoned(TimeZone::system()); - eprintln!("\ttime attested: {zdt}"); - eprintln!("\tmerkle root: {}", Hexed(&expected)); + info!("\ttime attested: {zdt}"); + info!("\tmerkle root: {}", Hexed(&expected)); } Ok(()) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 1b4a7ff..36ea293 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,6 +1,8 @@ //! UTS Cli use crate::commands::Commands; use clap::Parser; +use tracing::warn; +use tracing_subscriber::{EnvFilter, filter::LevelFilter}; mod client; mod commands; @@ -15,6 +17,16 @@ struct Cli { async fn main() -> eyre::Result<()> { color_eyre::install()?; - eprintln!("UTS is current in TESTING, not for production use."); + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) + .without_time() + .with_target(false) + .init(); + + warn!("UTS is current in TESTING, not for production use."); Cli::parse().command.run().await } diff --git a/packages/sdk-go/sdk.go b/packages/sdk-go/sdk.go index ea9b0aa..ef9e753 100644 --- a/packages/sdk-go/sdk.go +++ b/packages/sdk-go/sdk.go @@ -23,6 +23,13 @@ import ( var DefaultCalendars = []string{ "https://lgm1.calendar.test.timestamps.now", + // Run by Peter Todd + "https://a.pool.opentimestamps.org/", + "https://b.pool.opentimestamps.org/", + // Run by Riccardo Casatta + "https://a.pool.eternitywall.com/", + // Run by Bull Bitcoin + "https://ots.btc.catallaxy.com/", } const ( diff --git a/packages/sdk-py/src/uts_sdk/sdk.py b/packages/sdk-py/src/uts_sdk/sdk.py index d505878..1cca737 100644 --- a/packages/sdk-py/src/uts_sdk/sdk.py +++ b/packages/sdk-py/src/uts_sdk/sdk.py @@ -56,6 +56,13 @@ DEFAULT_CALENDARS = [ "https://lgm1.calendar.test.timestamps.now/", + # Run by Peter Todd + "https://a.pool.opentimestamps.org/", + "https://b.pool.opentimestamps.org/", + # Run by Riccardo Casatta + "https://a.pool.eternitywall.com/", + # Run by Bull Bitcoin + "https://ots.btc.catallaxy.com/", ] DEFAULT_EAS_ADDRESSES: dict[int, Address] = { diff --git a/packages/sdk-ts/src/sdk.ts b/packages/sdk-ts/src/sdk.ts index fd13351..9503dc0 100644 --- a/packages/sdk-ts/src/sdk.ts +++ b/packages/sdk-ts/src/sdk.ts @@ -87,11 +87,14 @@ export const WELL_KNOWN_CHAINS: Record< } export const DEFAULT_CALENDARS = [ - // new URL('https://a.pool.opentimestamps.org/'), - // new URL('https://b.pool.opentimestamps.org/'), - // new URL('https://a.pool.eternitywall.com/'), - // new URL('https://ots.btc.catallaxy.com/'), new URL('https://lgm1.calendar.test.timestamps.now/'), + // Run by Peter Todd + new URL('https://a.pool.opentimestamps.org/'), + new URL('https://b.pool.opentimestamps.org/'), + // Run by Riccardo Casatta + new URL('https://a.pool.eternitywall.com/'), + // Run by Bull Bitcoin + new URL('https://ots.btc.catallaxy.com/'), ] export const DEFAULT_EAS_ADDRESSES: Record = { From 93a8358a107948d7b74b20c665ac1380957693cf Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 16 Mar 2026 10:57:41 +0800 Subject: [PATCH 02/11] display chain --- crates/cli/src/commands/verify.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/commands/verify.rs b/crates/cli/src/commands/verify.rs index c4139c8..37536b2 100644 --- a/crates/cli/src/commands/verify.rs +++ b/crates/cli/src/commands/verify.rs @@ -126,13 +126,13 @@ impl Verify { if attestation.tag == EASAttestation::TAG { let eas_attestation = EASAttestation::from_raw(attestation)?; let result = verifier.verify(&eas_attestation, expected).await?; - info!("EAS Onchain Attestation: {}", result.uid); + info!("EAS On Chain {chain} Attestation: {}", result.uid); time = result.time; info!("\tattester: {}", result.attester); } else { let timestamped = EASTimestamped::from_raw(attestation)?; time = verifier.verify(×tamped, expected).await?; - info!("EAS Timestamped"); + info!("EAS Timestamped On Chain {chain}"); } let ts = Timestamp::from_second(time.try_into().context("i64 overflow")?)?; From 4f7324556d5bbf8f7f803665360d07578f52d727 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 16 Mar 2026 14:27:04 +0800 Subject: [PATCH 03/11] rc not suitable for production --- Cargo.toml | 14 ++-- crates/bmt/Cargo.toml | 9 --- crates/bmt/benches/tree_construction.rs | 96 ------------------------- crates/bmt/src/lib.rs | 45 +++++++----- crates/calendar/Cargo.toml | 1 - crates/calendar/src/routes/ots.rs | 8 ++- crates/cli/Cargo.toml | 1 - crates/cli/src/commands/stamp.rs | 3 +- crates/core/Cargo.toml | 1 - crates/relayer/src/relayer.rs | 2 +- crates/stamper/Cargo.toml | 1 - crates/stamper/src/kv.rs | 3 +- crates/stamper/src/lib.rs | 12 ++-- 13 files changed, 45 insertions(+), 151 deletions(-) delete mode 100644 crates/bmt/benches/tree_construction.rs diff --git a/Cargo.toml b/Cargo.toml index 5586789..f57153b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ axum = { version = "0.8", default-features = false } axum-extra = "0.12" bitcode = "0.6" bump-scope = { version = "2.2", features = ["nightly"] } -bytemuck = "1" bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } @@ -83,13 +82,12 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.5" -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" +crypto-common = "0.1" +digest = "0.10" +ripemd = "0.1" +sha1 = "0.10" +sha2 = "0.10" +sha3 = "0.10" tempfile = "3" uts-bmt = { version = "0.1.1", path = "crates/bmt" } diff --git a/crates/bmt/Cargo.toml b/crates/bmt/Cargo.toml index 2bd17b9..fc10c3e 100644 --- a/crates/bmt/Cargo.toml +++ b/crates/bmt/Cargo.toml @@ -14,22 +14,13 @@ repository.workspace = true version = "0.1.1" [dependencies] -bytemuck = { workspace = true } digest.workspace = true -hybrid-array = { workspace = true, features = ["bytemuck"] } [dev-dependencies] alloy-primitives.workspace = true alloy-sol-types.workspace = true -commonware-cryptography = "0.0.63" # for benchmarks -commonware-storage = "0.0.63" # for benchmarks -criterion.workspace = true sha2.workspace = true sha3.workspace = true -[[bench]] -harness = false -name = "tree_construction" - [lints] workspace = true diff --git a/crates/bmt/benches/tree_construction.rs b/crates/bmt/benches/tree_construction.rs deleted file mode 100644 index 6c89570..0000000 --- a/crates/bmt/benches/tree_construction.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Benchmark for Merkle tree construction. -use bytemuck::Pod; -use criterion::{ - BatchSize, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, - measurement::WallTime, -}; -use digest::{Digest, FixedOutputReset, Output}; -use sha2::Sha256; -use sha3::Keccak256; -use std::hint::black_box; -use uts_bmt::MerkleTree; - -const INPUT_SIZES: &[usize] = &[8, 1024, 65536, 1_048_576]; - -fn benchmark(c: &mut Criterion) { - let mut group = c.benchmark_group("tree_construction"); - bench_digest::(&mut group, "uts-bmt/Sha256"); - bench_digest::(&mut group, "uts-bmt/Keccak256"); - bench_commonware::( - &mut group, - "commonware_storage::bmt/Sha256", - ); - group.finish(); -} - -fn bench_digest(group: &mut BenchmarkGroup<'_, WallTime>, id: &str) -where - D: Digest + FixedOutputReset, - Output: Pod + Copy, -{ - for &size in INPUT_SIZES { - let leaves = generate_leaves::(size); - group.throughput(Throughput::Elements(size as u64)); - group.bench_function(BenchmarkId::new(id, size), move |b| { - // Tree construction is the operation under test. - b.iter(|| { - let tree = MerkleTree::::new(black_box(leaves.as_slice())); - black_box(tree); - }); - }); - } -} - -fn bench_commonware( - group: &mut BenchmarkGroup<'_, WallTime>, - id: &str, -) { - use commonware_storage::bmt::Builder; - - for &size in INPUT_SIZES { - let leaves: Vec = generate_commonware_leaves::(size); - group.throughput(Throughput::Elements(size as u64)); - group.bench_function(BenchmarkId::new(id, size), move |b| { - b.iter_batched( - || { - let mut builder = Builder::::new(leaves.len()); - for digest in leaves.iter() { - builder.add(black_box(digest)); - } - builder - }, - |builder| { - let tree = builder.build(); - black_box(tree); - }, - BatchSize::SmallInput, - ); - }); - } -} - -fn generate_leaves(count: usize) -> Vec> -where - D: Digest, - Output: Copy, -{ - (0..count) - .map(|i| { - let mut hasher = D::new(); - hasher.update(i.to_le_bytes()); - hasher.finalize() - }) - .collect() -} - -fn generate_commonware_leaves(count: usize) -> Vec { - (0..count) - .map(|i| { - let input = i.to_le_bytes(); - H::hash(&input) - }) - .collect() -} - -criterion_group!(benches, benchmark); -criterion_main!(benches); diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index bf67ce1..5c9fd1e 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -1,10 +1,6 @@ -#![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; +use digest::{Digest, FixedOutputReset, Output, typenum::Unsigned}; /// Prefix byte to distinguish internal nodes from leaves when hashing. pub const INNER_NODE_PREFIX: u8 = 0x01; @@ -28,7 +24,7 @@ pub struct UnhashedMerkleTree { impl MerkleTree where - Output: Pod + Copy, + Output: Copy, { /// Constructs a new Merkle tree from the given hash leaves. pub fn new(data: &[Output]) -> Self { @@ -62,9 +58,9 @@ where std::ptr::copy_nonoverlapping(src, dst, raw_len); // SAFETY: capacity + len is within the allocated size of `tree` - maybe_uninit - .get_unchecked_mut(len + raw_len..) - .write_filled(Output::::default()); + for e in maybe_uninit.get_unchecked_mut(len + raw_len..) { + e.write(Output::::default()); + } } UnhashedMerkleTree { buffer: nodes, len } @@ -100,8 +96,12 @@ where /// Returns the raw bytes of the Merkle tree nodes #[inline] - pub fn as_raw_bytes(&self) -> &[u8] { - bytemuck::cast_slice(&self.nodes) + pub fn to_raw_bytes(&self) -> Vec { + self.nodes + .iter() + .flat_map(|node| node.as_slice()) + .copied() + .collect() } /// From raw bytes, reconstruct the Merkle tree @@ -113,8 +113,17 @@ where /// Merkle tree structure. #[inline] pub fn from_raw_bytes(bytes: &[u8]) -> Self { - let nodes: &[Output] = bytemuck::cast_slice(bytes); - assert!(nodes.len().is_multiple_of(2)); + assert!( + bytes.len().is_multiple_of(D::OutputSize::USIZE), + "Invalid raw bytes length" + ); + let len = bytes.len() / D::OutputSize::USIZE; + assert!(len.is_multiple_of(2)); + let mut nodes: Vec> = Vec::with_capacity(len); + for chunk in bytes.chunks_exact(D::OutputSize::USIZE) { + let node = Output::::from_slice(chunk); + nodes.push(*node); + } assert_eq!(nodes[0], Output::::default()); let len = nodes.len() / 2; Self { @@ -126,7 +135,7 @@ where impl UnhashedMerkleTree where - Output: Pod + Copy, + Output: Copy, { /// Finalizes the Merkle tree by hashing internal nodes pub fn finalize(self) -> MerkleTree { @@ -181,7 +190,7 @@ impl<'a, D: Digest> Iterator for SiblingIter<'a, D> { type Item = (NodePosition, &'a Output); fn next(&mut self) -> Option { - if unlikely(self.current <= 1) { + if self.current <= 1 { return None; } let side = if (self.current & 1) == 0 { @@ -229,7 +238,7 @@ mod tests { fn test_merkle_tree() where - Output: Pod + Copy, + Output: Copy, { let leaves = vec![ D::digest(b"leaf1"), @@ -262,7 +271,7 @@ mod tests { fn test_proof() where - Output: Pod + Copy, + Output: Copy, { let leaves = vec![ D::digest(b"apple"), @@ -313,7 +322,7 @@ mod tests { for i in 0..=10u32 { let tree = MerkleTree::::new(&leaves[..2usize.pow(i)]); - let root = B256::new(tree.root().0); + let root = B256::from_slice(tree.root()); println!("bytes32({root}),"); } } diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index bdc88ad..7a043c7 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -28,7 +28,6 @@ axum = { workspace = true, default-features = false, features = [ "json", ] } # http2 only bump-scope.workspace = true -bytemuck = { workspace = true } bytes = { workspace = true } config = { workspace = true } digest = { workspace = true } diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 255a4f0..4044c2e 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -14,7 +14,7 @@ use axum::{ }; use bump_scope::Bump; use bytes::BytesMut; -use digest::Digest; +use digest::{Digest, Output}; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; use uts_core::codec::{ @@ -80,7 +80,8 @@ pub fn submit_digest_inner( hasher.finalize_reset() }); - let undeniable_sig = signer.sign_hash_sync(&hash.0.into()).unwrap(); + let hash = B256::from_slice(&hash); + let undeniable_sig = signer.sign_hash_sync(&hash).unwrap(); undeniable_sig.as_erc2098() }; @@ -152,8 +153,9 @@ pub async fn get_timestamp( .expect("DB error") .expect("bug: entry not found"); + let commitment = Output::::from_slice(commitment.as_slice()); let proof = trie - .get_proof_iter(bytemuck::cast_ref(&*commitment)) + .get_proof_iter(commitment) .expect("bug: proof not found"); let mut builder = Timestamp::builder(); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index cf1dbf5..c2f92ce 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,7 +19,6 @@ path = "src/main.rs" [dependencies] alloy-primitives = { workspace = true } alloy-provider = { workspace = true, default-features = false, features = ["reqwest"] } -bytemuck = { workspace = true } clap = { workspace = true, features = ["derive"] } color-eyre = { workspace = true } digest = { workspace = true } diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs index 805db0a..3555f7d 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -1,5 +1,4 @@ use crate::client::CLIENT; -use bytemuck::Pod; use clap::{Args, ValueEnum}; use digest::{Digest, FixedOutputReset, Output}; use futures::TryFutureExt; @@ -70,7 +69,7 @@ impl Stamp { async fn run_inner(self) -> eyre::Result<()> where D: Digest + FixedOutputReset + DigestOpExt + Send, - Output: Pod + Copy, + Output: Copy, { info!("Hashing files..."); let digests = diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index a5e121f..71b0b70 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -49,5 +49,4 @@ tracing = ["dep:tracing"] verifier = [] [dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } serde_json.workspace = true diff --git a/crates/relayer/src/relayer.rs b/crates/relayer/src/relayer.rs index a185b64..ee002f7 100644 --- a/crates/relayer/src/relayer.rs +++ b/crates/relayer/src/relayer.rs @@ -175,7 +175,7 @@ impl Relayer { .map(|hash| Output::::from(hash.0)) .collect::>(); let trie = MerkleTree::::new(&leaves); - let root = B256::new(trie.root().0); + let root = B256::from_slice(trie.root()); sql::l1_batch::upsert_l1batch( &self.db, diff --git a/crates/stamper/Cargo.toml b/crates/stamper/Cargo.toml index 59e5d89..22cd382 100644 --- a/crates/stamper/Cargo.toml +++ b/crates/stamper/Cargo.toml @@ -20,7 +20,6 @@ workspace = true [dependencies] alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true } -bytemuck = { workspace = true } digest = { workspace = true } eyre = { workspace = true } rocksdb = { workspace = true } diff --git a/crates/stamper/src/kv.rs b/crates/stamper/src/kv.rs index 9ccffd9..1a0cf3c 100644 --- a/crates/stamper/src/kv.rs +++ b/crates/stamper/src/kv.rs @@ -1,5 +1,4 @@ use alloy_primitives::B256; -use bytemuck::Pod; use digest::{Digest, FixedOutputReset, Output}; use rocksdb::DB; use uts_bmt::MerkleTree; @@ -15,7 +14,7 @@ pub trait DbExt { impl DbExt for DB where - Output: Pod + Copy, + Output: Copy, { fn load_trie(&self, root: B256) -> Result>, rocksdb::Error> { let Some(data) = self.get(root)? else { diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index 6cb54b4..041614b 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -1,6 +1,3 @@ -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] - //! Timestamping #[macro_use] @@ -8,7 +5,6 @@ extern crate tracing; use alloy_primitives::B256; use alloy_provider::Provider; -use bytemuck::Pod; use digest::{Digest, FixedOutputReset, Output, typenum::Unsigned}; use eyre::{Context, bail}; use rocksdb::{DB, WriteBatch}; @@ -87,7 +83,7 @@ impl Stamper where D: Digest + FixedOutputReset + 'static, P: Provider + Clone + 'static, - Output: Pod + Copy, + Output: Copy, { /// Create a new Stamper pub fn new( @@ -182,7 +178,7 @@ where } debug_assert_eq!(buffer.len(), target_size); - let merkle_tree = MerkleTree::::new_unhashed(bytemuck::cast_slice(buffer)); + let merkle_tree = MerkleTree::::new_unhashed(buffer); let merkle_tree = tokio::task::spawn_blocking(move || { let merkle_tree = merkle_tree.finalize(); // CPU intensive @@ -193,7 +189,7 @@ where .await .context("failed to create Merkle tree")?; - let root = B256::new(bytemuck::cast(*merkle_tree.root())); + let root = B256::from_slice(merkle_tree.root()); let mut batch = WriteBatch::default(); // store leaf->root mappings for quick lookup @@ -201,7 +197,7 @@ where batch.put(leaf, root); } // if it's a single-leaf tree, the root == the leaf, so we write mapping first. - batch.put(root, merkle_tree.as_raw_bytes()); + batch.put(root, merkle_tree.to_raw_bytes()); self.kv_storage .write(batch) .context("failed to write to kv db")?; From 0a1d062ffe97e27e5da0c00288ec483bd363a3e1 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 16 Mar 2026 15:14:23 +0800 Subject: [PATCH 04/11] switch to stable rust --- Cargo.toml | 3 ++- crates/calendar/Cargo.toml | 3 --- crates/calendar/src/lib.rs | 3 --- crates/calendar/src/routes/ots.rs | 13 ++++++++----- crates/calendar/src/time.rs | 6 +++++- crates/cli/src/commands/stamp.rs | 4 ++-- crates/cli/src/commands/upgrade.rs | 2 +- crates/core/Cargo.toml | 2 ++ crates/core/src/codec.rs | 8 +++++--- crates/core/src/codec/imp.rs | 15 +++++++++++++-- crates/core/src/codec/imp/primitives.rs | 10 ++++++---- crates/core/src/codec/proof.rs | 2 +- crates/core/src/codec/v1/attestation.rs | 7 ++----- crates/core/src/codec/v1/detached_timestamp.rs | 10 ++++++---- crates/core/src/codec/v1/digest.rs | 2 +- crates/core/src/codec/v1/opcode.rs | 18 +++++++++--------- crates/core/src/codec/v1/timestamp.rs | 7 ++++--- crates/core/src/codec/v1/timestamp/builder.rs | 14 ++++++++------ crates/core/src/codec/v1/timestamp/fmt.rs | 2 +- crates/core/src/lib.rs | 15 ++++++++++++--- rust-toolchain.toml | 2 +- 21 files changed, 89 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f57153b..cc12106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ unnecessary-box-returns = "warn" unnecessary-debug-formatting = "warn" [workspace.dependencies] +allocator-api2 = "0.4" alloy = "1" alloy-chains = "0.2" alloy-contract = "1.7" @@ -44,7 +45,7 @@ auto_impl = "1.3" axum = { version = "0.8", default-features = false } axum-extra = "0.12" bitcode = "0.6" -bump-scope = { version = "2.2", features = ["nightly"] } +bump-scope = { version = "2.2", features = ["allocator-api2-04"] } bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index 7a043c7..e42c1cd 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -47,9 +47,6 @@ uts-core = { workspace = true, features = ["bytes"] } uts-journal = { workspace = true } uts-stamper = { workspace = true } -hashbrown_15 = { package = "hashbrown", version = "0.15", features = ["nightly"] } # why? -hashbrown_16 = { package = "hashbrown", version = "0.16", features = ["nightly"] } # why? - [dev-dependencies] criterion.workspace = true diff --git a/crates/calendar/src/lib.rs b/crates/calendar/src/lib.rs index 173b976..274cb98 100644 --- a/crates/calendar/src/lib.rs +++ b/crates/calendar/src/lib.rs @@ -1,6 +1,3 @@ -#![feature(thread_sleep_until)] -#![feature(allocator_api)] - //! Calendar server library. #[macro_use] diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 4044c2e..5c5b21b 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -17,9 +17,12 @@ use bytes::BytesMut; use digest::{Digest, Output}; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; -use uts_core::codec::{ - Encode, - v1::{EASTimestamped, PendingAttestation, Timestamp}, +use uts_core::{ + alloc::SliceExt, + codec::{ + Encode, + v1::{EASTimestamped, PendingAttestation, Timestamp}, + }, }; use uts_journal::Error; use uts_stamper::{kv::DbExt, sql, sql::AttestationResult}; @@ -98,8 +101,8 @@ pub fn submit_digest_inner( let mut builder = Timestamp::builder_in(&*bump); builder - .prepend(recv_timestamp.to_vec_in(&bump)) - .append(undeniable_sig.to_vec_in(&bump)) + .prepend(SliceExt::to_vec_in(recv_timestamp.as_slice(), &bump)) + .append(SliceExt::to_vec_in(undeniable_sig.as_slice(), &bump)) .keccak256(); let mut commitment = [0u8; 32]; diff --git a/crates/calendar/src/time.rs b/crates/calendar/src/time.rs index 86c4b52..3009c73 100644 --- a/crates/calendar/src/time.rs +++ b/crates/calendar/src/time.rs @@ -46,7 +46,11 @@ pub fn updater() { // 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); + let now = Instant::now(); + + if let Some(delay) = next_tick.checked_duration_since(now) { + std::thread::sleep(delay); + } } else { // If we've fallen behind, resynchronize to avoid accumulating drift. next_tick = now_instant; diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs index 3555f7d..5599b24 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -96,7 +96,7 @@ impl Stamp { Digest::update(&mut hasher, digest.digest()); let nonce: [u8; 32] = rand::random(); Digest::update(&mut hasher, nonce); - builder.append(nonce.to_vec()).digest::(); + builder.append(nonce.into()).digest::(); hasher.finalize() }) .collect::>(); @@ -135,7 +135,7 @@ impl Stamp { .await .into_iter() .filter_map(|res| res.ok()) - .collect::>(); + .collect::>(); if stamps.len() < quorum { eyre::bail!( "Only received {} valid responses from calendars, which does not meet the quorum of {quorum}", diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index 21dd896..e4b8df7 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -95,7 +95,7 @@ async fn upgrade_one( ); *step = if keep_pending { - Timestamp::merge(vec![attestation, step.clone()]) + Timestamp::merge(uts_core::alloc::vec![attestation, step.clone()]) } else { attestation }; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 71b0b70..c12ae5f 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,6 +16,7 @@ repository.workspace = true version.workspace = true [dependencies] +allocator-api2 = { workspace = true } alloy-chains = { workspace = true } alloy-contract = { workspace = true, optional = true } alloy-primitives = { workspace = true } @@ -43,6 +44,7 @@ bytes = ["dep:bytes"] default = ["std"] eas-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-contract", "dep:alloy-sol-types", "dep:uts-contracts"] io-utils = ["dep:tokio", "tokio/fs"] +nightly = [] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] std = [] tracing = ["dep:tracing"] diff --git a/crates/core/src/codec.rs b/crates/core/src/codec.rs index acc83ed..3068f58 100644 --- a/crates/core/src/codec.rs +++ b/crates/core/src/codec.rs @@ -1,7 +1,9 @@ -use crate::error::{DecodeError, EncodeError}; -use alloc::alloc::Global; +use crate::{ + alloc::{Allocator, Global}, + error::{DecodeError, EncodeError}, +}; use auto_impl::auto_impl; -use core::{alloc::Allocator, ops::RangeBounds}; +use core::ops::RangeBounds; mod proof; pub use proof::{Proof, Version, VersionedProof}; diff --git a/crates/core/src/codec/imp.rs b/crates/core/src/codec/imp.rs index d393fd9..88c268d 100644 --- a/crates/core/src/codec/imp.rs +++ b/crates/core/src/codec/imp.rs @@ -1,5 +1,4 @@ -use crate::codec::*; -use alloc::vec::Vec; +use crate::{alloc::vec::Vec, codec::*}; mod alloy; #[cfg(feature = "bytes")] @@ -23,6 +22,18 @@ impl Encoder for Vec { } } +impl Encoder for std::vec::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 { diff --git a/crates/core/src/codec/imp/primitives.rs b/crates/core/src/codec/imp/primitives.rs index afd129d..09aed39 100644 --- a/crates/core/src/codec/imp/primitives.rs +++ b/crates/core/src/codec/imp/primitives.rs @@ -31,7 +31,7 @@ macro_rules! leb128 { } } - impl crate::codec::DecodeIn for $ty { + impl crate::codec::DecodeIn for $ty { #[inline] fn decode_in(decoder: &mut impl crate::codec::Decoder, _alloc: A) -> Result { let mut ret: $ty = 0; @@ -40,9 +40,11 @@ macro_rules! leb128 { loop { // Bottom 7 bits are value bits let byte = decoder.decode_byte()?; - ret |= ((byte & 0x7f) as $ty) - .shl_exact(shift) - .ok_or($crate::error::DecodeError::LEB128Overflow(<$ty>::BITS))?; + let value = (byte & 0x7f) as $ty; + if shift < value.leading_zeros() || shift < value.leading_ones() { + return Err($crate::error::DecodeError::LEB128Overflow(<$ty>::BITS)); + } + ret |= ((byte & 0x7f) as $ty) << shift; // Top bit is a continue bit if byte & 0x80 == 0 { break; diff --git a/crates/core/src/codec/proof.rs b/crates/core/src/codec/proof.rs index ac0ec6a..9fff566 100644 --- a/crates/core/src/codec/proof.rs +++ b/crates/core/src/codec/proof.rs @@ -1,8 +1,8 @@ use crate::{ + alloc::{Allocator, Global}, codec::{DecodeIn, Decoder, Encode, Encoder}, error::{DecodeError, EncodeError}, }; -use alloc::alloc::{Allocator, Global}; use core::fmt; /// Version number of the serialization format. diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index 149f682..325d558 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -4,18 +4,15 @@ //! comes from some server or from a blockchain. use crate::{ + alloc::{Allocator, Global, vec::Vec}, codec::{Decode, DecodeIn, Decoder, Encode, Encoder, v1::MayHaveInput}, error::{DecodeError, EncodeError}, utils::{Hexed, OnceLock}, }; -use alloc::{ - alloc::{Allocator, Global}, - borrow::Cow, - vec::Vec, -}; use alloy_chains::Chain; use alloy_primitives::{B256, FixedBytes, fixed_bytes as tag}; use core::fmt; +use std::borrow::Cow; /// Size in bytes of the tag identifying the attestation type. const TAG_SIZE: usize = 8; diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 353ac91..363bcb9 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -1,8 +1,10 @@ -use crate::codec::{ - Decode, DecodeIn, Encode, Encoder, Proof, Version, - v1::{DigestHeader, FinalizationError, Timestamp}, +use crate::{ + alloc::{Allocator, Global}, + codec::{ + Decode, DecodeIn, Encode, Encoder, Proof, Version, + v1::{DigestHeader, FinalizationError, Timestamp}, + }, }; -use alloc::alloc::{Allocator, Global}; use core::{fmt, fmt::Formatter}; use std::ops::{Deref, DerefMut}; diff --git a/crates/core/src/codec/v1/digest.rs b/crates/core/src/codec/v1/digest.rs index 93a6118..eaf290c 100644 --- a/crates/core/src/codec/v1/digest.rs +++ b/crates/core/src/codec/v1/digest.rs @@ -1,4 +1,5 @@ use crate::{ + alloc::Allocator, codec::{ DecodeIn, Decoder, Encode, Encoder, v1::opcode::{DigestOp, DigestOpExt}, @@ -6,7 +7,6 @@ use crate::{ error::{DecodeError, EncodeError}, utils::Hexed, }; -use alloc::alloc::Allocator; use core::fmt; use digest::{Output, typenum::Unsigned}; diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index 4a4565e..9a14d01 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -3,10 +3,10 @@ //! It contains opcode information and utilities to work with opcodes. use crate::{ + alloc::{Allocator, Global, vec::Vec}, codec::{Decode, DecodeIn, Decoder, Encode, Encoder}, error::{DecodeError, EncodeError}, }; -use alloc::{alloc::Allocator, vec::Vec}; use alloy_primitives::hex; use core::{fmt, hint::unreachable_unchecked}; use digest::Digest; @@ -95,7 +95,7 @@ impl OpCode { /// Panics if the opcode is a control opcode. #[inline] pub fn execute(&self, input: impl AsRef<[u8]>, immediate: impl AsRef<[u8]>) -> Vec { - self.execute_in(input, immediate, alloc::alloc::Global) + self.execute_in(input, immediate, Global) } /// Executes the opcode on the given input data, with an optional immediate value. @@ -326,18 +326,18 @@ macro_rules! define_digest_opcodes { } /// Executes the digest operation on the input data. - pub fn execute(&self, input: impl AsRef<[u8]>) -> ::alloc::vec::Vec { - self.execute_in(input, ::alloc::alloc::Global) + pub fn execute(&self, input: impl AsRef<[u8]>) -> $crate::alloc::vec::Vec { + self.execute_in(input, $crate::alloc::Global) } /// Executes the digest operation on the input data. - pub fn execute_in(&self, input: impl AsRef<[u8]>, alloc: A) -> ::alloc::vec::Vec { + pub fn execute_in(&self, input: impl AsRef<[u8]>, alloc: A) -> $crate::alloc::vec::Vec { match *self { $( Self::$variant => { paste::paste! { let mut hasher = [<$variant:camel>]::new(); hasher.update(input); - hasher.finalize().to_vec_in(alloc) + $crate::alloc::SliceExt::to_vec_in(hasher.finalize().as_slice(), alloc) } }, )* // SAFETY: unreachable as all variants are covered. @@ -362,7 +362,7 @@ macro_rules! define_digest_opcodes { macro_rules! impl_simple_step { ($variant:ident) => {paste::paste! { - impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] pub fn [< $variant:lower >](&mut self) -> &mut Self { self.push_step(OpCode::[<$variant>]) @@ -378,9 +378,9 @@ macro_rules! impl_simple_step { macro_rules! impl_step_with_data { ($variant:ident) => {paste::paste! { - impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] - pub fn [< $variant:lower >](&mut self, data: ::alloc::vec::Vec) -> &mut Self { + pub fn [< $variant:lower >](&mut self, data: $crate::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 c612cf5..5b9b346 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -1,14 +1,15 @@ //! ** The implementation here is subject to change as this is a read-only version. ** use crate::{ + alloc::{Allocator, Global, vec, vec::Vec}, codec::v1::{ Attestation, FinalizationError, MayHaveInput, PendingAttestation, attestation::RawAttestation, opcode::OpCode, }, utils::{Hexed, OnceLock}, }; -use alloc::{alloc::Global, vec::Vec}; -use core::{alloc::Allocator, fmt::Debug}; +use allocator_api2::SliceExt; +use core::fmt::Debug; pub(crate) mod builder; mod decode; @@ -186,7 +187,7 @@ impl Timestamp { /// /// 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()); + let init_fn = || SliceExt::to_vec_in(input, self.allocator().clone()); match self { Self::Attestation(attestation) => { if let Some(already) = attestation.value.get() { diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index eac98a4..9c47eec 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -1,6 +1,7 @@ //! Timestamp Builder use crate::{ + alloc::{Allocator, Global, vec, vec::Vec}, codec::v1::{ Attestation, Timestamp, opcode::{DigestOpExt, OpCode}, @@ -9,7 +10,7 @@ use crate::{ error::EncodeError, utils::OnceLock, }; -use alloc::alloc::{Allocator, Global}; +use allocator_api2::SliceExt; use uts_bmt::{NodePosition, SiblingIter}; #[derive(Debug, Clone)] @@ -67,13 +68,14 @@ impl TimestampBuilder { pub fn merkle_proof(&mut self, proof: SiblingIter<'_, D>) -> &mut Self { let alloc = self.allocator().clone(); for (side, sibling_hash) in proof { + let sibling_hash = SliceExt::to_vec_in(sibling_hash.as_slice(), alloc.clone()); match side { NodePosition::Left => self - .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(alloc.clone())) - .append(sibling_hash.to_vec_in(alloc.clone())), + .prepend(vec![in alloc.clone(); uts_bmt::INNER_NODE_PREFIX]) + .append(sibling_hash), NodePosition::Right => self - .prepend(sibling_hash.to_vec_in(alloc.clone())) - .prepend([uts_bmt::INNER_NODE_PREFIX].to_vec_in(alloc.clone())), + .prepend(sibling_hash) + .prepend(vec![in alloc.clone(); uts_bmt::INNER_NODE_PREFIX]), } .digest::(); } @@ -96,7 +98,7 @@ impl TimestampBuilder { /// 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()); + let mut commitment = SliceExt::to_vec_in(input.as_ref(), alloc.clone()); for step in &self.steps { commitment = step.op.execute_in(&commitment, &step.data, alloc.clone()); } diff --git a/crates/core/src/codec/v1/timestamp/fmt.rs b/crates/core/src/codec/v1/timestamp/fmt.rs index 17c6d6a..c7749d1 100644 --- a/crates/core/src/codec/v1/timestamp/fmt.rs +++ b/crates/core/src/codec/v1/timestamp/fmt.rs @@ -61,7 +61,7 @@ impl Timestamp { } let result = if let Some(value) = step.next.first().and_then(|next| next.input()) { - Some(value.to_vec_in(step.allocator().clone())) + Some(SliceExt::to_vec_in(value, step.allocator().clone())) } else if let Some(input) = input { let result = op.execute_in(input, &step.data, step.allocator().clone()); indent(f, depth, false)?; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index a5fe5f4..6106376 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,9 +1,6 @@ -#![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; @@ -18,3 +15,15 @@ pub mod error; pub mod utils; #[cfg(feature = "verifier")] pub mod verifier; + +/// Re-export the allocator2 API for use. +#[cfg(not(feature = "nightly"))] +pub mod alloc { + pub use allocator_api2::{alloc::*, *}; +} + +#[cfg(feature = "nightly")] +pub mod alloc { + pub use alloc::*; + pub use core::alloc::*; +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 79211b7..76a06e6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2026-01-01" +channel = "1.94.0" From 048c7a9546b96305097206ae0ae7f8165fab0a16 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 16 Mar 2026 20:33:15 +0800 Subject: [PATCH 05/11] move stamp & upgrade to sdk --- Cargo.lock | 1207 +++-------------- Cargo.toml | 6 +- crates/cli/Cargo.toml | 17 +- crates/cli/src/client.rs | 9 - crates/cli/src/commands/stamp.rs | 180 +-- crates/cli/src/commands/upgrade.rs | 118 +- crates/cli/src/main.rs | 1 - crates/core/src/codec/imp/primitives.rs | 4 +- crates/core/src/codec/proof.rs | 15 + .../core/src/codec/v1/detached_timestamp.rs | 6 +- crates/relayer/src/relayer.rs | 4 +- packages/sdk-rs/Cargo.toml | 47 + packages/sdk-rs/src/builder.rs | 209 +++ packages/sdk-rs/src/error.rs | 45 + packages/sdk-rs/src/lib.rs | 130 ++ packages/sdk-rs/src/stamp.rs | 187 +++ packages/sdk-rs/src/upgrade.rs | 89 ++ 17 files changed, 1004 insertions(+), 1270 deletions(-) delete mode 100644 crates/cli/src/client.rs create mode 100644 packages/sdk-rs/Cargo.toml create mode 100644 packages/sdk-rs/src/builder.rs create mode 100644 packages/sdk-rs/src/error.rs create mode 100644 packages/sdk-rs/src/lib.rs create mode 100644 packages/sdk-rs/src/stamp.rs create mode 100644 packages/sdk-rs/src/upgrade.rs diff --git a/Cargo.lock b/Cargo.lock index 0554c36..24fd09e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,16 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -65,9 +55,9 @@ checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" [[package]] name = "alloy-chains" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" +checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" dependencies = [ "alloy-primitives", "num_enum", @@ -225,7 +215,7 @@ dependencies = [ "either", "serde", "serde_with", - "sha2 0.10.9", + "sha2", "thiserror 2.0.18", ] @@ -319,7 +309,7 @@ dependencies = [ "ruint", "rustc-hash", "serde", - "sha3 0.10.8", + "sha3", ] [[package]] @@ -346,7 +336,7 @@ dependencies = [ "async-stream", "async-trait", "auto_impl", - "dashmap 6.1.0", + "dashmap", "either", "futures", "futures-utils-wasm", @@ -536,7 +526,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "sha3 0.10.8", + "sha3", "syn 2.0.117", "syn-solidity", ] @@ -591,7 +581,7 @@ dependencies = [ "derive_more", "futures", "futures-utils-wasm", - "governor 0.8.1", + "governor", "parking_lot", "serde", "serde_json", @@ -638,13 +628,12 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", "derive_more", "nybbles", "serde", @@ -659,7 +648,7 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.117", @@ -682,9 +671,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -697,15 +686,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -931,32 +920,11 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", - "zeroize", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] [[package]] name = "async-stream" @@ -1065,7 +1033,6 @@ dependencies = [ "axum-core", "axum-macros", "bytes", - "form_urlencoded", "futures-util", "http", "http-body", @@ -1081,13 +1048,11 @@ dependencies = [ "serde_core", "serde_json", "serde_path_to_error", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1106,7 +1071,6 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1120,6 +1084,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -1241,30 +1216,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake3" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures 0.2.17", - "zeroize", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -1274,15 +1225,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" -dependencies = [ - "hybrid-array", -] - [[package]] name = "blst" version = "0.3.16" @@ -1324,7 +1266,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.9", + "sha2", "tinyvec", ] @@ -1351,12 +1293,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - [[package]] name = "byteorder" version = "1.5.0" @@ -1384,9 +1320,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.6" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -1405,9 +1341,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1442,17 +1378,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - [[package]] name = "chacha20" version = "0.10.0" @@ -1464,19 +1389,6 @@ dependencies = [ "rand_core 0.10.0", ] -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.44" @@ -1516,17 +1428,6 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.7", - "inout", - "zeroize", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -1540,9 +1441,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -1550,9 +1451,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -1562,9 +1463,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -1574,9 +1475,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" @@ -1599,7 +1500,7 @@ dependencies = [ "hmac", "k256", "serde", - "sha2 0.10.9", + "sha2", "thiserror 1.0.69", ] @@ -1615,7 +1516,7 @@ dependencies = [ "once_cell", "pbkdf2", "rand 0.8.5", - "sha2 0.10.9", + "sha2", "thiserror 1.0.69", ] @@ -1631,10 +1532,10 @@ dependencies = [ "const-hex", "digest 0.10.7", "generic-array", - "ripemd 0.1.3", + "ripemd", "serde", - "sha2 0.10.9", - "sha3 0.10.8", + "sha2", + "sha3", "thiserror 1.0.69", ] @@ -1667,9 +1568,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -1681,132 +1582,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "commonware-codec" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d473c9be55b1143abebd3fa6a748fe9d35151df4614ea7f68b314e504559af08" -dependencies = [ - "bytes", - "paste", - "thiserror 2.0.18", -] - -[[package]] -name = "commonware-cryptography" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7157f6e4a84b7a70108f15bf402f539b48d52adf727b9938fe59f752efc5b3e" -dependencies = [ - "blake3", - "blst", - "bytes", - "cfg-if", - "chacha20poly1305", - "commonware-codec", - "commonware-utils", - "ed25519-consensus", - "getrandom 0.2.17", - "p256", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "rayon", - "sha2 0.10.9", - "thiserror 2.0.18", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "commonware-macros" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff368ef4db2d10686c1a89b7f4a3f1e7dddd9641a96cac42b72bf71a10360291" -dependencies = [ - "futures", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "commonware-runtime" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4d08e6e11088ed5b42e5ad93b18ffd745cbcc04c6ef7f723f5d237e146abc8" -dependencies = [ - "async-lock", - "axum", - "bytes", - "cfg-if", - "commonware-macros", - "commonware-utils", - "criterion 0.5.1", - "futures", - "getrandom 0.2.17", - "governor 0.6.3", - "libc", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry_sdk", - "prometheus-client", - "rand 0.8.5", - "rayon", - "sha2 0.10.9", - "sysinfo", - "thiserror 2.0.18", - "tokio", - "tracing", - "tracing-opentelemetry", - "tracing-subscriber", -] - -[[package]] -name = "commonware-storage" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3116985d7a761aa7bf26d5226d51b8de0eaa6458277963f3c5d903f4078d504" -dependencies = [ - "bytes", - "cfg-if", - "commonware-codec", - "commonware-cryptography", - "commonware-macros", - "commonware-runtime", - "commonware-utils", - "crc32fast", - "futures", - "futures-util", - "prometheus-client", - "rayon", - "thiserror 2.0.18", - "tracing", - "zstd", -] - -[[package]] -name = "commonware-utils" -version = "0.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c03e7057e9d4d5f0bade24685e2bc8845471b89e4ab54684f1af8c287ad3b" -dependencies = [ - "bytes", - "cfg-if", - "commonware-codec", - "futures", - "getrandom 0.2.17", - "num-bigint", - "num-integer", - "num-rational", - "num-traits", - "rand 0.8.5", - "thiserror 2.0.18", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1818,9 +1593,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.19" +version = "0.15.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "4fe5feec195269515c4722937cd7ffcfe7b4205d18d2e6577b7223ecb159ab00" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -1854,12 +1629,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "const-random" version = "0.1.18" @@ -1900,12 +1669,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - [[package]] name = "convert_case" version = "0.6.0" @@ -1973,42 +1736,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot 0.5.0", - "futures", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - [[package]] name = "criterion" version = "0.8.2" @@ -2020,7 +1747,7 @@ dependencies = [ "cast", "ciborium", "clap", - "criterion-plot 0.8.2", + "criterion-plot", "itertools 0.13.0", "num-traits", "oorandom", @@ -2034,16 +1761,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - [[package]] name = "criterion-plot" version = "0.8.2" @@ -2113,79 +1830,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] [[package]] -name = "crypto-common" -version = "0.2.1" +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "hybrid-array", + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "fiat-crypto", - "rustc_version 0.4.1", - "subtle", - "zeroize", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "darling_core" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "serde", + "strsim", "syn 2.0.117", ] -[[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" -dependencies = [ - "byteorder", - "digest 0.9.0", - "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" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", - "serde", "strsim", "syn 2.0.117", ] @@ -2196,22 +1887,20 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", "quote", "syn 2.0.117", ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "darling_core 0.23.0", + "quote", + "syn 2.0.117", ] [[package]] @@ -2240,7 +1929,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", + "const-oid", "pem-rfc7468", "zeroize", ] @@ -2304,23 +1993,12 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.7", + "block-buffer", + "const-oid", + "crypto-common", "subtle", ] -[[package]] -name = "digest" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" -dependencies = [ - "block-buffer 0.12.0", - "const-oid 0.10.2", - "crypto-common 0.2.1", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -2347,12 +2025,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - [[package]] name = "dunce" version = "1.0.5" @@ -2380,20 +2052,6 @@ dependencies = [ "spki", ] -[[package]] -name = "ed25519-consensus" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" -dependencies = [ - "curve25519-dalek-ng", - "hex", - "rand_core 0.6.4", - "sha2 0.9.9", - "thiserror 1.0.69", - "zeroize", -] - [[package]] name = "educe" version = "0.6.0" @@ -2513,16 +2171,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -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" @@ -2571,12 +2219,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2836,23 +2478,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] -name = "governor" -version = "0.6.3" +name = "gloo-timers" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ - "cfg-if", - "dashmap 5.5.3", - "futures", - "futures-timer", - "no-std-compat", - "nonzero_ext", - "parking_lot", - "portable-atomic", - "quanta", - "rand 0.8.5", - "smallvec", - "spinning_top", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3061,16 +2695,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hybrid-array" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" -dependencies = [ - "bytemuck", - "typenum", -] - [[package]] name = "hyper" version = "1.8.1" @@ -3162,7 +2786,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -3337,15 +2961,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -3362,17 +2977,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -3517,7 +3121,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2 0.10.9", + "sha2", "signature", ] @@ -3530,15 +3134,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[package]] -name = "keccak" -version = "0.2.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882b69cb15b1f78b51342322a97ccd16f5123d1dc8a3da981a95244f488e8692" -dependencies = [ - "cpufeatures 0.3.0", -] - [[package]] name = "keccak-asm" version = "0.1.5" @@ -3566,9 +3161,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -3624,9 +3219,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.24" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "pkg-config", @@ -3788,15 +3383,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -3858,17 +3444,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3891,9 +3466,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -3901,9 +3476,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", @@ -3935,9 +3510,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -3951,17 +3526,11 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ "bitflags", "cfg-if", @@ -4000,9 +3569,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -4011,86 +3580,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "opentelemetry-http" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry", - "reqwest 0.12.28", - "tracing", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" -dependencies = [ - "async-trait", - "futures-core", - "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "reqwest 0.12.28", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", - "tonic", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" -dependencies = [ - "async-trait", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "opentelemetry", - "percent-encoding", - "rand 0.8.5", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", -] - [[package]] name = "ordered-multimap" version = "0.7.3" @@ -4107,18 +3596,6 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.9", -] - [[package]] name = "page_size" version = "0.6.0" @@ -4263,7 +3740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -4407,20 +3884,9 @@ checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", + "plotters-backend", ] [[package]] @@ -4431,9 +3897,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -4472,15 +3938,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "primitive-types" version = "0.12.2" @@ -4532,29 +3989,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prometheus-client" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" -dependencies = [ - "dtoa", - "itoa", - "parking_lot", - "prometheus-client-derive-encode", -] - -[[package]] -name = "prometheus-client-derive-encode" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "proptest" version = "1.10.0" @@ -4574,29 +4008,6 @@ dependencies = [ "unarray", ] -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "quanta" version = "0.12.6" @@ -4640,9 +4051,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", @@ -4730,7 +4141,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ - "chacha20 0.10.0", + "chacha20", "getrandom 0.4.2", "rand_core 0.10.0", ] @@ -4902,9 +4313,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", - "futures-channel", "futures-core", - "futures-util", "http", "http-body", "http-body-util", @@ -5011,15 +4420,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "ripemd" -version = "0.2.0-rc.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86b56f40dd1fd9e48d4b66529a0b968e48f646fd6bec337be44d49f54ba7cf8" -dependencies = [ - "digest 0.11.1", -] - [[package]] name = "rlp" version = "0.5.2" @@ -5060,7 +4460,7 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid 0.9.6", + "const-oid", "digest 0.10.7", "num-bigint-dig", "num-integer", @@ -5279,9 +4679,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -5494,9 +4894,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", @@ -5513,11 +4913,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5544,30 +4944,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0-rc.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.11.1", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.9" @@ -5579,17 +4955,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.11.0-rc.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.11.1", -] - [[package]] name = "sha3" version = "0.10.8" @@ -5597,17 +4962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.7", - "keccak 0.1.6", -] - -[[package]] -name = "sha3" -version = "0.11.0-rc.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f78cd62cc39ece5aefbeb6caaa2ea44f70b4815d4b85f7e150ac685ada2bb5" -dependencies = [ - "digest 0.11.1", - "keccak 0.2.0-rc.2", + "keccak", ] [[package]] @@ -5678,12 +5033,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5752,7 +5107,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "smallvec", "thiserror 2.0.18", "tokio", @@ -5789,7 +5144,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -5831,8 +5186,8 @@ dependencies = [ "rand 0.8.5", "rsa", "serde", - "sha1 0.10.6", - "sha2 0.10.9", + "sha1", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -5869,7 +5224,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -5979,12 +5334,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - [[package]] name = "syn" version = "1.0.109" @@ -6039,20 +5388,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "sysinfo" -version = "0.33.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows", -] - [[package]] name = "tap" version = "1.0.1" @@ -6061,9 +5396,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -6192,9 +5527,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -6296,26 +5631,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -6332,7 +5658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] @@ -6346,27 +5672,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonic" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bytes", - "http", - "http-body", - "http-body-util", - "percent-encoding", - "pin-project", - "prost", - "tokio-stream", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -6380,7 +5685,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -6467,53 +5771,22 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry", - "opentelemetry_sdk", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] @@ -6536,7 +5809,7 @@ dependencies = [ "rand 0.9.2", "rustls", "rustls-pki-types", - "sha1 0.10.6", + "sha1", "thiserror 2.0.18", "utf-8", ] @@ -6616,16 +5889,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -6691,14 +5954,9 @@ version = "0.1.1" dependencies = [ "alloy-primitives", "alloy-sol-types", - "bytemuck", - "commonware-cryptography", - "commonware-storage", - "criterion 0.8.2", - "digest 0.11.1", - "hybrid-array", - "sha2 0.11.0-rc.5", - "sha3 0.11.0-rc.8", + "digest 0.10.7", + "sha2", + "sha3", ] [[package]] @@ -6713,18 +5971,15 @@ dependencies = [ "alloy-signer-local", "axum", "bump-scope", - "bytemuck", "bytes", "config", - "criterion 0.8.2", - "digest 0.11.1", + "criterion", + "digest 0.10.7", "eyre", - "hashbrown 0.15.5", - "hashbrown 0.16.1", "itoa", "rocksdb", "serde", - "sha3 0.11.0-rc.8", + "sha3", "sqlx", "tokio", "tokio-util", @@ -6743,26 +5998,23 @@ version = "0.1.0-alpha.0" dependencies = [ "alloy-primitives", "alloy-provider", - "bytemuck", "clap", "color-eyre", - "digest 0.11.1", + "digest 0.10.7", "eyre", "futures", "jiff", - "rand 0.10.0", - "reqwest 0.13.2", - "ripemd 0.2.0-rc.5", - "sha1 0.11.0-rc.5", - "sha2 0.11.0-rc.5", - "sha3 0.11.0-rc.8", + "ripemd", + "sha1", + "sha2", + "sha3", "tokio", "tracing", "tracing-subscriber", "url", - "uts-bmt", "uts-contracts", "uts-core", + "uts-sdk", ] [[package]] @@ -6782,6 +6034,7 @@ dependencies = [ name = "uts-core" version = "0.1.0-alpha.0" dependencies = [ + "allocator-api2 0.4.0", "alloy-chains", "alloy-contract", "alloy-primitives", @@ -6789,17 +6042,16 @@ dependencies = [ "alloy-sol-types", "auto_impl", "bytes", - "criterion 0.5.1", - "digest 0.11.1", + "digest 0.10.7", "once_cell", "paste", - "ripemd 0.2.0-rc.5", + "ripemd", "serde", "serde_json", "serde_with", - "sha1 0.11.0-rc.5", - "sha2 0.11.0-rc.5", - "sha3 0.11.0-rc.8", + "sha1", + "sha2", + "sha3", "thiserror 2.0.18", "tokio", "tracing", @@ -6837,7 +6089,7 @@ dependencies = [ "jiff", "rustls", "serde", - "sha3 0.11.0-rc.8", + "sha3", "sqlx", "strum 0.28.0", "tokio", @@ -6849,6 +6101,35 @@ dependencies = [ "uts-sql", ] +[[package]] +name = "uts-sdk" +version = "0.1.0-alpha.0" +dependencies = [ + "alloy-primitives", + "alloy-provider", + "backon", + "bytes", + "digest 0.10.7", + "futures", + "http", + "http-body-util", + "jiff", + "rand 0.10.0", + "reqwest 0.13.2", + "ripemd", + "sha1", + "sha2", + "sha3", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "uts-bmt", + "uts-contracts", + "uts-core", +] + [[package]] name = "uts-sql" version = "0.1.0-alpha.0" @@ -6863,8 +6144,7 @@ version = "0.1.0-alpha.0" dependencies = [ "alloy-primitives", "alloy-provider", - "bytemuck", - "digest 0.11.1", + "digest 0.10.7", "eyre", "rocksdb", "serde", @@ -7151,52 +6431,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "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-implement", + "windows-interface", "windows-link", - "windows-result 0.4.1", + "windows-result", "windows-strings", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -7208,17 +6455,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -7236,15 +6472,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -7553,9 +6780,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -7682,18 +6909,6 @@ dependencies = [ "tap", ] -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core 0.6.4", - "serde", - "zeroize", -] - [[package]] name = "yaml-rust2" version = "0.10.4" @@ -7730,18 +6945,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", @@ -7827,31 +7042,3 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index cc12106..0d46612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/*"] +members = ["crates/*", "packages/sdk-rs"] resolver = "3" [workspace.package] @@ -44,6 +44,7 @@ alloy-transport-ws = "1.7" auto_impl = "1.3" axum = { version = "0.8", default-features = false } axum-extra = "0.12" +backon = "1.6" bitcode = "0.6" bump-scope = { version = "2.2", features = ["allocator-api2-04"] } bytes = "1.11" @@ -58,6 +59,8 @@ dyn-clone = "1.0" eyre = "0.6" futures = "0.3" hex = "0.4" +http = "1.4" +http-body-util = "0.1.3" itoa = "1.0" jiff = "0.2" once_cell = { version = "1.21", default-features = false } @@ -95,6 +98,7 @@ uts-bmt = { version = "0.1.1", path = "crates/bmt" } uts-contracts = { version = "=0.1.0-alpha.0", path = "crates/contracts" } uts-core = { version = "=0.1.0-alpha.0", path = "crates/core" } uts-journal = { version = "0.1.0", path = "crates/journal" } +uts-sdk = { version = "=0.1.0-alpha.0", path = "packages/sdk-rs" } uts-sql = { version = "=0.1.0-alpha.0", path = "crates/sql" } uts-stamper = { version = "=0.1.0-alpha.0", path = "crates/stamper" } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index c2f92ce..60cea88 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -25,8 +25,6 @@ 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 } @@ -35,16 +33,17 @@ tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } url = { workspace = true } -uts-bmt = { workspace = true } uts-contracts = { workspace = true } uts-core = { workspace = true, features = ["std", "eas-verifier", "io-utils"] } +uts-sdk = { workspace = true, features = ["full"] } [features] default = ["reqwest-rustls"] performance = ["tracing/max_level_info"] -reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] -reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/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", "alloy-provider/reqwest-rustls-tls"] + +reqwest-default-tls = ["uts-sdk/reqwest-default-tls"] +reqwest-native-tls = ["uts-sdk/reqwest-native-tls"] +reqwest-native-tls-no-alpn = ["uts-sdk/reqwest-native-tls-no-alpn"] +reqwest-native-tls-vendored = ["uts-sdk/reqwest-native-tls-vendored"] +reqwest-native-tls-vendored-no-alpn = ["uts-sdk/reqwest-native-tls-vendored-no-alpn"] +reqwest-rustls = ["uts-sdk/reqwest-rustls"] diff --git a/crates/cli/src/client.rs b/crates/cli/src/client.rs deleted file mode 100644 index f0d0e53..0000000 --- a/crates/cli/src/client.rs +++ /dev/null @@ -1,9 +0,0 @@ -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/stamp.rs b/crates/cli/src/commands/stamp.rs index 5599b24..41a9040 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -1,32 +1,15 @@ -use crate::client::CLIENT; 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 futures::future::join_all; +use std::path::PathBuf; use tokio::{fs, io::AsyncWriteExt}; use tracing::{error, info}; use url::Url; -use uts_bmt::MerkleTree; -use uts_core::{ - codec::{ - Decode, Encode, VersionedProof, - v1::{DetachedTimestamp, DigestHeader, Timestamp, TimestampBuilder, opcode::DigestOpExt}, - }, - utils::{HashAsyncFsExt, Hexed}, +use uts_core::codec::{ + Encode, VersionedProof, + v1::{DetachedTimestamp, opcode::DigestOpExt}, }; - -static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { - vec![ - Url::parse("https://lgm1.calendar.test.timestamps.now/").unwrap(), - // Run by Peter Todd - Url::parse("https://a.pool.opentimestamps.org/").unwrap(), - Url::parse("https://b.pool.opentimestamps.org/").unwrap(), - // Run by Riccardo Casatta - Url::parse("https://a.pool.eternitywall.com/").unwrap(), - // Run by Bull Bitcoin - Url::parse("https://ots.btc.catallaxy.com/").unwrap(), - ] -}); +use uts_sdk::Sdk; #[derive(Debug, Args)] pub struct Stamp { @@ -42,9 +25,9 @@ pub struct Stamp { /// 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, + /// Timeout in seconds to wait for calendar responses + #[arg(long = "timeout")] + timeout: Option, } #[derive(Default, Debug, Copy, Clone, ValueEnum)] @@ -71,92 +54,35 @@ impl Stamp { D: Digest + FixedOutputReset + DigestOpExt + Send, Output: Copy, { - info!("Hashing files..."); - 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()) { - info!("- [{}] {header}", path.display()); + let mut builder = if self.calendars.is_empty() { + Sdk::builder() + } else { + Sdk::try_builder_from_calendars(self.calendars).expect("none empty") + }; + if let Some(quorum) = self.quorum { + builder = builder.with_quorum(quorum) } - - 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.into()).digest::(); - hasher.finalize() - }) - .collect::>(); - - let internal_tire = MerkleTree::::new(&nonced_digest); - let root = internal_tire.root(); - info!("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); + if let Some(timeout) = self.timeout { + builder = builder.with_timeout_seconds(timeout) } - let calendars = if self.calendars.is_empty() { - &*DEFAULT_CALENDARS - } else { - &*self.calendars - }; + let sdk = builder.build()?; - let quorum = self.quorum.unwrap_or_else(|| { - // Default quorum is 2/3 of the number of calendars, rounded up - (calendars.len() * 2).div_ceil(3) - }); - if quorum > calendars.len() { - eyre::bail!( - "Quorum of {quorum} cannot be achieved with only {} calendars", - self.calendars.len() - ); - } + let stamps = sdk.stamp_files::(&self.files).await?; - let stamps = futures::future::join_all( - calendars - .iter() - .map(|calendar| request_calendar(calendar.clone(), self.timeout, root)), + let tasks = join_all( + self.files + .clone() + .into_iter() + .zip(stamps) + .map(|(path, timestamp)| write_stamp(path, timestamp)), ) - .await - .into_iter() - .filter_map(|res| res.ok()) - .collect::>(); - if stamps.len() < quorum { - eyre::bail!( - "Only received {} valid responses from calendars, which does not meet the quorum of {quorum}", - stamps.len(), - ); - } - let merged = if stamps.len() == 1 { - stamps.into_iter().next().unwrap() - } else { - Timestamp::merge(stamps) - }; + .await; - 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(_) => info!("[{}] timestamped", path.display()), - Err(e) => error!("[{}] failed to write timestamp for {e}", path.display()), + for (path, result) in self.files.iter().zip(tasks) { + match result { + Ok(()) => info!("[{}] successfully stamped", path.display()), + Err(e) => error!("[{}] failed to stamp: {e}", path.display()), } } @@ -164,49 +90,7 @@ impl Stamp { } } -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 { - info!("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() { - error!("Calendar {calendar} responded with error: {e}"); - } else if e.is_timeout() { - error!("Calendar {calendar} timed out after {timeout} seconds"); - } else { - error!("Failed to submit to calendar {calendar}: {e}"); - } - })?; - - let ts = Timestamp::decode(&mut &*response).inspect_err(|e| { - error!("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); +async fn write_stamp(mut path: PathBuf, timestamp: DetachedTimestamp) -> eyre::Result<()> { let timestamp = VersionedProof::::new(timestamp); let mut buf = Vec::new(); timestamp.encode(&mut buf)?; diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index e4b8df7..77009ae 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -1,17 +1,8 @@ -use crate::client::CLIENT; use clap::Args; -use futures::TryFutureExt; -use reqwest::StatusCode; -use std::{fs, future::ready, path::PathBuf, time::Duration}; +use std::path::PathBuf; use tracing::{error, info, warn}; -use url::Url; -use uts_core::{ - codec::{ - Decode, Encode, VersionedProof, - v1::{Attestation, DetachedTimestamp, PendingAttestation, Timestamp}, - }, - utils::Hexed, -}; +use uts_core::codec::{Decode, Encode, VersionedProof, v1::DetachedTimestamp}; +use uts_sdk::{Sdk, UpgradeResult}; #[derive(Debug, Args)] pub struct Upgrade { @@ -22,30 +13,31 @@ pub struct Upgrade { /// attestations will be removed from the proof once the upgrade process is complete. #[arg(short, long, default_value_t = false)] keep_pending: bool, - /// Timeout in seconds to wait for calendar responses. Default is 5 seconds. - #[arg(long = "timeout", default_value = "5")] - timeout: u64, + /// Timeout in seconds to wait for calendar responses. + #[arg(long = "timeout")] + timeout: Option, } 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(fs::read) - .collect::, _>>()?; + let mut builder = Sdk::builder(); + if self.keep_pending { + builder = builder.keep_pending(); + } + if let Some(timeout) = self.timeout { + builder = builder.with_timeout_seconds(timeout); + } + let sdk = builder.build()?; let results = futures::future::join_all( self.files .iter() - .cloned() - .zip(files) - .map(|(path, file)| upgrade_one(path, file, self.keep_pending, self.timeout)), + .map(|path| upgrade_one(sdk.clone(), path.clone())), ) .await .into_iter() .collect::>(); + for (path, result) in self.files.iter().zip(results) { if let Err(e) = result { error!("[{}] failed to upgrade: {e}", path.display()) @@ -55,74 +47,28 @@ impl Upgrade { } } -async fn upgrade_one( - path: PathBuf, - file: Vec, - keep_pending: bool, - timeout: u64, -) -> eyre::Result<()> { +async fn upgrade_one(sdk: Sdk, path: PathBuf) -> eyre::Result<()> { + let file = tokio::fs::read(&path).await?; let mut proof = VersionedProof::::decode(&mut &*file)?; - - for step in proof.proof.pending_attestations_mut() { - let (calendar_server, retrieve_uri) = { - let Timestamp::Attestation(attestation) = step else { - unreachable!("bug: PendingAttestationIterMut should only yield Attestations"); - }; - let commitment = attestation.value().expect("finalized when decode"); - let calendar_server = PendingAttestation::from_raw(&*attestation)?.uri; - - let retrieve_uri = - Url::parse(&calendar_server)?.join(&format!("timestamp/{}", Hexed(commitment)))?; - - (calendar_server, retrieve_uri) - }; - - let result = CLIENT - .get(retrieve_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; - + let results = sdk.upgrade(&mut proof).await?; + for (calendar_server, result) in results { match result { - Ok(response) => { - let attestation = Timestamp::decode(&mut &*response)?; - info!( - "[{}] successfully upgraded pending attestation from {calendar_server}", - path.display() - ); - - *step = if keep_pending { - Timestamp::merge(uts_core::alloc::vec![attestation, step.clone()]) - } else { - attestation - }; - } - Err(e) => { - if let Some(status) = e.status() - && status == StatusCode::NOT_FOUND - { - // calendar not ready yet - info!( - "[{}] attestation from {calendar_server} not ready yet, skipping", - path.display() - ); - continue; - } - warn!( - "[{}] failed to upgrade pending attestation from {calendar_server}: {e}", - path.display() - ); - continue; - } + UpgradeResult::Upgraded => info!( + "[{}] attestation from {calendar_server} upgraded successfully", + path.display() + ), + UpgradeResult::Pending => info!( + "[{}] attestation from {calendar_server} is still pending, skipping", + path.display() + ), + UpgradeResult::Failed(e) => warn!( + "[{}] failed to upgrade attestation from {calendar_server}: {e}", + path.display() + ), } } - let mut buf = Vec::new(); proof.encode(&mut buf)?; tokio::fs::write(path, buf).await?; - Ok(()) } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 36ea293..7dc80cb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -4,7 +4,6 @@ use clap::Parser; use tracing::warn; use tracing_subscriber::{EnvFilter, filter::LevelFilter}; -mod client; mod commands; #[derive(Debug, Parser)] diff --git a/crates/core/src/codec/imp/primitives.rs b/crates/core/src/codec/imp/primitives.rs index 09aed39..e417f73 100644 --- a/crates/core/src/codec/imp/primitives.rs +++ b/crates/core/src/codec/imp/primitives.rs @@ -42,9 +42,11 @@ macro_rules! leb128 { let byte = decoder.decode_byte()?; let value = (byte & 0x7f) as $ty; if shift < value.leading_zeros() || shift < value.leading_ones() { + ret |= ((byte & 0x7f) as $ty) << shift; + } else { return Err($crate::error::DecodeError::LEB128Overflow(<$ty>::BITS)); } - ret |= ((byte & 0x7f) as $ty) << shift; + // Top bit is a continue bit if byte & 0x80 == 0 { break; diff --git a/crates/core/src/codec/proof.rs b/crates/core/src/codec/proof.rs index 9fff566..d1566eb 100644 --- a/crates/core/src/codec/proof.rs +++ b/crates/core/src/codec/proof.rs @@ -4,6 +4,7 @@ use crate::{ error::{DecodeError, EncodeError}, }; use core::fmt; +use std::ops::{Deref, DerefMut}; /// Version number of the serialization format. pub type Version = u32; @@ -75,3 +76,17 @@ impl + fmt::Display, A: Allocator> fmt::Display for VersionedProof, A: Allocator> Deref for VersionedProof { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.proof + } +} + +impl, A: Allocator> DerefMut for VersionedProof { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.proof + } +} diff --git a/crates/core/src/codec/v1/detached_timestamp.rs b/crates/core/src/codec/v1/detached_timestamp.rs index 363bcb9..01a6822 100644 --- a/crates/core/src/codec/v1/detached_timestamp.rs +++ b/crates/core/src/codec/v1/detached_timestamp.rs @@ -116,15 +116,15 @@ impl DetachedTimestamp { } } -impl Deref for DetachedTimestamp { - type Target = Timestamp; +impl Deref for DetachedTimestamp { + type Target = Timestamp; fn deref(&self) -> &Self::Target { &self.timestamp } } -impl DerefMut for DetachedTimestamp { +impl DerefMut for DetachedTimestamp { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.timestamp } diff --git a/crates/relayer/src/relayer.rs b/crates/relayer/src/relayer.rs index ee002f7..c63bf00 100644 --- a/crates/relayer/src/relayer.rs +++ b/crates/relayer/src/relayer.rs @@ -247,7 +247,7 @@ impl Relayer { .get_balance(self.wallet) .block_id(block_number.into()) .await?; - let gas_fee = U256::from(receipt.gas_used as u128 * receipt.effective_gas_price); + let gas_fee = U256::from(u128::from(receipt.gas_used) * receipt.effective_gas_price); let cross_chain_fee = if prev > current + gas_fee { prev - current - gas_fee } else { @@ -303,7 +303,7 @@ impl Relayer { .block_number .context("missing block number in log")?; info!(%tx_hash, block_number, "Finalize transaction mined"); - let gas_fee = U256::from(receipt.gas_used as u128 * receipt.effective_gas_price); + let gas_fee = U256::from(u128::from(receipt.gas_used) * receipt.effective_gas_price); let mut db_tx = self.db.begin().await?; insert_tx_receipt(&mut *db_tx, self.l2_chain_id, &receipt).await?; diff --git a/packages/sdk-rs/Cargo.toml b/packages/sdk-rs/Cargo.toml new file mode 100644 index 0000000..dec8a11 --- /dev/null +++ b/packages/sdk-rs/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "uts-sdk" +repository.workspace = true +version.workspace = true + +[dependencies] +alloy-primitives = { workspace = true } +alloy-provider = { workspace = true, default-features = false, features = ["reqwest"] } +backon = { workspace = true } +bytes = { workspace = true } +digest = { workspace = true } +futures = { workspace = true } +http = { workspace = true } +http-body-util = { 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 } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } +url = { workspace = true } +uts-bmt = { workspace = true } +uts-contracts = { workspace = true } +uts-core = { workspace = true, features = ["std", "eas-verifier", "io-utils"] } + +[features] +full = [] + +reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] +reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/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", "alloy-provider/reqwest-rustls-tls"] + +[lints] +workspace = true diff --git a/packages/sdk-rs/src/builder.rs b/packages/sdk-rs/src/builder.rs new file mode 100644 index 0000000..87c03c0 --- /dev/null +++ b/packages/sdk-rs/src/builder.rs @@ -0,0 +1,209 @@ +use crate::{Sdk, SdkInner}; +use backon::ExponentialBuilder; +use reqwest::Client; +use std::{ + collections::HashSet, + sync::{Arc, LazyLock}, + time::Duration, +}; +use url::Url; + +/// Default public calendars to use. +static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { + HashSet::from([ + Url::parse("https://lgm1.calendar.test.timestamps.now/").unwrap(), + // Run by Peter Todd + Url::parse("https://a.pool.opentimestamps.org/").unwrap(), + Url::parse("https://b.pool.opentimestamps.org/").unwrap(), + // Run by Riccardo Casatta + Url::parse("https://a.pool.eternitywall.com/").unwrap(), + // Run by Bull Bitcoin + Url::parse("https://ots.btc.catallaxy.com/").unwrap(), + ]) +}); + +#[derive(Debug, thiserror::Error)] +pub enum BuilderError { + #[error("At least one calendar must be specified")] + NoCalendars, + #[error("Quorum of {quorum} is too high for only {calendar_count} calendars")] + QuorumTooHigh { + quorum: usize, + calendar_count: usize, + }, +} + +type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub struct SdkBuilder { + http_client: Option, + + calendars: HashSet, + quorum: usize, + timeout_seconds: u64, + retry: ExponentialBuilder, + + nonce_size: usize, + + keep_pending: bool, +} + +impl Default for SdkBuilder { + fn default() -> Self { + Self::try_default_from_calendars(DEFAULT_CALENDARS.clone()) + .expect("Default calendars should be valid") + } +} + +impl SdkBuilder { + /// Create a builder with no calendars and default settings. + pub fn empty() -> Self { + Self { + http_client: None, + + calendars: HashSet::new(), + quorum: 0, + timeout_seconds: 5, + retry: ExponentialBuilder::default(), + + nonce_size: 32, + + keep_pending: false, + } + } + + /// Create a builder with the given calendars and default settings. + pub fn try_default_from_calendars(calendars: impl IntoIterator) -> Result { + let calendars = calendars.into_iter().collect::>(); + if calendars.is_empty() { + return Err(BuilderError::NoCalendars); + } + + let this = Self { + calendars, + ..Self::empty() + }; + + Ok(this.with_two_thirds_quorum()) + } + + /// Set the HTTP client to use for calendar requests. + /// + /// If not set, a default client with a user agent will be used. + pub fn with_http_client(mut self, http_client: Client) -> Self { + self.http_client = Some(http_client); + self + } + + /// Add a calendar to the builder. + pub fn add_calendar(mut self, calendar: Url) -> Self { + self.calendars.insert(calendar); + self + } + + /// Set the quorum for the builder. This is capped to at least 1. + pub fn with_quorum(mut self, quorum: usize) -> Self { + self.quorum = quorum.max(1); + self + } + + /// Set the quorum to 2/3 of the number of calendars, rounded up. This is capped to at least 1. + pub fn with_two_thirds_quorum(self) -> Self { + let two_thirds = (self.calendars.len() * 2).div_ceil(3); + self.with_quorum(two_thirds) + } + + /// Set the timeout for calendar requests in seconds. + pub fn with_timeout_seconds(mut self, timeout_seconds: u64) -> Self { + self.timeout_seconds = timeout_seconds; + self + } + + // Set the retry strategy for calendar requests. + + /// Enable jitter for the backoff. + pub fn with_jitter(mut self) -> Self { + self.retry = self.retry.with_jitter(); + self + } + + /// Set the backoff factor for the backoff. + pub fn with_backoff_factor(mut self, factor: f32) -> Self { + self.retry = self.retry.with_factor(factor); + self + } + + /// Set the minimum delay for the backoff. + pub fn with_min_backoff_delay(mut self, min_delay: Duration) -> Self { + self.retry = self.retry.with_min_delay(min_delay); + self + } + + /// Set the maximum delay for the backoff. + pub fn with_max_backoff_delay(mut self, max_delay: Duration) -> Self { + self.retry = self.retry.with_max_delay(max_delay); + self + } + + /// Set the maximum number of retry attempts for the backoff. + pub fn with_max_backoff_attempts(mut self, max_times: usize) -> Self { + self.retry = self.retry.with_max_times(max_times); + self + } + + /// Set the maximum total delay for the backoff. + pub fn with_max_backoff_total_delay(mut self, total_delay: Option) -> Self { + self.retry = self.retry.with_total_delay(total_delay); + self + } + + /// Set the size of the nonce to use when stamping digests. If 0, no nonce will be added. + pub fn with_nonce_size(mut self, nonce_size: usize) -> Self { + self.nonce_size = nonce_size; + self + } + + /// Keep pending attestations in the proof when upgrading. + pub fn keep_pending(mut self) -> Self { + self.keep_pending = true; + self + } + + /// Build the SDK from the builder, validating the configuration. Returns an error if the configuration is invalid. + pub fn build(self) -> Result { + if self.calendars.is_empty() { + return Err(BuilderError::NoCalendars); + } + if self.quorum > self.calendars.len() { + return Err(BuilderError::QuorumTooHigh { + quorum: self.quorum, + calendar_count: self.calendars.len(), + }); + } + + let http_client = if let Some(client) = self.http_client { + client + } else { + Client::builder() + .user_agent(concat!("uts/", env!("CARGO_PKG_VERSION"))) + .build() + .expect("default HTTP client should be valid") + }; + + Ok(Sdk { + inner: Arc::new(SdkInner { + http_client, + + calendars: self.calendars, + timeout_seconds: self.timeout_seconds, + retry: self.retry, + quorum: self.quorum, + + nonce_size: self.nonce_size, + + keep_pending: self.keep_pending, + }), + }) + } +} diff --git a/packages/sdk-rs/src/error.rs b/packages/sdk-rs/src/error.rs new file mode 100644 index 0000000..f36c7ab --- /dev/null +++ b/packages/sdk-rs/src/error.rs @@ -0,0 +1,45 @@ +use alloy_primitives::private::serde::de::StdError; + +pub(crate) type BoxError = Box; + +/// Error type for the SDK, encompassing various error scenarios that can occur during operations. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Error during an I/O operation, such as reading or writing files. + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + /// Error during an HTTP request, such as a network failure or non-success status code. + #[error("HTTP error: {0}")] + Http(BoxError), + /// Error parsing a URL, such as a calendar endpoint. + #[error("URL parse error: {0}")] + Url(#[from] url::ParseError), + + /// Error happened during decoding of a proof. + #[error("uts decoding error: {0}")] + Decode(#[from] uts_core::error::DecodeError), + /// Error happened during encoding of a proof. + #[error("uts encoding error: {0}")] + Encode(#[from] uts_core::error::EncodeError), + + /// Error indicating that a quorum of responses was not reached from the calendars. + #[error("Quorum of {required} not reached, only {received} responses received")] + QuorumNotReached { + /// Number of responses required to reach quorum + required: usize, + /// Number of responses actually received + received: usize, + }, +} + +impl From for Error { + fn from(value: reqwest::Error) -> Self { + Self::Http(Box::new(value)) + } +} + +impl From for Error { + fn from(value: http::Error) -> Self { + Self::Http(Box::new(value)) + } +} diff --git a/packages/sdk-rs/src/lib.rs b/packages/sdk-rs/src/lib.rs new file mode 100644 index 0000000..a01c05f --- /dev/null +++ b/packages/sdk-rs/src/lib.rs @@ -0,0 +1,130 @@ +//! Rust SDK for the Universal Timestamps protocol. + +use backon::{ExponentialBuilder, Retryable}; +use bytes::Bytes; +use http::StatusCode; +use reqwest::{Client, RequestBuilder}; +use std::{collections::HashSet, sync::Arc, time::Duration}; +use tracing::trace; +use url::Url; + +mod builder; +mod error; +mod stamp; +mod upgrade; + +pub use error::Error; +pub use upgrade::UpgradeResult; + +/// Alias `Result` to use the crate's error type by default. +pub type Result = std::result::Result; + +/// SDK for interacting with Universal Timestamping protocol. +#[derive(Debug, Clone)] +pub struct Sdk { + inner: Arc, +} + +#[derive(Debug)] +struct SdkInner { + http_client: Client, + + // Stamp behaviors + calendars: HashSet, + quorum: usize, + timeout_seconds: u64, + retry: ExponentialBuilder, + + // Privacy + nonce_size: usize, + + // Upgrade behaviors + keep_pending: bool, +} + +impl Default for Sdk { + fn default() -> Self { + Self::new() + } +} + +impl Sdk { + /// Create a new SDK with default settings. + pub fn new() -> Self { + Self::builder() + .build() + .expect("Default SDK should be valid") + } + + /// Create a new SDK builder with default settings. + pub fn builder() -> builder::SdkBuilder { + builder::SdkBuilder::default() + } + + /// Create a new SDK builder with given calendars and default settings. + pub fn try_builder_from_calendars( + calendars: impl IntoIterator, + ) -> Result { + builder::SdkBuilder::try_default_from_calendars(calendars) + } + + async fn http_request_with_retry( + &self, + url: Url, + response_size_limit: usize, + builder_fn: Builder, + ) -> Result<(http::response::Parts, Bytes)> + where + Builder: Fn(RequestBuilder) -> RequestBuilder + Send + Sync + 'static, + { + let client = self.inner.http_client.clone(); + let timeout_seconds = self.inner.timeout_seconds; + let res = { + move || { + let client = client.clone(); + let url = url.clone(); + let req = client + .get(url) + .timeout(Duration::from_secs(timeout_seconds)); + let req = builder_fn(req); + + async move { + let res = req.send().await?; + if res.status().is_server_error() + || ( + // specially treat 404 as non-error + res.status().is_client_error() && res.status() != StatusCode::NOT_FOUND + ) + { + res.error_for_status() + } else { + Ok::<_, reqwest::Error>(res) + } + } + } + } + .retry(self.inner.retry) + .when(|e| { + if e.is_connect() || e.is_timeout() { + return true; + } + if let Some(status) = e.status() { + return status.is_server_error() || status == StatusCode::TOO_MANY_REQUESTS; + } + false + }) + .notify(|e, duration| { + trace!("retrying error {e:?} after sleeping {duration:?}"); + }) + .await?; + + let res: http::Response = res.into(); + let (parts, body) = res.into_parts(); + let body = http_body_util::Limited::new(body, response_size_limit); + let bytes = http_body_util::BodyExt::collect(body) + .await + .map_err(Error::Http)? + .to_bytes(); + Ok((parts, bytes)) + } +} diff --git a/packages/sdk-rs/src/stamp.rs b/packages/sdk-rs/src/stamp.rs new file mode 100644 index 0000000..4d884f6 --- /dev/null +++ b/packages/sdk-rs/src/stamp.rs @@ -0,0 +1,187 @@ +use crate::{Result, Sdk, error::Error}; +use digest::{Digest, FixedOutputReset, Output}; +use std::path::PathBuf; +use tokio::fs; +use tracing::{debug, instrument}; +use url::Url; +use uts_bmt::MerkleTree; +use uts_core::{ + alloc, + alloc::{Allocator, Global}, + codec::{ + DecodeIn, + v1::{DetachedTimestamp, DigestHeader, Timestamp, TimestampBuilder, opcode::DigestOpExt}, + }, + utils::{HashAsyncFsExt, Hexed}, +}; + +impl Sdk { + /// Creates a timestamp for the given files. + pub async fn stamp_files(&self, files: &[PathBuf]) -> Result> + where + D: Digest + FixedOutputReset + DigestOpExt + Send, + Output: Copy, + { + Ok(Vec::from_iter( + self.stamp_files_in::<_, D>(files, Global).await?, + )) + } + + /// Creates a timestamp for the given digests. + pub async fn stamp_digest(&self, digests: &[Output]) -> Result> + where + D: Digest + FixedOutputReset + DigestOpExt + Send, + Output: Copy, + { + Ok(Vec::from_iter( + self.stamp_digests_in::<_, D>(digests, Global).await?, + )) + } + + /// Creates a timestamp for the given files in the provided allocator. + /// + /// # Note + /// + /// This uses the `allocator_api2` crate for allocator api. + pub async fn stamp_files_in( + &self, + files: &[PathBuf], + allocator: A, + ) -> Result, A>> + where + A: Allocator + Clone, + D: Digest + FixedOutputReset + DigestOpExt + Send, + Output: Copy, + { + let digests = futures::future::join_all(files.iter().map(|f| hash_file::(f.clone()))) + .await + .into_iter() + .collect::, _>>()?; + + self.stamp_digests_in::<_, D>(&digests, allocator).await + } + + /// Creates a timestamp for the given digests in the provided allocator. + /// + /// # Note + /// + /// This uses the `allocator_api2` crate for allocator api. + pub async fn stamp_digests_in( + &self, + digests: &[Output], + allocator: A, + ) -> Result, A>> + where + A: Allocator + Clone, + D: Digest + FixedOutputReset + DigestOpExt + Send, + Output: Copy, + { + let mut builders: alloc::vec::Vec, A> = + alloc::vec![in allocator.clone(); Timestamp::builder_in(allocator.clone()) ]; + + let mut nonced_digest = alloc::vec::Vec::with_capacity_in(digests.len(), allocator.clone()); + + for (builder, digest) in builders.iter_mut().zip(digests.iter()) { + if self.inner.nonce_size == 0 { + nonced_digest.push(*digest); + continue; + } + + let mut hasher = D::new(); + Digest::update(&mut hasher, digest); + + let mut nonce = + alloc::vec::Vec::with_capacity_in(self.inner.nonce_size, allocator.clone()); + nonce.resize(self.inner.nonce_size, 0); + rand::fill(&mut nonce[..]); + + Digest::update(&mut hasher, &nonce); + builder.append(nonce).digest::(); + + nonced_digest.push(hasher.finalize()) + } + + let root = if digests.len() > 1 { + let internal_tire = MerkleTree::::new(&nonced_digest); + let root = internal_tire.root(); + debug!(internal_tire_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); + } + *root + } else { + nonced_digest[0] + }; + + let stamps_futures = futures::future::join_all( + self.inner + .calendars + .iter() + .map(|calendar| self.request_calendar(calendar.clone(), &root, allocator.clone())), + ) + .await + .into_iter() + .filter_map(|res| res.ok()); + let mut results = + alloc::vec::Vec::with_capacity_in(self.inner.calendars.len(), allocator.clone()); + for stamp in stamps_futures { + results.push(stamp); + } + + if results.len() < self.inner.quorum { + return Err(Error::QuorumNotReached { + required: self.inner.quorum, + received: results.len(), + }); + } + + let merged = if results.len() == 1 { + results.into_iter().next().unwrap() + } else { + Timestamp::::merge_in(results, allocator.clone()) + }; + + let mut stamps = alloc::vec::Vec::with_capacity_in(builders.len(), allocator.clone()); + for (builder, digest) in builders.into_iter().zip(digests.iter()) { + let timestamp = builder.concat(merged.clone()); + let header = DigestHeader::new::(*digest); + let timestamp = DetachedTimestamp::from_parts(header, timestamp); + stamps.push(timestamp); + } + + Ok(stamps) + } + + #[instrument(skip(self, allocator), level = "debug", err)] + async fn request_calendar( + &self, + calendar: Url, + root: &[u8], + allocator: A, + ) -> Result> { + let url = calendar.join("digest")?; + + let root = root.to_vec(); + let (_, body) = self + .http_request_with_retry( + url, + 10 * 1024, // 10 KB + move |req| { + req.header("Accept", "application/vnd.opentimestamps.v1") + .body(root.clone()) + }, + ) + .await?; + + Ok(Timestamp::::decode_in(&mut &*body, allocator)?) + } +} + +async fn hash_file(path: PathBuf) -> Result> { + let mut hasher = D::new(); + let file = fs::File::open(path).await?; + HashAsyncFsExt::update(&mut hasher, file).await?; + Ok(hasher.finalize()) +} diff --git a/packages/sdk-rs/src/upgrade.rs b/packages/sdk-rs/src/upgrade.rs new file mode 100644 index 0000000..e23fced --- /dev/null +++ b/packages/sdk-rs/src/upgrade.rs @@ -0,0 +1,89 @@ +use crate::{Result, Sdk, error::Error}; +use http::StatusCode; +use std::collections::BTreeMap; +use tracing::{debug, warn}; +use url::Url; +use uts_core::{ + alloc::Allocator, + codec::{ + DecodeIn, + v1::{Attestation, DetachedTimestamp, PendingAttestation, Timestamp}, + }, + utils::Hexed, +}; + +/// Result of attempting to upgrade a pending attestation. +#[derive(Debug)] +pub enum UpgradeResult { + /// The attestation has been successfully upgraded. + Upgraded, + /// The attestation is still pending and not ready to be upgraded. + Pending, + /// The attestation upgrade failed due to an error. + Failed(Error), +} + +impl Sdk { + /// Upgrades all pending attestations in the given detached timestamp. + pub async fn upgrade( + &self, + stamp: &mut DetachedTimestamp, + ) -> Result> { + let mut results = BTreeMap::new(); + + let alloc = stamp.allocator().clone(); + for step in stamp.pending_attestations_mut() { + let (calendar_server, retrieve_uri) = { + let Timestamp::Attestation(attestation) = step else { + unreachable!("bug: PendingAttestationIterMut should only yield Attestations"); + }; + let commitment = attestation.value().expect("finalized when decode"); + let calendar_server = PendingAttestation::from_raw(&*attestation)?.uri; + + let retrieve_uri = Url::parse(&calendar_server)? + .join(&format!("timestamp/{}", Hexed(commitment)))?; + + (calendar_server.to_string(), retrieve_uri) + }; + + let result = self + .http_request_with_retry( + retrieve_uri, + 10 * 1024 * 1024, // 10 MiB response size limit + |req| req.header("Accept", "application/vnd.opentimestamps.v1"), + ) + .await; + + let result = match result { + Ok((parts, _)) if parts.status == StatusCode::NOT_FOUND => { + debug!("attestation from {calendar_server} not ready yet, skipping"); + UpgradeResult::Pending + } + Ok((_, response)) => { + let attestation = Timestamp::decode_in(&mut &*response, alloc.clone())?; + + *step = if self.inner.keep_pending { + Timestamp::merge_in( + uts_core::alloc::vec![in alloc.clone(); attestation, step.clone()], + alloc.clone(), + ) + } else { + attestation + }; + UpgradeResult::Upgraded + } + Err(e) => { + warn!("failed to upgrade pending attestation from {calendar_server}: {e}"); + UpgradeResult::Failed(e) + } + }; + + if let Some(old) = results.insert(calendar_server.clone(), result) { + warn!( + "multiple pending attestations from {calendar_server}, previous result was {old:?}, you should only attest to a calendar once per timestamp" + ); + } + } + Ok(results) + } +} From e6de4b39dead10a36eee4811e90a2d4e304834a5 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 11:36:01 +0800 Subject: [PATCH 06/11] move verify to sdk --- Cargo.lock | 7 +- crates/cli/src/commands/upgrade.rs | 23 +- crates/cli/src/commands/verify.rs | 136 ++---------- crates/core/Cargo.toml | 30 ++- crates/core/src/verifier.rs | 13 +- crates/core/src/verifier/bitcoin.rs | 203 ++++++++++++++++++ crates/core/src/verifier/eas.rs | 4 +- packages/sdk-rs/Cargo.toml | 28 ++- packages/sdk-rs/src/builder.rs | 70 +++++- packages/sdk-rs/src/error.rs | 24 ++- packages/sdk-rs/src/lib.rs | 17 +- packages/sdk-rs/src/verify.rs | 320 ++++++++++++++++++++++++++++ 12 files changed, 721 insertions(+), 154 deletions(-) create mode 100644 crates/core/src/verifier/bitcoin.rs create mode 100644 packages/sdk-rs/src/verify.rs diff --git a/Cargo.lock b/Cargo.lock index 24fd09e..c8a2d77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6041,10 +6041,14 @@ dependencies = [ "alloy-provider", "alloy-sol-types", "auto_impl", + "backon", "bytes", "digest 0.10.7", + "http", + "http-body-util", "once_cell", "paste", + "reqwest 0.13.2", "ripemd", "serde", "serde_json", @@ -6055,6 +6059,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", + "url", "uts-bmt", "uts-contracts", ] @@ -6107,6 +6112,7 @@ version = "0.1.0-alpha.0" dependencies = [ "alloy-primitives", "alloy-provider", + "alloy-rpc-client", "backon", "bytes", "digest 0.10.7", @@ -6123,7 +6129,6 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", - "tracing-subscriber", "url", "uts-bmt", "uts-contracts", diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index 77009ae..063c859 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -48,15 +48,21 @@ impl Upgrade { } async fn upgrade_one(sdk: Sdk, path: PathBuf) -> eyre::Result<()> { + info!("[{}] upgrading attestation...", path.display()); let file = tokio::fs::read(&path).await?; let mut proof = VersionedProof::::decode(&mut &*file)?; let results = sdk.upgrade(&mut proof).await?; + + let mut changed = false; for (calendar_server, result) in results { match result { - UpgradeResult::Upgraded => info!( - "[{}] attestation from {calendar_server} upgraded successfully", - path.display() - ), + UpgradeResult::Upgraded => { + info!( + "[{}] attestation from {calendar_server} upgraded successfully", + path.display() + ); + changed = true; + } UpgradeResult::Pending => info!( "[{}] attestation from {calendar_server} is still pending, skipping", path.display() @@ -67,8 +73,11 @@ async fn upgrade_one(sdk: Sdk, path: PathBuf) -> eyre::Result<()> { ), } } - let mut buf = Vec::new(); - proof.encode(&mut buf)?; - tokio::fs::write(path, buf).await?; + + if changed { + 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 index 37536b2..2f136e6 100644 --- a/crates/cli/src/commands/verify.rs +++ b/crates/cli/src/commands/verify.rs @@ -1,146 +1,38 @@ -use alloy_primitives::Address; -use alloy_provider::ProviderBuilder; use clap::Args; -use digest::{Digest, DynDigest}; -use eyre::{Context, bail}; -use jiff::{Timestamp, tz::TimeZone}; -use std::{ - fs::File, - io::{BufReader, Read}, - path::PathBuf, - process, -}; -use tracing::{error, info, warn}; -use uts_contracts::eas::EAS_ADDRESSES; -use uts_core::{ - codec::{ - Decode, Reader, VersionedProof, - v1::{ - Attestation, DetachedTimestamp, EASAttestation, EASTimestamped, PendingAttestation, - opcode::*, - }, - }, - utils::Hexed, - verifier::{AttestationVerifier, EASVerifier}, -}; +use std::{fs::File, path::PathBuf}; +use tracing::info; +use uts_core::codec::{Decode, Reader, VersionedProof, v1::DetachedTimestamp}; +use uts_sdk::Sdk; #[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, - /// Optional EAS contract address for verifying EAS attestations. If not provided, the default - /// EAS contract for the chain will be used. - #[arg(long)] - eas: Option
, } impl Verify { pub async fn run(self) -> eyre::Result<()> { + let sdk = Sdk::new(); + 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)?))? + VersionedProof::::decode(&mut Reader(File::open(&stamp_file)?))? .proof; + let results = sdk.verify(&self.file, ×tamp).await?; - 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() { - error!( - "Digest mismatch! Expected: {}, Found: {}", - Hexed(&expected), - Hexed(digest_header.digest()) + for result in results.iter() { + info!( + "Attestation: {}, Status: {:?}", + result.attestation, result.status ); - process::exit(1); - } - info!("Digest matches: {}", Hexed(&expected)); - - timestamp.try_finalize()?; - - for attestation in timestamp.attestations() { - if attestation.tag == PendingAttestation::TAG { - continue; // skip pending attestations - } - if attestation.tag != EASAttestation::TAG && attestation.tag != EASTimestamped::TAG { - warn!("Unknown attestation type: {}", Hexed(&attestation.tag)); - } - - let expected = attestation - .value() - .expect("Attestation value should be finalized"); - let chain = if attestation.tag == EASAttestation::TAG { - EASAttestation::from_raw(attestation)?.chain - } else { - EASTimestamped::from_raw(attestation)?.chain - }; - - let provider_url = if let Some(url) = self.eth_provider.as_deref() { - url - } else { - match 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: {chain}"), - } - }; - - let eas_address = if let Some(addr) = self.eas { - addr - } else { - EAS_ADDRESSES - .get(&chain.id()) - .copied() - .ok_or_else(|| eyre::eyre!("No default EAS contract for chain: {chain}"))? - }; - - let provider = ProviderBuilder::new().connect(provider_url).await?; - let verifier = EASVerifier::new(eas_address, provider); - - let time: u64; - if attestation.tag == EASAttestation::TAG { - let eas_attestation = EASAttestation::from_raw(attestation)?; - let result = verifier.verify(&eas_attestation, expected).await?; - info!("EAS On Chain {chain} Attestation: {}", result.uid); - time = result.time; - info!("\tattester: {}", result.attester); - } else { - let timestamped = EASTimestamped::from_raw(attestation)?; - time = verifier.verify(×tamped, expected).await?; - info!("EAS Timestamped On Chain {chain}"); - } - - let ts = Timestamp::from_second(time.try_into().context("i64 overflow")?)?; - let zdt = ts.to_zoned(TimeZone::system()); - info!("\ttime attested: {zdt}"); - info!("\tmerkle root: {}", Hexed(&expected)); } + let overall = sdk.aggregate_verify_results(&results); + info!("Overall verification result: {overall:?}"); Ok(()) } } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c12ae5f..c3e3e60 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -23,12 +23,17 @@ alloy-primitives = { workspace = true } alloy-provider = { workspace = true, optional = true } alloy-sol-types = { workspace = true, optional = true } auto_impl.workspace = true +backon = { workspace = true, optional = true } bytes = { workspace = true, optional = true } -digest.workspace = true +digest = { workspace = true } +http = { workspace = true, optional = true } +http-body-util = { workspace = true, optional = true } once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true +reqwest = { workspace = true, optional = true, default-features = false } ripemd.workspace = true serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } sha1.workspace = true sha2.workspace = true @@ -36,19 +41,34 @@ sha3.workspace = true thiserror.workspace = true tokio = { workspace = true, optional = true } tracing = { workspace = true, optional = true } +url = { workspace = true, optional = true } uts-bmt = { workspace = true } uts-contracts = { workspace = true, optional = true } [features] bytes = ["dep:bytes"] default = ["std"] -eas-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-contract", "dep:alloy-sol-types", "dep:uts-contracts"] -io-utils = ["dep:tokio", "tokio/fs"] -nightly = [] -serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] + std = [] + +serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] tracing = ["dep:tracing"] + +bitcoin-verifier = ["verifier", "dep:reqwest", "dep:url", "dep:backon", "dep:http", "dep:http-body-util", "dep:serde", "dep:serde_json", "reqwest/json", "serde/derive"] +eas-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-contract", "dep:alloy-sol-types", "dep:uts-contracts"] verifier = [] +io-utils = ["dep:tokio", "tokio/fs"] + +nightly = [] + +reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] +reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/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", "alloy-provider/reqwest-rustls-tls"] + [dev-dependencies] serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } diff --git a/crates/core/src/verifier.rs b/crates/core/src/verifier.rs index 7f6fc5a..d168c52 100644 --- a/crates/core/src/verifier.rs +++ b/crates/core/src/verifier.rs @@ -3,10 +3,15 @@ use crate::{ error::DecodeError, }; +#[cfg(feature = "bitcoin-verifier")] +mod bitcoin; #[cfg(feature = "eas-verifier")] mod eas; + +#[cfg(feature = "bitcoin-verifier")] +pub use bitcoin::{BitcoinVerifier, BitcoinVerifierError}; #[cfg(feature = "eas-verifier")] -pub use eas::EASVerifier; +pub use eas::{EASVerifier, EASVerifierError}; #[derive(Debug, thiserror::Error)] pub enum VerifyError { @@ -26,7 +31,11 @@ pub enum VerifyError { /// An error occurred while verifying the eas attestation. #[cfg(feature = "eas-verifier")] #[error("error verifying eas attestation: {0}")] - EAS(#[from] eas::EASVerifierError), + EAS(#[from] EASVerifierError), + /// An error occurred while verifying the bitcoin attestation. + #[cfg(feature = "bitcoin-verifier")] + #[error("error verifying bitcoin attestation: {0}")] + Bitcoin(#[from] BitcoinVerifierError), } pub trait AttestationVerifier

diff --git a/crates/core/src/verifier/bitcoin.rs b/crates/core/src/verifier/bitcoin.rs new file mode 100644 index 0000000..dcaa891 --- /dev/null +++ b/crates/core/src/verifier/bitcoin.rs @@ -0,0 +1,203 @@ +use crate::{ + codec::v1::BitcoinAttestation, + verifier::{AttestationVerifier, VerifyError}, +}; +use alloy_primitives::{hex, hex::FromHexError}; +use backon::{ExponentialBuilder, Retryable}; +use http_body_util::LengthLimitError; +use reqwest::Client; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use serde_json::json; +use url::Url; + +const RESPONSE_SIZE_LIMIT: usize = 10 * 1024; // 10 KiB + +#[derive(Debug, Clone)] +pub struct BitcoinVerifier { + client: Client, + provider: Url, + retry: ExponentialBuilder, +} + +#[derive(Debug, thiserror::Error)] +pub enum BitcoinVerifierError { + #[error("error making JSON-RPC request: {0}")] + Request(#[from] reqwest::Error), + #[error("error processing JSON-RPC response: {0}")] + Response(Box), + #[error("error parsing JSON-RPC response: {0}")] + Json(#[from] serde_json::Error), + #[error(transparent)] + Rpc(JsonRpcError), + #[error("invalid hex")] + Hex(#[from] FromHexError), + #[error("invalid attestation")] + Invalid, +} + +#[derive(Debug, Deserialize, thiserror::Error)] +#[error("rpc error {code}: {message}")] +pub struct JsonRpcError { + code: i32, + message: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BitcoinHeader { + pub hash: String, + pub merkleroot: String, + pub height: u32, + pub time: u64, +} + +#[derive(Deserialize)] +struct JsonRpcResponse { + result: T, + error: Option, +} + +impl BitcoinVerifier { + pub fn new(provider: Url) -> Self { + Self { + client: Client::new(), + provider, + retry: ExponentialBuilder::default(), + } + } + + pub fn from_parts(client: Client, provider: Url, retry: ExponentialBuilder) -> Self { + Self { + client, + provider, + retry, + } + } + + pub async fn get_blockhash(&self, height: u32) -> Result { + self.req("getblockhash", [height]).await + } + + pub async fn get_block_header( + &self, + hash: &str, + ) -> Result { + self.req("getblockheader", (hash, true)).await + } + + pub async fn verify( + &self, + attestation: &BitcoinAttestation, + value: &[u8], + ) -> Result { + let height = attestation.height; + + let blockhash = self.get_blockhash(height).await?; + + let header = self.get_block_header(&blockhash).await?; + + // Bitcoin reverses the blockhash in RPC responses, so we need to reverse it back to get the correct hash. + let mut hash = hex::decode(&header.merkleroot)?; + hash.reverse(); + if hash != value { + return Err(BitcoinVerifierError::Invalid); + } + + Ok(header) + } + + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = trace, err = "warn"))] + async fn req( + &self, + method: &str, + params: P, + ) -> Result { + let body = json!({ + "jsonrpc": "1.0", + "id": 1, + "method": method, + "params": params, + }); + let req = self.client.post(self.provider.clone()).json(&body); + + { + move || { + let req = req.try_clone().expect("infallible"); + + async move { + let res = req.send().await?.error_for_status()?; + + let res: http::Response = res.into(); + let body = http_body_util::Limited::new(res.into_body(), RESPONSE_SIZE_LIMIT); + let bytes = http_body_util::BodyExt::collect(body) + .await + .map_err(BitcoinVerifierError::Response)? + .to_bytes(); + + let response: JsonRpcResponse = serde_json::from_slice(&bytes)?; + if let Some(error) = response.error { + Err(BitcoinVerifierError::Rpc(error)) + } else { + Ok(response.result) + } + } + } + } + .retry(self.retry) + .when(|e| { + use BitcoinVerifierError::*; + match e { + Request(_) => true, + Response(e) => e.downcast_ref::().is_none(), + _ => false, + } + }) + .await + } +} + +impl AttestationVerifier for BitcoinVerifier { + type Output = BitcoinHeader; + + async fn verify( + &self, + attestation: &BitcoinAttestation, + value: &[u8], + ) -> Result { + Ok(self.verify(attestation, value).await?) + } +} + +impl BitcoinVerifierError { + /// The error indicates this attestation is invalid and cannot be verified. + #[inline] + pub fn is_fatal(&self) -> bool { + matches!(self, BitcoinVerifierError::Invalid) + } + + /// The error indicates this attestation may be valid but cannot be verified at the moment. + #[inline] + pub fn should_retry(&self) -> bool { + !matches!(self, BitcoinVerifierError::Invalid) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[ignore] + #[tokio::test] + async fn test_get_blockhash() { + let verifier = + BitcoinVerifier::new(Url::parse("https://bitcoin-rpc.publicnode.com").unwrap()); + let hash = verifier.get_blockhash(0).await.unwrap(); + assert_eq!( + hash, + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ); + let header = verifier.get_block_header(&hash).await.unwrap(); + assert_eq!(header.hash, hash); + assert_eq!(header.height, 0); + assert_eq!(header.time, 1231006505); + } +} diff --git a/crates/core/src/verifier/eas.rs b/crates/core/src/verifier/eas.rs index 5fd9fd1..bb17051 100644 --- a/crates/core/src/verifier/eas.rs +++ b/crates/core/src/verifier/eas.rs @@ -64,7 +64,7 @@ impl AttestationVerifier for EASVerifier

{ } impl EASVerifier

{ - async fn verify_attestation( + pub async fn verify_attestation( &self, attestation: &EASAttestation, value: &[u8], @@ -92,7 +92,7 @@ impl EASVerifier

{ Ok(attestation) } - async fn verify_timestamped( + pub async fn verify_timestamped( &self, _attestation: &EASTimestamped, value: &[u8], diff --git a/packages/sdk-rs/Cargo.toml b/packages/sdk-rs/Cargo.toml index dec8a11..c4593d2 100644 --- a/packages/sdk-rs/Cargo.toml +++ b/packages/sdk-rs/Cargo.toml @@ -11,6 +11,7 @@ version.workspace = true [dependencies] alloy-primitives = { workspace = true } alloy-provider = { workspace = true, default-features = false, features = ["reqwest"] } +alloy-rpc-client = { workspace = true } backon = { workspace = true } bytes = { workspace = true } digest = { workspace = true } @@ -27,21 +28,28 @@ sha3 = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } url = { workspace = true } uts-bmt = { workspace = true } -uts-contracts = { workspace = true } -uts-core = { workspace = true, features = ["std", "eas-verifier", "io-utils"] } +uts-contracts = { workspace = true, optional = true } +uts-core = { workspace = true, features = ["std", "io-utils"] } [features] -full = [] +full = [ + "eas-verifier", + "bitcoin-verifier", +] -reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] -reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/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", "alloy-provider/reqwest-rustls-tls"] +tracing = ["uts-core/tracing"] + +bitcoin-verifier = ["uts-core/bitcoin-verifier"] +eas-verifier = ["dep:uts-contracts", "uts-core/eas-verifier", "uts-contracts/provider-helper"] + +reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls", "uts-core/reqwest-default-tls"] +reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/reqwest-native-tls", "uts-core/reqwest-native-tls"] +reqwest-native-tls-no-alpn = ["reqwest/native-tls-no-alpn", "uts-core/reqwest-native-tls-no-alpn"] +reqwest-native-tls-vendored = ["reqwest/native-tls-vendored", "uts-core/reqwest-native-tls-vendored"] +reqwest-native-tls-vendored-no-alpn = ["reqwest/native-tls-vendored-no-alpn", "uts-core/reqwest-native-tls-vendored-no-alpn"] +reqwest-rustls = ["reqwest/rustls", "alloy-provider/reqwest-rustls-tls", "uts-core/reqwest-rustls"] [lints] workspace = true diff --git a/packages/sdk-rs/src/builder.rs b/packages/sdk-rs/src/builder.rs index 87c03c0..c9ad914 100644 --- a/packages/sdk-rs/src/builder.rs +++ b/packages/sdk-rs/src/builder.rs @@ -1,12 +1,15 @@ use crate::{Sdk, SdkInner}; +use alloy_primitives::ChainId; +use alloy_provider::{Provider, ProviderBuilder}; use backon::ExponentialBuilder; use reqwest::Client; use std::{ - collections::HashSet, + collections::{BTreeMap, HashSet}, sync::{Arc, LazyLock}, time::Duration, }; use url::Url; +use uts_contracts::provider_helper::{RetryBackoffArgs, ThrottleArgs}; /// Default public calendars to use. static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { @@ -22,6 +25,15 @@ static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { ]) }); +static DEFAULT_PROVIDERS: LazyLock> = LazyLock::new(|| { + BTreeMap::from([ + (1, Url::parse("https://0xrpc.io/eth").unwrap()), + (11155111, Url::parse("https://0xrpc.io/sep").unwrap()), + (534352, Url::parse("https://rpc.scroll.io").unwrap()), + (534351, Url::parse("https://sepolia-rpc.scroll.io").unwrap()), + ]) +}); + #[derive(Debug, thiserror::Error)] pub enum BuilderError { #[error("At least one calendar must be specified")] @@ -47,6 +59,12 @@ pub struct SdkBuilder { nonce_size: usize, keep_pending: bool, + + eth_providers: BTreeMap, + eth_compute_units_per_second: u64, + eth_requests_per_second: u32, + + bitcoin_rpc: Url, } impl Default for SdkBuilder { @@ -70,6 +88,12 @@ impl SdkBuilder { nonce_size: 32, keep_pending: false, + + eth_providers: BTreeMap::new(), + eth_compute_units_per_second: 20, + eth_requests_per_second: 25, + + bitcoin_rpc: Url::parse("https://bitcoin-rpc.publicnode.com").unwrap(), } } @@ -82,6 +106,8 @@ impl SdkBuilder { let this = Self { calendars, + + eth_providers: DEFAULT_PROVIDERS.clone(), ..Self::empty() }; @@ -170,6 +196,24 @@ impl SdkBuilder { self } + /// Add an Ethereum provider for a given chain ID. The URL should point to an Ethereum node that supports the JSON-RPC API. + pub fn add_eth_provider(mut self, chain_id: ChainId, url: Url) -> Self { + self.eth_providers.insert(chain_id, url); + self + } + + /// Set the compute units per second for Ethereum provider requests. This is used to rate limit requests to avoid overwhelming the provider. + pub fn with_eth_compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + self.eth_compute_units_per_second = compute_units_per_second; + self + } + + /// Set the requests per second for Ethereum provider requests. This is used to rate limit requests to avoid overwhelming the provider. + pub fn with_eth_requests_per_second(mut self, requests_per_second: u32) -> Self { + self.eth_requests_per_second = requests_per_second; + self + } + /// Build the SDK from the builder, validating the configuration. Returns an error if the configuration is invalid. pub fn build(self) -> Result { if self.calendars.is_empty() { @@ -191,6 +235,27 @@ impl SdkBuilder { .expect("default HTTP client should be valid") }; + let eth_retry = RetryBackoffArgs { + compute_units_per_second: self.eth_compute_units_per_second, + ..Default::default() + }; + let eth_throttle = ThrottleArgs { + requests_per_second: self.eth_requests_per_second, + }; + let eth_providers = self + .eth_providers + .into_iter() + .map(|(chain_id, url)| { + let provider = ProviderBuilder::new().connect_client( + alloy_rpc_client::ClientBuilder::default() + .layer(eth_retry.layer()) + .layer(eth_throttle.layer()) + .http(url), + ); + (chain_id, provider.erased()) + }) + .collect(); + Ok(Sdk { inner: Arc::new(SdkInner { http_client, @@ -203,6 +268,9 @@ impl SdkBuilder { nonce_size: self.nonce_size, keep_pending: self.keep_pending, + + eth_providers, + bitcoin_rpc: self.bitcoin_rpc, }), }) } diff --git a/packages/sdk-rs/src/error.rs b/packages/sdk-rs/src/error.rs index f36c7ab..c53fcc9 100644 --- a/packages/sdk-rs/src/error.rs +++ b/packages/sdk-rs/src/error.rs @@ -1,4 +1,4 @@ -use alloy_primitives::private::serde::de::StdError; +use alloy_primitives::{ChainId, private::serde::de::StdError}; pub(crate) type BoxError = Box; @@ -14,6 +14,9 @@ pub enum Error { /// Error parsing a URL, such as a calendar endpoint. #[error("URL parse error: {0}")] Url(#[from] url::ParseError), + /// Error during time-related operations, such as calculating timestamps or durations. + #[error("Time error: {0}")] + Jiff(#[from] jiff::Error), /// Error happened during decoding of a proof. #[error("uts decoding error: {0}")] @@ -21,6 +24,9 @@ pub enum Error { /// Error happened during encoding of a proof. #[error("uts encoding error: {0}")] Encode(#[from] uts_core::error::EncodeError), + /// Error indicating that finalization of a timestamp failed due to conflicting inputs. + #[error(transparent)] + Finalization(#[from] uts_core::codec::v1::FinalizationError), /// Error indicating that a quorum of responses was not reached from the calendars. #[error("Quorum of {required} not reached, only {received} responses received")] @@ -30,6 +36,22 @@ pub enum Error { /// Number of responses actually received received: usize, }, + + /// Error indicating that a digest mismatch occurred, with expected and actual digest values. + #[error("Digest mismatch: expected {expected:?}, got {actual:?}")] + DigestMismatch { + /// Expected digest value + expected: Box<[u8]>, + /// Actual digest value + actual: Box<[u8]>, + }, + + /// Error indicating that an unsupported feature was encountered, with a message describing the unsupported feature. + #[error("Unsupported feature: {0}")] + Unsupported(&'static str), + /// Error indicating that an unsupported chain ID was encountered. + #[error("Unsupported chain ID: {0}")] + UnsupportedChain(ChainId), } impl From for Error { diff --git a/packages/sdk-rs/src/lib.rs b/packages/sdk-rs/src/lib.rs index a01c05f..2cc3cc3 100644 --- a/packages/sdk-rs/src/lib.rs +++ b/packages/sdk-rs/src/lib.rs @@ -1,10 +1,16 @@ //! Rust SDK for the Universal Timestamps protocol. +use alloy_primitives::ChainId; +use alloy_provider::DynProvider; use backon::{ExponentialBuilder, Retryable}; use bytes::Bytes; use http::StatusCode; use reqwest::{Client, RequestBuilder}; -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, HashSet}, + sync::Arc, + time::Duration, +}; use tracing::trace; use url::Url; @@ -12,6 +18,7 @@ mod builder; mod error; mod stamp; mod upgrade; +mod verify; pub use error::Error; pub use upgrade::UpgradeResult; @@ -29,7 +36,7 @@ pub struct Sdk { struct SdkInner { http_client: Client, - // Stamp behaviors + // Stamp calendars: HashSet, quorum: usize, timeout_seconds: u64, @@ -38,8 +45,12 @@ struct SdkInner { // Privacy nonce_size: usize, - // Upgrade behaviors + // Upgrade keep_pending: bool, + + // Verify + eth_providers: BTreeMap, + bitcoin_rpc: Url, } impl Default for Sdk { diff --git a/packages/sdk-rs/src/verify.rs b/packages/sdk-rs/src/verify.rs new file mode 100644 index 0000000..db83848 --- /dev/null +++ b/packages/sdk-rs/src/verify.rs @@ -0,0 +1,320 @@ +use crate::{Error, Result, Sdk}; +use alloy_provider::DynProvider; +use backon::RetryableWithContext; +use digest::Digest; +use jiff::Timestamp; +use std::path::Path; +use tokio::{ + fs::File, + io::{AsyncReadExt, BufReader}, +}; +use uts_contracts::eas::EAS_ADDRESSES; +use uts_core::{ + alloc, + alloc::Allocator, + codec::v1::{ + Attestation, BitcoinAttestation, DetachedTimestamp, EASAttestation, EASTimestamped, + PendingAttestation, RawAttestation, + opcode::{KECCAK256, RIPEMD160, SHA1, SHA256}, + }, + verifier::{BitcoinVerifier, EASVerifier}, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AttestationStatusKind { + /// The attestation is valid. + Valid(Timestamp), + /// The attestation is invalid. + Invalid, + /// The attestation is pending and has not yet been verified. + Pending, + /// The attestation is unknown, either because it is of an unsupported type or because an error occurred during verification. + Unknown, +} + +#[derive(Debug, Clone)] +pub struct AttestationStatus { + pub attestation: RawAttestation, + pub status: AttestationStatusKind, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum VerifyStatus { + /// The timestamp is valid and all attestations are valid. + Valid(Timestamp), + /// The timestamp is partially valid, at least one attestation is not valid. + PartiallyValid(Timestamp), + /// All attestations are pending. + Pending, + /// All attestations are unknown. + Unknown, +} + +impl Sdk { + /// Verifies the given file against the given detached timestamp, returning a list of attestation statuses. + pub async fn verify( + &self, + file: impl AsRef, + timestamp: &DetachedTimestamp, + ) -> Result> { + Ok(Vec::from_iter( + self.verify_in(file, timestamp, alloc::Global).await?, + )) + } + + /// Verifies the given file against the given detached timestamp, returning a list of attestation statuses. + /// + /// This is the same as `verify`, but allows specifying a custom allocator for the attestation statuses. + /// + /// # Note + /// + /// This uses the `allocator_api2` crate for allocator api. + pub async fn verify_in( + &self, + file: impl AsRef, + timestamp: &DetachedTimestamp, + allocator: A, + ) -> Result, A>> { + let digest_header = timestamp.header(); + match digest_header.kind().tag() { + SHA1 => { + self.verify_digest::(file, digest_header.digest()) + .await + } + RIPEMD160 => { + self.verify_digest::(file, digest_header.digest()) + .await + } + SHA256 => { + self.verify_digest::(file, digest_header.digest()) + .await + } + KECCAK256 => { + self.verify_digest::(file, digest_header.digest()) + .await + } + _ => return Err(Error::Unsupported("unknown digest algorithm")), + }?; + + timestamp.try_finalize()?; + let mut result = + alloc::vec::Vec::with_capacity_in(timestamp.attestations().count(), allocator); + for attestation in timestamp.attestations() { + let attestation = attestation.to_owned(); + + if attestation.tag == PendingAttestation::TAG { + result.push(AttestationStatus { + attestation, + status: AttestationStatusKind::Pending, + }); + continue; + } + + let status = self + .verify_attestation_inner(&attestation) + .await + .unwrap_or(AttestationStatusKind::Unknown); + + result.push(AttestationStatus { + attestation, + status, + }); + } + + Ok(result) + } + + /// Verifies the digest of the given file against the expected digest. + pub async fn verify_digest( + &self, + file: impl AsRef, + expected: &[u8], + ) -> Result<()> { + let mut file = BufReader::new(File::open(file.as_ref()).await?); + let mut hasher = D::new(); + let mut buffer = [0u8; 64 * 1024]; // 64KB buffer + loop { + let bytes_read = file.read(&mut buffer).await?; + if bytes_read == 0 { + break; + } + hasher.update(&buffer[..bytes_read]); + } + let actual = hasher.finalize(); + + if *actual != *expected { + return Err(Error::DigestMismatch { + expected: expected.to_vec().into_boxed_slice(), + actual: actual.to_vec().into_boxed_slice(), + }); + } + Ok(()) + } + + /// Aggregate the individual attestation statuses into an overall verification status for the timestamp. + /// + /// The earliest valid attestation timestamp is used as the timestamp for the overall status, if there is at least one valid attestation. + /// + /// The logic is as follows: + /// - If there is at least one VALID attestation: + /// - If there are also INVALID or UNKNOWN attestations, the overall status is PARTIAL_VALID + /// - Otherwise, the overall status is VALID + /// - If there are no VALID attestations, but at least one PENDING attestation, the overall status is PENDING + /// - If there are no VALID or PENDING attestations, the overall status is INVALID + pub fn aggregate_verify_results(&self, results: &[AttestationStatus]) -> VerifyStatus { + let mut valid_ts = None; + let mut has_invalid = false; + let mut has_unknown = false; + let mut has_pending = false; + + for status in results { + match status.status { + AttestationStatusKind::Valid(ts) => { + if valid_ts.is_none() || ts < valid_ts.unwrap() { + valid_ts = Some(ts); + } + } + AttestationStatusKind::Invalid => has_invalid = true, + AttestationStatusKind::Unknown => has_unknown = true, + AttestationStatusKind::Pending => has_pending = true, + } + } + + if let Some(valid_ts) = valid_ts { + if has_invalid || has_unknown { + VerifyStatus::PartiallyValid(valid_ts) + } else { + VerifyStatus::Valid(valid_ts) + } + } else if has_pending { + VerifyStatus::Pending + } else { + VerifyStatus::Unknown + } + } + + async fn verify_attestation_inner( + &self, + attestation: &RawAttestation, + ) -> Result { + let expected = attestation + .value() + .expect("Attestation value should be finalized"); + + let status = if attestation.tag == EASAttestation::TAG { + let attestation = EASAttestation::from_raw(attestation)?; + self.verify_eas_attestation(expected, attestation).await? + } else if attestation.tag == EASTimestamped::TAG { + let attestation = EASTimestamped::from_raw(attestation)?; + self.verify_eas_timestamped(expected, attestation).await? + } else if attestation.tag == BitcoinAttestation::TAG { + let attestation = BitcoinAttestation::from_raw(attestation)?; + self.verify_bitcoin(expected, attestation).await? + } else { + AttestationStatusKind::Unknown + }; + Ok(status) + } + + async fn verify_eas_attestation( + &self, + expected: &[u8], + attestation: EASAttestation, + ) -> Result { + let chain = attestation.chain; + let provider = self + .inner + .eth_providers + .get(&chain.id()) + .ok_or_else(|| Error::UnsupportedChain(chain.id()))?; + let eas_address = EAS_ADDRESSES + .get(&chain.id()) + .ok_or_else(|| Error::UnsupportedChain(chain.id()))?; + + let (_, result) = { + |verifier: EASVerifier| async { + let res = verifier.verify_attestation(&attestation, expected).await; + (verifier, res) + } + } + .retry(self.inner.retry) + .when(|e| e.should_retry()) + .context(EASVerifier::new(*eas_address, provider.clone())) + .await; + + match result { + Ok(result) => { + let ts = Timestamp::from_second(result.time.try_into().expect("i64 overflow"))?; + Ok(AttestationStatusKind::Valid(ts)) + } + Err(e) if e.is_fatal() => Ok(AttestationStatusKind::Invalid), + Err(_) => Ok(AttestationStatusKind::Unknown), + } + } + + async fn verify_eas_timestamped( + &self, + expected: &[u8], + attestation: EASTimestamped, + ) -> Result { + let chain = attestation.chain; + let provider = self + .inner + .eth_providers + .get(&chain.id()) + .ok_or_else(|| Error::UnsupportedChain(chain.id()))?; + let eas_address = EAS_ADDRESSES + .get(&chain.id()) + .ok_or_else(|| Error::UnsupportedChain(chain.id()))?; + + let (_, result) = { + |verifier: EASVerifier| async { + let res = verifier.verify_timestamped(&attestation, expected).await; + (verifier, res) + } + } + .retry(self.inner.retry) + .when(|e| e.should_retry()) + .context(EASVerifier::new(*eas_address, provider.clone())) + .await; + + match result { + Ok(time) => { + let ts = Timestamp::from_second(time.try_into().expect("i64 overflow"))?; + Ok(AttestationStatusKind::Valid(ts)) + } + Err(e) if e.is_fatal() => Ok(AttestationStatusKind::Invalid), + Err(_) => Ok(AttestationStatusKind::Unknown), + } + } + + async fn verify_bitcoin( + &self, + expected: &[u8], + attestation: BitcoinAttestation, + ) -> Result { + let (_, result) = { + |verifier: BitcoinVerifier| async { + let res = verifier.verify(&attestation, expected).await; + (verifier, res) + } + } + .retry(self.inner.retry) + .when(|e| e.should_retry()) + .context(BitcoinVerifier::from_parts( + self.inner.http_client.clone(), + self.inner.bitcoin_rpc.clone(), + self.inner.retry, + )) + .await; + + match result { + Ok(header) => { + let ts = Timestamp::from_second(header.time.try_into().expect("i64 overflow"))?; + Ok(AttestationStatusKind::Valid(ts)) + } + Err(e) if e.is_fatal() => Ok(AttestationStatusKind::Invalid), + Err(_) => Ok(AttestationStatusKind::Unknown), + } + } +} From ec085211715357b7f2487329fd89857a723f53fc Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 11:37:19 +0800 Subject: [PATCH 07/11] remove unused dependencies --- Cargo.lock | 4 ---- crates/cli/Cargo.toml | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8a2d77..9539b92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5996,14 +5996,11 @@ dependencies = [ name = "uts-cli" version = "0.1.0-alpha.0" dependencies = [ - "alloy-primitives", - "alloy-provider", "clap", "color-eyre", "digest 0.10.7", "eyre", "futures", - "jiff", "ripemd", "sha1", "sha2", @@ -6012,7 +6009,6 @@ dependencies = [ "tracing", "tracing-subscriber", "url", - "uts-contracts", "uts-core", "uts-sdk", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 60cea88..e42e1c7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -17,14 +17,11 @@ name = "uts" path = "src/main.rs" [dependencies] -alloy-primitives = { workspace = true } -alloy-provider = { workspace = true, default-features = false, features = ["reqwest"] } clap = { workspace = true, features = ["derive"] } color-eyre = { workspace = true } digest = { workspace = true } eyre = { workspace = true } futures = { workspace = true } -jiff = { workspace = true } ripemd = { workspace = true } sha1 = { workspace = true } sha2 = { workspace = true } @@ -33,8 +30,7 @@ tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } url = { workspace = true } -uts-contracts = { workspace = true } -uts-core = { workspace = true, features = ["std", "eas-verifier", "io-utils"] } +uts-core = { workspace = true } uts-sdk = { workspace = true, features = ["full"] } [features] From ba75499d32673b0692caa52ee2e576be7f00c5a8 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 12:06:04 +0800 Subject: [PATCH 08/11] gate verifier --- Cargo.lock | 10 ----- crates/cli/Cargo.toml | 3 -- crates/core/Cargo.toml | 32 ++++++++++++---- packages/sdk-rs/Cargo.toml | 30 +++++++++++---- packages/sdk-rs/src/builder.rs | 67 ++++++++++++++++++++++------------ packages/sdk-rs/src/lib.rs | 12 +++--- packages/sdk-rs/src/verify.rs | 42 +++++++++++++-------- 7 files changed, 122 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9539b92..ed322bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3558,15 +3558,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-src" -version = "300.5.5+3.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.112" @@ -3575,7 +3566,6 @@ checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e42e1c7..f8c09c4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -39,7 +39,4 @@ performance = ["tracing/max_level_info"] reqwest-default-tls = ["uts-sdk/reqwest-default-tls"] reqwest-native-tls = ["uts-sdk/reqwest-native-tls"] -reqwest-native-tls-no-alpn = ["uts-sdk/reqwest-native-tls-no-alpn"] -reqwest-native-tls-vendored = ["uts-sdk/reqwest-native-tls-vendored"] -reqwest-native-tls-vendored-no-alpn = ["uts-sdk/reqwest-native-tls-vendored-no-alpn"] reqwest-rustls = ["uts-sdk/reqwest-rustls"] diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c3e3e60..8a1910d 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -54,20 +54,36 @@ std = [] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] tracing = ["dep:tracing"] -bitcoin-verifier = ["verifier", "dep:reqwest", "dep:url", "dep:backon", "dep:http", "dep:http-body-util", "dep:serde", "dep:serde_json", "reqwest/json", "serde/derive"] -eas-verifier = ["verifier", "dep:alloy-provider", "dep:alloy-contract", "dep:alloy-sol-types", "dep:uts-contracts"] +bitcoin-verifier = [ + "dep:reqwest", + "dep:url", + "dep:backon", + "dep:http", + "dep:http-body-util", + "dep:serde", + "dep:serde_json", + + "verifier", + "reqwest/json", + "serde/derive", +] +eas-verifier = [ + "dep:alloy-provider", + "dep:alloy-contract", + "dep:alloy-sol-types", + "dep:uts-contracts", + + "verifier", +] verifier = [] io-utils = ["dep:tokio", "tokio/fs"] nightly = [] -reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls"] -reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/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", "alloy-provider/reqwest-rustls-tls"] +reqwest-default-tls = ["reqwest/default-tls", "alloy-provider?/reqwest-default-tls"] +reqwest-native-tls = ["reqwest/native-tls", "alloy-provider?/reqwest-native-tls"] +reqwest-rustls = ["reqwest/rustls", "alloy-provider?/reqwest-rustls-tls"] [dev-dependencies] serde_json.workspace = true diff --git a/packages/sdk-rs/Cargo.toml b/packages/sdk-rs/Cargo.toml index c4593d2..6754ce6 100644 --- a/packages/sdk-rs/Cargo.toml +++ b/packages/sdk-rs/Cargo.toml @@ -10,7 +10,7 @@ version.workspace = true [dependencies] alloy-primitives = { workspace = true } -alloy-provider = { workspace = true, default-features = false, features = ["reqwest"] } +alloy-provider = { workspace = true, default-features = false, optional = true, features = ["reqwest"] } alloy-rpc-client = { workspace = true } backon = { workspace = true } bytes = { workspace = true } @@ -42,14 +42,28 @@ full = [ tracing = ["uts-core/tracing"] bitcoin-verifier = ["uts-core/bitcoin-verifier"] -eas-verifier = ["dep:uts-contracts", "uts-core/eas-verifier", "uts-contracts/provider-helper"] +eas-verifier = [ + "dep:alloy-provider", + "dep:uts-contracts", + "uts-core/eas-verifier", + "uts-contracts/provider-helper", +] -reqwest-default-tls = ["reqwest/default-tls", "alloy-provider/reqwest-default-tls", "uts-core/reqwest-default-tls"] -reqwest-native-tls = ["reqwest/native-tls", "alloy-provider/reqwest-native-tls", "uts-core/reqwest-native-tls"] -reqwest-native-tls-no-alpn = ["reqwest/native-tls-no-alpn", "uts-core/reqwest-native-tls-no-alpn"] -reqwest-native-tls-vendored = ["reqwest/native-tls-vendored", "uts-core/reqwest-native-tls-vendored"] -reqwest-native-tls-vendored-no-alpn = ["reqwest/native-tls-vendored-no-alpn", "uts-core/reqwest-native-tls-vendored-no-alpn"] -reqwest-rustls = ["reqwest/rustls", "alloy-provider/reqwest-rustls-tls", "uts-core/reqwest-rustls"] +reqwest-default-tls = [ + "reqwest/default-tls", + "alloy-provider?/reqwest-default-tls", + "uts-core/reqwest-default-tls", +] +reqwest-native-tls = [ + "reqwest/native-tls", + "alloy-provider?/reqwest-native-tls", + "uts-core/reqwest-native-tls", +] +reqwest-rustls = [ + "reqwest/rustls", + "alloy-provider?/reqwest-rustls-tls", + "uts-core/reqwest-rustls", +] [lints] workspace = true diff --git a/packages/sdk-rs/src/builder.rs b/packages/sdk-rs/src/builder.rs index c9ad914..2ad38bc 100644 --- a/packages/sdk-rs/src/builder.rs +++ b/packages/sdk-rs/src/builder.rs @@ -1,15 +1,19 @@ use crate::{Sdk, SdkInner}; -use alloy_primitives::ChainId; -use alloy_provider::{Provider, ProviderBuilder}; use backon::ExponentialBuilder; use reqwest::Client; use std::{ - collections::{BTreeMap, HashSet}, + collections::HashSet, sync::{Arc, LazyLock}, time::Duration, }; use url::Url; -use uts_contracts::provider_helper::{RetryBackoffArgs, ThrottleArgs}; +#[cfg(feature = "eas-verifier")] +use { + alloy_primitives::ChainId, + alloy_provider::{Provider, ProviderBuilder}, + std::collections::BTreeMap, + uts_contracts::provider_helper::{RetryBackoffArgs, ThrottleArgs}, +}; /// Default public calendars to use. static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { @@ -25,6 +29,7 @@ static DEFAULT_CALENDARS: LazyLock> = LazyLock::new(|| { ]) }); +#[cfg(feature = "eas-verifier")] static DEFAULT_PROVIDERS: LazyLock> = LazyLock::new(|| { BTreeMap::from([ (1, Url::parse("https://0xrpc.io/eth").unwrap()), @@ -60,10 +65,14 @@ pub struct SdkBuilder { keep_pending: bool, + #[cfg(feature = "eas-verifier")] eth_providers: BTreeMap, + #[cfg(feature = "eas-verifier")] eth_compute_units_per_second: u64, + #[cfg(feature = "eas-verifier")] eth_requests_per_second: u32, + #[cfg(feature = "bitcoin-verifier")] bitcoin_rpc: Url, } @@ -89,10 +98,14 @@ impl SdkBuilder { keep_pending: false, + #[cfg(feature = "eas-verifier")] eth_providers: BTreeMap::new(), + #[cfg(feature = "eas-verifier")] eth_compute_units_per_second: 20, + #[cfg(feature = "eas-verifier")] eth_requests_per_second: 25, + #[cfg(feature = "bitcoin-verifier")] bitcoin_rpc: Url::parse("https://bitcoin-rpc.publicnode.com").unwrap(), } } @@ -107,6 +120,7 @@ impl SdkBuilder { let this = Self { calendars, + #[cfg(feature = "eas-verifier")] eth_providers: DEFAULT_PROVIDERS.clone(), ..Self::empty() }; @@ -197,18 +211,21 @@ impl SdkBuilder { } /// Add an Ethereum provider for a given chain ID. The URL should point to an Ethereum node that supports the JSON-RPC API. + #[cfg(feature = "eas-verifier")] pub fn add_eth_provider(mut self, chain_id: ChainId, url: Url) -> Self { self.eth_providers.insert(chain_id, url); self } /// Set the compute units per second for Ethereum provider requests. This is used to rate limit requests to avoid overwhelming the provider. + #[cfg(feature = "eas-verifier")] pub fn with_eth_compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { self.eth_compute_units_per_second = compute_units_per_second; self } /// Set the requests per second for Ethereum provider requests. This is used to rate limit requests to avoid overwhelming the provider. + #[cfg(feature = "eas-verifier")] pub fn with_eth_requests_per_second(mut self, requests_per_second: u32) -> Self { self.eth_requests_per_second = requests_per_second; self @@ -235,26 +252,28 @@ impl SdkBuilder { .expect("default HTTP client should be valid") }; - let eth_retry = RetryBackoffArgs { - compute_units_per_second: self.eth_compute_units_per_second, - ..Default::default() - }; - let eth_throttle = ThrottleArgs { - requests_per_second: self.eth_requests_per_second, + #[cfg(feature = "eas-verifier")] + let eth_providers = { + let eth_retry = RetryBackoffArgs { + compute_units_per_second: self.eth_compute_units_per_second, + ..Default::default() + }; + let eth_throttle = ThrottleArgs { + requests_per_second: self.eth_requests_per_second, + }; + self.eth_providers + .into_iter() + .map(|(chain_id, url)| { + let provider = ProviderBuilder::new().connect_client( + alloy_rpc_client::ClientBuilder::default() + .layer(eth_retry.layer()) + .layer(eth_throttle.layer()) + .http(url), + ); + (chain_id, provider.erased()) + }) + .collect() }; - let eth_providers = self - .eth_providers - .into_iter() - .map(|(chain_id, url)| { - let provider = ProviderBuilder::new().connect_client( - alloy_rpc_client::ClientBuilder::default() - .layer(eth_retry.layer()) - .layer(eth_throttle.layer()) - .http(url), - ); - (chain_id, provider.erased()) - }) - .collect(); Ok(Sdk { inner: Arc::new(SdkInner { @@ -269,7 +288,9 @@ impl SdkBuilder { keep_pending: self.keep_pending, + #[cfg(feature = "eas-verifier")] eth_providers, + #[cfg(feature = "bitcoin-verifier")] bitcoin_rpc: self.bitcoin_rpc, }), }) diff --git a/packages/sdk-rs/src/lib.rs b/packages/sdk-rs/src/lib.rs index 2cc3cc3..2ff0c7b 100644 --- a/packages/sdk-rs/src/lib.rs +++ b/packages/sdk-rs/src/lib.rs @@ -1,18 +1,14 @@ //! Rust SDK for the Universal Timestamps protocol. -use alloy_primitives::ChainId; -use alloy_provider::DynProvider; use backon::{ExponentialBuilder, Retryable}; use bytes::Bytes; use http::StatusCode; use reqwest::{Client, RequestBuilder}; -use std::{ - collections::{BTreeMap, HashSet}, - sync::Arc, - time::Duration, -}; +use std::{collections::HashSet, sync::Arc, time::Duration}; use tracing::trace; use url::Url; +#[cfg(feature = "eas-verifier")] +use {alloy_primitives::ChainId, alloy_provider::DynProvider, std::collections::BTreeMap}; mod builder; mod error; @@ -49,7 +45,9 @@ struct SdkInner { keep_pending: bool, // Verify + #[cfg(feature = "eas-verifier")] eth_providers: BTreeMap, + #[cfg(feature = "bitcoin-verifier")] bitcoin_rpc: Url, } diff --git a/packages/sdk-rs/src/verify.rs b/packages/sdk-rs/src/verify.rs index db83848..96ef2e6 100644 --- a/packages/sdk-rs/src/verify.rs +++ b/packages/sdk-rs/src/verify.rs @@ -1,5 +1,5 @@ use crate::{Error, Result, Sdk}; -use alloy_provider::DynProvider; +#[cfg(any(feature = "eas-verifier", feature = "bitcoin-verifier"))] use backon::RetryableWithContext; use digest::Digest; use jiff::Timestamp; @@ -8,17 +8,23 @@ use tokio::{ fs::File, io::{AsyncReadExt, BufReader}, }; -use uts_contracts::eas::EAS_ADDRESSES; use uts_core::{ alloc, alloc::Allocator, codec::v1::{ - Attestation, BitcoinAttestation, DetachedTimestamp, EASAttestation, EASTimestamped, - PendingAttestation, RawAttestation, + Attestation, DetachedTimestamp, PendingAttestation, RawAttestation, opcode::{KECCAK256, RIPEMD160, SHA1, SHA256}, }, - verifier::{BitcoinVerifier, EASVerifier}, }; +#[cfg(feature = "eas-verifier")] +use { + alloy_provider::DynProvider, + uts_contracts::eas::EAS_ADDRESSES, + uts_core::codec::v1::{EASAttestation, EASTimestamped}, + uts_core::verifier::EASVerifier, +}; +#[cfg(feature = "bitcoin-verifier")] +use {uts_core::codec::v1::BitcoinAttestation, uts_core::verifier::BitcoinVerifier}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AttestationStatusKind { @@ -197,25 +203,29 @@ impl Sdk { &self, attestation: &RawAttestation, ) -> Result { - let expected = attestation + let _expected = attestation .value() .expect("Attestation value should be finalized"); - let status = if attestation.tag == EASAttestation::TAG { + #[cfg(feature = "eas-verifier")] + if attestation.tag == EASAttestation::TAG { let attestation = EASAttestation::from_raw(attestation)?; - self.verify_eas_attestation(expected, attestation).await? + return self.verify_eas_attestation(_expected, attestation).await; } else if attestation.tag == EASTimestamped::TAG { let attestation = EASTimestamped::from_raw(attestation)?; - self.verify_eas_timestamped(expected, attestation).await? - } else if attestation.tag == BitcoinAttestation::TAG { + return self.verify_eas_timestamped(_expected, attestation).await; + } + + #[cfg(feature = "bitcoin-verifier")] + if attestation.tag == BitcoinAttestation::TAG { let attestation = BitcoinAttestation::from_raw(attestation)?; - self.verify_bitcoin(expected, attestation).await? - } else { - AttestationStatusKind::Unknown - }; - Ok(status) + return self.verify_bitcoin(_expected, attestation).await; + } + + Ok(AttestationStatusKind::Unknown) } + #[cfg(feature = "eas-verifier")] async fn verify_eas_attestation( &self, expected: &[u8], @@ -252,6 +262,7 @@ impl Sdk { } } + #[cfg(feature = "eas-verifier")] async fn verify_eas_timestamped( &self, expected: &[u8], @@ -288,6 +299,7 @@ impl Sdk { } } + #[cfg(feature = "bitcoin-verifier")] async fn verify_bitcoin( &self, expected: &[u8], From 0f0ddecc3f1d5196bf77dc0b023c3e2df9e2b61d Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 12:09:34 +0800 Subject: [PATCH 09/11] remove no_std support --- Cargo.lock | 1 - crates/core/Cargo.toml | 7 ++----- crates/core/src/codec.rs | 1 - crates/core/src/codec/imp.rs | 2 -- crates/core/src/codec/v1.rs | 1 - crates/core/src/codec/v1/attestation.rs | 4 ++-- crates/core/src/codec/v1/opcode.rs | 1 - crates/core/src/codec/v1/timestamp.rs | 3 ++- crates/core/src/codec/v1/timestamp/builder.rs | 2 +- crates/core/src/error.rs | 3 --- crates/core/src/lib.rs | 1 - crates/core/src/utils.rs | 3 --- crates/core/src/utils/sync/race.rs | 20 ------------------- crates/core/src/utils/sync/std.rs | 20 ------------------- packages/sdk-rs/Cargo.toml | 2 +- 15 files changed, 8 insertions(+), 63 deletions(-) delete mode 100644 crates/core/src/utils/sync/race.rs delete mode 100644 crates/core/src/utils/sync/std.rs diff --git a/Cargo.lock b/Cargo.lock index ed322bb..7d0d817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6032,7 +6032,6 @@ dependencies = [ "digest 0.10.7", "http", "http-body-util", - "once_cell", "paste", "reqwest 0.13.2", "ripemd", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 8a1910d..c2c9c3b 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -28,7 +28,6 @@ bytes = { workspace = true, optional = true } digest = { workspace = true } http = { workspace = true, optional = true } http-body-util = { workspace = true, optional = true } -once_cell = { workspace = true, features = ["alloc"] } paste.workspace = true reqwest = { workspace = true, optional = true, default-features = false } ripemd.workspace = true @@ -46,11 +45,9 @@ uts-bmt = { workspace = true } uts-contracts = { workspace = true, optional = true } [features] -bytes = ["dep:bytes"] -default = ["std"] - -std = [] +default = [] +bytes = ["dep:bytes"] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] tracing = ["dep:tracing"] diff --git a/crates/core/src/codec.rs b/crates/core/src/codec.rs index 3068f58..8520cb8 100644 --- a/crates/core/src/codec.rs +++ b/crates/core/src/codec.rs @@ -9,7 +9,6 @@ mod proof; pub use proof::{Proof, Version, VersionedProof}; mod imp; -#[cfg(feature = "std")] pub use imp::{Reader, Writer}; /// Types and helpers for the version 1 serialization format. diff --git a/crates/core/src/codec/imp.rs b/crates/core/src/codec/imp.rs index 88c268d..7d30615 100644 --- a/crates/core/src/codec/imp.rs +++ b/crates/core/src/codec/imp.rs @@ -4,10 +4,8 @@ mod alloy; #[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 { diff --git a/crates/core/src/codec/v1.rs b/crates/core/src/codec/v1.rs index c520a05..b29f9c2 100644 --- a/crates/core/src/codec/v1.rs +++ b/crates/core/src/codec/v1.rs @@ -24,7 +24,6 @@ impl core::fmt::Display for FinalizationError { } } -#[cfg(feature = "std")] impl std::error::Error for FinalizationError {} /// Trait for objects that may have input data. diff --git a/crates/core/src/codec/v1/attestation.rs b/crates/core/src/codec/v1/attestation.rs index 325d558..80354ec 100644 --- a/crates/core/src/codec/v1/attestation.rs +++ b/crates/core/src/codec/v1/attestation.rs @@ -7,12 +7,12 @@ use crate::{ alloc::{Allocator, Global, vec::Vec}, codec::{Decode, DecodeIn, Decoder, Encode, Encoder, v1::MayHaveInput}, error::{DecodeError, EncodeError}, - utils::{Hexed, OnceLock}, + utils::Hexed, }; use alloy_chains::Chain; use alloy_primitives::{B256, FixedBytes, fixed_bytes as tag}; use core::fmt; -use std::borrow::Cow; +use std::{borrow::Cow, sync::OnceLock}; /// Size in bytes of the tag identifying the attestation type. const TAG_SIZE: usize = 8; diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index 9a14d01..278d0e1 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -285,7 +285,6 @@ macro_rules! define_opcodes { } } - #[cfg(feature = "std")] impl std::error::Error for OpCodeFromStrError {} impl core::str::FromStr for OpCode { diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 5b9b346..6980cca 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -6,10 +6,11 @@ use crate::{ Attestation, FinalizationError, MayHaveInput, PendingAttestation, attestation::RawAttestation, opcode::OpCode, }, - utils::{Hexed, OnceLock}, + utils::Hexed, }; use allocator_api2::SliceExt; use core::fmt::Debug; +use std::sync::OnceLock; pub(crate) mod builder; mod decode; diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index 9c47eec..92d4fb4 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -8,9 +8,9 @@ use crate::{ timestamp::Step, }, error::EncodeError, - utils::OnceLock, }; use allocator_api2::SliceExt; +use std::sync::OnceLock; use uts_bmt::{NodePosition, SiblingIter}; #[derive(Debug, Clone)] diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index e5a4946..1d3f2ab 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -37,7 +37,6 @@ pub enum DecodeError { #[error("unexpected end of file")] UnexpectedEof, /// General I/O error - #[cfg(feature = "std")] #[error("I/O error: {0}")] Io(std::io::Error), } @@ -55,12 +54,10 @@ pub enum EncodeError { #[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() { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 6106376..d3cf3c0 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,3 @@ -#![cfg_attr(not(feature = "std"), no_std)] //! # Universal Timestamps Core Library extern crate core; diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 1b50d53..0ffa830 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -1,9 +1,6 @@ mod hex; pub use hex::Hexed; -mod sync; -pub use sync::OnceLock; - mod hash; #[cfg(feature = "io-utils")] pub use hash::HashAsyncFsExt; diff --git a/crates/core/src/utils/sync/race.rs b/crates/core/src/utils/sync/race.rs deleted file mode 100644 index 1ee7567..0000000 --- a/crates/core/src/utils/sync/race.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[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 deleted file mode 100644 index 3078519..0000000 --- a/crates/core/src/utils/sync/std.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[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) - } -} diff --git a/packages/sdk-rs/Cargo.toml b/packages/sdk-rs/Cargo.toml index 6754ce6..fd1fd4e 100644 --- a/packages/sdk-rs/Cargo.toml +++ b/packages/sdk-rs/Cargo.toml @@ -31,7 +31,7 @@ tracing = { workspace = true } url = { workspace = true } uts-bmt = { workspace = true } uts-contracts = { workspace = true, optional = true } -uts-core = { workspace = true, features = ["std", "io-utils"] } +uts-core = { workspace = true, features = ["io-utils"] } [features] full = [ From 0ce2b90ac5c4955241cd8baae5b5e62a94868a7e Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 12:13:06 +0800 Subject: [PATCH 10/11] remove tracing optional --- crates/core/Cargo.toml | 3 +-- crates/core/src/codec/v1/digest.rs | 4 ++-- crates/core/src/lib.rs | 2 -- crates/core/src/tracing.rs | 20 -------------------- crates/core/src/verifier/bitcoin.rs | 3 ++- packages/sdk-rs/Cargo.toml | 2 -- 6 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 crates/core/src/tracing.rs diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c2c9c3b..ab8ddc7 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -39,7 +39,7 @@ sha2.workspace = true sha3.workspace = true thiserror.workspace = true tokio = { workspace = true, optional = true } -tracing = { workspace = true, optional = true } +tracing = { workspace = true } url = { workspace = true, optional = true } uts-bmt = { workspace = true } uts-contracts = { workspace = true, optional = true } @@ -49,7 +49,6 @@ default = [] bytes = ["dep:bytes"] serde = ["dep:serde", "dep:serde_with", "serde/derive", "serde_with/hex"] -tracing = ["dep:tracing"] bitcoin-verifier = [ "dep:reqwest", diff --git a/crates/core/src/codec/v1/digest.rs b/crates/core/src/codec/v1/digest.rs index eaf290c..2c4c6bc 100644 --- a/crates/core/src/codec/v1/digest.rs +++ b/crates/core/src/codec/v1/digest.rs @@ -61,7 +61,7 @@ impl DigestHeader { } impl Encode for DigestHeader { - #[cfg_attr(feature = "tracing", tracing::instrument(skip(writer), err))] + #[tracing::instrument(skip_all, err)] #[inline] fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> { encoder.encode(self.kind)?; @@ -71,7 +71,7 @@ impl Encode for DigestHeader { } impl DecodeIn for DigestHeader { - #[cfg_attr(feature = "tracing", tracing::instrument(skip(reader), ret, err))] + #[tracing::instrument(skip_all, ret(level = trace), err)] #[inline] fn decode_in(decoder: &mut impl Decoder, _alloc: A) -> Result { let kind = decoder.decode()?; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index d3cf3c0..be4d3d1 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -2,8 +2,6 @@ extern crate core; -mod tracing; - #[cfg(test)] pub mod fixtures; diff --git a/crates/core/src/tracing.rs b/crates/core/src/tracing.rs deleted file mode 100644 index 8b99c0b..0000000 --- a/crates/core/src/tracing.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(unused)] - -#[cfg(not(feature = "tracing"))] -macro_rules! noop_macro { - ($($t:tt)*) => {}; -} - -#[cfg(not(feature = "tracing"))] -pub(crate) use noop_macro as debug; -#[cfg(not(feature = "tracing"))] -pub(crate) use noop_macro as error; -#[cfg(not(feature = "tracing"))] -pub(crate) use noop_macro as info; -#[cfg(not(feature = "tracing"))] -pub(crate) use noop_macro as trace; -#[cfg(not(feature = "tracing"))] -pub(crate) use noop_macro as warn; - -#[cfg(feature = "tracing")] -pub use ::tracing::{debug, error, info, trace, warn}; diff --git a/crates/core/src/verifier/bitcoin.rs b/crates/core/src/verifier/bitcoin.rs index dcaa891..cfd7b3b 100644 --- a/crates/core/src/verifier/bitcoin.rs +++ b/crates/core/src/verifier/bitcoin.rs @@ -8,6 +8,7 @@ use http_body_util::LengthLimitError; use reqwest::Client; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::json; +use tracing::instrument; use url::Url; const RESPONSE_SIZE_LIMIT: usize = 10 * 1024; // 10 KiB @@ -105,7 +106,7 @@ impl BitcoinVerifier { Ok(header) } - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = trace, err = "warn"))] + #[instrument(skip(self, params), level = "trace", err(level = "warn"))] async fn req( &self, method: &str, diff --git a/packages/sdk-rs/Cargo.toml b/packages/sdk-rs/Cargo.toml index fd1fd4e..aa01870 100644 --- a/packages/sdk-rs/Cargo.toml +++ b/packages/sdk-rs/Cargo.toml @@ -39,8 +39,6 @@ full = [ "bitcoin-verifier", ] -tracing = ["uts-core/tracing"] - bitcoin-verifier = ["uts-core/bitcoin-verifier"] eas-verifier = [ "dep:alloy-provider", From 71bdecc31e76c88cabfee42f359efc78f053e0a0 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 17 Mar 2026 12:36:02 +0800 Subject: [PATCH 11/11] apply review --- crates/core/src/codec/imp/primitives.rs | 23 ++++++++++++++++++++++- packages/sdk-rs/src/error.rs | 3 +++ packages/sdk-rs/src/lib.rs | 3 ++- packages/sdk-rs/src/stamp.rs | 13 +++++++++++-- packages/sdk-rs/src/upgrade.rs | 3 ++- packages/sdk-rs/src/verify.rs | 13 +++++++++---- packages/sdk-ts/src/sdk.ts | 3 +++ packages/sdk-ts/src/types.ts | 1 + 8 files changed, 53 insertions(+), 9 deletions(-) diff --git a/crates/core/src/codec/imp/primitives.rs b/crates/core/src/codec/imp/primitives.rs index e417f73..0bf89c4 100644 --- a/crates/core/src/codec/imp/primitives.rs +++ b/crates/core/src/codec/imp/primitives.rs @@ -41,8 +41,29 @@ macro_rules! leb128 { // Bottom 7 bits are value bits let byte = decoder.decode_byte()?; let value = (byte & 0x7f) as $ty; + + // This is a stable port of `shl_exact` + // FIXME: This should be replaced with `shl_exact` once it is stabilized. + // ``` + // ret |= ((byte & 0x7f) as $ty) + // .shl_exact(shift) + // .ok_or($crate::error::DecodeError::LEB128Overflow(<$ty>::BITS))?; + // ``` + // #[unstable(feature = "exact_bitshifts", issue = "144336")] + // #[must_use = "this returns the result of the operation, \ + // without modifying the original"] + // #[inline] + // pub const fn shl_exact(self, rhs: u32) -> Option<$SelfT> { + // if rhs < self.leading_zeros() || rhs < self.leading_ones() { + // // SAFETY: rhs is checked above + // Some(unsafe { self.unchecked_shl(rhs) }) + // } else { + // None + // } + // } if shift < value.leading_zeros() || shift < value.leading_ones() { - ret |= ((byte & 0x7f) as $ty) << shift; + // SAFETY: shift is checked above + ret |= unsafe { ((byte & 0x7f) as $ty).unchecked_shl(shift) }; } else { return Err($crate::error::DecodeError::LEB128Overflow(<$ty>::BITS)); } diff --git a/packages/sdk-rs/src/error.rs b/packages/sdk-rs/src/error.rs index c53fcc9..3461610 100644 --- a/packages/sdk-rs/src/error.rs +++ b/packages/sdk-rs/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { #[error(transparent)] Finalization(#[from] uts_core::codec::v1::FinalizationError), + #[error("Input cannot be empty")] + EmptyInput, + /// Error indicating that a quorum of responses was not reached from the calendars. #[error("Quorum of {required} not reached, only {received} responses received")] QuorumNotReached { diff --git a/packages/sdk-rs/src/lib.rs b/packages/sdk-rs/src/lib.rs index 2ff0c7b..bcf0668 100644 --- a/packages/sdk-rs/src/lib.rs +++ b/packages/sdk-rs/src/lib.rs @@ -79,6 +79,7 @@ impl Sdk { async fn http_request_with_retry( &self, + method: http::Method, url: Url, response_size_limit: usize, builder_fn: Builder, @@ -93,7 +94,7 @@ impl Sdk { let client = client.clone(); let url = url.clone(); let req = client - .get(url) + .request(method, url) .timeout(Duration::from_secs(timeout_seconds)); let req = builder_fn(req); diff --git a/packages/sdk-rs/src/stamp.rs b/packages/sdk-rs/src/stamp.rs index 4d884f6..ec9dd0c 100644 --- a/packages/sdk-rs/src/stamp.rs +++ b/packages/sdk-rs/src/stamp.rs @@ -1,5 +1,6 @@ use crate::{Result, Sdk, error::Error}; use digest::{Digest, FixedOutputReset, Output}; +use http::Method; use std::path::PathBuf; use tokio::fs; use tracing::{debug, instrument}; @@ -53,6 +54,10 @@ impl Sdk { D: Digest + FixedOutputReset + DigestOpExt + Send, Output: Copy, { + if files.is_empty() { + return Err(Error::EmptyInput); + } + let digests = futures::future::join_all(files.iter().map(|f| hash_file::(f.clone()))) .await .into_iter() @@ -76,8 +81,11 @@ impl Sdk { D: Digest + FixedOutputReset + DigestOpExt + Send, Output: Copy, { - let mut builders: alloc::vec::Vec, A> = - alloc::vec![in allocator.clone(); Timestamp::builder_in(allocator.clone()) ]; + if digests.is_empty() { + return Err(Error::EmptyInput); + } + + let mut builders: alloc::vec::Vec, A> = alloc::vec![in allocator.clone(); Timestamp::builder_in(allocator.clone()); digests.len() ]; let mut nonced_digest = alloc::vec::Vec::with_capacity_in(digests.len(), allocator.clone()); @@ -166,6 +174,7 @@ impl Sdk { let root = root.to_vec(); let (_, body) = self .http_request_with_retry( + Method::POST, url, 10 * 1024, // 10 KB move |req| { diff --git a/packages/sdk-rs/src/upgrade.rs b/packages/sdk-rs/src/upgrade.rs index e23fced..0dedb8a 100644 --- a/packages/sdk-rs/src/upgrade.rs +++ b/packages/sdk-rs/src/upgrade.rs @@ -1,5 +1,5 @@ use crate::{Result, Sdk, error::Error}; -use http::StatusCode; +use http::{Method, StatusCode}; use std::collections::BTreeMap; use tracing::{debug, warn}; use url::Url; @@ -48,6 +48,7 @@ impl Sdk { let result = self .http_request_with_retry( + Method::GET, retrieve_uri, 10 * 1024 * 1024, // 10 MiB response size limit |req| req.header("Accept", "application/vnd.opentimestamps.v1"), diff --git a/packages/sdk-rs/src/verify.rs b/packages/sdk-rs/src/verify.rs index 96ef2e6..039ddca 100644 --- a/packages/sdk-rs/src/verify.rs +++ b/packages/sdk-rs/src/verify.rs @@ -50,6 +50,8 @@ pub enum VerifyStatus { Valid(Timestamp), /// The timestamp is partially valid, at least one attestation is not valid. PartiallyValid(Timestamp), + /// The timestamp is invalid, all attestations are invalid. + Invalid, /// All attestations are pending. Pending, /// All attestations are unknown. @@ -166,6 +168,7 @@ impl Sdk { /// - If there are also INVALID or UNKNOWN attestations, the overall status is PARTIAL_VALID /// - Otherwise, the overall status is VALID /// - If there are no VALID attestations, but at least one PENDING attestation, the overall status is PENDING + /// - If there are no VALID attestations, but at least one UNKNOWN attestation, the overall status is UNKNOWN /// - If there are no VALID or PENDING attestations, the overall status is INVALID pub fn aggregate_verify_results(&self, results: &[AttestationStatus]) -> VerifyStatus { let mut valid_ts = None; @@ -186,16 +189,18 @@ impl Sdk { } } - if let Some(valid_ts) = valid_ts { + if let Some(ts) = valid_ts { if has_invalid || has_unknown { - VerifyStatus::PartiallyValid(valid_ts) + VerifyStatus::PartiallyValid(ts) } else { - VerifyStatus::Valid(valid_ts) + VerifyStatus::Valid(ts) } } else if has_pending { VerifyStatus::Pending - } else { + } else if has_unknown { VerifyStatus::Unknown + } else { + VerifyStatus::Invalid } } diff --git a/packages/sdk-ts/src/sdk.ts b/packages/sdk-ts/src/sdk.ts index 9503dc0..35fb0e5 100644 --- a/packages/sdk-ts/src/sdk.ts +++ b/packages/sdk-ts/src/sdk.ts @@ -815,6 +815,7 @@ export default class SDK { * - If there are also INVALID or UNKNOWN attestations, the overall status is PARTIAL_VALID * - Otherwise, the overall status is VALID * - If there are no VALID attestations, but at least one PENDING attestation, the overall status is PENDING + * - If there are no VALID attestations, but at least one UNKNOWN attestation, the overall status is UNKNOWN * - If there are no VALID or PENDING attestations, the overall status is INVALID * @param attestations */ @@ -842,6 +843,8 @@ export default class SDK { } } else if (counts[AttestationStatusKind.PENDING] > 0) { status = VerifyStatus.PENDING + } else if (counts[AttestationStatusKind.UNKNOWN] > 0) { + status = VerifyStatus.UNKNOWN } return status } diff --git a/packages/sdk-ts/src/types.ts b/packages/sdk-ts/src/types.ts index 4e3738e..37c62ac 100644 --- a/packages/sdk-ts/src/types.ts +++ b/packages/sdk-ts/src/types.ts @@ -126,4 +126,5 @@ export enum VerifyStatus { PARTIAL_VALID = 'PARTIAL_VALID', INVALID = 'INVALID', PENDING = 'PENDING', + UNKNOWN = 'UNKNOWN', }