-
-
Notifications
You must be signed in to change notification settings - Fork 1
x25519_audit
Audit of the leviathan-crypto X25519 implementation
(AssemblyScript) against RFC 7748 (Elliptic Curves for Security)
§5, The X25519 and X448 Functions, and §6, Diffie-Hellman.
Verified against RFC 7748 §6.1, Curve25519 (Alice/Bob test
vectors), plus the iter=1 and iter=1000 chain records from RFC
7748 §5.
| Meta | Description |
|---|---|
| Target: |
leviathan-crypto WebAssembly implementation (AssemblyScript) |
| Spec: | RFC 7748 §5, The X25519 and X448 Functions, plus §6, Diffie-Hellman, and §6.1, Curve25519, security considerations |
| Parameter sets: | X25519 only (single-variant) |
| Test vectors: | RFC 7748 §6.1, Curve25519, Alice/Bob plus RFC 7748 §5 iter=1 and iter=1000 chain records |
| Source files: |
src/asm/curve25519/x25519.ts, src/asm/curve25519/montgomery.ts, src/asm/curve25519/scalar.ts, src/ts/x25519/index.ts
|
Per RFC 7748 §5, The X25519 and X448 Functions: "decodeScalar25519 clears the lower three bits, clears the highest bit, and sets the second highest bit." The library clamps the scalar internally on every call.
-
scalarClampcorrectness.scalarClamp(out, src)writesout[0] = src[0] & 0xF8,out[31] = (src[31] & 0x7F) | 0x40, and copies bytes 1..30 verbatim. Output buffer is distinct from input; the caller's sk bytes are preserved. - Idempotent. Running
scalarClampon an already-clamped scalar produces the same bytes. The bit-pattern check is asserted intest/unit/x25519/scalar_clamp.test.ts. - sk semantics opaque random bytes. The X25519 surface
treats
skas "any 32 random bytes" per RFC 7748 §5; the WASM does not surface a "clamped sk" type.X25519KeyPairstores the unclamped form so round-tripping a key through external storage preserves byte-equality. - Per-call clamping in
x25519Keygenandx25519DH. Both WASM exports runscalarClamp(X25519_SCALAR_CLAMP, skOff)as their first operation. The substrate ladder always sees the clamped form regardless of the caller's sk bytes.
Per RFC 7748 §5, The X25519 and X448 Functions. The ladder must run a fixed number of iterations and must not branch on secret scalar bits.
- Fixed loop count.
x25519Ladderinmontgomery.tsruns exactly 255 iterations regardless of scalar value. The high bit of the scalar (bit 254 after clamping) is always 1, so the loop processes bits 254 down to 0 with no data-dependent termination. -
feCondSwapmask-driven. The conditional swap of(x2:z2)and(x3:z3)based on the current scalar bit usesfeCondSwap(a, b, mask), which XOR-swaps under a mask. No conditional jump reads the scalar bit. - No branches on secret bits. The ladder step
a, aa, b, bb, e, c, d, da, cbarithmetic uses straight-line field operations (feAdd,feSub,feMul,feSqr,feMul121666). No comparison against a secret bit. - Final conditional swap. After the loop, one more
feCondSwapdriven by the bit-254 mask restores the canonical position. The output is(x2:z2); the wrapper computesx2 / z2 = x2 * feInv(z2)andfeToBytesthe result.
Per RFC 7748 §5, The X25519 and X448 Functions: "implementations MUST mask the most significant bit in the final byte" of the input u-coordinate before decoding to a field element. The library applies this internally rather than at the API boundary.
-
feFromBytesmasks bit 255.montgomery.ts's call tofeFromBytesreads the encoded u-coord and clears bit 255 of byte 31 before constructing the radix-2^51 limbs. Asserted intest/unit/x25519/fe_from_bytes_mask.test.ts. - Callers do NOT pre-mask.
X25519.dhpassespeerPkbyte-for-byte to the WASM. The TS-layervalidatePublicKeyperforms only a length check (32 bytes); no value-level check runs. - Non-canonical encodings pass through. A peer pk with
u >= pafter the bit-255 mask is reduced mod p insidefeFromBytes. RFC 7748 §5 accepts this posture, and Curve25519's twist security covers the non-curve case.
Per RFC 7748 §6.1, Curve25519, contributory-behaviour discussion.
A small-order peer pk produces an all-zero shared u-coordinate
that carries no contributory entropy from the local secret.
The TypeScript layer rejects with KeyAgreementError; the WASM
does NOT filter at the substrate level.
- Constant-time accumulator scan.
X25519.dhruns a 32-iteration OR-accumulate loop across the shared-secret bytes with no early exit on the first non-zero byte. The conditional that throws fires only after the full accumulator is computed. -
KeyAgreementErrorthrown on all-zero. When the accumulator is zero, the wrapper wipes the shared buffer and throwsKeyAgreementErrorwith the message'leviathan-crypto: X25519 shared secret is all-zero (peer public key is a small-order point)'. - WASM is filter-free.
x25519DHinsrc/asm/curve25519/x25519.tswrites the shared u-coordinate to the caller's slot regardless of whether it is all-zero. This matches the x25519-dalek posture and preserves WASM-vs-oracle byte agreement on any test record that exercises a small-order peer pk. - No fault-injection cross-check. Unlike
ed25519Sign,x25519Keygenhas no caller-supplied pk to cross-check andx25519DH's peerPk is genuinely external (the other party's actual choice). The all-zero rejection is the only protocol-level filter at the X25519 boundary. - Vector compatibility. Any future test record that
exercises a small-order peer pk verifies byte-for-byte at
the WASM boundary; the TS-layer rejection is the public
API contract.
test/unit/x25519/all_zero_reject.test.tsuses the eight canonical small-order u-coordinates from RFC 7748 §6.1, Curve25519, and confirms each produces an all-zero shared secret at the WASM level and aKeyAgreementErrorat the TS level.
Per RFC 7748 §5, The X25519 and X448 Functions. The chain-iteration vectors exercise the same scalar arithmetic and ladder loop under repeated composition.
- Alice/Bob vectors, RFC 7748 §6.1. The §6.1,
Curve25519, end-to-end DH vectors run in
test/unit/x25519/rfc7748_§6_1.test.ts. Both Alice and Bob compute the same shared u-coordinate byte-for-byte. - iter=1 record, RFC 7748 §5. The first iteration of
the §5 chain (
k = u = 09 00 00 ... 00, expected0x422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079) runs intest/unit/x25519/iterated.test.ts. - iter=1000 record, RFC 7748 §5. The 1000-iteration
chain (
k,uupdated per iteration, expected0x684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51) runs intest/unit/x25519/iterated.test.ts. The runtime is comfortably under the unit-group timeout. - iter=1000000 deferred. The 1,000,000-iteration record
from RFC 7748 §5 is out of scope for the CI suite per
vector_audit.md. The 1000-iteration record exercises the same code paths; the 1M record provides additional defence against rare aliasing bugs and is left to manual verification on demand.
Per AGENTS.md "Wipe discipline". X25519 owns less secret state than Ed25519 (only the clamped scalar and the shared secret), so the wipe path is correspondingly simpler.
- Per-call WASM wipe.
x25519Keygenandx25519DHend withwipeX25519(), which zeroesX25519_SCALAR_CLAMP(the only secret intermediate the substrate owns). Caller-provided buffers are not touched. - Module-level wipe covers the same slot. The TS
wrapper's
dispose()callswipeBuffers(), which zeroes the full mutable region fromMUTABLE_STARTtoBUFFER_END.X25519_SCALAR_CLAMPsits inside that range and gets cleared by the sweep. - TypeScript I/O staging wipe. The TS wrapper's
ioWipe(mx)helper zeroes the staging region aboveBUFFER_END(sk slot, pk slot, peer-pk slot, shared-secret slot). Every method'sfinallyrunsioWipe(mx)followed bymx.wipeBuffers(). - Wipe on all-zero rejection.
X25519.dhwipes the shared buffer before throwingKeyAgreementError. The all-zero buffer that triggered rejection does not linger on the JavaScript heap. -
dispose()idempotent.X25519.disposeruns both wipes inside atry / catch {}so multiple calls are safe even after the module instance has been torn down.
| Document | Description |
|---|---|
| x25519 | X25519 public API reference |
| asm_curve25519 | curve25519 WASM module reference |
| ed25519_audit | Companion Ed25519 audit on the same WASM module |
| 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
-