Skip to main content

Module ubft

Module ubft 

Source
Expand description

DSig as the signature service of a uBFT replica: the same slices, certificate unit and verify core as the batch kernels, behind four AXI4-Stream ports, so a consensus engine on the same U280 can sign and verify in microseconds instead of milliseconds. Built with the ubft feature; the hardware is crate::ubft.

In numbers (October 2026; every row says what kind of evidence it is):

uBFT today (inline xf Ed25519)With the DSig serviceEvidence
Sign one share3,071,249 cycles (19.0 ms projected at the estimated 161.3 MHz)207–217 cycles = 1.01–1.06 µs at 205 MHz through the whole service kernel (the signer alone: 171–188); profile 2: 207–257HLS C/RTL cosim of uBFT’s own wrapper (reproduced, crates/dsig/measure/ubft-baseline); native RHDL simulation (tests/ubft_engine.rs; tests/ubft_signer.rs)
Verify one share10,411,579 cycles (64.5 ms projected)731–737 cycles = 3.6 µs (certificate cached); +52,670 on a miss; profile 2: 766–769HLS cosim; native RHDL simulation (tests/ubft_blocks.rs, instant Ed25519 model) + the Ed25519 project’s measured core latency
uBFT kernel itself234,341 LUT, 4.68 ns (misses 300 MHz)37,971 LUT, 3.12 ns (meets 300 MHz)HLS synthesis of ubft with scheme 0 (inline) and scheme 1 (DSig streams)
Clock uBFT’s design closes at161.3 MHz (its signer is the worst path, −2.866 ns)not measured yetuBFT’s routed link log
The service on the card—building (UbftCard, link in progress)—

So a slow-path slot (one signature, 2f verifications) drops from hundreds of milliseconds to a few microseconds of crypto, and the consensus kernel loses 84 % of its LUTs and its timing problem.

§The story in five minutes

The job. uBFT signs a summary of each slot and checks its peers’ signatures until it holds a quorum. It does that with the Vitis Security Library’s Ed25519, called inline: the replica’s state machine stops for 19 ms per signature and 64.5 ms per verification, and that signer is so deep that Vitis pulled the whole design’s clock from 300 to 161.3 MHz.

The first idea was to hand uBFT the batch kernels. They are fast, but they run from HBM, start from the host and finish once; a consensus engine needs an answer to one request at a time, now.

So, one idea per step: put the batch engine’s slices behind a request stream (a dealer gives each request to the first idle slice; keys are made ahead, so a request never waits for key generation); put a verify core behind a second stream with an in-order tag FIFO; let the Ed25519 verify core hold one trusted key per peer (8 slots, N ≤ 9).

What broke was an assumption in the certificate cache: with one trusted key it was enough to match the 128-byte certificate, but with many a certificate checked for replica A would have answered for replica B. Every cache entry now remembers its key slot (crate::verify::certs).

Where it stands. The service signs and verifies in simulation at 1.01–1.06 µs (whole kernel) and 3.6 µs; uBFT’s code calls it over streams (a local, unpushed branch of ubft_new) and synthesizes 84 % smaller at 300 MHz; the one-card measurement kernel is being routed.

Where you come in. See What is not done.

§How uBFT talks to it

PortDirectionBeatContent
s_axis_signuBFT → service512 bitsbytes 0..16 message (uBFT’s {view, slot, k, m}), 16..24 tag
m_axis_frameservice → uBFT26 × 512 bitsthe 1,642-byte signature, tag at 1642..1650
s_axis_verifyuBFT → service27 × 512 bitsheader (key slot, tag), then the frame with the message at 1642..1658
m_axis_verdictservice → uBFT64 bitsverdict (0x80 valid), slot, tag, in request order

On uBFT’s side (branch dsig-integration of a local ubft_new clone, not pushed) a compile switch UBFT_SIG_SCHEME picks the original inline Ed25519 (0), the DSig service (1) or this repository’s Ed25519 service (2); config.cfg connects the four streams, the Makefile links our exported .xo at 205 MHz beside uBFT’s 300 MHz kernels, and each share in the HBM mailbox grows from 64 to 1,664 bytes. The branch also fixes two bugs that kept uBFT’s slow path from reaching a remote quorum (the valid flag was never sent to peers; every poll re-verified every share).

§The optimal configuration for uBFT

A slot needs one signature and 2f verifications; even one slice signs 6 million per second. So the right build is the smallest, and latency, not rate, is the measure:

ChoiceValueWhy
Message profile64 B (profile 2, DSIG_MESSAGE_BYTES=64)signs uBFT’s whole 48-byte context (session, role, CTB fields), so U33 is closed and CTB can be signed; profile 1 (16 B: view, slot, k, m) builds only with the Tuner’s profile 1 for uBFT set to allowed
Signer slices2the smallest buildable signer; a lone request uses one idle slice, so more slices add LUTs, not speed
T-table rounds4 (DSIG_TABLE_MASK=0x78)the measured builds’ setting; fewest LUTs that meet 205 MHz
Verify units12f verifications can pipeline through one unit at ~36 cycles each after the first (uBFT’s branch still sends one at a time, SIG_VERIFY_MAX_INFLIGHT=1, so today it pays 3.6 µs each)
Ed25519 verify core1,1,1,0, fix14a certificate check per peer per 128 signatures
Key slotscapacity 8, at most 7 active (uBFT needs an odd membership); 16 with the Ed25519 crate’s key-slots-16indexed by replica, own slot included
Clock205 MHz, its own clock domainuBFT’s kernels keep 300 MHz; v++ inserts the stream clock crossing

cargo tuner with --features ubft plans exactly this beside the uBFT replica (paper “uBFT … DSig”) and shows each slot’s crypto time against the inline baseline.

§The ≥5× target and how it is measured

The user’s target (2026-10-21) is at least 5× uBFT’s performance, measured on the card: completed, correct operations per second at the same replica count, workload, identity security, payload and cache state (warm or cold), with the clock measured on the card, finite bursts and continuous runs reported separately. It is a target, not a result.

Where the 5× has to come from:

WorkloadUpstream’s bottleneckWhat removes it
fast path (normal operation; no crypto)a 161.3 MHz clock (the inline signer is the worst path), one QP per FSM step, mostly one slot at a timecrypto out of the consensus kernel (37,971 LUT at 3.12 ns est. instead of 234,341 at 4.68 ns), so the controller can run at 300 MHz; all-peer broadcast; several slots in flight (rhdl_ubft)
slow path (any timeout or fault)19.0 ms per signature, 64.5 ms per verification, one after anotherthis service: 1.01–1.06 µs sign (whole kernel), 3.6 µs verify cached, the announce removes the certificate miss, verifications pipelined

The campaign (one table per scheme):

RowWhat runsExpected
pinned baselineubft_new a7537bd, unmodifiedno slow-path progress: reported as failure / zero, not hidden
repaired baselinepatches 0001 (valid-flag broadcast) and the QP-table decode only, inline Ed25519the reference for every ratio
rhdl_ubft + Ed25519this repository’s Ed25519 service
rhdl_ubft + DSigthis service, announce on

Each row is run at N = 3 with warm and cold caches, on a fast-path-only workload and a forced-slow-path one, and prints operations/s, slot latency and the ratio to the repaired baseline with pass/fail against 5×.

§What is not done

  1. No card number for the service yet. UbftCard (the service and a probe that plays uBFT on one card) is being routed; until it runs, the latencies above are native RHDL simulation.
  2. Not linked with uBFT. uBFT’s other kernels (RoCE stack, kvstore, hash table) come from its own IP repository; the combined xclbin has not been built, and a real slot needs two or more cards on a network.
  3. A certificate miss costs ~52,670 cycles (257 µs) of Ed25519. The certificate announce (built) checks each peer batch’s certificate ahead of use; a share that arrives before its announce still pays it.
  4. At most 7 active replicas with 8 key slots (uBFT allows 12).
  5. Bigger messages on the wire. A DSig share is 1,664 bytes (1,728 in profile 2) against Ed25519’s 64; uBFT’s RDMA writes go out as 26 (27) writes of 64 bytes (the larger RoCE write mode is untested).
  6. The uBFT apply contract (crates/planner/src/build_apply.rs) still pins DSIG_MESSAGE_BYTES=16; it needs the Tuner’s message profile and gate before a profile-2 replica can be applied from a saved plan. DSig as the signature service of a uBFT replica (feature ubft).

§The problem

uBFT is a consensus engine that runs entirely on an Alveo U280: a Vitis HLS state machine (ubft) that talks to its peers through a RoCE stack and keeps its mailboxes in HBM. On its slow path every replica signs a 64-bit summary of the slot, sends the signature to every peer, and verifies the shares it receives until it holds 2f + 1. It does both with the Vitis Security Library’s Ed25519 (xf::security::eddsaEd25519), called inline: the replica’s whole state machine stops for every signature and every verification, and that signer is the routed design’s worst path (−2.866 ns), which pulls the shared kernel clock from 300 MHz down to 161.3 MHz.

The rest of this crate signs and verifies in batches: the host fills HBM, starts a kernel, and reads frames or verdicts back when it is done. A consensus engine cannot work that way. It needs a service that sits next to it on the chip, takes one request at a time on a stream, and answers in microseconds.

§The idea

[UbftEngine] is that service: the same slices, certificate unit, verify core and Ed25519 verify core as the batch kernels, with AXI4-Stream ports where those have HBM masters.

  uBFT (HLS) ──sign request──▶ ┌──────────────── UbftEngine ───────────────┐
             ◀──26-beat frame── │ UbftSigner: dealer ▶ slices ▶ arbiter     │
                                │             cert unit (Ed25519 signer)    │
  uBFT (HLS) ──verify request─▶ │ UbftVerifier: framing ▶ verify core ▶ tags│
             ◀──verdict word─── │ Ed25519 verify core, 8 trusted key slots  │
                                └──────── AXI4-Lite: keys, counters ────────┘
  • Signing. A request is one 64-byte beat: the 16-byte message and an 8-byte tag. The dealer hands it to the first idle slice; the slice signs it with a one-time key it made in advance, and the frame (26 beats, the format of crate::slice_signer, the tag in the message index field) leaves on the frame stream. Keys are made all the time in the background, so a request never waits for key generation.
  • Verifying. A request is a header beat (the signer’s key slot and a 32-bit tag) followed by the 26 beats of the frame exactly as the signer produced it. The verdict word comes back in request order.
  • Peers. uBFT has one signer per replica, so the verifier trusts up to [KEY_SLOTS] Ed25519 keys at once (replicas N ≤ 9). The certificate cache keeps the key slot with every entry (see crate::verify::certs): a certificate checked for one replica never answers for another.

§Streams

PortDirectionBeatContent
s_axis_signin512 bitsbytes 0..16 message, 16..24 tag (LE64)
m_axis_frameout512 bits, tlast on beat 25the frame: 1,642-byte signature, tag at 1642..1650
s_axis_verifyin512 bits, tlast on beat 26beat 0: byte 0 key slot (4 bits), bytes 4..8 tag (LE32); beats 1..26: the frame
m_axis_verdictout64 bitsbits 0..8 verdict (crate::verify), 8..12 key slot, 32..64 tag
m_axis_certout512 bits, tlast on beat 1this signer’s new batch certificate: commitment, then Ed25519 signature
s_axis_certin512 bits, tlast on beat 2a peer’s announced certificate: header (byte 0 key slot), commitment, signature

The certificate streams are optional: uBFT forwards m_axis_cert to its peers and feeds theirs into s_axis_cert, so a batch’s certificate is checked before its first signature arrives (a cache miss otherwise costs one Ed25519 check, ~52,670 cycles). Leave s_axis_cert idle and m_axis_cert drained to skip it.

A verdict of 0x80 means valid; any other value has bit 7 set and names the failed checks.

§Register map (32-bit words)

OffsetNameAccess
0x000AP_CTRL: bit 0 ap_start, 1 ap_done (clear on read), 2 ap_idle, 3 ap_readyRW
0x004/0x008/0x00cGIE / IER / ISRRW
0x010arg sign_count: signatures this session (u32)W
0x014arg verify_count: verifications this session (u32)W
0x018arg batches: key batches to make (u32, 128 keys each)W
0x020arg epoch (u64)W
0x028arg first_batch (u64)W
0x030 + 4wargs cv0..cv7: derive-key prefix CV (secret)W
0x050 + 4wargs seed0..seed7: Ed25519 seed (secret)W
0x070arg key_mask: trusted key slots in use (bits 0..8)W
0x080 + 0x20·s + 4wargs key{s}_{w}: slot s’s Ed25519 public key, bytes 4w..4w+4 LEW
0x180 + 8·sargs epoch{s} (u64, lo then hi): slot s’s signer’s epoch this sessionW
0x1c0arg epoch_mask: slots whose epoch is enforced (bits 0..8; see [verifier], session epochs)W
0x200MAGIC = [MAGIC] (“DSU1”)R
0x204ABI = [ABI_VERSION]R
0x208SHAPE: slices bits 0..8, table mask 8..24, key slots 24..28R
0x20cFAULT: bit 0 slice, 1 certificate unit, 2 Ed25519 verify core, 3 a trusted key rejected, 4 verify stream framingR
0x210/0x214TICKS lo (snapshots hi) / hiR
0x218/0x21cSTART_TICK lo/hiR
0x220/0x224DONE_TICK lo/hiR
0x228/0x22cKEY_TICK lo/hi: the trusted keys finished loadingR
0x230SIGNED: frames sentR
0x234SIGN_TAKEN: sign requests acceptedR
0x238VERIFIED: verdicts sentR
0x23cVALID: verdicts that were 0x80R
0x240CERT_CHECKS: Ed25519 verifications issuedR
0x244CERT_FAILSR
0x248CERTIFIED: batches the signer certifiedR
0x24cANNOUNCED: certificates sent on m_axis_certR
0x250 + 4isign latency: N, LAST, MIN, MAX, SUM lo, SUM hiR
0x270 + 4iverify latency: same six wordsR
0x2a0 + 4wPUBLIC_KEY: the signer’s Ed25519 public key (8 × u32)R

§How latency is measured

The kernel times requests on its own clock. A request that arrives while no other request of its kind is in flight starts a measurement: the tick of its first beat’s handshake. The measurement ends on the handshake of its answer’s last beat, if no second request arrived meanwhile. So N counts isolated requests, and LAST/MIN/MAX/SUM are their latencies in cycles, request beat to answer beat, with nothing else in the service. Loaded latency is not measured here.

§The rules from the batch kernels still hold

One session per reset: the kernel never restarts, the host gives every session a fresh namespace (epoch, batch range, master seed), and the secrets have no read path. A one-time key is burned before its frame leaves, so a stalled frame stream can never cause a key to sign twice.

Re-exports§

pub use card::CardInput;
pub use card::CardOutput;
pub use card::UbftCard;
pub use engine::CfgWrite;
pub use engine::UbftEngine;
pub use engine::UbftInput;
pub use engine::UbftOutput;
pub use probe::UbftProbe;
pub use signer::UbftSigner;
pub use verifier::UbftVerifier;

Modules§

card
UbftCard: the uBFT service with its probe, as one card kernel for measurement.
engine
UbftEngine: the register file, the trusted-key loader and the Ed25519 verify core around UbftSigner and UbftVerifier.
probe
A uBFT stand-in for measuring super::UbftEngine on one card.
signer
The signing half of super::UbftEngine: a stream in front of the batch engine’s slices and certificate unit.
verifier
The verifying half of super::UbftEngine: one verify core behind a request stream.

Structs§

Axis64
One 64-bit stream beat (the verdict stream).
Axis512
One 512-bit stream beat (tvalid, tdata, tlast; tready travels the other way as a plain bool).
Latency
Isolated-request latency statistics, in cycles.

Constants§

ABI_VERSION
Register-map version.
ARG_BATCHES
batches.
ARG_CV
cv0.
ARG_EPOCH
epoch (u64).
ARG_EPOCHS
epoch{s} (slot 0’s low word; high word at +4, slots at +8).
ARG_EPOCH_MASK
epoch_mask.
ARG_FIRST_BATCH
first_batch (u64).
ARG_KEYS
key0_0; slot s, word w at + 0x20·s + 4w.
ARG_KEY_MASK
key_mask.
ARG_SEED
seed0.
ARG_SIGN_COUNT
Offsets of the host arguments.
ARG_VERIFY_COUNT
verify_count.
KEY_SLOTS
Trusted Ed25519 keys the verifier holds at once (the verify core’s key slots): replicas N ≤ KEY_SLOTS + 1.
MAGIC
MAGIC register value (“DSU1” in little-endian ASCII).
REG_ABI
ABI.
REG_ANNOUNCED
ANNOUNCED.
REG_CERTIFIED
CERTIFIED.
REG_CERT_CHECKS
CERT_CHECKS.
REG_CERT_FAILS
CERT_FAILS.
REG_DONE_TICK
DONE_TICK lo.
REG_FAULT
FAULT.
REG_KEY_TICK
KEY_TICK lo.
REG_MAGIC
Status registers.
REG_PUBLIC_KEY
PUBLIC_KEY words.
REG_SHAPE
SHAPE.
REG_SIGNED
SIGNED.
REG_SIGN_LATENCY
Sign latency block (six words).
REG_SIGN_TAKEN
SIGN_TAKEN.
REG_START_TICK
START_TICK lo.
REG_TICKS
TICKS lo.
REG_VALID
VALID.
REG_VERIFIED
VERIFIED.
REG_VERIFY_LATENCY
Verify latency block (six words).
VERIFY_IN_FLIGHT
Verify requests that may be in flight (the verify core’s contexts).
VERIFY_REQUEST_BEATS
Beats of a verify request: the header and the 26-beat frame.

Functions§

latency_kernel
Opens or closes a latency measurement.