kxco-post-quantum 1.3.0 → 1.4.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/CHANGELOG.md +374 -292
- package/CONFORMANCE.md +180 -0
- package/LICENSE +202 -202
- package/MIGRATION.md +143 -0
- package/README.md +215 -178
- package/SECURITY.md +41 -41
- package/THREAT-MODEL.md +194 -0
- package/package.json +124 -106
- package/src/derive.d.ts +20 -20
- package/src/derive.js +47 -47
- package/src/index.d.ts +13 -8
- package/src/index.js +24 -17
- package/src/kid.d.ts +21 -21
- package/src/kid.js +53 -53
- package/src/ml-dsa-87.d.ts +81 -0
- package/src/ml-dsa-87.js +127 -0
- package/src/ml-dsa.d.ts +78 -78
- package/src/ml-dsa.js +102 -102
- package/src/ml-kem-1024.d.ts +59 -0
- package/src/ml-kem-1024.js +90 -0
- package/src/ml-kem.d.ts +49 -49
- package/src/ml-kem.js +61 -61
- package/src/slh-dsa.d.ts +72 -72
- package/src/slh-dsa.js +106 -106
- package/src/webhook.d.ts +119 -119
- package/src/webhook.js +135 -135
package/src/kid.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compute a 16-hex-character fingerprint of a public key:
|
|
5
|
-
* the first 8 bytes of `SHA-256(public_key)`, hex-encoded.
|
|
6
|
-
*
|
|
7
|
-
* Stable for the lifetime of the keypair. Used to identify which
|
|
8
|
-
* platform key signed an outbound delivery without including the
|
|
9
|
-
* full 1952-byte public key in every request.
|
|
10
|
-
*
|
|
11
|
-
* @param publicKey — raw bytes or hex string
|
|
12
|
-
*/
|
|
13
|
-
export function fingerprint(publicKey: Buffer | Uint8Array | string): string
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Constant-time comparison of two kid strings.
|
|
17
|
-
*
|
|
18
|
-
* Use this instead of `===` when comparing kids that may be
|
|
19
|
-
* influenced by untrusted input — eg. an `X-KXCO-PQ-Kid` header.
|
|
20
|
-
*/
|
|
21
|
-
export function kidEquals(a: string, b: string): boolean
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute a 16-hex-character fingerprint of a public key:
|
|
5
|
+
* the first 8 bytes of `SHA-256(public_key)`, hex-encoded.
|
|
6
|
+
*
|
|
7
|
+
* Stable for the lifetime of the keypair. Used to identify which
|
|
8
|
+
* platform key signed an outbound delivery without including the
|
|
9
|
+
* full 1952-byte public key in every request.
|
|
10
|
+
*
|
|
11
|
+
* @param publicKey — raw bytes or hex string
|
|
12
|
+
*/
|
|
13
|
+
export function fingerprint(publicKey: Buffer | Uint8Array | string): string
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constant-time comparison of two kid strings.
|
|
17
|
+
*
|
|
18
|
+
* Use this instead of `===` when comparing kids that may be
|
|
19
|
+
* influenced by untrusted input — eg. an `X-KXCO-PQ-Kid` header.
|
|
20
|
+
*/
|
|
21
|
+
export function kidEquals(a: string, b: string): boolean
|
package/src/kid.js
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
// Public-key fingerprints (kid = "key identifier").
|
|
2
|
-
//
|
|
3
|
-
// Receivers pin a 16-hex fingerprint of the platform's public key, then
|
|
4
|
-
// compare against an X-KXCO-PQ-Kid header on every webhook. Fast rejection of
|
|
5
|
-
// stale or unknown keys without re-fetching the full 1952-byte public key on
|
|
6
|
-
// every request.
|
|
7
|
-
//
|
|
8
|
-
// Isomorphic: uses @noble/hashes/sha256 — runs identically in Node and
|
|
9
|
-
// browsers.
|
|
10
|
-
|
|
11
|
-
import { sha256 } from '@noble/hashes/sha2.js'
|
|
12
|
-
|
|
13
|
-
function hexToBytes(hex) {
|
|
14
|
-
if (hex.length % 2) throw new Error('odd hex length')
|
|
15
|
-
const b = new Uint8Array(hex.length / 2)
|
|
16
|
-
for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
17
|
-
return b
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function bytesToHex(bytes) {
|
|
21
|
-
let s = ''
|
|
22
|
-
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0')
|
|
23
|
-
return s
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Compute a stable 16-hex-character fingerprint of a public key.
|
|
28
|
-
*
|
|
29
|
-
* @param {Buffer|Uint8Array|string} publicKey — raw bytes or hex string
|
|
30
|
-
* @returns {string} 16 hex chars (8 bytes of SHA-256)
|
|
31
|
-
*/
|
|
32
|
-
export function fingerprint(publicKey) {
|
|
33
|
-
const bytes = typeof publicKey === 'string'
|
|
34
|
-
? hexToBytes(publicKey)
|
|
35
|
-
: (publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey))
|
|
36
|
-
return bytesToHex(sha256(bytes)).slice(0, 16)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Constant-time comparison of two kid strings.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} a
|
|
43
|
-
* @param {string} b
|
|
44
|
-
* @returns {boolean}
|
|
45
|
-
*/
|
|
46
|
-
export function kidEquals(a, b) {
|
|
47
|
-
if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) {
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
50
|
-
let diff = 0
|
|
51
|
-
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
|
|
52
|
-
return diff === 0
|
|
53
|
-
}
|
|
1
|
+
// Public-key fingerprints (kid = "key identifier").
|
|
2
|
+
//
|
|
3
|
+
// Receivers pin a 16-hex fingerprint of the platform's public key, then
|
|
4
|
+
// compare against an X-KXCO-PQ-Kid header on every webhook. Fast rejection of
|
|
5
|
+
// stale or unknown keys without re-fetching the full 1952-byte public key on
|
|
6
|
+
// every request.
|
|
7
|
+
//
|
|
8
|
+
// Isomorphic: uses @noble/hashes/sha256 — runs identically in Node and
|
|
9
|
+
// browsers.
|
|
10
|
+
|
|
11
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
12
|
+
|
|
13
|
+
function hexToBytes(hex) {
|
|
14
|
+
if (hex.length % 2) throw new Error('odd hex length')
|
|
15
|
+
const b = new Uint8Array(hex.length / 2)
|
|
16
|
+
for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
17
|
+
return b
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function bytesToHex(bytes) {
|
|
21
|
+
let s = ''
|
|
22
|
+
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0')
|
|
23
|
+
return s
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute a stable 16-hex-character fingerprint of a public key.
|
|
28
|
+
*
|
|
29
|
+
* @param {Buffer|Uint8Array|string} publicKey — raw bytes or hex string
|
|
30
|
+
* @returns {string} 16 hex chars (8 bytes of SHA-256)
|
|
31
|
+
*/
|
|
32
|
+
export function fingerprint(publicKey) {
|
|
33
|
+
const bytes = typeof publicKey === 'string'
|
|
34
|
+
? hexToBytes(publicKey)
|
|
35
|
+
: (publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey))
|
|
36
|
+
return bytesToHex(sha256(bytes)).slice(0, 16)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Constant-time comparison of two kid strings.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} a
|
|
43
|
+
* @param {string} b
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export function kidEquals(a, b) {
|
|
47
|
+
if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
let diff = 0
|
|
51
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
|
|
52
|
+
return diff === 0
|
|
53
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
export interface MlDsa87Keypair {
|
|
4
|
+
/** 2592-byte public key */
|
|
5
|
+
publicKey: Buffer
|
|
6
|
+
/** 4896-byte secret key */
|
|
7
|
+
secretKey: Buffer
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate an ML-DSA-87 (NIST FIPS 204) keypair deterministically
|
|
12
|
+
* from a master + domain-separation info string.
|
|
13
|
+
*
|
|
14
|
+
* Same inputs always produce the same keypair — no state, no DB row.
|
|
15
|
+
*
|
|
16
|
+
* Security Category 5. The default `info` differs from the ML-DSA-65 module's,
|
|
17
|
+
* so one master yields unrelated keys for the two parameter sets.
|
|
18
|
+
*
|
|
19
|
+
* CNSA 2.0 names ML-DSA-87. Support for the parameter set is not a CNSA 2.0
|
|
20
|
+
* compliance claim; see the note at the top of `ml-dsa-87.js`.
|
|
21
|
+
*/
|
|
22
|
+
export function keypairFromMaster(
|
|
23
|
+
master: Buffer | Uint8Array,
|
|
24
|
+
info?: string,
|
|
25
|
+
): MlDsa87Keypair
|
|
26
|
+
|
|
27
|
+
/** Maximum context length in bytes (FIPS 204 section 5.2). */
|
|
28
|
+
export const MAX_CONTEXT_BYTES: 255
|
|
29
|
+
|
|
30
|
+
export interface SignatureOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Optional FIPS 204 section 5.2 context string, at most 255 bytes.
|
|
33
|
+
* Strings are encoded as UTF-8.
|
|
34
|
+
*
|
|
35
|
+
* Gives domain separation at the signature level: a signature made under a
|
|
36
|
+
* context does not verify without it, or under a different one. An empty
|
|
37
|
+
* context is identical to omitting it.
|
|
38
|
+
*/
|
|
39
|
+
context?: Buffer | Uint8Array | string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sign a message under an ML-DSA-87 secret key. Returns the signature
|
|
44
|
+
* as a hex string (4627 bytes = 9254 hex characters).
|
|
45
|
+
*
|
|
46
|
+
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
47
|
+
* neither a string nor a Uint8Array
|
|
48
|
+
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
49
|
+
*/
|
|
50
|
+
export function sign(
|
|
51
|
+
secretKey: Buffer | Uint8Array,
|
|
52
|
+
message: Buffer | Uint8Array | string,
|
|
53
|
+
opts?: SignatureOptions,
|
|
54
|
+
): string
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Verify a hex-encoded ML-DSA-87 signature against a public key + message.
|
|
58
|
+
*
|
|
59
|
+
* Returns `false` on any cryptographic failure, including a signature or key
|
|
60
|
+
* from a different parameter set.
|
|
61
|
+
*
|
|
62
|
+
* Throws only on caller misuse of `opts`, which is a programming error rather
|
|
63
|
+
* than a failed verification and is not swallowed.
|
|
64
|
+
*
|
|
65
|
+
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
66
|
+
* neither a string nor a Uint8Array
|
|
67
|
+
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
68
|
+
*/
|
|
69
|
+
export function verify(
|
|
70
|
+
publicKey: Buffer | Uint8Array,
|
|
71
|
+
message: Buffer | Uint8Array | string,
|
|
72
|
+
sigHex: string,
|
|
73
|
+
opts?: SignatureOptions,
|
|
74
|
+
): boolean
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Raw `@noble/post-quantum` ML-DSA-87 primitive, re-exported for callers
|
|
78
|
+
* who want the lower-level API. The wrapper functions above are
|
|
79
|
+
* recommended for production use.
|
|
80
|
+
*/
|
|
81
|
+
export const ml_dsa87: typeof import('@noble/post-quantum/ml-dsa.js').ml_dsa87
|
package/src/ml-dsa-87.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ML-DSA-87 helpers (NIST FIPS 204, Dilithium5).
|
|
2
|
+
//
|
|
3
|
+
// Module-lattice signatures. Security Category 5 (≈ AES-256). Public key 2592
|
|
4
|
+
// bytes, secret key 4896 bytes, signature 4627 bytes. Resistant to attacks by
|
|
5
|
+
// quantum computers.
|
|
6
|
+
//
|
|
7
|
+
// Same API as ./ml-dsa.js, one security category higher. Use this where a
|
|
8
|
+
// counterparty specifies Category 5 or names ML-DSA-87. ML-DSA-65 remains the
|
|
9
|
+
// default for the KXCO stack; see the note on parameter choice below.
|
|
10
|
+
//
|
|
11
|
+
// Isomorphic: works in Node and modern browsers. Returns Buffer on Node
|
|
12
|
+
// (backwards compatible), Uint8Array in browsers.
|
|
13
|
+
//
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Parameter choice, and what this module does NOT establish
|
|
16
|
+
//
|
|
17
|
+
// CNSA 2.0 names ML-DSA-87 for National Security Systems. Exporting this module
|
|
18
|
+
// makes that parameter set available to callers. It does not make any deployed
|
|
19
|
+
// system CNSA 2.0 compliant, and it must not be cited as a compliance badge.
|
|
20
|
+
// Compliance is a property of a deployment, not of an available function: the
|
|
21
|
+
// KXCO estate signs with ML-DSA-65 at Category 3, including Armature L1 from
|
|
22
|
+
// block 0 and every issued KXCO ID, none of which this module changes.
|
|
23
|
+
//
|
|
24
|
+
// The accurate sentence is "supports ML-DSA-87". See CONFORMANCE.md.
|
|
25
|
+
//
|
|
26
|
+
// Cost of moving up: a signature is 4627 bytes against 3309 for ML-DSA-65, and
|
|
27
|
+
// a public key is 2592 against 1952. Once both sets are in use, anything
|
|
28
|
+
// downstream with a fixed-width signature field will meet both sizes.
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
import { ml_dsa87 } from '@noble/post-quantum/ml-dsa.js'
|
|
32
|
+
import { deriveSeed } from './derive.js'
|
|
33
|
+
import { normalizeContext, MAX_CONTEXT_BYTES } from './_context.js'
|
|
34
|
+
|
|
35
|
+
export { MAX_CONTEXT_BYTES }
|
|
36
|
+
|
|
37
|
+
const HAS_BUFFER = typeof Buffer !== 'undefined'
|
|
38
|
+
const enc = new TextEncoder()
|
|
39
|
+
|
|
40
|
+
function toBytes(input) {
|
|
41
|
+
if (input instanceof Uint8Array) return input
|
|
42
|
+
if (typeof input === 'string') return enc.encode(input)
|
|
43
|
+
throw new Error('expected Uint8Array or string')
|
|
44
|
+
}
|
|
45
|
+
function hexToBytes(hex) {
|
|
46
|
+
if (typeof hex !== 'string' || hex.length % 2) throw new Error('invalid hex')
|
|
47
|
+
const b = new Uint8Array(hex.length / 2)
|
|
48
|
+
for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
49
|
+
return b
|
|
50
|
+
}
|
|
51
|
+
function bytesToHex(bytes) {
|
|
52
|
+
let s = ''
|
|
53
|
+
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0')
|
|
54
|
+
return s
|
|
55
|
+
}
|
|
56
|
+
function wrap(bytes) {
|
|
57
|
+
return HAS_BUFFER ? Buffer.from(bytes) : bytes
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate an ML-DSA-87 keypair from a master + domain-separation info.
|
|
62
|
+
*
|
|
63
|
+
* The default info differs from the ML-DSA-65 default, so the same master
|
|
64
|
+
* yields unrelated keys for the two parameter sets rather than colliding.
|
|
65
|
+
*
|
|
66
|
+
* @returns {{ publicKey: Buffer|Uint8Array, secretKey: Buffer|Uint8Array }}
|
|
67
|
+
*/
|
|
68
|
+
export function keypairFromMaster(master, info = 'ml-dsa-87-v1') {
|
|
69
|
+
const seed = deriveSeed(master, info, 32)
|
|
70
|
+
const seedU8 = seed instanceof Uint8Array ? seed : new Uint8Array(seed)
|
|
71
|
+
const k = ml_dsa87.keygen(seedU8)
|
|
72
|
+
return {
|
|
73
|
+
publicKey: wrap(k.publicKey),
|
|
74
|
+
secretKey: wrap(k.secretKey),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sign a message. Returns the signature as a hex string.
|
|
80
|
+
*
|
|
81
|
+
* An optional FIPS 204 section 5.2 context string gives domain separation: a
|
|
82
|
+
* signature made under a context does not verify without it.
|
|
83
|
+
*
|
|
84
|
+
* @param {Buffer|Uint8Array} secretKey
|
|
85
|
+
* @param {Buffer|Uint8Array|string} message
|
|
86
|
+
* @param {{ context?: Uint8Array|Buffer|string }} [opts] at most 255 context bytes
|
|
87
|
+
* @returns {string} hex-encoded signature (9254 chars)
|
|
88
|
+
*/
|
|
89
|
+
export function sign(secretKey, message, opts) {
|
|
90
|
+
const context = normalizeContext(opts)
|
|
91
|
+
const sig = context === undefined
|
|
92
|
+
? ml_dsa87.sign(toBytes(message), secretKey)
|
|
93
|
+
: ml_dsa87.sign(toBytes(message), secretKey, { context })
|
|
94
|
+
return bytesToHex(sig)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Verify a hex-encoded signature.
|
|
99
|
+
*
|
|
100
|
+
* Pass the same context the signer used. A signature made under a context
|
|
101
|
+
* returns false here if the context is omitted or differs, which is the point
|
|
102
|
+
* of it.
|
|
103
|
+
*
|
|
104
|
+
* Returns false for any cryptographic failure, including a signature produced
|
|
105
|
+
* under a different parameter set. Throws only on caller misuse of `opts`
|
|
106
|
+
* (wrong type, or a context over 255 bytes), because that is a bug rather than
|
|
107
|
+
* a failed verification and should not be silently swallowed.
|
|
108
|
+
*
|
|
109
|
+
* @param {Buffer|Uint8Array} publicKey
|
|
110
|
+
* @param {Buffer|Uint8Array|string} message
|
|
111
|
+
* @param {string} sigHex
|
|
112
|
+
* @param {{ context?: Uint8Array|Buffer|string }} [opts]
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
export function verify(publicKey, message, sigHex, opts) {
|
|
116
|
+
// Outside the try: misuse must surface, not be swallowed as "invalid".
|
|
117
|
+
const context = normalizeContext(opts)
|
|
118
|
+
try {
|
|
119
|
+
return context === undefined
|
|
120
|
+
? ml_dsa87.verify(hexToBytes(sigHex), toBytes(message), publicKey)
|
|
121
|
+
: ml_dsa87.verify(hexToBytes(sigHex), toBytes(message), publicKey, { context })
|
|
122
|
+
} catch {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { ml_dsa87 }
|
package/src/ml-dsa.d.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
export interface MlDsaKeypair {
|
|
4
|
-
/** 1952-byte public key */
|
|
5
|
-
publicKey: Buffer
|
|
6
|
-
/** 4032-byte secret key */
|
|
7
|
-
secretKey: Buffer
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate an ML-DSA-65 (NIST FIPS 204) keypair deterministically
|
|
12
|
-
* from a master + domain-separation info string.
|
|
13
|
-
*
|
|
14
|
-
* Same inputs always produce the same keypair — no state, no DB row.
|
|
15
|
-
*/
|
|
16
|
-
export function keypairFromMaster(
|
|
17
|
-
master: Buffer | Uint8Array,
|
|
18
|
-
info?: string,
|
|
19
|
-
): MlDsaKeypair
|
|
20
|
-
|
|
21
|
-
/** Maximum context length in bytes (FIPS 204 section 5.2). */
|
|
22
|
-
export const MAX_CONTEXT_BYTES: 255
|
|
23
|
-
|
|
24
|
-
export interface SignatureOptions {
|
|
25
|
-
/**
|
|
26
|
-
* Optional FIPS 204 section 5.2 context string, at most 255 bytes.
|
|
27
|
-
* Strings are encoded as UTF-8.
|
|
28
|
-
*
|
|
29
|
-
* Gives domain separation at the signature level: a signature made under a
|
|
30
|
-
* context does not verify without it, or under a different one. An empty
|
|
31
|
-
* context is identical to omitting it.
|
|
32
|
-
*
|
|
33
|
-
* Complements `keypairFromMaster(master, info)`, which separates domains at
|
|
34
|
-
* the key level.
|
|
35
|
-
*/
|
|
36
|
-
context?: Buffer | Uint8Array | string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Sign a message under an ML-DSA-65 secret key. Returns the signature
|
|
41
|
-
* as a hex string (3309 bytes = 6618 hex characters).
|
|
42
|
-
*
|
|
43
|
-
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
44
|
-
* neither a string nor a Uint8Array
|
|
45
|
-
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
46
|
-
*/
|
|
47
|
-
export function sign(
|
|
48
|
-
secretKey: Buffer | Uint8Array,
|
|
49
|
-
message: Buffer | Uint8Array | string,
|
|
50
|
-
opts?: SignatureOptions,
|
|
51
|
-
): string
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Verify a hex-encoded ML-DSA-65 signature against a public key + message.
|
|
55
|
-
*
|
|
56
|
-
* Returns `false` on any cryptographic failure (invalid hex, wrong length,
|
|
57
|
-
* mismatch, or a missing/incorrect context).
|
|
58
|
-
*
|
|
59
|
-
* Throws only on caller misuse of `opts`, which is a programming error rather
|
|
60
|
-
* than a failed verification and is not swallowed.
|
|
61
|
-
*
|
|
62
|
-
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
63
|
-
* neither a string nor a Uint8Array
|
|
64
|
-
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
65
|
-
*/
|
|
66
|
-
export function verify(
|
|
67
|
-
publicKey: Buffer | Uint8Array,
|
|
68
|
-
message: Buffer | Uint8Array | string,
|
|
69
|
-
sigHex: string,
|
|
70
|
-
opts?: SignatureOptions,
|
|
71
|
-
): boolean
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Raw `@noble/post-quantum` ML-DSA-65 primitive, re-exported for callers
|
|
75
|
-
* who want the lower-level API. The wrapper functions above are
|
|
76
|
-
* recommended for production use.
|
|
77
|
-
*/
|
|
78
|
-
export const ml_dsa65: typeof import('@noble/post-quantum/ml-dsa.js').ml_dsa65
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
export interface MlDsaKeypair {
|
|
4
|
+
/** 1952-byte public key */
|
|
5
|
+
publicKey: Buffer
|
|
6
|
+
/** 4032-byte secret key */
|
|
7
|
+
secretKey: Buffer
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate an ML-DSA-65 (NIST FIPS 204) keypair deterministically
|
|
12
|
+
* from a master + domain-separation info string.
|
|
13
|
+
*
|
|
14
|
+
* Same inputs always produce the same keypair — no state, no DB row.
|
|
15
|
+
*/
|
|
16
|
+
export function keypairFromMaster(
|
|
17
|
+
master: Buffer | Uint8Array,
|
|
18
|
+
info?: string,
|
|
19
|
+
): MlDsaKeypair
|
|
20
|
+
|
|
21
|
+
/** Maximum context length in bytes (FIPS 204 section 5.2). */
|
|
22
|
+
export const MAX_CONTEXT_BYTES: 255
|
|
23
|
+
|
|
24
|
+
export interface SignatureOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Optional FIPS 204 section 5.2 context string, at most 255 bytes.
|
|
27
|
+
* Strings are encoded as UTF-8.
|
|
28
|
+
*
|
|
29
|
+
* Gives domain separation at the signature level: a signature made under a
|
|
30
|
+
* context does not verify without it, or under a different one. An empty
|
|
31
|
+
* context is identical to omitting it.
|
|
32
|
+
*
|
|
33
|
+
* Complements `keypairFromMaster(master, info)`, which separates domains at
|
|
34
|
+
* the key level.
|
|
35
|
+
*/
|
|
36
|
+
context?: Buffer | Uint8Array | string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sign a message under an ML-DSA-65 secret key. Returns the signature
|
|
41
|
+
* as a hex string (3309 bytes = 6618 hex characters).
|
|
42
|
+
*
|
|
43
|
+
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
44
|
+
* neither a string nor a Uint8Array
|
|
45
|
+
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
46
|
+
*/
|
|
47
|
+
export function sign(
|
|
48
|
+
secretKey: Buffer | Uint8Array,
|
|
49
|
+
message: Buffer | Uint8Array | string,
|
|
50
|
+
opts?: SignatureOptions,
|
|
51
|
+
): string
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Verify a hex-encoded ML-DSA-65 signature against a public key + message.
|
|
55
|
+
*
|
|
56
|
+
* Returns `false` on any cryptographic failure (invalid hex, wrong length,
|
|
57
|
+
* mismatch, or a missing/incorrect context).
|
|
58
|
+
*
|
|
59
|
+
* Throws only on caller misuse of `opts`, which is a programming error rather
|
|
60
|
+
* than a failed verification and is not swallowed.
|
|
61
|
+
*
|
|
62
|
+
* @throws {TypeError} if `opts` is not an options object, or `context` is
|
|
63
|
+
* neither a string nor a Uint8Array
|
|
64
|
+
* @throws {RangeError} if `context` exceeds 255 bytes
|
|
65
|
+
*/
|
|
66
|
+
export function verify(
|
|
67
|
+
publicKey: Buffer | Uint8Array,
|
|
68
|
+
message: Buffer | Uint8Array | string,
|
|
69
|
+
sigHex: string,
|
|
70
|
+
opts?: SignatureOptions,
|
|
71
|
+
): boolean
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Raw `@noble/post-quantum` ML-DSA-65 primitive, re-exported for callers
|
|
75
|
+
* who want the lower-level API. The wrapper functions above are
|
|
76
|
+
* recommended for production use.
|
|
77
|
+
*/
|
|
78
|
+
export const ml_dsa65: typeof import('@noble/post-quantum/ml-dsa.js').ml_dsa65
|