leviathan-crypto 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +86 -443
- package/README.md +198 -65
- package/dist/aes/aes-cbc.d.ts +40 -0
- package/dist/aes/aes-cbc.js +158 -0
- package/dist/aes/aes-ctr.d.ts +50 -0
- package/dist/aes/aes-ctr.js +141 -0
- package/dist/aes/aes-gcm-siv.d.ts +67 -0
- package/dist/aes/aes-gcm-siv.js +217 -0
- package/dist/aes/aes-gcm.d.ts +61 -0
- package/dist/aes/aes-gcm.js +226 -0
- package/dist/aes/cipher-suite.d.ts +21 -0
- package/dist/aes/cipher-suite.js +179 -0
- package/dist/aes/embedded.d.ts +1 -0
- package/dist/aes/embedded.js +26 -0
- package/dist/aes/generator.d.ts +14 -0
- package/dist/aes/generator.js +103 -0
- package/dist/aes/index.d.ts +58 -0
- package/dist/aes/index.js +125 -0
- package/dist/aes/ops.d.ts +60 -0
- package/dist/aes/ops.js +164 -0
- package/dist/aes/pool-worker.d.ts +1 -0
- package/dist/aes/pool-worker.js +92 -0
- package/dist/aes/types.d.ts +1 -0
- package/dist/aes/types.js +23 -0
- package/dist/aes.wasm +0 -0
- package/dist/blake3/embedded.d.ts +1 -0
- package/dist/blake3/embedded.js +26 -0
- package/dist/blake3/index.d.ts +143 -0
- package/dist/blake3/index.js +620 -0
- package/dist/blake3/types.d.ts +102 -0
- package/dist/blake3/types.js +31 -0
- package/dist/blake3/validate.d.ts +29 -0
- package/dist/blake3/validate.js +80 -0
- package/dist/blake3.wasm +0 -0
- package/dist/chacha20/cipher-suite.js +47 -25
- package/dist/chacha20/generator.d.ts +2 -2
- package/dist/chacha20/generator.js +4 -4
- package/dist/chacha20/index.d.ts +16 -15
- package/dist/chacha20/index.js +52 -46
- package/dist/chacha20/ops.d.ts +7 -7
- package/dist/chacha20/ops.js +34 -34
- package/dist/chacha20/pool-worker.js +5 -3
- package/dist/cte-wasm.d.ts +1 -0
- package/dist/cte-wasm.js +3 -0
- package/dist/curve25519.wasm +0 -0
- package/dist/ecdsa/der.d.ts +23 -0
- package/dist/ecdsa/der.js +192 -0
- package/dist/ecdsa/ecprivatekey-der.d.ts +32 -0
- package/dist/ecdsa/ecprivatekey-der.js +230 -0
- package/dist/ecdsa/embedded.d.ts +1 -0
- package/dist/ecdsa/embedded.js +25 -0
- package/dist/ecdsa/index.d.ts +124 -0
- package/dist/ecdsa/index.js +366 -0
- package/dist/ecdsa/types.d.ts +31 -0
- package/dist/ecdsa/types.js +28 -0
- package/dist/ecdsa/validate.d.ts +18 -0
- package/dist/ecdsa/validate.js +92 -0
- package/dist/ed25519/embedded.d.ts +1 -0
- package/dist/ed25519/embedded.js +31 -0
- package/dist/ed25519/index.d.ts +70 -0
- package/dist/ed25519/index.js +308 -0
- package/dist/ed25519/types.d.ts +27 -0
- package/dist/ed25519/types.js +27 -0
- package/dist/ed25519/validate.d.ts +7 -0
- package/dist/ed25519/validate.js +77 -0
- package/dist/embedded/aes-pool-worker.d.ts +1 -0
- package/dist/embedded/aes-pool-worker.js +5 -0
- package/dist/embedded/aes.d.ts +1 -0
- package/dist/embedded/aes.js +3 -0
- package/dist/embedded/blake3.d.ts +1 -0
- package/dist/embedded/blake3.js +3 -0
- package/dist/embedded/chacha20-pool-worker.d.ts +1 -1
- package/dist/embedded/chacha20-pool-worker.js +2 -2
- package/dist/embedded/chacha20.d.ts +1 -1
- package/dist/embedded/chacha20.js +2 -2
- package/dist/embedded/curve25519.d.ts +1 -0
- package/dist/embedded/curve25519.js +3 -0
- package/dist/embedded/mldsa.d.ts +1 -0
- package/dist/embedded/mldsa.js +3 -0
- package/dist/embedded/mlkem.d.ts +1 -0
- package/dist/embedded/mlkem.js +3 -0
- package/dist/embedded/p256.d.ts +1 -0
- package/dist/embedded/p256.js +3 -0
- package/dist/embedded/serpent-pool-worker.d.ts +1 -1
- package/dist/embedded/serpent-pool-worker.js +2 -2
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +2 -2
- package/dist/embedded/sha2.d.ts +1 -1
- package/dist/embedded/sha2.js +2 -2
- package/dist/embedded/sha3.d.ts +1 -1
- package/dist/embedded/sha3.js +2 -2
- package/dist/embedded/slhdsa.d.ts +1 -0
- package/dist/embedded/slhdsa.js +3 -0
- package/dist/errors.d.ts +92 -1
- package/dist/errors.js +111 -1
- package/dist/fortuna.d.ts +5 -5
- package/dist/fortuna.js +37 -64
- package/dist/index.d.ts +38 -9
- package/dist/index.js +63 -19
- package/dist/init.d.ts +1 -1
- package/dist/init.js +11 -25
- package/dist/keccak/embedded.js +1 -1
- package/dist/keccak/index.d.ts +2 -0
- package/dist/keccak/index.js +4 -2
- package/dist/loader.d.ts +1 -24
- package/dist/loader.js +13 -16
- package/dist/merkle/blake3-tree.d.ts +35 -0
- package/dist/merkle/blake3-tree.js +187 -0
- package/dist/merkle/checkpoint.d.ts +58 -0
- package/dist/merkle/checkpoint.js +217 -0
- package/dist/merkle/index.d.ts +19 -0
- package/dist/merkle/index.js +37 -0
- package/dist/merkle/merkle-log.d.ts +130 -0
- package/dist/merkle/merkle-log.js +207 -0
- package/dist/merkle/merkle-verifier.d.ts +126 -0
- package/dist/merkle/merkle-verifier.js +296 -0
- package/dist/merkle/proof.d.ts +70 -0
- package/dist/merkle/proof.js +300 -0
- package/dist/merkle/sha256-tree.d.ts +33 -0
- package/dist/merkle/sha256-tree.js +145 -0
- package/dist/merkle/signed-log.d.ts +156 -0
- package/dist/merkle/signed-log.js +356 -0
- package/dist/merkle/signed-note.d.ts +309 -0
- package/dist/merkle/signed-note.js +648 -0
- package/dist/merkle/sth.d.ts +31 -0
- package/dist/merkle/sth.js +31 -0
- package/dist/merkle/storage.d.ts +40 -0
- package/dist/merkle/storage.js +71 -0
- package/dist/merkle/tree.d.ts +68 -0
- package/dist/merkle/tree.js +94 -0
- package/dist/mldsa/embedded.d.ts +1 -0
- package/dist/{kyber → mldsa}/embedded.js +5 -5
- package/dist/mldsa/expand.d.ts +53 -0
- package/dist/mldsa/expand.js +188 -0
- package/dist/mldsa/format.d.ts +16 -0
- package/dist/mldsa/format.js +68 -0
- package/dist/mldsa/hashvariant.d.ts +32 -0
- package/dist/mldsa/hashvariant.js +248 -0
- package/dist/mldsa/index.d.ts +142 -0
- package/dist/mldsa/index.js +463 -0
- package/dist/mldsa/keygen.d.ts +16 -0
- package/dist/mldsa/keygen.js +232 -0
- package/dist/mldsa/params.d.ts +21 -0
- package/dist/mldsa/params.js +55 -0
- package/dist/mldsa/sha3-helpers.d.ts +30 -0
- package/dist/mldsa/sha3-helpers.js +124 -0
- package/dist/mldsa/sign.d.ts +36 -0
- package/dist/mldsa/sign.js +380 -0
- package/dist/mldsa/types.d.ts +91 -0
- package/dist/mldsa/types.js +25 -0
- package/dist/mldsa/validate.d.ts +55 -0
- package/dist/mldsa/validate.js +125 -0
- package/dist/mldsa/verify.d.ts +29 -0
- package/dist/mldsa/verify.js +269 -0
- package/dist/mldsa.wasm +0 -0
- package/dist/mlkem/embedded.d.ts +1 -0
- package/dist/mlkem/embedded.js +27 -0
- package/dist/mlkem/indcpa.d.ts +49 -0
- package/dist/{kyber → mlkem}/indcpa.js +44 -44
- package/dist/mlkem/index.d.ts +37 -0
- package/dist/{kyber → mlkem}/index.js +24 -34
- package/dist/mlkem/kem.d.ts +21 -0
- package/dist/{kyber → mlkem}/kem.js +44 -64
- package/dist/{kyber → mlkem}/params.d.ts +4 -4
- package/dist/{kyber → mlkem}/params.js +2 -2
- package/dist/mlkem/suite.d.ts +12 -0
- package/dist/{kyber → mlkem}/suite.js +17 -12
- package/dist/{kyber → mlkem}/types.d.ts +3 -3
- package/dist/{kyber → mlkem}/types.js +1 -1
- package/dist/{kyber → mlkem}/validate.d.ts +7 -7
- package/dist/{kyber → mlkem}/validate.js +7 -7
- package/dist/{kyber.wasm → mlkem.wasm} +0 -0
- package/dist/p256.wasm +0 -0
- package/dist/ratchet/index.d.ts +2 -0
- package/dist/ratchet/index.js +1 -0
- package/dist/ratchet/kdf-chain.js +3 -3
- package/dist/ratchet/ratchet-keypair.js +2 -2
- package/dist/ratchet/root-kdf.js +7 -7
- package/dist/ratchet/skipped-key-store.js +4 -4
- package/dist/ratchet/types.d.ts +1 -1
- package/dist/serpent/cipher-suite.js +20 -17
- package/dist/serpent/generator.d.ts +1 -1
- package/dist/serpent/generator.js +2 -2
- package/dist/serpent/index.d.ts +8 -7
- package/dist/serpent/index.js +18 -27
- package/dist/serpent/pool-worker.js +7 -5
- package/dist/serpent/serpent-cbc.d.ts +4 -4
- package/dist/serpent/serpent-cbc.js +11 -8
- package/dist/serpent/shared-ops.d.ts +3 -23
- package/dist/serpent/shared-ops.js +50 -85
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.js +5 -5
- package/dist/sha2/index.d.ts +21 -1
- package/dist/sha2/index.js +65 -10
- package/dist/sha2/types.d.ts +41 -2
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +72 -3
- package/dist/sha3/index.js +240 -14
- package/dist/sha3/kmac.d.ts +121 -0
- package/dist/sha3/kmac.js +800 -0
- package/dist/sha3.wasm +0 -0
- package/dist/shared/pkcs7.d.ts +22 -0
- package/dist/shared/pkcs7.js +84 -0
- package/dist/sign/ctx.d.ts +41 -0
- package/dist/sign/ctx.js +102 -0
- package/dist/sign/envelope.d.ts +45 -0
- package/dist/sign/envelope.js +152 -0
- package/dist/sign/hasher.d.ts +9 -0
- package/dist/sign/hasher.js +132 -0
- package/dist/sign/index.d.ts +11 -0
- package/dist/sign/index.js +34 -0
- package/dist/sign/sign-stream.d.ts +25 -0
- package/dist/sign/sign-stream.js +112 -0
- package/dist/sign/suites/ecdsa-p256.d.ts +2 -0
- package/dist/sign/suites/ecdsa-p256.js +120 -0
- package/dist/sign/suites/ed25519.d.ts +3 -0
- package/dist/sign/suites/ed25519.js +165 -0
- package/dist/sign/suites/hybrid-classical.d.ts +23 -0
- package/dist/sign/suites/hybrid-classical.js +526 -0
- package/dist/sign/suites/hybrid-pq.d.ts +4 -0
- package/dist/sign/suites/hybrid-pq.js +234 -0
- package/dist/sign/suites/mldsa.d.ts +7 -0
- package/dist/sign/suites/mldsa.js +161 -0
- package/dist/sign/suites/slhdsa.d.ts +7 -0
- package/dist/sign/suites/slhdsa.js +176 -0
- package/dist/sign/types.d.ts +106 -0
- package/dist/sign/types.js +28 -0
- package/dist/sign/verify-stream.d.ts +30 -0
- package/dist/sign/verify-stream.js +227 -0
- package/dist/slhdsa/embedded.d.ts +1 -0
- package/dist/slhdsa/embedded.js +26 -0
- package/dist/slhdsa/index.d.ts +149 -0
- package/dist/slhdsa/index.js +493 -0
- package/dist/slhdsa/params.d.ts +26 -0
- package/dist/slhdsa/params.js +70 -0
- package/dist/slhdsa/prehash.d.ts +68 -0
- package/dist/slhdsa/prehash.js +307 -0
- package/dist/slhdsa/sign.d.ts +39 -0
- package/dist/slhdsa/sign.js +116 -0
- package/dist/slhdsa/types.d.ts +129 -0
- package/dist/slhdsa/types.js +27 -0
- package/dist/slhdsa/validate.d.ts +60 -0
- package/dist/slhdsa/validate.js +127 -0
- package/dist/slhdsa/verify.d.ts +32 -0
- package/dist/slhdsa/verify.js +107 -0
- package/dist/slhdsa.wasm +0 -0
- package/dist/stream/header.js +3 -3
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/open-stream.js +31 -10
- package/dist/stream/seal-stream-pool.d.ts +1 -0
- package/dist/stream/seal-stream-pool.js +63 -26
- package/dist/stream/seal-stream.d.ts +1 -1
- package/dist/stream/seal-stream.js +20 -9
- package/dist/stream/seal.js +6 -6
- package/dist/stream/types.d.ts +3 -1
- package/dist/stream/types.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +46 -54
- package/dist/wasm-source.d.ts +7 -7
- package/dist/wasm-source.js +1 -1
- package/dist/x25519/embedded.d.ts +1 -0
- package/dist/x25519/embedded.js +31 -0
- package/dist/x25519/index.d.ts +43 -0
- package/dist/x25519/index.js +159 -0
- package/dist/x25519/types.d.ts +25 -0
- package/dist/x25519/types.js +27 -0
- package/dist/x25519/validate.d.ts +2 -0
- package/dist/x25519/validate.js +39 -0
- package/package.json +70 -26
- package/SECURITY.md +0 -163
- package/dist/ct-wasm.d.ts +0 -1
- package/dist/ct-wasm.js +0 -3
- package/dist/docs/aead.md +0 -363
- package/dist/docs/architecture.md +0 -1011
- package/dist/docs/argon2id.md +0 -305
- package/dist/docs/chacha20.md +0 -781
- package/dist/docs/exports.md +0 -277
- package/dist/docs/fortuna.md +0 -530
- package/dist/docs/init.md +0 -301
- package/dist/docs/loader.md +0 -256
- package/dist/docs/serpent.md +0 -617
- package/dist/docs/sha2.md +0 -671
- package/dist/docs/sha3.md +0 -612
- package/dist/docs/types.md +0 -416
- package/dist/docs/utils.md +0 -457
- package/dist/embedded/kyber.d.ts +0 -1
- package/dist/embedded/kyber.js +0 -3
- package/dist/kyber/embedded.d.ts +0 -1
- package/dist/kyber/indcpa.d.ts +0 -49
- package/dist/kyber/index.d.ts +0 -38
- package/dist/kyber/kem.d.ts +0 -21
- package/dist/kyber/suite.d.ts +0 -12
- /package/dist/{ct.wasm → cte.wasm} +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ecdsa/index.ts
|
|
23
|
+
//
|
|
24
|
+
// ECDSA-P256 public API. FIPS 186-5 §6 over the NIST P-256 curve
|
|
25
|
+
// (SP 800-186 §3.2.1.3). Hedged-or-deterministic K per RFC 6979 §3.2
|
|
26
|
+
// and draft-irtf-cfrg-det-sigs-with-noise-05. Strict-S verify (low-S
|
|
27
|
+
// enforced).
|
|
28
|
+
//
|
|
29
|
+
// Uncompressed pk per SEC 1 §2.3.4 is available via
|
|
30
|
+
// `pointDecompress` and `EcdsaP256.keygenUncompressed`. The composite
|
|
31
|
+
// ML-DSA + ECDSA suites need the 65-byte `0x04 || X || Y` form.
|
|
32
|
+
import { getInstance, initModule, isInitialized, _assertNotOwned } from '../init.js';
|
|
33
|
+
import { randomBytes, wipe } from '../utils.js';
|
|
34
|
+
import { SigningError } from '../errors.js';
|
|
35
|
+
import { validateSeed, validateSecretKey, validatePublicKey, validateMessageHash, validateSignature, validateEntropy, } from './validate.js';
|
|
36
|
+
/**
|
|
37
|
+
* Initialise the p256 WASM module. Loads the underlying binary
|
|
38
|
+
* (scalar, no SIMD) into the `p256` slot.
|
|
39
|
+
*/
|
|
40
|
+
export async function ecdsaP256Init(source) {
|
|
41
|
+
return initModule('p256', source);
|
|
42
|
+
}
|
|
43
|
+
export { isInitialized };
|
|
44
|
+
export { encodeEcPrivateKey, decodeEcPrivateKey } from './ecprivatekey-der.js';
|
|
45
|
+
// ── I/O staging layout ─────────────────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// The p256 module's mutable buffer region ends at BUFFER_END = 7054
|
|
48
|
+
// (see src/asm/p256/buffers.ts). The TS layer stages caller-supplied
|
|
49
|
+
// inputs and reads outputs from a fixed region above that. The module
|
|
50
|
+
// is allocated 3 pages (196608 bytes); the I/O region stretches from
|
|
51
|
+
// 8192 to the end of linear memory.
|
|
52
|
+
//
|
|
53
|
+
// Each slot is sized to its maximum useful content and rounded to a
|
|
54
|
+
// 32-byte boundary for layout clarity. The WASM exports read every
|
|
55
|
+
// input once at the start of execution, so co-locating per-call
|
|
56
|
+
// inputs in this region is safe.
|
|
57
|
+
const IO_BASE = 8192;
|
|
58
|
+
const SEED_STAGE = IO_BASE; // 32 bytes (seed for keygen, sk for sign)
|
|
59
|
+
const PK_STAGE = IO_BASE + 64; // 33 bytes (compressed pk, SEC 1 §2.3.3)
|
|
60
|
+
const SIG_STAGE = IO_BASE + 128; // 64 bytes (raw r||s)
|
|
61
|
+
const MSG_HASH_STAGE = IO_BASE + 192; // 32 bytes (SHA-256 digest)
|
|
62
|
+
const RND_STAGE = IO_BASE + 224; // 32 bytes (per-call entropy Z)
|
|
63
|
+
const POINT_STAGE = IO_BASE + 256; // 96 bytes (projective X:Y:Z, decompression output)
|
|
64
|
+
const PK_XY_STAGE = IO_BASE + 352; // 64 bytes (BE X || Y, staging for SEC 1 §2.3.4 emit)
|
|
65
|
+
function ioWipe(mx) {
|
|
66
|
+
// Zero the entire TS-managed staging region. wipeBuffers covers
|
|
67
|
+
// MUTABLE_START..BUFFER_END only; the I/O region above lives outside
|
|
68
|
+
// that range and must be scrubbed by the wrapper.
|
|
69
|
+
new Uint8Array(mx.memory.buffer).fill(0, IO_BASE, mx.memory.buffer.byteLength);
|
|
70
|
+
}
|
|
71
|
+
function rethrowTrap(err, discriminator, message) {
|
|
72
|
+
// The WASM module's fault-injection check (sign's pk-mismatch path)
|
|
73
|
+
// terminates with `unreachable`, which surfaces as
|
|
74
|
+
// WebAssembly.RuntimeError. Re-throw any such trap as a typed
|
|
75
|
+
// SigningError so callers can branch on it.
|
|
76
|
+
if (err instanceof WebAssembly.RuntimeError)
|
|
77
|
+
throw new SigningError(discriminator, message);
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Normalise a caller-supplied public key to the 33-byte compressed
|
|
82
|
+
* SEC 1 §2.3.3 form required by the WASM ABI. 33-byte inputs are
|
|
83
|
+
* returned as-is; 65-byte inputs (uncompressed, SEC 1 §2.3.4,
|
|
84
|
+
* 0x04 || x || y) are converted by dropping y and setting the prefix
|
|
85
|
+
* to 0x02 or 0x03 based on the parity of the y coordinate (LSB of
|
|
86
|
+
* y[31] in big-endian). Constant-time is not required: pk is public.
|
|
87
|
+
*
|
|
88
|
+
* Validation of the prefix byte happens at the WASM layer
|
|
89
|
+
* (`pointDecompress` rejects anything that is not 0x02 / 0x03 with an
|
|
90
|
+
* on-curve x), so this helper does not branch on whether the input
|
|
91
|
+
* was a strict uncompressed (0x04) or hybrid (0x06 / 0x07, SEC 1
|
|
92
|
+
* §2.3.5) encoding.
|
|
93
|
+
*/
|
|
94
|
+
function normalizePublicKey(pk) {
|
|
95
|
+
if (pk.length === 33)
|
|
96
|
+
return pk;
|
|
97
|
+
// pk has already passed validatePublicKey, so length is 33 or 65.
|
|
98
|
+
const out = new Uint8Array(33);
|
|
99
|
+
out[0] = 0x02 | (pk[64] & 0x01);
|
|
100
|
+
out.set(pk.subarray(1, 33), 1);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Decompress a 33-byte SEC 1 §2.3.3 compressed P-256 public key to the
|
|
105
|
+
* 65-byte SEC 1 §2.3.4 uncompressed encoding `0x04 || X || Y`.
|
|
106
|
+
*
|
|
107
|
+
* The compressed form encodes only the affine x coordinate plus a
|
|
108
|
+
* single parity bit (in the prefix byte: 0x02 even-y, 0x03 odd-y).
|
|
109
|
+
* Recovery of y solves the curve equation
|
|
110
|
+
* `y² = x³ - 3x + b mod p` (SP 800-186 §3.2.1.3, P-256 has a = -3)
|
|
111
|
+
* and selects the y root whose parity matches the prefix. The
|
|
112
|
+
* substrate runs the modular square root inside the p256 WASM
|
|
113
|
+
* (`feSqrt` via the p ≡ 3 (mod 4) shortcut, x^((p+1)/4)); rejecting
|
|
114
|
+
* invalid inputs that have no square root or whose recovered (x, y)
|
|
115
|
+
* lies off-curve.
|
|
116
|
+
*
|
|
117
|
+
* Rejection cases (all throw `SigningError('sig-malformed-input')`):
|
|
118
|
+
* - prefix byte not in {0x02, 0x03}
|
|
119
|
+
* - x coordinate is not the x of any on-curve point (no quadratic
|
|
120
|
+
* residue exists for `x³ - 3x + b mod p`)
|
|
121
|
+
*
|
|
122
|
+
* Length / shape rejections throw `TypeError` / `RangeError` per the
|
|
123
|
+
* usual leviathan-crypto contract-violation posture.
|
|
124
|
+
*
|
|
125
|
+
* Requires `init({ p256: ... })`. Uses the same p256 module singleton
|
|
126
|
+
* as `EcdsaP256`; concurrency-safe alongside non-stateful uses (the
|
|
127
|
+
* `_assertNotOwned` check fires if a stateful instance is holding
|
|
128
|
+
* the module).
|
|
129
|
+
*
|
|
130
|
+
* @param pk33 33-byte compressed pk per SEC 1 §2.3.3
|
|
131
|
+
* @returns 65-byte uncompressed pk per SEC 1 §2.3.4 (0x04 || X || Y)
|
|
132
|
+
*/
|
|
133
|
+
export function pointDecompress(pk33) {
|
|
134
|
+
if (!isInitialized('p256'))
|
|
135
|
+
throw new Error('leviathan-crypto: call init({ p256: ... }) before using pointDecompress');
|
|
136
|
+
if (!(pk33 instanceof Uint8Array))
|
|
137
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 compressed public key must be a Uint8Array');
|
|
138
|
+
if (pk33.length !== 33)
|
|
139
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 compressed public key must be 33 bytes (got ${pk33.length})`);
|
|
140
|
+
if (pk33[0] !== 0x02 && pk33[0] !== 0x03)
|
|
141
|
+
throw new SigningError('sig-malformed-input', 'leviathan-crypto: ecdsa-p256 compressed public key prefix must be 0x02 or 0x03 per SEC 1 §2.3.3 '
|
|
142
|
+
+ `(got 0x${pk33[0].toString(16).padStart(2, '0')})`);
|
|
143
|
+
_assertNotOwned('p256');
|
|
144
|
+
const mx = getInstance('p256').exports;
|
|
145
|
+
const mem = new Uint8Array(mx.memory.buffer);
|
|
146
|
+
mem.set(pk33, PK_STAGE);
|
|
147
|
+
try {
|
|
148
|
+
const ok = mx.pointDecompress(POINT_STAGE, PK_STAGE);
|
|
149
|
+
if (ok !== 1)
|
|
150
|
+
throw new SigningError('sig-malformed-input', 'leviathan-crypto: ecdsa-p256 compressed public key x coordinate has no on-curve y '
|
|
151
|
+
+ '(point decompression failed per SEC 1 §2.3.4)');
|
|
152
|
+
// pointDecompress writes (X : Y : Z = 1) in the FE limb form
|
|
153
|
+
// at POINT_STAGE..+96. feToBytes converts each FE to 32-byte BE
|
|
154
|
+
// per SP 800-186 §3.2.1.3 coordinate encoding.
|
|
155
|
+
mx.feToBytes(PK_XY_STAGE, POINT_STAGE); // X (32 BE)
|
|
156
|
+
mx.feToBytes(PK_XY_STAGE + 32, POINT_STAGE + 32); // Y (32 BE)
|
|
157
|
+
const out = new Uint8Array(65);
|
|
158
|
+
out[0] = 0x04;
|
|
159
|
+
out.set(mem.subarray(PK_XY_STAGE, PK_XY_STAGE + 64), 1);
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
ioWipe(mx);
|
|
164
|
+
mx.wipeBuffers();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export class EcdsaP256 {
|
|
168
|
+
constructor() {
|
|
169
|
+
if (!isInitialized('p256'))
|
|
170
|
+
throw new Error('leviathan-crypto: call init({ p256: ... }) before using EcdsaP256');
|
|
171
|
+
}
|
|
172
|
+
get mx() {
|
|
173
|
+
return getInstance('p256').exports;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Deterministic ECDSA-P256 key generation from a 32-byte seed.
|
|
177
|
+
* d = seed mod n per FIPS 186-5 §A.4.2 (testing-candidates style,
|
|
178
|
+
* single candidate). pk = [d]G compressed to 33 bytes per SEC 1
|
|
179
|
+
* §2.3.3. The vanishingly rare seed mod n == 0 case traps in the
|
|
180
|
+
* WASM and surfaces as a SigningError here.
|
|
181
|
+
*
|
|
182
|
+
* @param seed 32-byte BE input
|
|
183
|
+
* @returns 33-byte compressed pk and a fresh 32-byte copy of the
|
|
184
|
+
* secret scalar d (sk === seed for this derivation, the
|
|
185
|
+
* caller may use either as the private value).
|
|
186
|
+
*/
|
|
187
|
+
keygenDerand(seed) {
|
|
188
|
+
_assertNotOwned('p256');
|
|
189
|
+
validateSeed(seed);
|
|
190
|
+
const mx = this.mx;
|
|
191
|
+
const mem = new Uint8Array(mx.memory.buffer);
|
|
192
|
+
mem.set(seed, SEED_STAGE);
|
|
193
|
+
try {
|
|
194
|
+
try {
|
|
195
|
+
mx.ecdsaKeygen(SEED_STAGE, PK_STAGE);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
rethrowTrap(err, 'sig-malformed-input', 'leviathan-crypto: ecdsa-p256 keygen aborted, seed mod n is zero '
|
|
199
|
+
+ '(2^-256 probability event; supply a different seed)');
|
|
200
|
+
}
|
|
201
|
+
const publicKey = mem.slice(PK_STAGE, PK_STAGE + 33);
|
|
202
|
+
const secretKey = new Uint8Array(32);
|
|
203
|
+
secretKey.set(seed);
|
|
204
|
+
return { publicKey, secretKey };
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
ioWipe(mx);
|
|
208
|
+
mx.wipeBuffers();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Random ECDSA-P256 key generation, wraps `keygenDerand` with `randomBytes(32)`. */
|
|
212
|
+
keygen() {
|
|
213
|
+
const seed = randomBytes(32);
|
|
214
|
+
try {
|
|
215
|
+
return this.keygenDerand(seed);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
wipe(seed);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Key generation that returns the public key in the 65-byte SEC 1
|
|
223
|
+
* §2.3.4 uncompressed encoding `0x04 || X || Y`, rather than the
|
|
224
|
+
* 33-byte compressed form `keygen` / `keygenDerand` return. The
|
|
225
|
+
* secret-key half is the same 32-byte raw scalar `d`.
|
|
226
|
+
*
|
|
227
|
+
* Internally runs `keygen` (or `keygenDerand` if a seed is supplied)
|
|
228
|
+
* to obtain the compressed pk, then `pointDecompress` to expand it.
|
|
229
|
+
* The compressed intermediate is wiped before return.
|
|
230
|
+
*
|
|
231
|
+
* @param seed Optional 32-byte seed; passes through to `keygenDerand`
|
|
232
|
+
* when present, falls back to `keygen` (CSPRNG seed) when
|
|
233
|
+
* omitted.
|
|
234
|
+
*/
|
|
235
|
+
keygenUncompressed(seed) {
|
|
236
|
+
const kp = seed === undefined ? this.keygen() : this.keygenDerand(seed);
|
|
237
|
+
try {
|
|
238
|
+
const publicKey = pointDecompress(kp.publicKey);
|
|
239
|
+
return { publicKey, secretKey: kp.secretKey };
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
wipe(kp.secretKey);
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
wipe(kp.publicKey);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Hedged-or-deterministic ECDSA-P256 sign per FIPS 186-5 §6.4 with
|
|
251
|
+
* RFC 6979 §3.5 low-S normalisation. The K nonce is derived per
|
|
252
|
+
* RFC 6979 §3.2 (deterministic) when `rnd` is all-zero, or per
|
|
253
|
+
* draft-irtf-cfrg-det-sigs-with-noise-05 (hedged) otherwise. The
|
|
254
|
+
* hedged path is the recommended default; pass `randomBytes(32)`.
|
|
255
|
+
*
|
|
256
|
+
* The WASM re-derives pk = [d]G internally and compares it against
|
|
257
|
+
* the caller-supplied `pk`. A mismatch traps via `unreachable` and
|
|
258
|
+
* is rethrown as `SigningError('sig-malformed-input')`. This
|
|
259
|
+
* defends against fault injection that would bias the per-signature
|
|
260
|
+
* randomness derivation by forcing the caller to also know pk.
|
|
261
|
+
*
|
|
262
|
+
* @param sk 32-byte secret scalar d
|
|
263
|
+
* @param pk 33-byte compressed or 65-byte uncompressed pk;
|
|
264
|
+
* cross-checked by WASM after derivation
|
|
265
|
+
* @param msgHash 32-byte SHA-256(M) digest (caller-computed)
|
|
266
|
+
* @param rnd 32-byte per-call entropy Z; all-zero selects
|
|
267
|
+
* deterministic RFC 6979 §3.2, non-zero selects
|
|
268
|
+
* the hedged path
|
|
269
|
+
* @returns 64-byte raw r || s signature, low-S normalised
|
|
270
|
+
* @throws SigningError('sig-malformed-input') on pk-mismatch
|
|
271
|
+
* (fault-injection trap)
|
|
272
|
+
*/
|
|
273
|
+
sign(sk, pk, msgHash, rnd) {
|
|
274
|
+
_assertNotOwned('p256');
|
|
275
|
+
validateSecretKey(sk);
|
|
276
|
+
validatePublicKey(pk);
|
|
277
|
+
validateMessageHash(msgHash);
|
|
278
|
+
validateEntropy(rnd);
|
|
279
|
+
const pkC = normalizePublicKey(pk);
|
|
280
|
+
const mx = this.mx;
|
|
281
|
+
const mem = new Uint8Array(mx.memory.buffer);
|
|
282
|
+
mem.set(sk, SEED_STAGE);
|
|
283
|
+
mem.set(pkC, PK_STAGE);
|
|
284
|
+
mem.set(msgHash, MSG_HASH_STAGE);
|
|
285
|
+
mem.set(rnd, RND_STAGE);
|
|
286
|
+
try {
|
|
287
|
+
try {
|
|
288
|
+
mx.ecdsaSign(SEED_STAGE, PK_STAGE, MSG_HASH_STAGE, RND_STAGE, SIG_STAGE);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
rethrowTrap(err, 'sig-malformed-input', 'leviathan-crypto: ecdsa-p256 sign aborted, pk does not match the pk derived from sk '
|
|
292
|
+
+ '(likely fault injection or caller misuse)');
|
|
293
|
+
}
|
|
294
|
+
return mem.slice(SIG_STAGE, SIG_STAGE + 64);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
ioWipe(mx);
|
|
298
|
+
mx.wipeBuffers();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Suite-only: hedged-or-deterministic sign that derives pk
|
|
303
|
+
* internally and skips the fault-injection cross-check. See
|
|
304
|
+
* AGENTS.md "SignatureSuite lifecycle". Underscore-prefixed,
|
|
305
|
+
* not part of the public API.
|
|
306
|
+
*/
|
|
307
|
+
_signInternalPk(sk, msgHash, rnd) {
|
|
308
|
+
_assertNotOwned('p256');
|
|
309
|
+
validateSecretKey(sk);
|
|
310
|
+
validateMessageHash(msgHash);
|
|
311
|
+
validateEntropy(rnd);
|
|
312
|
+
const mx = this.mx;
|
|
313
|
+
const mem = new Uint8Array(mx.memory.buffer);
|
|
314
|
+
mem.set(sk, SEED_STAGE);
|
|
315
|
+
mem.set(msgHash, MSG_HASH_STAGE);
|
|
316
|
+
mem.set(rnd, RND_STAGE);
|
|
317
|
+
try {
|
|
318
|
+
mx.ecdsaSignInternalPk(SEED_STAGE, MSG_HASH_STAGE, RND_STAGE, SIG_STAGE);
|
|
319
|
+
return mem.slice(SIG_STAGE, SIG_STAGE + 64);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
ioWipe(mx);
|
|
323
|
+
mx.wipeBuffers();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Strict ECDSA-P256 verify per FIPS 186-5 §6.5 with low-S
|
|
328
|
+
* enforcement (RFC 6979 §3.5). Returns `true` on success, `false`
|
|
329
|
+
* on every signature failure mode: off-curve / identity pk, r or
|
|
330
|
+
* s out of [1, n-1], high-S, or the signature equation failing.
|
|
331
|
+
* Throws only on caller-side contract violations (wrong-length
|
|
332
|
+
* inputs).
|
|
333
|
+
*
|
|
334
|
+
* @param pk 33-byte compressed or 65-byte uncompressed pk
|
|
335
|
+
* @param msgHash 32-byte SHA-256(M) digest
|
|
336
|
+
* @param sig 64-byte raw r || s (use `ecdsaSignatureFromDer`
|
|
337
|
+
* to convert DER-encoded signatures first)
|
|
338
|
+
*/
|
|
339
|
+
verify(pk, msgHash, sig) {
|
|
340
|
+
_assertNotOwned('p256');
|
|
341
|
+
validatePublicKey(pk);
|
|
342
|
+
validateMessageHash(msgHash);
|
|
343
|
+
validateSignature(sig);
|
|
344
|
+
const pkC = normalizePublicKey(pk);
|
|
345
|
+
const mx = this.mx;
|
|
346
|
+
const mem = new Uint8Array(mx.memory.buffer);
|
|
347
|
+
mem.set(pkC, PK_STAGE);
|
|
348
|
+
mem.set(msgHash, MSG_HASH_STAGE);
|
|
349
|
+
mem.set(sig, SIG_STAGE);
|
|
350
|
+
try {
|
|
351
|
+
return mx.ecdsaVerify(PK_STAGE, MSG_HASH_STAGE, SIG_STAGE) === 1;
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
ioWipe(mx);
|
|
355
|
+
mx.wipeBuffers();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
dispose() {
|
|
359
|
+
// Idempotent; per-method wipe runs anyway.
|
|
360
|
+
try {
|
|
361
|
+
this.mx.wipeBuffers();
|
|
362
|
+
ioWipe(this.mx);
|
|
363
|
+
}
|
|
364
|
+
catch { /* idempotent */ }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface EcdsaP256KeyPair {
|
|
2
|
+
/** 33-byte compressed public key per SEC 1 §2.3.3, 0x02 / 0x03 || x. */
|
|
3
|
+
publicKey: Uint8Array;
|
|
4
|
+
/** 32-byte secret scalar d ∈ [1, n-1] per FIPS 186-5 §6.2.1, private-key generation. */
|
|
5
|
+
secretKey: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The ECDSA-P256-relevant subset of the p256 WASM exports.
|
|
9
|
+
*
|
|
10
|
+
* The p256 module hosts the full elliptic-curve substrate (field,
|
|
11
|
+
* scalar, point, RFC 6979 K derivation, embedded SHA-256 + HMAC) and
|
|
12
|
+
* the four high-level ECDSA entry points (keygen, sign, signInternalPk,
|
|
13
|
+
* verify). The wrapper additionally drives `pointDecompress` + `feToBytes`
|
|
14
|
+
* for the SEC 1 §2.3.4 uncompressed-pk emission path; those two
|
|
15
|
+
* substrate exports are public because no high-level ABI covers
|
|
16
|
+
* "given a compressed pk, return its uncompressed form" without
|
|
17
|
+
* round-tripping through verify. Substrate test hooks live outside
|
|
18
|
+
* the consumer-facing ABI.
|
|
19
|
+
*/
|
|
20
|
+
export interface EcdsaP256Exports {
|
|
21
|
+
memory: WebAssembly.Memory;
|
|
22
|
+
getModuleId: () => number;
|
|
23
|
+
getMemoryPages: () => number;
|
|
24
|
+
feToBytes: (outOff: number, feOff: number) => void;
|
|
25
|
+
pointDecompress: (outOff: number, srcOff: number) => number;
|
|
26
|
+
ecdsaKeygen: (seedOff: number, pkOff: number) => void;
|
|
27
|
+
ecdsaSign: (skOff: number, pkOff: number, msgHashOff: number, rndOff: number, sigOff: number) => void;
|
|
28
|
+
ecdsaSignInternalPk: (skOff: number, msgHashOff: number, rndOff: number, sigOff: number) => void;
|
|
29
|
+
ecdsaVerify: (pkOff: number, msgHashOff: number, sigOff: number) => number;
|
|
30
|
+
wipeBuffers: () => void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ecdsa/types.ts
|
|
23
|
+
//
|
|
24
|
+
// ECDSA-P256 type surface: the WASM export interface for the p256
|
|
25
|
+
// module and the public key-pair shape returned by keygen /
|
|
26
|
+
// keygenDerand. FIPS 186-5 §6, ECDSA and SP 800-186 §3.2.1.3, P-256
|
|
27
|
+
// parameters.
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function validateSeed(seed: Uint8Array): void;
|
|
2
|
+
export declare function validateSecretKey(sk: Uint8Array): void;
|
|
3
|
+
/**
|
|
4
|
+
* Accepts both 33-byte compressed (SEC 1 §2.3.3, 0x02 / 0x03 || x) and
|
|
5
|
+
* 65-byte uncompressed (SEC 1 §2.3.4, 0x04 || x || y) inputs. The WASM
|
|
6
|
+
* ABI consumes only the 33-byte compressed form; the wrapper layer
|
|
7
|
+
* normalises 65-byte inputs to compressed before staging into WASM
|
|
8
|
+
* memory (see `normalizePublicKey` in `./index.ts`). Constant-time is
|
|
9
|
+
* not required at pk import since pk is public material.
|
|
10
|
+
*
|
|
11
|
+
* Length-only at this layer; on-curve / canonical-x checks happen in
|
|
12
|
+
* `pointDecompress` (verify) or via the fault-injection pk-mismatch
|
|
13
|
+
* trap (sign).
|
|
14
|
+
*/
|
|
15
|
+
export declare function validatePublicKey(pk: Uint8Array): void;
|
|
16
|
+
export declare function validateMessageHash(h: Uint8Array): void;
|
|
17
|
+
export declare function validateSignature(sig: Uint8Array): void;
|
|
18
|
+
export declare function validateEntropy(rnd: Uint8Array): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ecdsa/validate.ts
|
|
23
|
+
//
|
|
24
|
+
// ECDSA-P256 caller-side input validation. Pure length / type checks;
|
|
25
|
+
// curve membership, canonical-encoding rejection, scalar-range checks
|
|
26
|
+
// ([1, n-1] for d, r, s), low-S enforcement, and the fault-injection
|
|
27
|
+
// pk-mismatch trap all live inside the WASM layer.
|
|
28
|
+
//
|
|
29
|
+
// TypeError for non-Uint8Array, RangeError for wrong-length. Matches
|
|
30
|
+
// the ed25519 / mldsa / mlkem validation conventions.
|
|
31
|
+
export function validateSeed(seed) {
|
|
32
|
+
if (!(seed instanceof Uint8Array))
|
|
33
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 seed must be a Uint8Array');
|
|
34
|
+
if (seed.length !== 32)
|
|
35
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 seed must be 32 bytes (got ${seed.length})`);
|
|
36
|
+
}
|
|
37
|
+
export function validateSecretKey(sk) {
|
|
38
|
+
// 32-byte big-endian scalar d per FIPS 186-5 §6.2.1, private-key
|
|
39
|
+
// generation. The d ∈ [1, n-1] range check lives in WASM
|
|
40
|
+
// (scalarIsZero rejection plus scalar arithmetic mod n).
|
|
41
|
+
if (!(sk instanceof Uint8Array))
|
|
42
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 secret key must be a Uint8Array');
|
|
43
|
+
if (sk.length !== 32)
|
|
44
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 secret key must be 32 bytes (got ${sk.length})`);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Accepts both 33-byte compressed (SEC 1 §2.3.3, 0x02 / 0x03 || x) and
|
|
48
|
+
* 65-byte uncompressed (SEC 1 §2.3.4, 0x04 || x || y) inputs. The WASM
|
|
49
|
+
* ABI consumes only the 33-byte compressed form; the wrapper layer
|
|
50
|
+
* normalises 65-byte inputs to compressed before staging into WASM
|
|
51
|
+
* memory (see `normalizePublicKey` in `./index.ts`). Constant-time is
|
|
52
|
+
* not required at pk import since pk is public material.
|
|
53
|
+
*
|
|
54
|
+
* Length-only at this layer; on-curve / canonical-x checks happen in
|
|
55
|
+
* `pointDecompress` (verify) or via the fault-injection pk-mismatch
|
|
56
|
+
* trap (sign).
|
|
57
|
+
*/
|
|
58
|
+
export function validatePublicKey(pk) {
|
|
59
|
+
if (!(pk instanceof Uint8Array))
|
|
60
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 public key must be a Uint8Array');
|
|
61
|
+
if (pk.length !== 33 && pk.length !== 65)
|
|
62
|
+
throw new RangeError('leviathan-crypto: ecdsa-p256 public key must be 33 bytes (compressed, SEC 1 §2.3.3) '
|
|
63
|
+
+ `or 65 bytes (uncompressed, SEC 1 §2.3.4) (got ${pk.length})`);
|
|
64
|
+
}
|
|
65
|
+
export function validateMessageHash(h) {
|
|
66
|
+
// ECDSA-P256 takes a 32-byte SHA-256 digest; the suite layer drives
|
|
67
|
+
// the actual hashing. FIPS 186-5 §6.4.1 requires hlen = qlen = 256
|
|
68
|
+
// for P-256 + SHA-256 so the bits2int truncation is a no-op.
|
|
69
|
+
if (!(h instanceof Uint8Array))
|
|
70
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 message hash must be a Uint8Array');
|
|
71
|
+
if (h.length !== 32)
|
|
72
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 message hash must be 32 bytes (got ${h.length})`);
|
|
73
|
+
}
|
|
74
|
+
export function validateSignature(sig) {
|
|
75
|
+
// Raw r || s wire form, 64 bytes total. DER-wrapped signatures pass
|
|
76
|
+
// through `ecdsaSignatureFromDer` first; that helper produces a
|
|
77
|
+
// 64-byte output. The r, s ∈ [1, n-1] and low-S checks happen in
|
|
78
|
+
// WASM during ecdsaVerify.
|
|
79
|
+
if (!(sig instanceof Uint8Array))
|
|
80
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 signature must be a Uint8Array');
|
|
81
|
+
if (sig.length !== 64)
|
|
82
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 signature must be 64 bytes raw r||s (got ${sig.length})`);
|
|
83
|
+
}
|
|
84
|
+
export function validateEntropy(rnd) {
|
|
85
|
+
// Per-call entropy Z, 32 bytes. All-zero selects the deterministic
|
|
86
|
+
// RFC 6979 §3.2 K-derivation path; non-zero selects the hedged
|
|
87
|
+
// path per draft-irtf-cfrg-det-sigs-with-noise-05.
|
|
88
|
+
if (!(rnd instanceof Uint8Array))
|
|
89
|
+
throw new TypeError('leviathan-crypto: ecdsa-p256 entropy must be a Uint8Array');
|
|
90
|
+
if (rnd.length !== 32)
|
|
91
|
+
throw new RangeError(`leviathan-crypto: ecdsa-p256 entropy must be 32 bytes (got ${rnd.length})`);
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WASM_GZ_BASE64 as curve25519Wasm, WASM_GZ_BASE64 as ed25519Wasm, } from '../embedded/curve25519.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/ed25519/embedded.ts
|
|
23
|
+
//
|
|
24
|
+
// Exports the gzip+base64 curve25519 WASM blob for use as a WasmSource.
|
|
25
|
+
// Ed25519 and X25519 share the same curve25519 WASM binary; both the
|
|
26
|
+
// `leviathan-crypto/ed25519/embedded` and `leviathan-crypto/x25519/embedded`
|
|
27
|
+
// subpaths re-export the same blob under three names: `curve25519Wasm`
|
|
28
|
+
// (canonical), `ed25519Wasm`, and `x25519Wasm` (aliases that read more
|
|
29
|
+
// naturally in the matching subpath context). All three resolve to the
|
|
30
|
+
// identical underlying string; tree-shaking is unaffected.
|
|
31
|
+
export { WASM_GZ_BASE64 as curve25519Wasm, WASM_GZ_BASE64 as ed25519Wasm, } from '../embedded/curve25519.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isInitialized } from '../init.js';
|
|
2
|
+
import type { WasmSource } from '../wasm-source.js';
|
|
3
|
+
import type { Ed25519KeyPair } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Initialise the curve25519 WASM module under the `ed25519` alias.
|
|
6
|
+
* Equivalent to `x25519Init(source)`; both target the same WASM module
|
|
7
|
+
* and the init layer de-dupes when given identical sources.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ed25519Init(source: WasmSource): Promise<void>;
|
|
10
|
+
export type { WasmSource };
|
|
11
|
+
export type { Ed25519KeyPair, Ed25519Exports } from './types.js';
|
|
12
|
+
export { isInitialized };
|
|
13
|
+
export declare class Ed25519 {
|
|
14
|
+
constructor();
|
|
15
|
+
private get mx();
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic Ed25519 key generation, RFC 8032 §5.1.5.
|
|
18
|
+
* @param seed 32-byte secret seed
|
|
19
|
+
* @returns 32-byte verifying key and a fresh 32-byte secret-key copy
|
|
20
|
+
* of the supplied seed (the spec defines sk = seed).
|
|
21
|
+
*/
|
|
22
|
+
keygenDerand(seed: Uint8Array): Ed25519KeyPair;
|
|
23
|
+
/** Random Ed25519 key generation, wraps `keygenDerand` with `randomBytes(32)`. */
|
|
24
|
+
keygen(): Ed25519KeyPair;
|
|
25
|
+
/**
|
|
26
|
+
* Pure Ed25519 sign, RFC 8032 §5.1.6.
|
|
27
|
+
*
|
|
28
|
+
* The WASM re-derives pk from `sk` internally and compares it against
|
|
29
|
+
* the caller-supplied `pk`; a mismatch traps via `unreachable` and is
|
|
30
|
+
* rethrown as `SigningError('sig-ed25519-pk-mismatch')`. This defends
|
|
31
|
+
* against fault injection that bias the per-signature randomness
|
|
32
|
+
* derivation by forcing the caller to also know pk.
|
|
33
|
+
*/
|
|
34
|
+
sign(sk: Uint8Array, pk: Uint8Array, M: Uint8Array): Uint8Array;
|
|
35
|
+
/**
|
|
36
|
+
* Ed25519ph sign, RFC 8032 §5.1.7 (prehash, dom2 phflag=1).
|
|
37
|
+
*
|
|
38
|
+
* Caller supplies the 64-byte SHA-512(M) digest; the library does not
|
|
39
|
+
* compute it. Same pk-mismatch fault-injection trap as `sign`.
|
|
40
|
+
*/
|
|
41
|
+
signPrehashed(sk: Uint8Array, pk: Uint8Array, digest: Uint8Array, ctx: Uint8Array): Uint8Array;
|
|
42
|
+
/**
|
|
43
|
+
* Suite-only: pure Ed25519 sign that derives pk internally and
|
|
44
|
+
* skips the fault-injection cross-check. See AGENTS.md
|
|
45
|
+
* "SignatureSuite lifecycle". Underscore-prefixed, not part of
|
|
46
|
+
* the public API.
|
|
47
|
+
*/
|
|
48
|
+
_signInternalPk(sk: Uint8Array, M: Uint8Array): Uint8Array;
|
|
49
|
+
/**
|
|
50
|
+
* Suite-only: Ed25519ph mirror of `_signInternalPk`. See
|
|
51
|
+
* AGENTS.md "SignatureSuite lifecycle".
|
|
52
|
+
*/
|
|
53
|
+
_signPrehashedInternalPk(sk: Uint8Array, digest: Uint8Array, ctx: Uint8Array): Uint8Array;
|
|
54
|
+
/**
|
|
55
|
+
* Strict pure Ed25519 verify, RFC 8032 §5.1.7 / FIPS 186-5 §7.6.4.
|
|
56
|
+
*
|
|
57
|
+
* Returns `true` on success, `false` on every signature failure mode:
|
|
58
|
+
* off-curve pk, non-canonical R, non-canonical S (>= L), small-order
|
|
59
|
+
* pk, or signature equation inequality. Throws only on caller-side
|
|
60
|
+
* contract violations (wrong-length pk / M / sig).
|
|
61
|
+
*/
|
|
62
|
+
verify(pk: Uint8Array, M: Uint8Array, sig: Uint8Array): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Strict Ed25519ph verify, RFC 8032 §5.1.7 prehash. Same rejection
|
|
65
|
+
* conditions as {@link verify} plus the dom2(F=1, ctx) prefix on the
|
|
66
|
+
* per-spec SHA-512 inputs (handled inside the WASM).
|
|
67
|
+
*/
|
|
68
|
+
verifyPrehashed(pk: Uint8Array, digest: Uint8Array, ctx: Uint8Array, sig: Uint8Array): boolean;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
}
|