From 411c48fc39a5b4d5261e28de6278c3e192fc9057 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 23 Dec 2025 22:06:41 +0800 Subject: [PATCH 1/6] complete --- Cargo.lock | 55 ++++---- Cargo.toml | 2 +- crates/calendar/Cargo.toml | 7 +- crates/calendar/src/main.rs | 21 ++- crates/calendar/src/routes/ots.rs | 131 +++++++++--------- crates/calendar/src/time.rs | 28 ++++ crates/core/src/codec/v1/opcode.rs | 46 ++++++ crates/core/src/codec/v1/timestamp.rs | 14 +- crates/core/src/codec/v1/timestamp/builder.rs | 99 +++++++++++++ 9 files changed, 306 insertions(+), 97 deletions(-) create mode 100644 crates/calendar/src/time.rs diff --git a/Cargo.lock b/Cargo.lock index fad715a..c425074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,24 @@ dependencies = [ "cc", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "allocator-api2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c583acf993cf4245c4acb0a2cc2ab1f9cc097de73411bb6d3647ff6af2b1013d" + +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "alloy-consensus" version = "1.1.3" @@ -750,27 +768,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfe9f610fe4e99cf0cfcd03ccf8c63c28c616fe714d80475ef731f3b13dd21b" -dependencies = [ - "axum", - "axum-core", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-macros" version = "0.5.0" @@ -940,6 +937,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bump-scope" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a117a0e80c453511760a08c607060a778967a8dec5415c1d1a75e20630825824" +dependencies = [ + "allocator-api2 0.2.21", + "allocator-api2 0.3.1", + "allocator-api2 0.4.0", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -4313,7 +4321,6 @@ dependencies = [ "futures-util", "http", "http-body", - "http-body-util", "iri-string", "pin-project-lite", "tower", @@ -4528,13 +4535,11 @@ dependencies = [ "alloy-signer", "alloy-signer-local", "axum", - "axum-extra", + "bump-scope", "bytes", "eyre", "sha3 0.11.0-rc.3", - "smallvec", "tokio", - "tower-http", "tracing", "tracing-subscriber", "uts-core", diff --git a/Cargo.toml b/Cargo.toml index 4887329..109d797 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ paste = "1.0" regex = "1.12" serde = "1.0" serde_with = "3.16" -smallvec = { version = "1.15", features = ["union", "const_generics", "const_new"] } strum = "0.27" thiserror = "2" tokio = { version = "1", features = ["rt"] } @@ -57,6 +56,7 @@ toml = "0.9" tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +bump-scope = { version = "1.5", features = ["nightly"] } digest = "0.11.0-rc.4" ripemd = "0.2.0-rc.3" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index e8ca796..85ce324 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -13,16 +13,17 @@ alloy-primitives = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } axum = { workspace = true, features = ["macros"] } -axum-extra = { workspace = true } bytes = { workspace = true } eyre = { workspace = true } sha3 = { workspace = true } -smallvec = { workspace = true } tokio = { workspace = true, features = ["full"] } -tower-http = { workspace = true, features = ["limit"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } +bump-scope.workspace = true [lints] workspace = true + +[features] +performance = ["tracing/release_max_level_info"] diff --git a/crates/calendar/src/main.rs b/crates/calendar/src/main.rs index f968da4..bf1db24 100644 --- a/crates/calendar/src/main.rs +++ b/crates/calendar/src/main.rs @@ -1,20 +1,38 @@ +#![feature(allocator_api)] //! Calendar server #[macro_use] extern crate tracing; +use alloy_primitives::b256; +use alloy_signer::k256::ecdsa::SigningKey; +use alloy_signer_local::LocalSigner; use axum::{ Router, extract::DefaultBodyLimit, routing::{get, post}, }; +use std::sync::Arc; mod routes; +pub mod time; + +/// Application state shared across handlers. +#[derive(Debug)] +pub struct AppState { + signer: LocalSigner, +} #[tokio::main] async fn main() -> eyre::Result<()> { tracing_subscriber::fmt::init(); + tokio::spawn(time::updater()); + + let signer = LocalSigner::from_bytes(&b256!( + "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" + ))?; + let app = Router::new() .route( "/digest", @@ -24,7 +42,8 @@ async fn main() -> eyre::Result<()> { .route( "/timestamp/{hex_commitment}", get(routes::ots::get_timestamp), - ); + ) + .with_state(Arc::new(AppState { signer })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 4b847b0..be026dc 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -1,21 +1,19 @@ -use alloy_primitives::{Keccak256, b256}; +use crate::{AppState, time::current_time_sec}; use alloy_signer::SignerSync; -use alloy_signer_local::LocalSigner; -use axum::body::Bytes; +use axum::{body::Bytes, extract::State}; +use bump_scope::Bump; use bytes::BytesMut; -use smallvec::SmallVec; -use std::time::SystemTime; -use tracing::Level; +use sha3::{Digest, Keccak256}; +use std::{cell::RefCell, sync::Arc}; use uts_core::{ codec::{ - Encoder, - v1::{Attestation, PendingAttestation, opcode::OpCode}, + Encode, + v1::{PendingAttestation, Timestamp}, }, utils::Hexed, }; pub const MAX_DIGEST_SIZE: usize = 64; // e.g., SHA3-512 -const ERC2098_SIGNATURE_SIZE: usize = 64; // Test this with official ots client: // ots stamp -c "http://localhost:3000/" -m 1 @@ -36,74 +34,75 @@ const ERC2098_SIGNATURE_SIZE: usize = 64; // result c15b4e8b93e9aaee5b8c736f5b73e5f313062e389925a0b1fc6495053f99d352 // result attested by Pending: update URI https://localhost:3000 // ``` -#[instrument(level = Level::TRACE, skip_all)] -pub async fn submit_digest(digest: Bytes) -> Bytes { - const MAX_MESSAGE_SIZE: usize = MAX_DIGEST_SIZE + size_of::() + ERC2098_SIGNATURE_SIZE; +pub async fn submit_digest(State(state): State>, digest: Bytes) -> Bytes { + let (output, _commitment) = submit_digest_inner(digest, &state.signer); + // TODO: submit commitment to journal + output +} + +// TODO: We need to benchmark this. +pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u8; 32]) { + const PRE_ALLOCATION_SIZE_HINT: usize = 4096; + thread_local! { + // We don't have `.await` in this function, so it's safe to borrow thread local. + static BUMP: RefCell = RefCell::new(Bump::with_size(PRE_ALLOCATION_SIZE_HINT)); + static HASHER: RefCell = RefCell::new(Keccak256::new()); + } - let uri = "https://localhost:3000".to_string(); + // ots uses 32-bit unix time, but we use u64 here for future proofing, as it's not part of the ots spec. + let recv_timestamp = current_time_sec().to_le_bytes(); - let buf_size = 1 // OpCode::PREPEND - + 1 // length of u64 length in leb128 - + 8 // u64 timestamp - + 1 // OpCode::APPEND - + 1 // length of signature length in leb128 - + ERC2098_SIGNATURE_SIZE // signature - + 1 // FIXME: TBD: OpCode::KECCAK256 - + 1 // OpCode::ATTESTATION - + 8 // Pending tag - + 1 // length of packed ATTESTATION data length in leb128 - + (1 + uri.len()); // length of uri in leb128 + uri bytes - let attestation = PendingAttestation { uri: uri.into() }; + let undeniable_sig = { + // sign_message_sync invoke heap allocation, so manually hash it. + const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n"; + let hash = HASHER.with(|hasher| { + let mut hasher = hasher.borrow_mut(); + hasher.update(EIP191_PREFIX.as_bytes()); + hasher.update(&recv_timestamp); + hasher.update(&digest); + hasher.finalize_reset() + }); - let mut timestamp = BytesMut::with_capacity(buf_size); + let undeniable_sig = signer.sign_hash_sync(&hash.0.into()).unwrap(); + undeniable_sig.as_erc2098() + }; - let mut pending_attestation = SmallVec::<[u8; MAX_MESSAGE_SIZE]>::new(); + #[cfg(any(debug_assertions, not(feature = "performance")))] + trace!( + recv_timestamp = ?Hexed(&recv_timestamp), + digest = ?Hexed(&digest), + undeniable_sig = ?Hexed(&undeniable_sig), + ); - // ots uses 32-bit unix time, but we use u64 here for future proofing, as it's not part of the ots spec. - let recv_timestamp: u64 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Clock MUST not go backwards") - .as_secs(); - trace!(recv_timestamp); - let recv_timestamp = recv_timestamp.to_le_bytes(); - timestamp.encode(OpCode::PREPEND).unwrap(); - timestamp.encode_bytes(recv_timestamp).unwrap(); - pending_attestation.extend(recv_timestamp); + BUMP.with(|bump| { + let mut bump = bump.borrow_mut(); + bump.reset(); - trace!(digest = ?Hexed(&digest)); - pending_attestation.extend_from_slice(&digest); + let builder = Timestamp::builder_in(&*bump) + .prepend(recv_timestamp.to_vec_in(&bump)) + .append(undeniable_sig.to_vec_in(&bump)) + .keccak256(); - let signer = LocalSigner::from_bytes(&b256!( - "9ba9926331eb5f4995f1e358f57ba1faab8b005b51928d2fdaea16e69a6ad225" - )) - .unwrap(); // TODO: load from app state - let undeniable_sig = signer.sign_message_sync(&digest).unwrap(); - let undeniable_sig = undeniable_sig.as_erc2098(); - trace!(undeniable_sig = ?Hexed(&undeniable_sig)); - timestamp.encode(OpCode::APPEND).unwrap(); - timestamp.encode_bytes(undeniable_sig).unwrap(); - pending_attestation.extend(undeniable_sig); + let mut commitment = [0u8; 32]; + commitment.copy_from_slice(&builder.commitment(&digest)); - trace!(pending_attestation = ?Hexed(&pending_attestation)); + let timestamp = builder + .attest(PendingAttestation { + uri: "https://localhost:3000".into(), + }) + .unwrap(); - // FIXME: - // discussion: return the hash or the raw timestamp message? - // if using hash, client will request upgrade timestamp by hash (256 bits, 64 hex chars) - // - // if using raw timestamp message, client will request timestamp by whole message (variable size, 208 hex chars if request is 32 bytes), - // but we will have info about the receiving time of the request, - // which can narrow down the search space - let mut hasher = Keccak256::new(); - hasher.update(&pending_attestation); - hasher.finalize_into(&mut pending_attestation[0..32]); - timestamp.encode(OpCode::KECCAK256).unwrap(); + // copy data out of bump + // TODO: eliminate this allocation by reusing from a pool + // TODO: warp the buffer with a drop trait to return to pool + let mut buf = BytesMut::with_capacity(128); + timestamp.encode(&mut buf).unwrap(); - timestamp.encode(OpCode::ATTESTATION).unwrap(); - timestamp.encode(attestation.to_raw().unwrap()).unwrap(); + #[cfg(any(debug_assertions, not(feature = "performance")))] + trace!(timestamp = ?timestamp, encoded_length = buf.len()); - // TODO: store the pending_attestation into journal - debug_assert_eq!(timestamp.len(), buf_size, "buffer size mismatch"); - timestamp.freeze() + (buf.freeze(), commitment) + }) } pub async fn get_timestamp() {} diff --git a/crates/calendar/src/time.rs b/crates/calendar/src/time.rs new file mode 100644 index 0000000..e8ebd70 --- /dev/null +++ b/crates/calendar/src/time.rs @@ -0,0 +1,28 @@ +//! A module that maintains a globally accessible current time in seconds since the Unix epoch. +//! +//! This is for performance optimization to avoid frequent syscalls for time retrieval. +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +static CURRENT_TIME_SEC: AtomicU64 = AtomicU64::new(0); + +/// Returns the current time in seconds since the Unix epoch. +#[inline] +pub fn current_time_sec() -> u64 { + CURRENT_TIME_SEC.load(Ordering::Relaxed) +} + +/// An asynchronous task that updates the current time every second. +pub async fn updater() { + let mut sleep = tokio::time::interval(Duration::from_secs(1)); + loop { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + CURRENT_TIME_SEC.store(now, Ordering::Relaxed); + sleep.tick().await; + } +} diff --git a/crates/core/src/codec/v1/opcode.rs b/crates/core/src/codec/v1/opcode.rs index f9772f4..6b97073 100644 --- a/crates/core/src/codec/v1/opcode.rs +++ b/crates/core/src/codec/v1/opcode.rs @@ -309,6 +309,38 @@ macro_rules! define_digest_opcodes { }; } +macro_rules! impl_simple_step { + ($variant:ident) => {paste::paste! { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] + pub fn [< $variant:lower >](self) -> Self { + self.push_step(OpCode::[<$variant>]) + } + } + }}; + ($($variant:ident),* $(,)?) => { + $( + impl_simple_step! { $variant } + )* + }; +} + +macro_rules! impl_step_with_data { + ($variant:ident) => {paste::paste! { + impl $crate::codec::v1::timestamp::builder::TimestampBuilder { + #[doc = concat!("Push the `", stringify!($variant), "` opcode.")] + pub fn [< $variant:lower >](self, data: ::alloc::vec::Vec) -> Self { + self.push_immediate_step(OpCode::[<$variant>], data) + } + } + }}; + ($($variant:ident),* $(,)?) => { + $( + impl_step_with_data! { $variant } + )* + }; +} + define_opcodes! { 0x02 => SHA1, 0x03 => RIPEMD160, @@ -329,6 +361,20 @@ define_digest_opcodes! { 0x67 => KECCAK256, } +impl_simple_step! { + SHA1, + RIPEMD160, + SHA256, + KECCAK256, + REVERSE, + HEXLIFY, +} + +impl_step_with_data! { + APPEND, + PREPEND, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/core/src/codec/v1/timestamp.rs b/crates/core/src/codec/v1/timestamp.rs index 245ce6b..7f604d0 100644 --- a/crates/core/src/codec/v1/timestamp.rs +++ b/crates/core/src/codec/v1/timestamp.rs @@ -7,7 +7,7 @@ use crate::{ use alloc::{alloc::Global, vec::Vec}; use core::{alloc::Allocator, fmt::Debug}; -mod builder; +pub(crate) mod builder; mod decode; mod encode; mod fmt; @@ -73,6 +73,13 @@ impl Debug for Step { } } +impl Timestamp { + /// Creates a new timestamp builder. + pub fn builder() -> builder::TimestampBuilder { + builder::TimestampBuilder::new_in(Global) + } +} + impl Timestamp { /// Returns the opcode of this timestamp node. pub fn op(&self) -> OpCode { @@ -127,6 +134,11 @@ impl Timestamp { } impl Timestamp { + /// Creates a new timestamp builder with the given allocator. + pub fn builder_in(alloc: A) -> builder::TimestampBuilder { + builder::TimestampBuilder::new_in(alloc) + } + /// Finalizes the timestamp with the given input data. /// /// # Panics diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index d5ee9ab..630c671 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -1 +1,100 @@ //! Timestamp Builder + +use crate::{ + codec::v1::{Attestation, Timestamp, opcode::OpCode, timestamp::Step}, + error::EncodeError, + utils::OnceLock, +}; +use alloc::alloc::{Allocator, Global}; + +#[derive(Debug, Clone)] +pub struct TimestampBuilder { + steps: Vec, A>, +} + +#[derive(Debug, Clone)] +struct LinearStep { + op: OpCode, + data: Vec, +} + +impl TimestampBuilder { + /// Creates a new `TimestampRootBuilder`. + pub fn new_in(alloc: A) -> TimestampBuilder { + TimestampBuilder { + steps: Vec::new_in(alloc), + } + } + + /// Pushes a new execution step with immediate data to the timestamp. + /// + /// # Panics + /// + /// Panics if the opcode is not an opcode with immediate data. + pub(crate) fn push_immediate_step(mut self, op: OpCode, data: Vec) -> Self { + assert!(op.has_immediate()); + self.steps.push(LinearStep { op, data }); + self + } + + /// Pushes a new execution step without immediate data to the timestamp. + /// + /// # Panics + /// + /// Panics if: + /// - the opcode is control opcode + /// - the opcode is an opcode with immediate data + pub(crate) fn push_step(mut self, op: OpCode) -> Self { + self.steps.push(LinearStep { + op, + data: Vec::new_in(self.allocator().clone()), + }); + self + } + + /// Computes the commitment of the timestamp. + pub fn commitment(&self, input: impl AsRef<[u8]>) -> Vec { + let alloc = self.allocator().clone(); + let mut commitment = input.as_ref().to_vec_in(alloc.clone()); + for step in &self.steps { + commitment = step.op.execute_in(&commitment, &step.data, alloc.clone()); + } + commitment + } + + /// Finalizes the timestamp with the given attestation. + /// + /// # Notes + /// + /// The built timestamp does not include any input data. The input data must be + /// provided later using the `finalize` method on the `Timestamp` object. + pub fn attest<'a, T: Attestation<'a>>( + self, + attestation: T, + ) -> Result, EncodeError> { + let alloc = self.allocator().clone(); + + let mut current = Timestamp::Attestation(attestation.to_raw_in(alloc.clone())?); + + for step in self.steps.into_iter().rev() { + let step_node = Step { + op: step.op, + data: step.data, + input: OnceLock::new(), + next: { + let mut v = Vec::with_capacity_in(1, alloc.clone()); + v.push(current); + v + }, + }; + current = Timestamp::Step(step_node); + } + + Ok(current) + } + + #[inline] + fn allocator(&self) -> &A { + self.steps.allocator() + } +} From 3656c2227409044b1362d53d29cbbbab52722af4 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 23 Dec 2025 22:07:05 +0800 Subject: [PATCH 2/6] fmt --- Cargo.toml | 2 +- crates/calendar/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 109d797..1788092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ alloy-signer-local = "1.1" auto_impl = "1.3" axum = "0.8" axum-extra = "0.12" +bump-scope = { version = "1.5", features = ["nightly"] } bytes = "1.11" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } @@ -56,7 +57,6 @@ toml = "0.9" tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -bump-scope = { version = "1.5", features = ["nightly"] } digest = "0.11.0-rc.4" ripemd = "0.2.0-rc.3" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index 85ce324..1807848 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -13,6 +13,7 @@ alloy-primitives = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } axum = { workspace = true, features = ["macros"] } +bump-scope.workspace = true bytes = { workspace = true } eyre = { workspace = true } sha3 = { workspace = true } @@ -20,7 +21,6 @@ tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } uts-core = { workspace = true, features = ["bytes"] } -bump-scope.workspace = true [lints] workspace = true From d80f9b9b80b98b13bf7d76542d53719f8fa30ec0 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 23 Dec 2025 22:07:18 +0800 Subject: [PATCH 3/6] clippy --- crates/calendar/src/routes/ots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index be026dc..38e1fec 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -58,7 +58,7 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u let hash = HASHER.with(|hasher| { let mut hasher = hasher.borrow_mut(); hasher.update(EIP191_PREFIX.as_bytes()); - hasher.update(&recv_timestamp); + hasher.update(recv_timestamp); hasher.update(&digest); hasher.finalize_reset() }); From 2d57a594d017b65fc188443ca5bd74a7fc1ee654 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 23 Dec 2025 22:15:11 +0800 Subject: [PATCH 4/6] apply review --- Cargo.lock | 1 + Cargo.toml | 1 + crates/calendar/Cargo.toml | 1 + crates/calendar/src/routes/ots.rs | 14 +++++++++++++- crates/core/src/codec/v1/timestamp/builder.rs | 2 +- 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c425074..207bdeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4538,6 +4538,7 @@ dependencies = [ "bump-scope", "bytes", "eyre", + "itoa", "sha3 0.11.0-rc.3", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 1788092..a16c008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ const_format = "0.2" criterion = { version = "0.8", features = ["html_reports"] } eyre = "0.6" hex = "0.4" +itoa = "1.0" once_cell = { version = "1.21", default-features = false } paste = "1.0" regex = "1.12" diff --git a/crates/calendar/Cargo.toml b/crates/calendar/Cargo.toml index 1807848..bf24afa 100644 --- a/crates/calendar/Cargo.toml +++ b/crates/calendar/Cargo.toml @@ -16,6 +16,7 @@ axum = { workspace = true, features = ["macros"] } bump-scope.workspace = true bytes = { workspace = true } eyre = { workspace = true } +itoa = { workspace = true } sha3 = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 38e1fec..7d08362 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -53,11 +53,23 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u let recv_timestamp = current_time_sec().to_le_bytes(); let undeniable_sig = { - // sign_message_sync invoke heap allocation, so manually hash it. + // sign_message_sync invokes heap allocation, so manually hash it. const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n"; let hash = HASHER.with(|hasher| { let mut hasher = hasher.borrow_mut(); hasher.update(EIP191_PREFIX.as_bytes()); + match digest.len() { + // 32 + 8 + 32 => hasher.update(b"40"), + // 64 + 8 + 64 => hasher.update(b"72"), + _ => { + let length = digest.len() + size_of::(); + let mut buffer = itoa::Buffer::new(); + let printed = buffer.format(length); + hasher.update(printed.as_bytes()); + } + } hasher.update(recv_timestamp); hasher.update(&digest); hasher.finalize_reset() diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index 630c671..585a34a 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -19,7 +19,7 @@ struct LinearStep { } impl TimestampBuilder { - /// Creates a new `TimestampRootBuilder`. + /// Creates a new `TimestampBuilder`. pub fn new_in(alloc: A) -> TimestampBuilder { TimestampBuilder { steps: Vec::new_in(alloc), From 875b9344085c2a77f645c9ddc1b279106ab6baa7 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Tue, 23 Dec 2025 22:19:28 +0800 Subject: [PATCH 5/6] apply review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/calendar/src/time.rs | 4 ++-- crates/core/src/codec/v1/timestamp/builder.rs | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/calendar/src/time.rs b/crates/calendar/src/time.rs index e8ebd70..d073b58 100644 --- a/crates/calendar/src/time.rs +++ b/crates/calendar/src/time.rs @@ -16,13 +16,13 @@ pub fn current_time_sec() -> u64 { /// An asynchronous task that updates the current time every second. pub async fn updater() { - let mut sleep = tokio::time::interval(Duration::from_secs(1)); + let mut interval = tokio::time::interval(Duration::from_secs(1)); loop { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); CURRENT_TIME_SEC.store(now, Ordering::Relaxed); - sleep.tick().await; + interval.tick().await; } } diff --git a/crates/core/src/codec/v1/timestamp/builder.rs b/crates/core/src/codec/v1/timestamp/builder.rs index 585a34a..9275f37 100644 --- a/crates/core/src/codec/v1/timestamp/builder.rs +++ b/crates/core/src/codec/v1/timestamp/builder.rs @@ -52,7 +52,20 @@ impl TimestampBuilder { self } - /// Computes the commitment of the timestamp. + /// Computes the commitment of the timestamp for the given input. + /// + /// In this context, the **commitment** is the deterministic result of + /// executing the timestamp's linear chain of operations over the input + /// bytes. It is computed by: + /// + /// 1. Taking the provided `input` bytes as the initial value. + /// 2. Iterating over all steps in the order they were added to the builder. + /// 3. For each step, applying its opcode to the current value together + /// with the step's immediate data via [`OpCode::execute_in`], and using + /// the result as the new current value. + /// + /// The final value after all steps have been applied is returned as the + /// commitment. pub fn commitment(&self, input: impl AsRef<[u8]>) -> Vec { let alloc = self.allocator().clone(); let mut commitment = input.as_ref().to_vec_in(alloc.clone()); From 035a0d455d402d3071d95d9cbfa03ee3bd2f5c11 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 23 Dec 2025 22:20:08 +0800 Subject: [PATCH 6/6] typo --- crates/calendar/src/routes/ots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 7d08362..c1c6680 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -106,7 +106,7 @@ pub fn submit_digest_inner(digest: Bytes, signer: impl SignerSync) -> (Bytes, [u // copy data out of bump // TODO: eliminate this allocation by reusing from a pool - // TODO: warp the buffer with a drop trait to return to pool + // TODO: wrap the buffer with a drop trait to return to pool let mut buf = BytesMut::with_capacity(128); timestamp.encode(&mut buf).unwrap();