kxco-post-quantum 1.3.0 → 1.4.1
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/BENCHMARKS.md +124 -0
- package/CHANGELOG.md +413 -292
- package/CONFORMANCE.md +211 -0
- package/LICENSE +202 -202
- package/MIGRATION.md +143 -0
- package/README.md +216 -178
- package/SECURITY.md +63 -41
- package/THREAT-MODEL.md +194 -0
- package/package.json +126 -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/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
|
package/src/ml-dsa.js
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
// ML-DSA-65 helpers (NIST FIPS 204, Dilithium3).
|
|
2
|
-
//
|
|
3
|
-
// Module-lattice signatures. Security Category 3 (≈ AES-192). Public key 1952
|
|
4
|
-
// bytes, signature 3309 bytes. Resistant to attacks by quantum computers.
|
|
5
|
-
//
|
|
6
|
-
// Isomorphic: works in Node and modern browsers. Returns Buffer on Node
|
|
7
|
-
// (backwards compatible), Uint8Array in browsers.
|
|
8
|
-
|
|
9
|
-
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'
|
|
10
|
-
import { deriveSeed } from './derive.js'
|
|
11
|
-
import { normalizeContext, MAX_CONTEXT_BYTES } from './_context.js'
|
|
12
|
-
|
|
13
|
-
export { MAX_CONTEXT_BYTES }
|
|
14
|
-
|
|
15
|
-
const HAS_BUFFER = typeof Buffer !== 'undefined'
|
|
16
|
-
const enc = new TextEncoder()
|
|
17
|
-
|
|
18
|
-
function toBytes(input) {
|
|
19
|
-
if (input instanceof Uint8Array) return input
|
|
20
|
-
if (typeof input === 'string') return enc.encode(input)
|
|
21
|
-
throw new Error('expected Uint8Array or string')
|
|
22
|
-
}
|
|
23
|
-
function hexToBytes(hex) {
|
|
24
|
-
if (typeof hex !== 'string' || hex.length % 2) throw new Error('invalid hex')
|
|
25
|
-
const b = new Uint8Array(hex.length / 2)
|
|
26
|
-
for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
27
|
-
return b
|
|
28
|
-
}
|
|
29
|
-
function bytesToHex(bytes) {
|
|
30
|
-
let s = ''
|
|
31
|
-
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0')
|
|
32
|
-
return s
|
|
33
|
-
}
|
|
34
|
-
function wrap(bytes) {
|
|
35
|
-
return HAS_BUFFER ? Buffer.from(bytes) : bytes
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Generate an ML-DSA-65 keypair from a master + domain-separation info.
|
|
40
|
-
*
|
|
41
|
-
* @returns {{ publicKey: Buffer|Uint8Array, secretKey: Buffer|Uint8Array }}
|
|
42
|
-
*/
|
|
43
|
-
export function keypairFromMaster(master, info = 'ml-dsa-65-v1') {
|
|
44
|
-
const seed = deriveSeed(master, info, 32)
|
|
45
|
-
const seedU8 = seed instanceof Uint8Array ? seed : new Uint8Array(seed)
|
|
46
|
-
const k = ml_dsa65.keygen(seedU8)
|
|
47
|
-
return {
|
|
48
|
-
publicKey: wrap(k.publicKey),
|
|
49
|
-
secretKey: wrap(k.secretKey),
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Sign a message. Returns the signature as a hex string.
|
|
55
|
-
*
|
|
56
|
-
* An optional FIPS 204 section 5.2 context string gives domain separation: a
|
|
57
|
-
* signature made under a context does not verify without it. Omit it and the
|
|
58
|
-
* behaviour is exactly as before this parameter existed.
|
|
59
|
-
*
|
|
60
|
-
* @param {Buffer|Uint8Array} secretKey
|
|
61
|
-
* @param {Buffer|Uint8Array|string} message
|
|
62
|
-
* @param {{ context?: Uint8Array|Buffer|string }} [opts] at most 255 context bytes
|
|
63
|
-
* @returns {string} hex-encoded signature (6618 chars)
|
|
64
|
-
*/
|
|
65
|
-
export function sign(secretKey, message, opts) {
|
|
66
|
-
const context = normalizeContext(opts)
|
|
67
|
-
const sig = context === undefined
|
|
68
|
-
? ml_dsa65.sign(toBytes(message), secretKey)
|
|
69
|
-
: ml_dsa65.sign(toBytes(message), secretKey, { context })
|
|
70
|
-
return bytesToHex(sig)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Verify a hex-encoded signature.
|
|
75
|
-
*
|
|
76
|
-
* Pass the same context the signer used. A signature made under a context
|
|
77
|
-
* returns false here if the context is omitted or differs, which is the point
|
|
78
|
-
* of it.
|
|
79
|
-
*
|
|
80
|
-
* Returns false for any cryptographic failure. Throws only on caller misuse of
|
|
81
|
-
* `opts` (wrong type, or a context over 255 bytes), because that is a bug
|
|
82
|
-
* rather than a failed verification and should not be silently swallowed.
|
|
83
|
-
*
|
|
84
|
-
* @param {Buffer|Uint8Array} publicKey
|
|
85
|
-
* @param {Buffer|Uint8Array|string} message
|
|
86
|
-
* @param {string} sigHex
|
|
87
|
-
* @param {{ context?: Uint8Array|Buffer|string }} [opts]
|
|
88
|
-
* @returns {boolean}
|
|
89
|
-
*/
|
|
90
|
-
export function verify(publicKey, message, sigHex, opts) {
|
|
91
|
-
// Outside the try: misuse must surface, not be swallowed as "invalid".
|
|
92
|
-
const context = normalizeContext(opts)
|
|
93
|
-
try {
|
|
94
|
-
return context === undefined
|
|
95
|
-
? ml_dsa65.verify(hexToBytes(sigHex), toBytes(message), publicKey)
|
|
96
|
-
: ml_dsa65.verify(hexToBytes(sigHex), toBytes(message), publicKey, { context })
|
|
97
|
-
} catch {
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export { ml_dsa65 }
|
|
1
|
+
// ML-DSA-65 helpers (NIST FIPS 204, Dilithium3).
|
|
2
|
+
//
|
|
3
|
+
// Module-lattice signatures. Security Category 3 (≈ AES-192). Public key 1952
|
|
4
|
+
// bytes, signature 3309 bytes. Resistant to attacks by quantum computers.
|
|
5
|
+
//
|
|
6
|
+
// Isomorphic: works in Node and modern browsers. Returns Buffer on Node
|
|
7
|
+
// (backwards compatible), Uint8Array in browsers.
|
|
8
|
+
|
|
9
|
+
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'
|
|
10
|
+
import { deriveSeed } from './derive.js'
|
|
11
|
+
import { normalizeContext, MAX_CONTEXT_BYTES } from './_context.js'
|
|
12
|
+
|
|
13
|
+
export { MAX_CONTEXT_BYTES }
|
|
14
|
+
|
|
15
|
+
const HAS_BUFFER = typeof Buffer !== 'undefined'
|
|
16
|
+
const enc = new TextEncoder()
|
|
17
|
+
|
|
18
|
+
function toBytes(input) {
|
|
19
|
+
if (input instanceof Uint8Array) return input
|
|
20
|
+
if (typeof input === 'string') return enc.encode(input)
|
|
21
|
+
throw new Error('expected Uint8Array or string')
|
|
22
|
+
}
|
|
23
|
+
function hexToBytes(hex) {
|
|
24
|
+
if (typeof hex !== 'string' || hex.length % 2) throw new Error('invalid hex')
|
|
25
|
+
const b = new Uint8Array(hex.length / 2)
|
|
26
|
+
for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
27
|
+
return b
|
|
28
|
+
}
|
|
29
|
+
function bytesToHex(bytes) {
|
|
30
|
+
let s = ''
|
|
31
|
+
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0')
|
|
32
|
+
return s
|
|
33
|
+
}
|
|
34
|
+
function wrap(bytes) {
|
|
35
|
+
return HAS_BUFFER ? Buffer.from(bytes) : bytes
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate an ML-DSA-65 keypair from a master + domain-separation info.
|
|
40
|
+
*
|
|
41
|
+
* @returns {{ publicKey: Buffer|Uint8Array, secretKey: Buffer|Uint8Array }}
|
|
42
|
+
*/
|
|
43
|
+
export function keypairFromMaster(master, info = 'ml-dsa-65-v1') {
|
|
44
|
+
const seed = deriveSeed(master, info, 32)
|
|
45
|
+
const seedU8 = seed instanceof Uint8Array ? seed : new Uint8Array(seed)
|
|
46
|
+
const k = ml_dsa65.keygen(seedU8)
|
|
47
|
+
return {
|
|
48
|
+
publicKey: wrap(k.publicKey),
|
|
49
|
+
secretKey: wrap(k.secretKey),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sign a message. Returns the signature as a hex string.
|
|
55
|
+
*
|
|
56
|
+
* An optional FIPS 204 section 5.2 context string gives domain separation: a
|
|
57
|
+
* signature made under a context does not verify without it. Omit it and the
|
|
58
|
+
* behaviour is exactly as before this parameter existed.
|
|
59
|
+
*
|
|
60
|
+
* @param {Buffer|Uint8Array} secretKey
|
|
61
|
+
* @param {Buffer|Uint8Array|string} message
|
|
62
|
+
* @param {{ context?: Uint8Array|Buffer|string }} [opts] at most 255 context bytes
|
|
63
|
+
* @returns {string} hex-encoded signature (6618 chars)
|
|
64
|
+
*/
|
|
65
|
+
export function sign(secretKey, message, opts) {
|
|
66
|
+
const context = normalizeContext(opts)
|
|
67
|
+
const sig = context === undefined
|
|
68
|
+
? ml_dsa65.sign(toBytes(message), secretKey)
|
|
69
|
+
: ml_dsa65.sign(toBytes(message), secretKey, { context })
|
|
70
|
+
return bytesToHex(sig)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Verify a hex-encoded signature.
|
|
75
|
+
*
|
|
76
|
+
* Pass the same context the signer used. A signature made under a context
|
|
77
|
+
* returns false here if the context is omitted or differs, which is the point
|
|
78
|
+
* of it.
|
|
79
|
+
*
|
|
80
|
+
* Returns false for any cryptographic failure. Throws only on caller misuse of
|
|
81
|
+
* `opts` (wrong type, or a context over 255 bytes), because that is a bug
|
|
82
|
+
* rather than a failed verification and should not be silently swallowed.
|
|
83
|
+
*
|
|
84
|
+
* @param {Buffer|Uint8Array} publicKey
|
|
85
|
+
* @param {Buffer|Uint8Array|string} message
|
|
86
|
+
* @param {string} sigHex
|
|
87
|
+
* @param {{ context?: Uint8Array|Buffer|string }} [opts]
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
export function verify(publicKey, message, sigHex, opts) {
|
|
91
|
+
// Outside the try: misuse must surface, not be swallowed as "invalid".
|
|
92
|
+
const context = normalizeContext(opts)
|
|
93
|
+
try {
|
|
94
|
+
return context === undefined
|
|
95
|
+
? ml_dsa65.verify(hexToBytes(sigHex), toBytes(message), publicKey)
|
|
96
|
+
: ml_dsa65.verify(hexToBytes(sigHex), toBytes(message), publicKey, { context })
|
|
97
|
+
} catch {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ml_dsa65 }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
export interface MlKem1024Keypair {
|
|
4
|
+
/** 1568-byte public key */
|
|
5
|
+
publicKey: Buffer
|
|
6
|
+
/** 3168-byte secret key */
|
|
7
|
+
secretKey: Buffer
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MlKem1024Encapsulation {
|
|
11
|
+
/** 1568-byte KEM ciphertext to transmit to the recipient */
|
|
12
|
+
ciphertext: Buffer
|
|
13
|
+
/** Alias for `ciphertext` (mirrors @noble/post-quantum's camelCase) */
|
|
14
|
+
cipherText: Buffer
|
|
15
|
+
/** 32-byte shared secret; run it through a KDF before use as a key */
|
|
16
|
+
sharedSecret: Buffer
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate an ML-KEM-1024 (NIST FIPS 203) keypair deterministically
|
|
21
|
+
* from a master + domain-separation info string.
|
|
22
|
+
*
|
|
23
|
+
* Security Category 5. The default `info` differs from the ML-KEM-768 module's,
|
|
24
|
+
* so one master yields unrelated keys for the two parameter sets.
|
|
25
|
+
*
|
|
26
|
+
* CNSA 2.0 names ML-KEM-1024. Support for the parameter set is not a CNSA 2.0
|
|
27
|
+
* compliance claim; see the note at the top of `ml-kem-1024.js`.
|
|
28
|
+
*/
|
|
29
|
+
export function keypairFromMaster(
|
|
30
|
+
master: Buffer | Uint8Array,
|
|
31
|
+
info?: string,
|
|
32
|
+
): MlKem1024Keypair
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Encapsulate a shared secret to the recipient's ML-KEM-1024 public key.
|
|
36
|
+
*
|
|
37
|
+
* The recipient calls `decapsulate(ciphertext, secretKey)` to recover
|
|
38
|
+
* the same shared secret. Derive the symmetric key from it with a KDF rather
|
|
39
|
+
* than using it directly.
|
|
40
|
+
*/
|
|
41
|
+
export function encapsulate(publicKey: Buffer | Uint8Array): MlKem1024Encapsulation
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Decapsulate: recover the shared secret from a KEM ciphertext
|
|
45
|
+
* using the recipient's secret key.
|
|
46
|
+
*
|
|
47
|
+
* A corrupted ciphertext returns an unrelated 32-byte secret rather than
|
|
48
|
+
* throwing (FIPS 203 implicit rejection), so a successful return is not
|
|
49
|
+
* evidence the ciphertext was authentic.
|
|
50
|
+
*/
|
|
51
|
+
export function decapsulate(
|
|
52
|
+
ciphertext: Buffer | Uint8Array,
|
|
53
|
+
secretKey: Buffer | Uint8Array,
|
|
54
|
+
): Buffer
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Raw `@noble/post-quantum` ML-KEM-1024 primitive, re-exported.
|
|
58
|
+
*/
|
|
59
|
+
export const ml_kem1024: typeof import('@noble/post-quantum/ml-kem.js').ml_kem1024
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ML-KEM-1024 helpers (NIST FIPS 203, Kyber1024).
|
|
2
|
+
//
|
|
3
|
+
// Module-lattice key encapsulation. Security Category 5 (≈ AES-256). Public
|
|
4
|
+
// key 1568 bytes, ciphertext 1568 bytes, shared secret 32 bytes. Resistant to
|
|
5
|
+
// attacks by quantum computers.
|
|
6
|
+
//
|
|
7
|
+
// Same API as ./ml-kem.js, one security category higher. Use this where a
|
|
8
|
+
// counterparty specifies Category 5 or names ML-KEM-1024. ML-KEM-768 remains
|
|
9
|
+
// the default for the KXCO stack.
|
|
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-KEM-1024 for National Security Systems. Exporting this
|
|
18
|
+
// module makes that parameter set available to callers. It does not make any
|
|
19
|
+
// deployed system CNSA 2.0 compliant, and it must not be cited as a compliance
|
|
20
|
+
// badge. Compliance is a property of a deployment, not of an available
|
|
21
|
+
// function. The accurate sentence is "supports ML-KEM-1024".
|
|
22
|
+
// See CONFORMANCE.md.
|
|
23
|
+
//
|
|
24
|
+
// The shared secret is 32 bytes at both parameter sets, so moving up does not
|
|
25
|
+
// change key-derivation code downstream. Public keys and ciphertexts do grow:
|
|
26
|
+
// 1568 bytes each, against 1184 and 1088 at ML-KEM-768.
|
|
27
|
+
//
|
|
28
|
+
// As with ML-KEM-768: do not use the returned shared secret as a key directly.
|
|
29
|
+
// Run it through a KDF with a context label. That is what deriveSeed is for.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
import { ml_kem1024 } from '@noble/post-quantum/ml-kem.js'
|
|
33
|
+
import { deriveSeed } from './derive.js'
|
|
34
|
+
|
|
35
|
+
const HAS_BUFFER = typeof Buffer !== 'undefined'
|
|
36
|
+
|
|
37
|
+
function wrap(bytes) {
|
|
38
|
+
return HAS_BUFFER ? Buffer.from(bytes) : bytes
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate an ML-KEM-1024 keypair from a master + domain-separation info.
|
|
43
|
+
*
|
|
44
|
+
* The default info differs from the ML-KEM-768 default, so the same master
|
|
45
|
+
* yields unrelated keys for the two parameter sets rather than colliding.
|
|
46
|
+
*
|
|
47
|
+
* @returns {{ publicKey: Buffer|Uint8Array, secretKey: Buffer|Uint8Array }}
|
|
48
|
+
*/
|
|
49
|
+
export function keypairFromMaster(master, info = 'ml-kem-1024-v1') {
|
|
50
|
+
const seed = deriveSeed(master, info, 64)
|
|
51
|
+
const seedU8 = seed instanceof Uint8Array ? seed : new Uint8Array(seed)
|
|
52
|
+
const k = ml_kem1024.keygen(seedU8)
|
|
53
|
+
return {
|
|
54
|
+
publicKey: wrap(k.publicKey),
|
|
55
|
+
secretKey: wrap(k.secretKey),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Encapsulate a shared secret to the recipient's public key.
|
|
61
|
+
*
|
|
62
|
+
* @param {Buffer|Uint8Array} publicKey
|
|
63
|
+
* @returns {{ ciphertext: Buffer|Uint8Array, cipherText: Buffer|Uint8Array, sharedSecret: Buffer|Uint8Array }}
|
|
64
|
+
*/
|
|
65
|
+
export function encapsulate(publicKey) {
|
|
66
|
+
const r = ml_kem1024.encapsulate(publicKey)
|
|
67
|
+
const ct = wrap(r.cipherText ?? r.ciphertext)
|
|
68
|
+
return {
|
|
69
|
+
ciphertext: ct,
|
|
70
|
+
cipherText: ct,
|
|
71
|
+
sharedSecret: wrap(r.sharedSecret),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Decapsulate: recover the shared secret from a ciphertext using the secret key.
|
|
77
|
+
*
|
|
78
|
+
* A corrupted ciphertext yields an unrelated 32-byte secret rather than an
|
|
79
|
+
* error, which is FIPS 203 implicit rejection and is deliberate. Never treat a
|
|
80
|
+
* successful return as proof the ciphertext was authentic.
|
|
81
|
+
*
|
|
82
|
+
* @param {Buffer|Uint8Array} ciphertext
|
|
83
|
+
* @param {Buffer|Uint8Array} secretKey
|
|
84
|
+
* @returns {Buffer|Uint8Array} shared secret (32 bytes)
|
|
85
|
+
*/
|
|
86
|
+
export function decapsulate(ciphertext, secretKey) {
|
|
87
|
+
return wrap(ml_kem1024.decapsulate(ciphertext, secretKey))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { ml_kem1024 }
|
package/src/ml-kem.d.ts
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
export interface MlKemKeypair {
|
|
4
|
-
/** 1184-byte public key */
|
|
5
|
-
publicKey: Buffer
|
|
6
|
-
/** 2400-byte secret key */
|
|
7
|
-
secretKey: Buffer
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface MlKemEncapsulation {
|
|
11
|
-
/** 1088-byte KEM ciphertext to transmit to the recipient */
|
|
12
|
-
ciphertext: Buffer
|
|
13
|
-
/** Alias for `ciphertext` (mirrors @noble/post-quantum's camelCase) */
|
|
14
|
-
cipherText: Buffer
|
|
15
|
-
/** 32-byte shared secret to use as a symmetric AEAD key */
|
|
16
|
-
sharedSecret: Buffer
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Generate an ML-KEM-768 (NIST FIPS 203) keypair deterministically
|
|
21
|
-
* from a master + domain-separation info string.
|
|
22
|
-
*/
|
|
23
|
-
export function keypairFromMaster(
|
|
24
|
-
master: Buffer | Uint8Array,
|
|
25
|
-
info?: string,
|
|
26
|
-
): MlKemKeypair
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Encapsulate a shared secret to the recipient's ML-KEM-768 public key.
|
|
30
|
-
*
|
|
31
|
-
* The recipient calls `decapsulate(ciphertext, secretKey)` to recover
|
|
32
|
-
* the same shared secret. Use the shared secret as a symmetric AEAD
|
|
33
|
-
* key (eg. AES-256-GCM).
|
|
34
|
-
*/
|
|
35
|
-
export function encapsulate(publicKey: Buffer | Uint8Array): MlKemEncapsulation
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Decapsulate: recover the shared secret from a KEM ciphertext
|
|
39
|
-
* using the recipient's secret key.
|
|
40
|
-
*/
|
|
41
|
-
export function decapsulate(
|
|
42
|
-
ciphertext: Buffer | Uint8Array,
|
|
43
|
-
secretKey: Buffer | Uint8Array,
|
|
44
|
-
): Buffer
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Raw `@noble/post-quantum` ML-KEM-768 primitive, re-exported.
|
|
48
|
-
*/
|
|
49
|
-
export const ml_kem768: typeof import('@noble/post-quantum/ml-kem.js').ml_kem768
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
export interface MlKemKeypair {
|
|
4
|
+
/** 1184-byte public key */
|
|
5
|
+
publicKey: Buffer
|
|
6
|
+
/** 2400-byte secret key */
|
|
7
|
+
secretKey: Buffer
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MlKemEncapsulation {
|
|
11
|
+
/** 1088-byte KEM ciphertext to transmit to the recipient */
|
|
12
|
+
ciphertext: Buffer
|
|
13
|
+
/** Alias for `ciphertext` (mirrors @noble/post-quantum's camelCase) */
|
|
14
|
+
cipherText: Buffer
|
|
15
|
+
/** 32-byte shared secret to use as a symmetric AEAD key */
|
|
16
|
+
sharedSecret: Buffer
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate an ML-KEM-768 (NIST FIPS 203) keypair deterministically
|
|
21
|
+
* from a master + domain-separation info string.
|
|
22
|
+
*/
|
|
23
|
+
export function keypairFromMaster(
|
|
24
|
+
master: Buffer | Uint8Array,
|
|
25
|
+
info?: string,
|
|
26
|
+
): MlKemKeypair
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Encapsulate a shared secret to the recipient's ML-KEM-768 public key.
|
|
30
|
+
*
|
|
31
|
+
* The recipient calls `decapsulate(ciphertext, secretKey)` to recover
|
|
32
|
+
* the same shared secret. Use the shared secret as a symmetric AEAD
|
|
33
|
+
* key (eg. AES-256-GCM).
|
|
34
|
+
*/
|
|
35
|
+
export function encapsulate(publicKey: Buffer | Uint8Array): MlKemEncapsulation
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Decapsulate: recover the shared secret from a KEM ciphertext
|
|
39
|
+
* using the recipient's secret key.
|
|
40
|
+
*/
|
|
41
|
+
export function decapsulate(
|
|
42
|
+
ciphertext: Buffer | Uint8Array,
|
|
43
|
+
secretKey: Buffer | Uint8Array,
|
|
44
|
+
): Buffer
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Raw `@noble/post-quantum` ML-KEM-768 primitive, re-exported.
|
|
48
|
+
*/
|
|
49
|
+
export const ml_kem768: typeof import('@noble/post-quantum/ml-kem.js').ml_kem768
|