-
-
Notifications
You must be signed in to change notification settings - Fork 1
ed25519_audit
Audit of the leviathan-crypto Ed25519 implementation
(AssemblyScript) against RFC 8032 (Edwards-Curve Digital
Signature Algorithm) §5.1, Ed25519, and FIPS 186-5 (Digital
Signature Standard) §7.6, EdDSA Signature Algorithm. The strict
verification posture follows FIPS 186-5 §7.6.4, Verification,
and is verified against the NIST ACVP EDDSA corpora (keyGen,
sigGen, sigVer).
| Meta | Description |
|---|---|
| Target: |
leviathan-crypto WebAssembly implementation (AssemblyScript) |
| Spec: | RFC 8032 §5.1, Ed25519, plus FIPS 186-5 §7.6, EdDSA Signature Algorithm |
| Parameter sets: | Ed25519 only (single-variant) |
| Test vectors: | RFC 8032 §7, Test Vectors for Ed25519, plus NIST ACVP EDDSA keyGen / sigGen / sigVer corpora |
| Source files: |
src/asm/curve25519/ed25519.ts, src/asm/curve25519/sha512.ts, src/ts/ed25519/index.ts, src/ts/sign/suites/ed25519.ts
|
Per FIPS 186-5 §7.6.4, Verification. The strict form [s]B = R + [k]A is enforced; the permissive cofactor-eight form [8s]G = [8](R + [k]A) is NOT implemented. ACVP's testPassed=false
sigVer records targeting strict-S and small-order rejection
exercise these checks.
- Public-key canonicality.
edPointDecompressrejects pk encodings with y >= p; the §5.1.3, Decoding, step 4 edge case (x = 0 with the sign bit set) is also rejected.ed25519Verifyreturns 0 on decompression failure. - R canonicality. The same decompression rules apply to
the R component of the signature. Non-canonical R encodings
return 0 from
ed25519Verifyanded25519VerifyPrehashed. - Strict S,
s < L.scalarIsCanonicalrejects scalars withs >= Lper FIPS 186-5 §7.6.4. The ACVP "modify s" sigVer records depend on this rejection. - Small-order pk rejection,
[8]A != identity. Three substrateedPointDoublecalls plus one equality check against(0:1:1:0)reject small-order pk values. A small-order A has order dividing 8, so[8]A = identity; the rejection inverts that test. - Signature equation.
[s]B = R + [k]Aevaluated with the strict (non-cofactor) form, usingedPointMulBasefor[s]B,edPointMulfor[k]A, andedPointAddfor theR + [k]Aaccumulation. - Verify gate ordering. Decompress pk, decompress R,
check
s < L, check[8]A != identity, then evaluate the equation. Every early return wipes the mutable region before exiting and returns 0.
Per RFC 8032 §5.1.6, signature generation, the per-signature
nonce r = SHA-512(prefix || M) is derived from sk-internal
state. A fault that biases prefix bytes can leak the long-term
scalar through ECC fault analysis. The library defends by
requiring the caller to ALSO know the encoded pk.
- Caller pk vs derived pk compare.
ed25519Signre-derivespk_check = compress([a]B)from the freshly clamped scalar and compares it against the caller-suppliedpkOffvia the shared constant-timectEqualhelper fromcte/shared.ts(inlined into curve25519.wasm at compile time). Mismatch wipes the mutable region and traps viaunreachable. - TypeScript rethrow. The wrapper's
rethrowTraphelper catchesWebAssembly.RuntimeErrorand rethrows asSigningError('sig-malformed-input', ...)so callers can branch on the failure. - Prehash path parity.
ed25519SignPrehashedruns the same pk re-derivation andctEqualcheck before computing r. Same wipe and trap discipline. - Suite-layer collapse documented.
Ed25519SuiteandEd25519PreHashSuiteroute through_signInternalPk/_signPrehashedInternalPk, which derive pk inside the same WASM call and skip the cross-check. At the suite call site the comparison would be between two outputs of the same potentially-faulted module on the same call, so the defence collapses to no defence. The skip saves one basepoint scalar mult per sign. See ed25519.md §Fault-Injection Defense and architecture.md §Threat model. - No side effects on caller buffers. sk, pk, M, digest, sig, and ctx buffers passed in are read but NEVER mutated by either the TS layer or the WASM.
The Ed25519 hash chain runs through src/asm/curve25519/sha512.ts,
a verbatim port of src/asm/sha2/sha512.ts. Only four
permitted deviations apply.
- Verbatim port from sha2. Source pin commit recorded in
the file header. Re-verify via
diff src/asm/sha2/sha512.ts src/asm/curve25519/sha512.ts, ignoring the buffer-offset import lines. - Permitted deviation 1: buffer-offset imports. The
offset imports point at
./buffers(curve25519 local memory layout). The offset constant NAMES are preserved so the algorithm code compiles unchanged. - Permitted deviation 2: variant strip. SHA-384, SHA-512/224, and SHA-512/256 are stripped. Ed25519 uses only SHA-512.
- Permitted deviation 3:
sha512UpdateByteshelper. A module-internal helper appended at the bottom of the file loopsmemory.copyandsha512Updatein 128-byte chunks for the Ed25519 hot path where input pieces live at arbitrary memory offsets. - Permitted deviation 4: header comment. The header comment carries the source-pin commit and the deviation list for future re-diffs. No other delta is permitted.
- ABI invisibility.
sha512Init,sha512Update,sha512Final, andsha512UpdateBytesare NOT re-exported fromindex.ts. The curve25519 ABI surfaces nosha512*function.
Per RFC 8032 §6, Security Considerations, and standard EdDSA implementation discipline.
- Scalar reduce64 binary division.
scalarReduce64runs a bit-by-bit binary division with a fixed 255-iteration loop. The mask-drivenctSubL33(conditional subtract of L extended to 33 bytes) andctLessThan32(constant-time 32-byte compare) helpers carry no branches on byte values. - L_LE byte-14 regression test.
test/unit/ed25519/scalar_reduce64.test.tsexercises a BigInt-oracle cross-check on randomized 64-byte inputs to catch L_LE transcription errors. An earlier draft ofscalar.tshadL_LE[14] = 0x4Dinstead of the spec value0xDE(RFC 8032 §5.1, Ed25519); the regression catches that and any future transcription drift. - Edwards point ops branch-free on secret data.
edPointDouble,edPointAdd,edPointSub,edPointMul, andedPointMulBaseuse only straight-line field arithmetic plusfeCondSwapfor the ladder branch.feCondSwapis mask-driven, no conditional jump on a secret bit. - Ladder loop count fixed.
edPointMulruns a 256-bit ladder with no early termination. The operation set per bit is independent of the scalar bit value. - Verify decompose aggregation.
edPointDecompressaggregates its success flag across the failure paths and returns it at the end of the function. The early branches that detect each spec-defined failure mode write into the same accumulator rather than returning early at the WASM-internal level. - Public-data branches documented. Branches on the loop
counter
iinlByte(i), on the dom2 F=1 byte and the |C| byte indom2Update, and on the SHA-512 round number in the message schedule index are all public values. No branch reads a secret bit.
Per RFC 8032 §5.1.7, signature verification, Ed25519ph wraps
both SHA-512 inputs (the r-hash and the k-hash) in
dom2(F=1, C). The library binds ctx through dom2 only; pure
Ed25519 omits dom2 entirely (RFC 8032 §5.1, Ed25519, "the empty
string for F=0 without context").
- dom2 prefix string.
loadDom2Prefix(dst)writes the 32-byte ASCII constant'SigEd25519 no Ed25519 collisions'byte-by-byte with per-byte spec-glyph comments. Thebuffers.tssource is auditable against the spec text. - F=1 phflag for Ed25519ph.
dom2UpdatewritesF = 1atSHA512_INPUT_OFFSET + 32and|C|atSHA512_INPUT_OFFSET + 33, then callssha512Update(34). The 34-byte header always fits in the 128-byte SHA-512 input staging slot. - Pure mode does NOT call dom2Update.
ed25519Signanded25519Verifybuild the SHA-512 inputs directly without the dom2 prefix. The prehash entries (ed25519SignPrehashed,ed25519VerifyPrehashed) calldom2Updatebefore theprefix || digestandR || pk || digestinputs. - Context length bound.
ed25519SignPrehashedaborts viaunreachableifctxLen > 255;ed25519VerifyPrehashedreturns 0 in the same case. The 255 ceiling matches the RFC 8032 §5.1, Ed25519, single-octet|C|encoding. - Effective ctx through the suite layer.
Ed25519PreHashSuitebuilds the effective ctx aslengthPrefix(suite.ctxDomain) || lengthPrefix(user_ctx)viabuildEffectiveCtxand passes the result toed25519SignPrehashedas the WASM ctx parameter. The suite ctxDomain is'ed25519-prehash-envelope-v3', 14 bytes UTF-8.
Per AGENTS.md "Wipe discipline". Every Ed25519 path zeroes secret-derived state on the way out.
- WASM wipe at end of every export.
ed25519Keygen,ed25519Sign,ed25519Verify,ed25519SignPrehashed, anded25519VerifyPrehashedeach end withwipeAll(), which is byte-equivalent towipeBuffersand zeroes the mutable region fromMUTABLE_STARTtoBUFFER_END. - Early-failure wipes. Each
unreachable/return 0path inside the high-level exports callswipeAll()first. Decompose failure, scalar non-canonical, ctxLen out of range, and the pk-fault check all clear before exiting. - TypeScript I/O staging wipe. The TS wrapper's
ioWipe(mx)helper zeroes the staging region aboveBUFFER_ENDto the end of linear memory. Every method'sfinallyrunsioWipe(mx)followed bymx.wipeBuffers(). -
dispose()idempotent.Ed25519.disposeruns both wipes inside atry / catch {}so multiple calls are safe even after the module instance has been torn down.
Per src/ts/sign/suites/ed25519.ts and the signaturesuite.md
Ed25519 suites section.
- Pure suite rejects user_ctx.
Ed25519Suite(format byte0x01) throwsSigningError('sig-ctx-unsupported', ...)on any non-empty user_ctx insignorverify. The error message routes callers toEd25519PreHashSuitefor context-bound signatures. The suite'sctxDomainis set to'ed25519-envelope-v3'forformatName/ display but is never bound into the signature. - Prehash suite binds effective ctx through dom2.
Ed25519PreHashSuite(format byte0x11) buildseffective_ctx = buildEffectiveCtx(ctxDomain, user_ctx)and passes it toEd25519.signPrehashed/Ed25519.verifyPrehashed, which forwards it as the WASM ctx parameter consumed bydom2Update. - Prehash algorithm pinned to sha-512.
prehashSize: 64andprehashAlgorithm: 'sha-512'are constants; the message-taking sign and verify paths route throughsha512OneShot(msg)fromsrc/ts/sign/hasher.ts, which drives the sha2 WASM module. The streaming path usesSHA512Streamfrom the sha2 module viacreateRunningHash('sha-512'). -
wasmModulesadvertised correctly.Ed25519Suitelists['curve25519'].Ed25519PreHashSuitelists['curve25519', 'sha2']because the TS-side SHA-512 used by the message-taking and streaming paths drives the sha2 module. The curve25519-embedded SHA-512 covers dom2 prefixing inside the WASM and is not exposed at the ABI; sha2 is purely a TS-layer dependency. - Per-call WASM lifecycle. Each suite method instantiates
a fresh
Ed25519inside atry / finally { dispose() }block. No suite-level long-lived instance is held. - Fault-injection defence inherited. The suite layer
calls
Ed25519.sign(sk, pk, M)withpkre-derived from sk viakeygenDerand; the WASM then re-derives pk a second time and aborts on mismatch. Two redundant derivations defend against fault injection between thekeygenDerandcall and thesigncall.
| Document | Description |
|---|---|
| ed25519 | Ed25519 public API reference |
| asm_curve25519 | curve25519 WASM module reference |
| x25519_audit | Companion X25519 audit on the same WASM module |
| signaturesuite |
Ed25519Suite / Ed25519PreHashSuite consts, envelope wire format |
| audits | Project audit index |
| architecture | Repository structure, build and CI, WASM modules, public API, test suite, and security posture |
- Sign Tools
-
SignatureSuite
- format-byte catalog, hybrid composite encodings, custom suite contract
- Serpent-256 TypeScript | WASM
-
Serpent,SerpentCtr,SerpentCbc,SerpentGenerator
-
- ChaCha20 TypeScript | WASM
-
ChaCha20,Poly1305,ChaCha20Poly1305,XChaCha20Poly1305,ChaCha20Generator
-
- AES TypeScript | WASM
-
AES,AESCbc,AESCtr,AESGCM,AESGCMSIV,AESGenerator
-
- ML-DSA TypeScript | WASM
- pure (FIPS 204):
MlDsa44,MlDsa65,MlDsa87 - pure-mode suites:
MlDsa44Suite,MlDsa65Suite,MlDsa87Suite - prehash suites:
MlDsa44PreHashSuite,MlDsa65PreHashSuite,MlDsa87PreHashSuite
- pure (FIPS 204):
- SLH-DSA TypeScript | WASM
- pure (FIPS 205):
SlhDsa128f,SlhDsa192f,SlhDsa256f - pure-mode suites:
SlhDsa128fSuite,SlhDsa192fSuite,SlhDsa256fSuite - prehash suites:
SlhDsa128fPreHashSuite,SlhDsa192fPreHashSuite,SlhDsa256fPreHashSuite
- pure (FIPS 205):
- Ed25519 TypeScript | WASM
-
Ed25519(pure + Ed25519ph),Ed25519Suite,Ed25519PreHashSuite
-
- ECDSA-P256 TypeScript | WASM
-
EcdsaP256(hedged + RFC 6979),EcdsaP256Suite - DER codec:
ecdsaSignatureToDer,ecdsaSignatureFromDer,encodeEcPrivateKey,decodeEcPrivateKey,pointDecompress
-
- Hybrid composites PQ-only | Classical+PQ
- PQ-only:
MlDsa44SlhDsa128fSuite,MlDsa65SlhDsa192fSuite,MlDsa87SlhDsa256fSuite - Classical+PQ:
MlDsa44Ed25519Suite,MlDsa65Ed25519Suite,MlDsa44EcdsaP256Suite,MlDsa65EcdsaP256Suite
- PQ-only:
- X25519 TypeScript | WASM
-
X25519,KeyAgreementError(RFC 7748)
-
- ML-KEM TypeScript | WASM
-
MlKem512,MlKem768,MlKem1024
-
-
Ratchet (SPQR)
-
KDFChain,ratchetInit,kemRatchetEncap,kemRatchetDecap,RatchetKeypair,SkippedKeyStore
-
- Hashing overview
- SHA-2 TypeScript | WASM
-
SHA256,SHA384,SHA512,SHA224,SHA512_224,SHA512_256 -
HMAC_SHA256,HMAC_SHA384,HMAC_SHA512,HKDF_SHA256,HKDF_SHA512
-
- SHA-3 TypeScript | WASM
-
SHA3_224,SHA3_256,SHA3_384,SHA3_512,SHAKE128,SHAKE256
-
- BLAKE3 TypeScript | WASM
-
BLAKE3,BLAKE3Stream,BLAKE3KeyedHash,BLAKE3KeyedHashStream -
BLAKE3DeriveKey,BLAKE3DeriveKeyStream,BLAKE3OutputReader,BLAKE3Hash
-
-
KMAC
-
CSHAKE128,CSHAKE256,KMAC128,KMAC256,KMACXOF128,KMACXOF256
-
-
Merkle
-
MerkleVerifier,MerkleLog -
SignedLog,Sha256Tree,Blake3Tree,MemoryStorage
-
-
Fortuna CSPRNG
-
Fortuna,SerpentGenerator,ChaCha20Generator,AESGenerator,SHA256Hash,SHA3_256Hash,BLAKE3Hash
-
- Utils TypeScript | WASM
-
constantTimeEqual,randomBytes,wipe, encoding helpers
-
-
TypeScript interfaces
-
Hash,KeyedHash,Blockcipher,Streamcipher,AEAD,Generator,HashFn
-