Skip to content

clean API - #262

Merged
TomWambsgans merged 25 commits into
mainfrom
xmss-api-rework
Jul 23, 2026
Merged

clean API#262
TomWambsgans merged 25 commits into
mainfrom
xmss-api-rework

Conversation

@TomWambsgans

Copy link
Copy Markdown
Collaborator

No description provided.

TomWambsgans and others added 25 commits July 23, 2026 08:36
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Check the slot range before grinding the encoding randomness (no
  expensive work on out-of-range slots).
- Reuse the encoding found during grinding instead of recomputing it,
  which removes the unreachable InvalidRandomness error variant and the
  now-unused WotsSecretKey::sign_with_randomness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xmss_key_gen(rng, activation_slot, num_active_slots) -> (pk, sk),
sampling the seed from the caller's RNG and validating the activation
interval against the 2^32 lifetime.

The 'sequential' flag is gone from the public API: key generation asks
the pool whether it already runs inside a pool task - where a nested
dispatch would panic - via the new parallel::is_in_pool_task(), and
falls back to a sequential build in that case (e.g. generating many
keys in a parallel batch). Bottom-subtree builds during signing remain
always sequential: signing must never wait on the thread pool while
holding the signing-cache mutex, or a pool task blocked on the same key
would deadlock the pool, and hence the signer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The public API now takes message: &[u8; 32] (like leanSig), in leanSig's
parameter order: sign(rng, sk, slot, msg) / verify(pk, slot, msg, sig).

The message is embedded injectively into 9 field elements (little-endian
base-p decomposition, identical convention to leanSig's encode_message)
and hashed with a domain-separated poseidon16_compress into the [F; 8]
'message' the WOTS encoding - and hence the snark, which is unchanged -
consumes. hash_message() is public so the aggregation pipeline can
compute the snark-side message at the boundary.

encode_message is checked against independently computed base-p
reference vectors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xmss_sign(&sk, slot, message) no longer takes an RNG: the encoding
randomness for attempt i is Poseidon-derived from (secret seed, slot,
attempt, hashed message). Re-signing the same (slot, message) pair now
returns the identical signature, so an accidental double-sign of the
same message is harmless (signing a different message at the same slot
remains fatal, as in any synchronized scheme).

Grinding is bounded by MAX_SIGNING_ATTEMPTS (expected ~a few hundred
attempts for target_sum = 184); exceeding it returns
EncodingAttemptsExceeded instead of looping forever. The unbounded
find_randomness_for_wots_encoding is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first signature after crossing into a new bottom subtree pays an
O(sqrt(R)) rebuild inside xmss_sign. XmssSecretKey::prepare(slot) lets
callers do that work off the signing critical path; signing itself is
unchanged (same lazy cache, now factored into cached_bottom_subtree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Serializes (seed, slot range, top tree): storing the top tree makes
reload cheap (no re-hashing of the key's whole range), while the derived
fields are recomputed and the tree shape is revalidated against the
range on deserialization. The bottom-subtree cache restarts empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Manual ethereum_ssz implementations; field elements are canonical u32
little-endian, rejected on decode when non-canonical.

- XmssPublicKey: fixed 32 bytes (root | public_param)
- XmssSignature: fixed 1208 bytes (chain_tips | randomness | merkle_proof)

Only these two appear in consensus objects. The secret key deliberately
gets no SSZ: it never crosses the consensus layer, serde covers local
persistence, and a future keystore standard (a la EIP-2335 for BLS,
which encrypts the raw scalar, not an SSZ object) should own the
canonical secret-key format - for this scheme the minimal secret
material is just the 32-byte seed and the activation range.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Missed in the per-commit migration because cargo check without
--all-targets skips the root integration tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pool tasks and a plain thread signing with one shared key: exercises
the cache-mutex contention paths (sequential subtree builds under the
lock, so no signer ever waits on the pool while holding it) and checks
that derandomization yields identical signatures from either context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only WotsSignature (a field of XmssSignature) is API; the one-time
secret keys (whose pre-image array was even a public field), chain
walking, public-key recovery, and the encoding are building blocks of
xmss_sign / xmss_verify that no external caller should touch. The
grinding measurement moves in-crate as a unit test with them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
XmssSecretKey's derived Debug printed the seed (and the seed-derived
tree); one {:?} in a log line would leak the key. The manual impl shows
only the slot range and split level. WotsSecretKey (crate-internal)
loses Debug entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
signers_cache generates 10k keys, writes caches under target/, and
prints progress - benchmark machinery, not signature API. It is now
compiled only for in-crate tests or under the new 'test-utils' feature,
which rec_aggregation and the root crate enable explicitly (postcard
becomes an optional dependency tied to it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
They already appeared in public signatures (pub merkle_root: Digest,
...), so rustdoc rendered types downstream users could not name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xmss_key_gen_from_seed(seed, activation_slot, num_active_slots): the
same (seed, range) always regenerates the same key pair, so a backed-up
seed is enough to recover a key. xmss_key_gen becomes a thin wrapper
that samples the seed from the RNG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without them a lean-multisig user could not match on the errors
returned by the re-exported xmss_key_gen / xmss_sign / xmss_verify
without adding a direct xmss dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A leading format-version byte in the serde encoding, checked on decode,
so a future layout change fails with an explicit error (and can be
migrated) instead of a confusing shape mismatch. pk/sig SSZ formats are
consensus-spec'd and stay untagged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aggregate_single_message_signatures, SingleMessageInfo.message, and
split_multi_message_aggregate_by_message now take the raw 32-byte
message; the Poseidon message hash the snark consumes is computed
internally (once per aggregation / input-data build). Callers no longer
deal in field-element messages anywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The public API deals exclusively in 32-byte messages: MESSAGE_LEN_FE,
MESSAGE_EMBEDDING_LEN_FE, and encode_message become crate-private, and
hash_message is doc(hidden) (it stays technically pub only because
rec_aggregation needs it for the snark public input). The zkDSL
placeholder uses DIGEST_LEN, which the hashed message is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workspace build always enables xmss's 'test-utils' feature via
rec_aggregation, so the configuration downstream users get (no feature,
optional postcard off) was never built in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TomWambsgans TomWambsgans changed the title Xmss api rework clean API Jul 23, 2026
@TomWambsgans
TomWambsgans merged commit a73ab11 into main Jul 23, 2026
3 checks passed
@TomWambsgans
TomWambsgans deleted the xmss-api-rework branch July 26, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant