Submission
A submission contains:
- Four RISC-V programs:
keygen,sign,expand, andverify. - Four integers:
S(signature size in bytes),W(witness size in bytes),K(cache size in bytes), andC(verification cycles). - Six byte offsets specifying where RISC-V inputs and outputs reside in memory.
- Lean 4 proofs for every required statement.
Goal: minimize S × C.
Parameters
| Constant | Value | Meaning |
|---|---|---|
| 220 | Keygen compression budget | |
| 217 | Signing compression budget | |
| 220 | Expansion compression budget | |
| 2−128 | Honest failure probability | |
| 232 | Signing lifetime | |
| 127 | Security level in bits | |
| 232 | Program cycle limit | |
| 220 | Program size limit (bytes) |
Programs
keygen
EnclaveDerives the public key and a public, untrusted cache from the secret key.
- Inputs
- Secret key
- Outputs
- Public key and cache, or failure
sign
EnclaveProduces a compact signature for a given message.
- Inputs
- Secret key, cache, message
- Outputs
- Signature or failure
Suggestion: the cache is untrusted, so a tampered cache must not leak secrets or aid a forgery. A MAC prevents this: keygen stores H(domain separator ‖ secret key ‖ rest of the cache) in the cache, and sign checks it.
expand
Prover hostConverts the signature into a verification witness.
- Inputs
- Message, public key, signature
- Outputs
- Witness or failure
Example: It may restore pruned Merkle paths or copy the signature when S = W.
verify
zkVMChecks whether the witness authenticates the message under the public key.
- Inputs
- Message, public key, witness
- Outputs
- Accept or reject
Model and costs
Random oracle
All programs and the security game's adversary share one random oracle H, mapping each input of 64·k bytes (k ≥ 1) to an independent uniform 32-byte answer. Security is proved in Lean in this model and counts calls to H.
RISC-V programs
Each program is an RV64IM program with one extra instruction, HASH(input, n, output), issued as a system call. It reads n bytes starting at address input, queries H on them, and writes the 32-byte answer at address output. The input and output addresses must both be multiples of 8, and n must be a nonzero multiple of 64. Hashing n bytes costs n / 64 compressions.
| Operation | Cost |
|---|---|
| Ordinary instruction | 1 cycle |
Multiplication or division: MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU, and their W forms | 4 cycles |
| HASH | 8 cycles per compression; no extra instruction charge |
| HALT | 1 cycle |
Verification also pays for its witness: ⌈W / 256⌉ cycles.
Required Lean statements
Honest experiment
Experiment: For any secretKey, message, and oracle H:
keygen(secretKey)returns the public key and cache.sign(secretKey, cache, message)returns the signature.expand(message, public key, signature)returns the witness.verify(message, public key, witness)returns the verdict.
Stop at the first failure. The experiment succeeds when all stages succeed and verification accepts.
counts program P's compressions; it is zero if P is never reached. denotes P's named budget.
- CompletenessLean statementFor every secret key, .
- Compression budgetsLean statementFor every secret key and
Pin {keygen,sign,expand}:
is over H; is over an independently sampled random oracle H and uniform 32-byte message M.
Remark. After H is fixed, adaptively chosen messages may cost much more than this average. Any scheme can rule this out by signing the message hashed with a salt derived from the secret key and the message, and carried in the signature. This costs only 16 signature bytes and one hash, so we prefer to rely on this heuristic rather than complicating the rules.
- Verification cyclesLean statementFor every secret key, message, and oracle, if the experiment succeeds,
verify's cycles plus the witness charge⌈W / 256⌉are at mostC.
Security
Experiment: Let A be a classical probabilistic adversary with unrestricted computation and an integer hash-call budget Q ≥ 1.
- Sample
Hand a uniform secret key independently. Initialize an empty transcriptTand count every call toHthroughout the experiment. - Run
keygen(secretKey). Failure ends the experiment without a win; otherwise giveAthe public key and cache. Amay then adaptively query two oracles:random_oracle(input_A)For an
input_Aof64·kbytes, returnH(input_A).signing_oracle(message_A, cache_A)Run
sign(secretKey, cache_A, message_A)with the original secret key. Return the signature or failure. Add each returned(message_A, signature)toT. Allow at most requests.Amakes one final submission in either form:Witness weak unforgeabilitySubmit
(message_A, witness_A). Win if:verify(message_A, public key, witness_A)accepts- no pair in
Thas messagemessage_A - the total hash-call count is at most
Q
Signature strong unforgeabilitySubmit
(message_A, signature_A). Win if:expand(message_A, public key, signature_A)returns a witness thatverifyaccepts(message_A, signature_A)is not inT- the total hash-call count is at most
Q
The total hash-call count includes key generation, signing, A's queries, and final expansion and verification when performed.
A and Q ≥ 1, over the secret key, H, and A's private randomness: Termination
RISC-V interface
Use RV64I and the M extension. Each program’s image must be smaller than : four bytes per instruction plus embedded-data bytes.
Code and memory
Code occupies a separate, immutable instruction address space. Instruction i is at 0x1000 + 4 × i. Memory occupies 0x000000–0xFFFFFF (16 MiB).
Load D embedded bytes at . Initially, sp = data_base and PC = 0x1000; all other registers are zero.
Inputs and outputs
Each submission specifies six byte offsets, shared by all four programs: δmessage, δsecret key, δpublic key, δcache, δsignature, δwitness. Each gives the memory address where that object is written or read. The buffers have the sizes in Parameters, start at 8-byte-aligned addresses, do not overlap, and end at or below data_base for every program.
Before each execution, memory is zero except for embedded data and the inputs listed under Programs; unused object buffers remain zero.
Example: before sign, write the secret key, cache, and message at their offsets. On success, read the signature at δsignature.
HALT ends execution with exit code a0: 0 means success and any other value failure. For verify, these mean acceptance and rejection, respectively.
System calls
ECALL selects one of two services through t0:
t0 | Service | Arguments |
|---|---|---|
0 | HASH | a0 = input address, a1 = input length in bytes, a2 = output address |
1 | HALT | a0 = exit code (0 = success) |
HASH writes H's 32-byte answer at the output address. Any other t0 fails.
Further details
- Proof checking
- The certificate must pass Lean's kernel against the organizer's definitions. Its transitive axiom dependencies may contain only
propext,Classical.choice, andQuot.sound. - Instructions
- Encodings are 32 bits. Fetching outside the code or at a non-4-byte-aligned address fails.
FENCEhas no effect;EBREAKfails. - Registers
x0–x31are 64 bits.x0always reads zero and ignores writes. Aliases aresp = x2,t0 = x5, anda0–a2 = x10–x12. PC is separate.- Memory access
- Addresses count bytes; multi-byte integers are little-endian. Accesses of 1, 2, 4, or 8 bytes require alignment to their size. Misaligned accesses fail.
- Bounds
- Every memory access and buffer must fit completely in memory. For unsigned byte address
pand lengthn, require . Instruction arithmetic and effective-address calculation follow RV64IM. - HASH arguments
- Addresses and byte length
nare unsigned 64-bit values. The input and output addresses must both be 8-byte aligned, andnmust be a nonzero multiple of 64. The input'snbytes and the output's 32 bytes must fit entirely in memory. - HASH execution
- Read the
ninput bytes in increasing address order; they areH's input. Read all input before writing the answer, so buffers may overlap. Preserve registers and advance PC by 4. - Faults
- An encoding outside RV64IM (such as compressed, A, F, D, CSR, or
FENCE.I), invalid HASH arguments, or any other fault ends the run as a failure; forverify, a rejection.
Lean project
SigGolf.Certificate submission C in SigGolf/Statements.lean is the competition claim for the exact four program images and declared sizes. Besides the five statements above, it contains Admission: the size maxima, the program size limit, and the buffer layout rules. SigGolf/Security.lean defines the attacker and both forgery experiments; SigGolf/Riscv.lean defines execution and costs. In Lean, the adversary is an OracleComp over coins, H, and the signing oracle: a computation that makes finitely many queries and then submits a forgery or gives up. A strategy that could run for ever is represented by its truncations, which give up where they are cut. Giving up never wins, and such a strategy's win probability is the limit of its truncations', so the bound over all adversaries bounds every adaptive strategy.
Build the statements and regression checks with lake build SigGolf SigGolfTests. Dependencies are pinned in lake-manifest.json. These files define the requirements; they do not certify a particular signature scheme. Submissions are verified from the sig.golf-submissions repository.
Known limitations
- Quantum security
- The security game only considers classical adversaries. NIST level 1 requires ≈ 64 bits of security against quantum adversaries.
- Single-user security
- The security game targets a single key, but a real attacker can target many users at once. The standard defense starts every hash with a per-key public parameter, so work against one user is useless against others. Drake's trick makes this cheap: a 16-byte parameter in the public key, padded with 48 zero bytes, fills the first 64-byte block of every hash, so its hash state is computed once and reused. Multi-user security then costs one compression and 16 bytes of public key.
- MPC for threshold signing
- The keygen and signing budgets let reasonably weak devices, such as hardware wallets, sign. Threshold signing runs keygen and sign inside multi-party computation (MPC), where hashing secret data costs far more. MPC precomputation followed by grinding on public values at signing can help (see RivaLabs).
- Trading lifetime for faster keygen and signing
- Hypertree pruning replaces most hypertree leaves with cheap placeholder hashes and grinds the randomizer until each message lands on a kept leaf, which speeds up keygen and signing without changing verification but lowers the safe number of signatures.
- Choice of hash function
- The oracle
Htakes inputs made of 64-byte blocks, returns 32-byte answers, and costs one compression per block. This cost model is accurate for BLAKE2s, for BLAKE3 on inputs up to 1 KiB, and for the SHA-256 compression function, but less precise for standard SHA-256, whose padding adds a block to every input, and for SHA-3, which absorbs 136 bytes per permutation. - Choice of ISA and metering
- RV64IM, the cost of each instruction, and details such as where HASH reads its inputs are one choice among many, and may not match a given zkVM.
- Delegating hashes of public values
- The budgets assume the enclave, such as a hardware wallet, computes every hash. Hashes of public values, typically for grinding, could be offloaded to a powerful host, possibly with a SNARK proving correctness.
- 127 bits of security
- Schemes built on 128-bit hash digests, such as SLH-DSA's 128-bit parameter sets, reach 127 bits of security rather than 128: an adversary can try to guess second preimages, and this bound is tight. This note gives a security proof and a matching attack.