@maatara/core-pqc 0.2.3 → 0.3.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/README.md +196 -196
- package/dist/index.js +222 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -2
- package/dist/index.mjs.map +1 -1
- package/dist/src/index.d.ts +129 -0
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +50 -50
- package/dist/index.d.ts +0 -80
- package/dist/index.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,196 +1,196 @@
|
|
|
1
|
-
# Ma'atara Core PQC Toolkit
|
|
2
|
-
A portable, audited Post-Quantum Cryptography toolkit for browser and Node.js environments.
|
|
3
|
-
|
|
4
|
-
## Features
|
|
5
|
-
# Ma'atara Core PQC Toolkit
|
|
6
|
-
|
|
7
|
-
A portable, audited Post-Quantum Cryptography toolkit for browser and Node.js. Includes deterministic helpers for governance multisig and NFT/art token flows.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- Kyber ML-KEM-768 (key encapsulation)
|
|
12
|
-
- Dilithium2 (sign/verify)
|
|
13
|
-
- HKDF-SHA256 (key derivation)
|
|
14
|
-
- AES-256-GCM (AEAD)
|
|
15
|
-
- Base64url helpers and constant‑time compare
|
|
16
|
-
- Deterministic preimage builders (JCS) for governance, mint, transfer, and anchor
|
|
17
|
-
- Royalty validation and multisig attestation verification
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @maatara/core-pqc
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Initialize WASM once on startup:
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import { initWasm } from '@maatara/core-pqc';
|
|
29
|
-
await initWasm();
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Core PQC usage
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import {
|
|
36
|
-
kyberKeygen, kyberEncaps, kyberDecaps,
|
|
37
|
-
dilithiumKeygen, dilithiumSign, dilithiumVerify,
|
|
38
|
-
hkdfSha256, aesGcmWrap, aesGcmUnwrap, b64uEncode
|
|
39
|
-
} from '@maatara/core-pqc';
|
|
40
|
-
|
|
41
|
-
await initWasm();
|
|
42
|
-
|
|
43
|
-
// Kyber
|
|
44
|
-
const { public_b64u, secret_b64u } = await kyberKeygen();
|
|
45
|
-
const { kem_ct_b64u, shared_b64u } = await kyberEncaps(public_b64u);
|
|
46
|
-
const { shared_b64u: shared2 } = await kyberDecaps(secret_b64u, kem_ct_b64u);
|
|
47
|
-
|
|
48
|
-
// Dilithium
|
|
49
|
-
const signKeys = await dilithiumKeygen();
|
|
50
|
-
const msg = b64uEncode(new TextEncoder().encode('hello pqc'));
|
|
51
|
-
const sig = await dilithiumSign(msg, signKeys.secret_b64u);
|
|
52
|
-
const ok = await dilithiumVerify(msg, sig.signature_b64u, signKeys.public_b64u);
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Deterministic helpers (multisig and NFT)
|
|
56
|
-
|
|
57
|
-
All helpers return canonical JSON and a base64url message suitable for signing with Dilithium2.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import {
|
|
61
|
-
jcsCanonicalize,
|
|
62
|
-
buildPolicyPreimage,
|
|
63
|
-
buildMintPreimage,
|
|
64
|
-
buildTransferPreimage,
|
|
65
|
-
buildAnchorPreimage,
|
|
66
|
-
validateRoyalty,
|
|
67
|
-
verifyAttestations,
|
|
68
|
-
dilithiumSign
|
|
69
|
-
} from '@maatara/core-pqc';
|
|
70
|
-
|
|
71
|
-
// 1) Governance policy (multisig)
|
|
72
|
-
const policy = { version: 'v1', threshold: 2, signers: [{ id: 'a', publicKeyB64u: '...' }, { id: 'b', publicKeyB64u: '...' }] };
|
|
73
|
-
const { canonical: govCanon, msg_b64u: govMsg } = await buildPolicyPreimage(policy);
|
|
74
|
-
const att = await dilithiumSign(govMsg!, signerSecret);
|
|
75
|
-
|
|
76
|
-
// 2) Mint preimage (royalty optional)
|
|
77
|
-
const header = { createdAt: Date.now(), royalty: { receiver: '0xabc...', bps: 500 } };
|
|
78
|
-
const asset = { id: 'uuid', sha256: '...', mediaType: 'image/png' };
|
|
79
|
-
if (!await validateRoyalty(header.royalty.receiver, header.royalty.bps)) throw new Error('Bad royalty');
|
|
80
|
-
const { msg_b64u: mintMsg } = await buildMintPreimage(header, asset);
|
|
81
|
-
const mintSig = await dilithiumSign(mintMsg!, signerSecret);
|
|
82
|
-
|
|
83
|
-
// 3) Transfer preimage
|
|
84
|
-
const { msg_b64u: xferMsg } = await buildTransferPreimage({ assetId: 'uuid', to: 'did:key:...' });
|
|
85
|
-
const xferSig = await dilithiumSign(xferMsg!, signerSecret);
|
|
86
|
-
|
|
87
|
-
// 4) Anchor preimage (Merkle root + chains)
|
|
88
|
-
const { msg_b64u: anchorMsg } = await buildAnchorPreimage('user-123', 'deadbeef...', '2025-01', { apf: ['root1','root2'] });
|
|
89
|
-
|
|
90
|
-
// Verify a set of governance attestations for a canonical message
|
|
91
|
-
const validCount = await verifyAttestations(govMsg!, [
|
|
92
|
-
{ alg: 'dilithium2', publicKeyB64u: signerPub1, signatureB64u: sig1.signature_b64u },
|
|
93
|
-
{ alg: 'dilithium2', publicKeyB64u: signerPub2, signatureB64u: sig2.signature_b64u }
|
|
94
|
-
], [signerPub1, signerPub2]);
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Notes app integration (independent repo)
|
|
98
|
-
|
|
99
|
-
Use the deterministic helpers to prepare messages client‑side, sign, and POST to the Core Worker endpoints.
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import {
|
|
103
|
-
initWasm,
|
|
104
|
-
buildMintPreimage,
|
|
105
|
-
dilithiumSign,
|
|
106
|
-
verifyAttestations
|
|
107
|
-
} from '@maatara/core-pqc';
|
|
108
|
-
|
|
109
|
-
await initWasm();
|
|
110
|
-
|
|
111
|
-
// Build preimage and sign
|
|
112
|
-
const { msg_b64u } = await buildMintPreimage(header, asset);
|
|
113
|
-
const sig = await dilithiumSign(msg_b64u!, userSecret_b64u);
|
|
114
|
-
const attestation = { alg: 'dilithium2', publicKeyB64u: userPublic_b64u, signatureB64u: sig.signature_b64u };
|
|
115
|
-
|
|
116
|
-
// Optionally validate locally before submit
|
|
117
|
-
const valid = await verifyAttestations(msg_b64u!, [attestation], [userPublic_b64u]);
|
|
118
|
-
if (!valid) throw new Error('Attestation failed');
|
|
119
|
-
|
|
120
|
-
// Submit to Core
|
|
121
|
-
await fetch(`${CORE_URL}/api/assets/mint`, {
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: { 'content-type': 'application/json' },
|
|
124
|
-
body: JSON.stringify({ header, asset, attestations: [attestation] })
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Build from source
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# Build npm wrapper
|
|
132
|
-
cd packages/core-pqc
|
|
133
|
-
npm run build
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
WASM bindings live in `@maatara/core-pqc-wasm` (Rust + wasm-bindgen). This package consumes those exports and provides a browser/Node‑friendly API.
|
|
137
|
-
|
|
138
|
-
## Security notes
|
|
139
|
-
|
|
140
|
-
- Zero‑knowledge: server never receives user secrets/DEKs
|
|
141
|
-
- Post‑quantum primitives (Kyber, Dilithium)
|
|
142
|
-
- Constant‑time operations where applicable
|
|
143
|
-
- Deterministic JSON canonicalization (JCS‑like) for signature stability
|
|
144
|
-
|
|
145
|
-
## License
|
|
146
|
-
|
|
147
|
-
Apache‑2.0
|
|
148
|
-
|
|
149
|
-
## Veritas Block Preimage (v2 JCS)
|
|
150
|
-
|
|
151
|
-
Blocks (Veritas chain) now use a canonical JCS JSON preimage for signatures (version 2). Legacy version 1 used a plain JSON.stringify of an object with insertion ordering. The new helper ensures deterministic ordering consistent with governance/asset preimages.
|
|
152
|
-
|
|
153
|
-
Helper:
|
|
154
|
-
```ts
|
|
155
|
-
import { buildBlockPreimage } from '@maatara/core-pqc';
|
|
156
|
-
|
|
157
|
-
const { canonical, msg_b64u } = await buildBlockPreimage({
|
|
158
|
-
index: block.index.toString(), // string (bigint safe)
|
|
159
|
-
timestamp: block.timestamp.toString(),
|
|
160
|
-
previousHash: block.previousHash,
|
|
161
|
-
dataHash: block.dataHash,
|
|
162
|
-
metadataHash: block.metadataHash,
|
|
163
|
-
signatureAlg: 'dilithium2',
|
|
164
|
-
ownerPublicKey: dilithiumPublicKeyB64u,
|
|
165
|
-
contentType: block.contentType || 'application/json',
|
|
166
|
-
version: 2,
|
|
167
|
-
});
|
|
168
|
-
// Sign msg_b64u with Dilithium secret
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Canonical object fields included (lexicographically ordered by JCS):
|
|
172
|
-
- contentType
|
|
173
|
-
- dataHash
|
|
174
|
-
- index (string)
|
|
175
|
-
- metadataHash
|
|
176
|
-
- ownerPublicKey (Dilithium public key, base64url)
|
|
177
|
-
- previousHash
|
|
178
|
-
- signatureAlg
|
|
179
|
-
- timestamp (string)
|
|
180
|
-
- version
|
|
181
|
-
|
|
182
|
-
Excluded: signature, encryptionKeyHash, any transient fields.
|
|
183
|
-
|
|
184
|
-
Message to sign: `base64url( UTF-8(canonical JSON) )`. No hashing layer (if you need prehash use Dilithium pre-hash mode externally before signature—current flow signs raw canonical bytes).
|
|
185
|
-
|
|
186
|
-
### Migration Notes (v1 -> v2)
|
|
187
|
-
- Existing blocks signed under legacy scheme still verify: server falls back to legacy ordering if JCS verification fails.
|
|
188
|
-
- New blocks should set `version: 2` and use Dilithium key for `ownerPublicKey` (not the userId hash) to assert authorship.
|
|
189
|
-
- Enable server debug logging of preimage by setting env `DEBUG_PREIMAGE=1`.
|
|
190
|
-
- Future breaking change removal: once all clients migrate, legacy fallback can be removed.
|
|
191
|
-
|
|
192
|
-
### Client Checklist
|
|
193
|
-
1. Always stringify bigint counters to decimal strings before building the preimage.
|
|
194
|
-
2. Provide the true Dilithium public key as `ownerPublicKey`.
|
|
195
|
-
3. Do not inject extra fields; they will be ignored (and change canonical ordering if you wrongly sign them locally).
|
|
196
|
-
4. Ensure base64url is unpadded (`-` / `_` substitutions). The helper already does this.
|
|
1
|
+
# Ma'atara Core PQC Toolkit
|
|
2
|
+
A portable, audited Post-Quantum Cryptography toolkit for browser and Node.js environments.
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
# Ma'atara Core PQC Toolkit
|
|
6
|
+
|
|
7
|
+
A portable, audited Post-Quantum Cryptography toolkit for browser and Node.js. Includes deterministic helpers for governance multisig and NFT/art token flows.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Kyber ML-KEM-768 (key encapsulation)
|
|
12
|
+
- Dilithium2 (sign/verify)
|
|
13
|
+
- HKDF-SHA256 (key derivation)
|
|
14
|
+
- AES-256-GCM (AEAD)
|
|
15
|
+
- Base64url helpers and constant‑time compare
|
|
16
|
+
- Deterministic preimage builders (JCS) for governance, mint, transfer, and anchor
|
|
17
|
+
- Royalty validation and multisig attestation verification
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @maatara/core-pqc
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Initialize WASM once on startup:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { initWasm } from '@maatara/core-pqc';
|
|
29
|
+
await initWasm();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core PQC usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
kyberKeygen, kyberEncaps, kyberDecaps,
|
|
37
|
+
dilithiumKeygen, dilithiumSign, dilithiumVerify,
|
|
38
|
+
hkdfSha256, aesGcmWrap, aesGcmUnwrap, b64uEncode
|
|
39
|
+
} from '@maatara/core-pqc';
|
|
40
|
+
|
|
41
|
+
await initWasm();
|
|
42
|
+
|
|
43
|
+
// Kyber
|
|
44
|
+
const { public_b64u, secret_b64u } = await kyberKeygen();
|
|
45
|
+
const { kem_ct_b64u, shared_b64u } = await kyberEncaps(public_b64u);
|
|
46
|
+
const { shared_b64u: shared2 } = await kyberDecaps(secret_b64u, kem_ct_b64u);
|
|
47
|
+
|
|
48
|
+
// Dilithium
|
|
49
|
+
const signKeys = await dilithiumKeygen();
|
|
50
|
+
const msg = b64uEncode(new TextEncoder().encode('hello pqc'));
|
|
51
|
+
const sig = await dilithiumSign(msg, signKeys.secret_b64u);
|
|
52
|
+
const ok = await dilithiumVerify(msg, sig.signature_b64u, signKeys.public_b64u);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Deterministic helpers (multisig and NFT)
|
|
56
|
+
|
|
57
|
+
All helpers return canonical JSON and a base64url message suitable for signing with Dilithium2.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import {
|
|
61
|
+
jcsCanonicalize,
|
|
62
|
+
buildPolicyPreimage,
|
|
63
|
+
buildMintPreimage,
|
|
64
|
+
buildTransferPreimage,
|
|
65
|
+
buildAnchorPreimage,
|
|
66
|
+
validateRoyalty,
|
|
67
|
+
verifyAttestations,
|
|
68
|
+
dilithiumSign
|
|
69
|
+
} from '@maatara/core-pqc';
|
|
70
|
+
|
|
71
|
+
// 1) Governance policy (multisig)
|
|
72
|
+
const policy = { version: 'v1', threshold: 2, signers: [{ id: 'a', publicKeyB64u: '...' }, { id: 'b', publicKeyB64u: '...' }] };
|
|
73
|
+
const { canonical: govCanon, msg_b64u: govMsg } = await buildPolicyPreimage(policy);
|
|
74
|
+
const att = await dilithiumSign(govMsg!, signerSecret);
|
|
75
|
+
|
|
76
|
+
// 2) Mint preimage (royalty optional)
|
|
77
|
+
const header = { createdAt: Date.now(), royalty: { receiver: '0xabc...', bps: 500 } };
|
|
78
|
+
const asset = { id: 'uuid', sha256: '...', mediaType: 'image/png' };
|
|
79
|
+
if (!await validateRoyalty(header.royalty.receiver, header.royalty.bps)) throw new Error('Bad royalty');
|
|
80
|
+
const { msg_b64u: mintMsg } = await buildMintPreimage(header, asset);
|
|
81
|
+
const mintSig = await dilithiumSign(mintMsg!, signerSecret);
|
|
82
|
+
|
|
83
|
+
// 3) Transfer preimage
|
|
84
|
+
const { msg_b64u: xferMsg } = await buildTransferPreimage({ assetId: 'uuid', to: 'did:key:...' });
|
|
85
|
+
const xferSig = await dilithiumSign(xferMsg!, signerSecret);
|
|
86
|
+
|
|
87
|
+
// 4) Anchor preimage (Merkle root + chains)
|
|
88
|
+
const { msg_b64u: anchorMsg } = await buildAnchorPreimage('user-123', 'deadbeef...', '2025-01', { apf: ['root1','root2'] });
|
|
89
|
+
|
|
90
|
+
// Verify a set of governance attestations for a canonical message
|
|
91
|
+
const validCount = await verifyAttestations(govMsg!, [
|
|
92
|
+
{ alg: 'dilithium2', publicKeyB64u: signerPub1, signatureB64u: sig1.signature_b64u },
|
|
93
|
+
{ alg: 'dilithium2', publicKeyB64u: signerPub2, signatureB64u: sig2.signature_b64u }
|
|
94
|
+
], [signerPub1, signerPub2]);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Notes app integration (independent repo)
|
|
98
|
+
|
|
99
|
+
Use the deterministic helpers to prepare messages client‑side, sign, and POST to the Core Worker endpoints.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
initWasm,
|
|
104
|
+
buildMintPreimage,
|
|
105
|
+
dilithiumSign,
|
|
106
|
+
verifyAttestations
|
|
107
|
+
} from '@maatara/core-pqc';
|
|
108
|
+
|
|
109
|
+
await initWasm();
|
|
110
|
+
|
|
111
|
+
// Build preimage and sign
|
|
112
|
+
const { msg_b64u } = await buildMintPreimage(header, asset);
|
|
113
|
+
const sig = await dilithiumSign(msg_b64u!, userSecret_b64u);
|
|
114
|
+
const attestation = { alg: 'dilithium2', publicKeyB64u: userPublic_b64u, signatureB64u: sig.signature_b64u };
|
|
115
|
+
|
|
116
|
+
// Optionally validate locally before submit
|
|
117
|
+
const valid = await verifyAttestations(msg_b64u!, [attestation], [userPublic_b64u]);
|
|
118
|
+
if (!valid) throw new Error('Attestation failed');
|
|
119
|
+
|
|
120
|
+
// Submit to Core
|
|
121
|
+
await fetch(`${CORE_URL}/api/assets/mint`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'content-type': 'application/json' },
|
|
124
|
+
body: JSON.stringify({ header, asset, attestations: [attestation] })
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Build from source
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Build npm wrapper
|
|
132
|
+
cd packages/core-pqc
|
|
133
|
+
npm run build
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
WASM bindings live in `@maatara/core-pqc-wasm` (Rust + wasm-bindgen). This package consumes those exports and provides a browser/Node‑friendly API.
|
|
137
|
+
|
|
138
|
+
## Security notes
|
|
139
|
+
|
|
140
|
+
- Zero‑knowledge: server never receives user secrets/DEKs
|
|
141
|
+
- Post‑quantum primitives (Kyber, Dilithium)
|
|
142
|
+
- Constant‑time operations where applicable
|
|
143
|
+
- Deterministic JSON canonicalization (JCS‑like) for signature stability
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Apache‑2.0
|
|
148
|
+
|
|
149
|
+
## Veritas Block Preimage (v2 JCS)
|
|
150
|
+
|
|
151
|
+
Blocks (Veritas chain) now use a canonical JCS JSON preimage for signatures (version 2). Legacy version 1 used a plain JSON.stringify of an object with insertion ordering. The new helper ensures deterministic ordering consistent with governance/asset preimages.
|
|
152
|
+
|
|
153
|
+
Helper:
|
|
154
|
+
```ts
|
|
155
|
+
import { buildBlockPreimage } from '@maatara/core-pqc';
|
|
156
|
+
|
|
157
|
+
const { canonical, msg_b64u } = await buildBlockPreimage({
|
|
158
|
+
index: block.index.toString(), // string (bigint safe)
|
|
159
|
+
timestamp: block.timestamp.toString(),
|
|
160
|
+
previousHash: block.previousHash,
|
|
161
|
+
dataHash: block.dataHash,
|
|
162
|
+
metadataHash: block.metadataHash,
|
|
163
|
+
signatureAlg: 'dilithium2',
|
|
164
|
+
ownerPublicKey: dilithiumPublicKeyB64u,
|
|
165
|
+
contentType: block.contentType || 'application/json',
|
|
166
|
+
version: 2,
|
|
167
|
+
});
|
|
168
|
+
// Sign msg_b64u with Dilithium secret
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Canonical object fields included (lexicographically ordered by JCS):
|
|
172
|
+
- contentType
|
|
173
|
+
- dataHash
|
|
174
|
+
- index (string)
|
|
175
|
+
- metadataHash
|
|
176
|
+
- ownerPublicKey (Dilithium public key, base64url)
|
|
177
|
+
- previousHash
|
|
178
|
+
- signatureAlg
|
|
179
|
+
- timestamp (string)
|
|
180
|
+
- version
|
|
181
|
+
|
|
182
|
+
Excluded: signature, encryptionKeyHash, any transient fields.
|
|
183
|
+
|
|
184
|
+
Message to sign: `base64url( UTF-8(canonical JSON) )`. No hashing layer (if you need prehash use Dilithium pre-hash mode externally before signature—current flow signs raw canonical bytes).
|
|
185
|
+
|
|
186
|
+
### Migration Notes (v1 -> v2)
|
|
187
|
+
- Existing blocks signed under legacy scheme still verify: server falls back to legacy ordering if JCS verification fails.
|
|
188
|
+
- New blocks should set `version: 2` and use Dilithium key for `ownerPublicKey` (not the userId hash) to assert authorship.
|
|
189
|
+
- Enable server debug logging of preimage by setting env `DEBUG_PREIMAGE=1`.
|
|
190
|
+
- Future breaking change removal: once all clients migrate, legacy fallback can be removed.
|
|
191
|
+
|
|
192
|
+
### Client Checklist
|
|
193
|
+
1. Always stringify bigint counters to decimal strings before building the preimage.
|
|
194
|
+
2. Provide the true Dilithium public key as `ownerPublicKey`.
|
|
195
|
+
3. Do not inject extra fields; they will be ignored (and change canonical ordering if you wrongly sign them locally).
|
|
196
|
+
4. Ensure base64url is unpadded (`-` / `_` substitutions). The helper already does this.
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,20 @@ var wasmPkg__namespace = /*#__PURE__*/_interopNamespaceDefault(wasmPkg);
|
|
|
23
23
|
|
|
24
24
|
// Ma'atara Core PQC Toolkit
|
|
25
25
|
// Post-Quantum Cryptography for Browser and Node.js
|
|
26
|
-
//
|
|
26
|
+
//
|
|
27
|
+
// SECURITY NOTICE: This library implements FIPS 204 (ML-DSA) and FIPS 203 (ML-KEM)
|
|
28
|
+
// post-quantum cryptographic algorithms. Key material should be:
|
|
29
|
+
// 1. Stored securely (encrypted at rest)
|
|
30
|
+
// 2. Transmitted only over secure channels
|
|
31
|
+
// 3. Zeroized after use when possible
|
|
32
|
+
// 4. Protected by hardware security modules for high-value operations
|
|
33
|
+
//
|
|
34
|
+
// See docs/WASM_SECURITY_MODEL.md for browser-specific security considerations.
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// SECURITY CONSTANTS
|
|
37
|
+
// ============================================================================
|
|
38
|
+
const MAX_MESSAGE_SIZE = 16 * 1024 * 1024; // 16MB - prevent memory exhaustion
|
|
39
|
+
const MAX_KEY_SIZE = 1024 * 1024; // 1MB - reasonable for PQC keys
|
|
27
40
|
const wasm_kyber_keygen = wasmPkg__namespace.kyber_keygen;
|
|
28
41
|
const wasm_kyber_encaps = wasmPkg__namespace.kyber_encaps;
|
|
29
42
|
const wasm_kyber_decaps = wasmPkg__namespace.kyber_decaps;
|
|
@@ -102,6 +115,11 @@ async function aesGcmUnwrap(keyB64u, ivB64u, ctB64u, aadB64u) {
|
|
|
102
115
|
return result;
|
|
103
116
|
}
|
|
104
117
|
// Dilithium functions
|
|
118
|
+
/**
|
|
119
|
+
* Generate a new ML-DSA-65 (Dilithium) keypair.
|
|
120
|
+
* SECURITY: Keys should be stored encrypted at rest.
|
|
121
|
+
* For high-value operations, consider hardware security modules.
|
|
122
|
+
*/
|
|
105
123
|
async function dilithiumKeygen() {
|
|
106
124
|
await initWasm();
|
|
107
125
|
const result = JSON.parse(wasm_dilithium_keygen());
|
|
@@ -109,14 +127,40 @@ async function dilithiumKeygen() {
|
|
|
109
127
|
throw new Error(result.error);
|
|
110
128
|
return result;
|
|
111
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Sign a message using ML-DSA-65 (Dilithium).
|
|
132
|
+
* @param messageB64u - Base64url-encoded message to sign
|
|
133
|
+
* @param secretB64u - Base64url-encoded secret key
|
|
134
|
+
* SECURITY: Secret key should be zeroized after use if possible.
|
|
135
|
+
*/
|
|
112
136
|
async function dilithiumSign(messageB64u, secretB64u) {
|
|
137
|
+
// Input validation - prevent memory exhaustion attacks
|
|
138
|
+
if (messageB64u.length > MAX_MESSAGE_SIZE) {
|
|
139
|
+
throw new Error(`Message exceeds maximum size of ${MAX_MESSAGE_SIZE} bytes`);
|
|
140
|
+
}
|
|
141
|
+
if (secretB64u.length > MAX_KEY_SIZE) {
|
|
142
|
+
throw new Error(`Secret key exceeds maximum size of ${MAX_KEY_SIZE} bytes`);
|
|
143
|
+
}
|
|
113
144
|
await initWasm();
|
|
114
145
|
const result = JSON.parse(wasm_dilithium_sign(messageB64u, secretB64u));
|
|
115
146
|
if (result.error)
|
|
116
147
|
throw new Error(result.error);
|
|
117
148
|
return result;
|
|
118
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Verify a ML-DSA-65 (Dilithium) signature.
|
|
152
|
+
* @param messageB64u - Base64url-encoded message
|
|
153
|
+
* @param signatureB64u - Base64url-encoded signature
|
|
154
|
+
* @param publicB64u - Base64url-encoded public key
|
|
155
|
+
*/
|
|
119
156
|
async function dilithiumVerify(messageB64u, signatureB64u, publicB64u) {
|
|
157
|
+
// Input validation - prevent memory exhaustion attacks
|
|
158
|
+
if (messageB64u.length > MAX_MESSAGE_SIZE) {
|
|
159
|
+
throw new Error(`Message exceeds maximum size of ${MAX_MESSAGE_SIZE} bytes`);
|
|
160
|
+
}
|
|
161
|
+
if (signatureB64u.length > MAX_KEY_SIZE || publicB64u.length > MAX_KEY_SIZE) {
|
|
162
|
+
throw new Error(`Signature or public key exceeds maximum size`);
|
|
163
|
+
}
|
|
120
164
|
await initWasm();
|
|
121
165
|
const result = JSON.parse(wasm_dilithium_verify(messageB64u, signatureB64u, publicB64u));
|
|
122
166
|
if (result.error)
|
|
@@ -241,6 +285,177 @@ function constantTimeEqual(a, b) {
|
|
|
241
285
|
}
|
|
242
286
|
return result === 0;
|
|
243
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Create an attestation for a threshold signature operation.
|
|
290
|
+
*/
|
|
291
|
+
async function createAttestation(operation, signerKeys, expiresInSeconds = 3600) {
|
|
292
|
+
await initWasm();
|
|
293
|
+
// Build deterministic preimage for the operation
|
|
294
|
+
const preimage = await jcsCanonicalize({ type: operation.type, payload: operation.payload });
|
|
295
|
+
const preimageBytes = new TextEncoder().encode(preimage);
|
|
296
|
+
const operationHash = await sha256B64u(preimageBytes);
|
|
297
|
+
const msgB64u = b64uEncode(preimageBytes);
|
|
298
|
+
// Sign the preimage
|
|
299
|
+
const sig = await dilithiumSign(msgB64u, signerKeys.secretKeyB64u);
|
|
300
|
+
return {
|
|
301
|
+
signerKeyId: signerKeys.keyId,
|
|
302
|
+
signerPublicKeyB64u: signerKeys.publicKeyB64u,
|
|
303
|
+
operationHash,
|
|
304
|
+
operationType: operation.type,
|
|
305
|
+
attestedAt: Date.now(),
|
|
306
|
+
expiresAt: Date.now() + expiresInSeconds * 1000,
|
|
307
|
+
signatureAlg: 'dilithium2',
|
|
308
|
+
signatureB64u: sig.signature_b64u
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Verify an attestation signature.
|
|
313
|
+
*/
|
|
314
|
+
async function verifyAttestation(attestation, operation) {
|
|
315
|
+
await initWasm();
|
|
316
|
+
// Rebuild expected operation hash
|
|
317
|
+
const preimage = await jcsCanonicalize({ type: operation.type, payload: operation.payload });
|
|
318
|
+
const preimageBytes = new TextEncoder().encode(preimage);
|
|
319
|
+
const expectedHash = await sha256B64u(preimageBytes);
|
|
320
|
+
if (attestation.operationHash !== expectedHash) {
|
|
321
|
+
return { valid: false, error: 'Operation hash mismatch' };
|
|
322
|
+
}
|
|
323
|
+
// Check expiry
|
|
324
|
+
if (attestation.expiresAt < Date.now()) {
|
|
325
|
+
return { valid: false, error: 'Attestation expired' };
|
|
326
|
+
}
|
|
327
|
+
// Verify signature
|
|
328
|
+
const msgB64u = b64uEncode(preimageBytes);
|
|
329
|
+
const result = await dilithiumVerify(msgB64u, attestation.signatureB64u, attestation.signerPublicKeyB64u);
|
|
330
|
+
if (!result.is_valid) {
|
|
331
|
+
return { valid: false, error: 'Invalid signature' };
|
|
332
|
+
}
|
|
333
|
+
return { valid: true };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Verify that an aggregated signature meets the threshold policy.
|
|
337
|
+
*/
|
|
338
|
+
async function verifyThreshold(aggregated, operation, policy) {
|
|
339
|
+
const errors = [];
|
|
340
|
+
// Check policy match
|
|
341
|
+
if (aggregated.policyId !== policy.policyId) {
|
|
342
|
+
errors.push('Policy ID mismatch');
|
|
343
|
+
}
|
|
344
|
+
// Verify each attestation and compute weight
|
|
345
|
+
let validWeight = 0;
|
|
346
|
+
for (const attestation of aggregated.attestations) {
|
|
347
|
+
const signer = policy.signers.find(s => s.publicKeyB64u === attestation.signerPublicKeyB64u);
|
|
348
|
+
if (!signer) {
|
|
349
|
+
errors.push(`Signer ${attestation.signerKeyId} not in policy`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const result = await verifyAttestation(attestation, operation);
|
|
353
|
+
if (!result.valid) {
|
|
354
|
+
errors.push(`Attestation from ${attestation.signerKeyId}: ${result.error}`);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
validWeight += signer.weight;
|
|
358
|
+
}
|
|
359
|
+
// Check threshold
|
|
360
|
+
if (validWeight < policy.threshold) {
|
|
361
|
+
errors.push(`Threshold not met: weight ${validWeight} < required ${policy.threshold}`);
|
|
362
|
+
}
|
|
363
|
+
return { valid: errors.length === 0, errors };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Create a provenance commitment for an asset before public disclosure.
|
|
367
|
+
* This establishes a timestamped claim without revealing the asset content.
|
|
368
|
+
*/
|
|
369
|
+
async function createProvenanceCommitment(assetBytes, keys) {
|
|
370
|
+
await initWasm();
|
|
371
|
+
// Generate random salt
|
|
372
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
373
|
+
// Compute commitment hash = SHA256(asset || salt || publicKey)
|
|
374
|
+
const publicKeyBytes = b64uDecode(keys.publicKeyB64u);
|
|
375
|
+
const preimage = new Uint8Array(assetBytes.length + salt.length + publicKeyBytes.length);
|
|
376
|
+
preimage.set(assetBytes, 0);
|
|
377
|
+
preimage.set(salt, assetBytes.length);
|
|
378
|
+
preimage.set(publicKeyBytes, assetBytes.length + salt.length);
|
|
379
|
+
const commitmentHash = await sha256B64u(preimage);
|
|
380
|
+
const assetHash = await sha256B64u(assetBytes);
|
|
381
|
+
// Build commitment object (without signature)
|
|
382
|
+
const commitmentData = {
|
|
383
|
+
type: 'ProvenanceCommitment',
|
|
384
|
+
version: 1,
|
|
385
|
+
commitmentHash,
|
|
386
|
+
creatorPublicKey: keys.publicKeyB64u,
|
|
387
|
+
timestamp: Date.now()
|
|
388
|
+
};
|
|
389
|
+
// Sign the commitment
|
|
390
|
+
const canonical = await jcsCanonicalize(commitmentData);
|
|
391
|
+
const sig = await dilithiumSign(b64uEncode(new TextEncoder().encode(canonical)), keys.secretKeyB64u);
|
|
392
|
+
const commitment = {
|
|
393
|
+
...commitmentData,
|
|
394
|
+
signature: sig.signature_b64u
|
|
395
|
+
};
|
|
396
|
+
const reveal = {
|
|
397
|
+
commitmentHash,
|
|
398
|
+
salt: b64uEncode(salt),
|
|
399
|
+
assetHash
|
|
400
|
+
};
|
|
401
|
+
return { commitment, reveal };
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Verify a provenance commitment signature.
|
|
405
|
+
*/
|
|
406
|
+
async function verifyProvenanceCommitment(commitment) {
|
|
407
|
+
await initWasm();
|
|
408
|
+
// Reconstruct the signed data (without signature field)
|
|
409
|
+
const commitmentData = {
|
|
410
|
+
type: commitment.type,
|
|
411
|
+
version: commitment.version,
|
|
412
|
+
commitmentHash: commitment.commitmentHash,
|
|
413
|
+
creatorPublicKey: commitment.creatorPublicKey,
|
|
414
|
+
timestamp: commitment.timestamp
|
|
415
|
+
};
|
|
416
|
+
const canonical = await jcsCanonicalize(commitmentData);
|
|
417
|
+
const msgB64u = b64uEncode(new TextEncoder().encode(canonical));
|
|
418
|
+
const result = await dilithiumVerify(msgB64u, commitment.signature, commitment.creatorPublicKey);
|
|
419
|
+
if (!result.is_valid) {
|
|
420
|
+
return { valid: false, error: 'Invalid commitment signature' };
|
|
421
|
+
}
|
|
422
|
+
return { valid: true };
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Verify that a revealed asset matches a commitment.
|
|
426
|
+
*/
|
|
427
|
+
async function verifyCommitmentReveal(commitment, assetBytes, reveal) {
|
|
428
|
+
await initWasm();
|
|
429
|
+
// First verify the commitment signature
|
|
430
|
+
const sigResult = await verifyProvenanceCommitment(commitment);
|
|
431
|
+
if (!sigResult.valid) {
|
|
432
|
+
return { valid: false, error: `Commitment signature invalid: ${sigResult.error}` };
|
|
433
|
+
}
|
|
434
|
+
// Verify asset hash matches
|
|
435
|
+
const computedAssetHash = await sha256B64u(assetBytes);
|
|
436
|
+
if (computedAssetHash !== reveal.assetHash) {
|
|
437
|
+
return { valid: false, error: 'Asset hash does not match reveal' };
|
|
438
|
+
}
|
|
439
|
+
// Reconstruct commitment hash
|
|
440
|
+
const salt = b64uDecode(reveal.salt);
|
|
441
|
+
const publicKeyBytes = b64uDecode(commitment.creatorPublicKey);
|
|
442
|
+
const preimage = new Uint8Array(assetBytes.length + salt.length + publicKeyBytes.length);
|
|
443
|
+
preimage.set(assetBytes, 0);
|
|
444
|
+
preimage.set(salt, assetBytes.length);
|
|
445
|
+
preimage.set(publicKeyBytes, assetBytes.length + salt.length);
|
|
446
|
+
const computedCommitmentHash = await sha256B64u(preimage);
|
|
447
|
+
if (computedCommitmentHash !== commitment.commitmentHash) {
|
|
448
|
+
return { valid: false, error: 'Commitment hash does not match reveal' };
|
|
449
|
+
}
|
|
450
|
+
return { valid: true };
|
|
451
|
+
}
|
|
452
|
+
// ============================================================================
|
|
453
|
+
// HELPER: SHA-256 with base64url output
|
|
454
|
+
// ============================================================================
|
|
455
|
+
async function sha256B64u(data) {
|
|
456
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
457
|
+
return b64uEncode(new Uint8Array(hashBuffer));
|
|
458
|
+
}
|
|
244
459
|
|
|
245
460
|
exports.aesGcmUnwrap = aesGcmUnwrap;
|
|
246
461
|
exports.aesGcmWrap = aesGcmWrap;
|
|
@@ -252,6 +467,8 @@ exports.buildMintPreimage = buildMintPreimage;
|
|
|
252
467
|
exports.buildPolicyPreimage = buildPolicyPreimage;
|
|
253
468
|
exports.buildTransferPreimage = buildTransferPreimage;
|
|
254
469
|
exports.constantTimeEqual = constantTimeEqual;
|
|
470
|
+
exports.createAttestation = createAttestation;
|
|
471
|
+
exports.createProvenanceCommitment = createProvenanceCommitment;
|
|
255
472
|
exports.dilithiumKeygen = dilithiumKeygen;
|
|
256
473
|
exports.dilithiumSign = dilithiumSign;
|
|
257
474
|
exports.dilithiumVerify = dilithiumVerify;
|
|
@@ -262,5 +479,9 @@ exports.kyberDecaps = kyberDecaps;
|
|
|
262
479
|
exports.kyberEncaps = kyberEncaps;
|
|
263
480
|
exports.kyberKeygen = kyberKeygen;
|
|
264
481
|
exports.validateRoyalty = validateRoyalty;
|
|
482
|
+
exports.verifyAttestation = verifyAttestation;
|
|
265
483
|
exports.verifyAttestations = verifyAttestations;
|
|
484
|
+
exports.verifyCommitmentReveal = verifyCommitmentReveal;
|
|
485
|
+
exports.verifyProvenanceCommitment = verifyProvenanceCommitment;
|
|
486
|
+
exports.verifyThreshold = verifyThreshold;
|
|
266
487
|
//# sourceMappingURL=index.js.map
|