Skip to main content

Module ed25519

Module ed25519 

Source
Expand description

§Direct DSig certificate bridge

CertificateSigner connects the actual DSig batch_commitment_words kernel to one actual compact Ed25519 CachedSignCore. The eight words become SignRequest.message directly. SHA-512, key expansion, and Ed25519 signing remain inside that core; this bridge performs no host prehash and repeats no DSig domain or profile constants.

The wrapper has exactly one stateful child and adds no registers, queues, credits, fault latch, or bank-admission controller. Its output is the original CachedSignOut, including ready, one-cycle valid, signature, tag, occupancy, and internal_error. This is a composition boundary, not a continuous key-preparation/refill system or a throughput claim.

§Signed bytes and the routing tag

The producer supplies the full epoch, batch ID, and completed 128-key Merkle root in CertificateSignerInput. certificate_request calls DSig’s packer and returns the real Ed25519 request type. The signed message is DSig’s exact 64-byte versioned certificate envelope; the 16-bit tag is outside those bytes.

Use the tag only to look up a live outstanding record containing the full epoch, batch ID, root, and trusted Ed25519 signer identity. A tag is neither an anti-reuse namespace nor evidence that a bank is authenticated. This bridge does not generate a Merkle root, authenticate a returned certificate, or decide whether a bank may be admitted.

§Caller obligations

  1. Reserve a durable DSig namespace before producing keys or requesting its certificate. Reset, timeout, and uncertain completion never permit reuse.
  2. Load a 32-byte seed only when load_key_ready is asserted. Wait for key_loaded, and check the returned public key against the trusted signer identity before submitting any envelope. Seed words are little endian.
  3. Reserve result-buffer capacity for every request before asserting valid. Accept only on valid && ready; hold a pending request stable otherwise. There is no output-ready signal: capture every completion pulse.
  4. Latch internal_error externally and stop admission/certification. The underlying core does not suppress readiness or completion on fault. A fault takes priority over a simultaneous valid pulse; do not certify that result or interpret a later apparently healthy pulse as recovery.
  5. Reject unknown, stale, or duplicate completion tags. Resolve the full saved envelope and signer, then strictly verify the certificate before any admission policy marks a key bank certified.
  6. Drain every accepted request and retained result before clear_key or a key change. Keep valid and load_key_valid low during clear. Clear is not a cancellation handshake and does not erase all intermediate pipeline/memory storage. The caller must quarantine outstanding work across reset and keep its durable namespace burned.

The wrapper deliberately forwards all child outputs and controls unchanged; it cannot enforce these policies without adding the controller excluded from this boundary.

§Dependency and validation boundaries

The root-owned dependency graph selects the frozen Ed25519 snapshot with default-features = false, under the same Git RHDL identities and compiler patch as DSig. Before any native or RTL gate, the integration owner must audit the resolved Cargo features. (The current core, rhdl_ed25519_scalable_core, has no wide-sign feature.) Disabling features on this bridge’s command line cannot disable a dependency feature enabled elsewhere through Cargo feature unification. A bridge-local cfg!(feature = "wide-sign") would not establish the required topology.

The small tests exercise the actual packing helper against DSig’s byte serializer, including namespace/root bit coverage and high-bit tags. A wiring test checks unmasked coincident completion/fault forwarding without issuing an illegal transaction to the core.

The ignored native_compact_certificate_handoff test instantiates the actual CertificateSigner through the imported constant-memory CycleSim, with no waveform tracing. It expands one public fixture seed, signs three distinct DSig envelopes with high-bit tags, matches every signature exactly to Dalek 2.2, verifies each through the actual DSig BatchCertificate::verify_strict, rejects changed signed bytes and a wrong trusted signer, tracks all tags, and drains before clear. It uses a dedicated 256 MiB stack and at most 60,000 non-reset cycles. All test material is fixed and public, never production keys.

After central resource reservation and feature-graph audit:

cargo test -p dsig-ed25519-rhdl --profile dsig-sim --offline --locked --no-default-features -j1 --lib -- --test-threads=1
cargo test -p dsig-ed25519-rhdl --profile dsig-sim --offline --locked --no-default-features -j1 --test certificate_handoff actual_dsig_packer_preserves_namespace_root_and_high_tag_bits -- --exact --test-threads=1
cargo test -p dsig-ed25519-rhdl --profile dsig-sim --offline --locked --no-default-features -j1 --test certificate_handoff native_compact_certificate_handoff -- --ignored --exact --nocapture --test-threads=1

These commands are proposed gates, not passing evidence. Native correctness does not establish generated-RTL equivalence, LUT/register/DSP fit, routed frequency, on-card operation, continuous refill, or sustained DSig throughput. The existing frozen Ed25519 source has a missing publication notice; see vendor/ed25519-compact/PROVENANCE.md before distributing that snapshot. Its upstream unchecked-export bypass is not a valid joint RTL acceptance gate.

§Where this bridge sits, and why it is so thin

Every batch of 128 one-time keys needs one Ed25519 signature over its 64-byte envelope (see crate::certificate). The Ed25519 math is large and was already built and tested by a separate project, so DSig reuses that core unchanged (vendored as rhdl_ed25519_scalable_core) instead of writing its own. This file is only the plug between the two.

  crate::cert_unit (bookkeeping: tags, ≤ 32 in flight, in-order delivery)
       │ CertificateSignerInput: key control + epoch, batch_id, root, tag
       ▼
  ┌──────────── CertificateSigner (this file, no state of its own) ───────┐
  │ certificate_request(): batch_commitment_words() → SignRequest          │
  │        │ CachedSignIn                                                 │
  │        ▼                                                              │
  │ Ed25519SignCoreSmall  (SHA-512, key expansion, scalar multiply)       │
  └────────┬──────────────────────────────────────────────────────────────┘
           │ CachedSignOut: ready, valid pulse, signature, tag, occupancy,
           ▼                internal_error (forwarded unchanged)
  crate::cert_unit

Why no registers or queues here? Every extra stage would be a new place where a request could be lost, duplicated or reordered, and a new piece of state that a reset could leave half-finished. Keeping the bridge stateless means all policy (credits, fault latching, tag records, draining) lives in one place, crate::cert_unit, where it can be reviewed as a whole.

Figures: the core cost 25,383 CLB LUTs in context on the routed slices-2 link (the planner’s ED25519_LUT) and 25,911 LUTs and 248 DSPs inside the certificate unit of build v13 (routed, measured). It finishes one signature per ~895 cycles with at least 32 requests in flight (Ed25519 project, simulation), far more than the one certificate per 128 signatures, about 94,000 per second at 12M signatures per second, that the engine needs.

Structs§

CachedSignOut
Unmodified cached-signer output, including unbackpressured completion and fault. Signer output port (the CachedSignOut layout).
CertificateSigner
Direct RHDL composition of one DSig packer and exactly one cached signer.
CertificateSignerInput
Key-control signals and one complete DSig certificate request.
ScalarWords
Actual Ed25519 seed/public-key word type used at the bridge boundary. A scalar or 32-byte value as four little-endian 64-bit words.

Functions§

certificate_request
Pack the actual DSig fixed-64 message into the actual Ed25519 request type.
certificate_signer_kernel
Stateless wiring for CertificateSigner; clock/reset reaches its child through RHDL’s synchronous hierarchy, and every child output is forwarded.