@smartledger/envelope 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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AA8DpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAuE5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAwH7B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAEhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAEhF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAEtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAEjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,EACV,cAAc,EAAE,cAAc,CAAC,GAAG,CAAC,EACnC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,GAC/C,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAW5B"}
|
package/dist/cjs/envelope.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { canonicalize } from './jcs.js';
|
|
2
|
-
const ENVELOPE_VERSION = '
|
|
2
|
+
const ENVELOPE_VERSION = '3.0.0';
|
|
3
|
+
/**
|
|
4
|
+
* How the key SDK turned the signing bytes into what the key signed, by
|
|
5
|
+
* envelope version. Envelopes before 3.0 were signed through @smartledger/keys
|
|
6
|
+
* 3.x, where an ML-DSA key signed SHA3-256 of the bytes; 3.0 envelopes are
|
|
7
|
+
* signed through keys 4.x, where it signs the bytes themselves under a
|
|
8
|
+
* context string. The version is inside the signed metadata, so it cannot be
|
|
9
|
+
* relabelled to pick the weaker rule. (ECDSA signatures are the same either way.)
|
|
10
|
+
*/
|
|
11
|
+
function signatureScheme(major) {
|
|
12
|
+
return major === 3 ? 'v4' : 'v3-digest';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The major version of an envelope, or null if `meta.version` is not exactly
|
|
16
|
+
* `MAJOR.MINOR.PATCH` with a major this package knows. Nothing is guessed: a
|
|
17
|
+
* missing version, `v3.0.0`, ` 3.0.0` or `3` is refused rather than read as
|
|
18
|
+
* some version, because the version decides which signing rule applies.
|
|
19
|
+
*/
|
|
20
|
+
function envelopeMajor(meta) {
|
|
21
|
+
const m = typeof meta?.version === 'string' ? /^([1-3])\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.exec(meta.version) : null;
|
|
22
|
+
return m ? Number(m[1]) : null;
|
|
23
|
+
}
|
|
3
24
|
/** Suites whose security does not rest on factoring or discrete logarithms. */
|
|
4
25
|
function isPostQuantumSuite(suiteId) {
|
|
5
26
|
return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);
|
|
@@ -146,6 +167,15 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
146
167
|
error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,
|
|
147
168
|
};
|
|
148
169
|
}
|
|
170
|
+
// The version decides how the bytes were built and what the key signed,
|
|
171
|
+
// so an envelope without a version this package knows is refused outright.
|
|
172
|
+
const major = envelopeMajor(envelope.meta);
|
|
173
|
+
if (major === null) {
|
|
174
|
+
return {
|
|
175
|
+
valid: false,
|
|
176
|
+
error: `Unsupported envelope version: ${JSON.stringify(envelope.meta?.version)}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
149
179
|
// Rebuild the signed bytes the way the envelope's own version says they
|
|
150
180
|
// were built, so v1 envelopes keep verifying unchanged.
|
|
151
181
|
const message = signingBytes(envelope.payload, envelope.meta);
|
|
@@ -158,7 +188,8 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
158
188
|
error: 'No keyId in signature metadata',
|
|
159
189
|
};
|
|
160
190
|
}
|
|
161
|
-
const
|
|
191
|
+
const scheme = signatureScheme(major);
|
|
192
|
+
const valid = await sdk.verifySignature(keyId, message, signature, { scheme });
|
|
162
193
|
if (!valid) {
|
|
163
194
|
return {
|
|
164
195
|
valid: false,
|
|
@@ -196,7 +227,7 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
196
227
|
error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,
|
|
197
228
|
};
|
|
198
229
|
}
|
|
199
|
-
const pqValid = (await sdk.verifySignature(pq.keyId, message, Buffer.from(pq.signature, 'base64'))) === true;
|
|
230
|
+
const pqValid = (await sdk.verifySignature(pq.keyId, message, Buffer.from(pq.signature, 'base64'), { scheme })) === true;
|
|
200
231
|
if (!pqValid) {
|
|
201
232
|
return { valid: false, error: 'Post-quantum co-signature verification failed' };
|
|
202
233
|
}
|
package/dist/cjs/envelope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { KeySDK } from '@smartledger/keys';\nimport type {\n SignedEnvelope,\n CreateEnvelopeOptions,\n VerifyEnvelopeOptions,\n VerificationResult,\n EnvelopeMetadata,\n SignatureMetadata,\n} from './types.js';\nimport { canonicalize } from './jcs.js';\n\nconst ENVELOPE_VERSION = '2.0.0';\n\n/** Suites whose security does not rest on factoring or discrete logarithms. */\nfunction isPostQuantumSuite(suiteId: string): boolean {\n return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);\n}\n\n/**\n * How the bytes a signature covers are built, per envelope version.\n *\n * v1 used `JSON.stringify({ payload, meta })` with no canonicalization, so the\n * signed bytes depended on the insertion order of ordinary string keys. Two\n * semantically identical payloads built in different orders signed differently:\n *\n * { name: 'x', total: 1 } -> {\"payload\":{\"name\":\"x\",\"total\":1},…}\n * { total: 1, name: 'x' } -> {\"payload\":{\"total\":1,\"name\":\"x\"},…}\n *\n * Signing and verifying the same object graph in one process agreed, and a\n * JSON round-trip through V8 happened to be stable, which is why this held up in\n * practice. It fails as soon as the value is rebuilt by a different code path —\n * read back from a database, mapped through a different shape, or produced by a\n * non-JavaScript implementation. `meta` was worse: it is assembled with\n * `...options.metadata`, so a caller's own key order leaked into the signature.\n *\n * v2 canonicalizes with RFC 8785 first. v1 envelopes keep verifying byte for\n * byte: `meta.version` selects the construction, so nothing already signed\n * changes meaning, and no envelope has to be re-signed.\n */\nfunction signingBytes(payload: unknown, meta: EnvelopeMetadata): Uint8Array {\n const isV1 = String(meta.version ?? '').startsWith('1.');\n const serialized = isV1\n ? JSON.stringify({ payload, meta })\n : canonicalize({ payload, meta });\n return new TextEncoder().encode(serialized);\n}\n\n/**\n * Create a signed envelope from payload\n * \n * @example\n * ```typescript\n * import { createKeySDK } from '@smartledger/keys';\n * import { createSignedEnvelope } from '@smartledger/envelope';\n * \n * const sdk = createKeySDK();\n * const key = await sdk.createKey('my-agent', { primarySignatureSuite: 'ml-dsa-87' });\n * \n * const envelope = await createSignedEnvelope(\n * sdk,\n * key.meta.keyId,\n * { message: \"Hello, world!\" },\n * { creator: 'my-agent', contentType: 'greeting' }\n * );\n * ```\n */\nexport async function createSignedEnvelope<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n options: CreateEnvelopeOptions\n): Promise<SignedEnvelope<T>> {\n const now = new Date().toISOString();\n \n // Build metadata\n const meta: EnvelopeMetadata = {\n version: ENVELOPE_VERSION,\n creator: options.creator,\n creatorType: options.creatorType,\n createdAt: now,\n contentType: options.contentType,\n anchor: options.anchor,\n parentId: options.parentId,\n ...options.metadata,\n };\n\n // Dual signing. The co-signer is declared inside `meta`, which the primary\n // signature covers, so the co-signature cannot be stripped: an envelope that\n // declares one and does not carry a valid one fails verification.\n let pqKey: Awaited<ReturnType<KeySDK['getKey']>> = null;\n if (options.dualSign) {\n if (!options.pqKeyId) {\n throw new Error('dualSign requires options.pqKeyId (the post-quantum co-signing key)');\n }\n pqKey = await sdk.getKey(options.pqKeyId);\n if (!pqKey) {\n throw new Error(`Key ${options.pqKeyId} not found`);\n }\n if (!isPostQuantumSuite(pqKey.meta.suiteId)) {\n throw new Error(\n `dualSign needs a post-quantum co-signing key; ${options.pqKeyId} is ${pqKey.meta.suiteId}`\n );\n }\n meta.coSigner = { algorithm: pqKey.meta.suiteId, keyId: options.pqKeyId };\n } else if (meta.coSigner !== undefined) {\n // Reserved: only this function may declare a co-signer.\n throw new Error('metadata.coSigner is reserved; use dualSign with pqKeyId');\n }\n\n const message = signingBytes(payload, meta);\n \n // Sign with primary key\n const signature = await sdk.signWithKey(keyId, message);\n const key = await sdk.getKey(keyId);\n \n if (!key) {\n throw new Error(`Key ${keyId} not found`);\n }\n \n const signatureMetadata: SignatureMetadata = {\n algorithm: key.meta.suiteId,\n keyId,\n publicKey: Buffer.from(key.publicKey).toString('base64'),\n signature: Buffer.from(signature).toString('base64'),\n timestamp: now,\n };\n \n if (pqKey && options.pqKeyId) {\n const pqSignature = await sdk.signWithKey(options.pqKeyId, message);\n signatureMetadata.pqSignature = {\n algorithm: pqKey.meta.suiteId,\n keyId: options.pqKeyId,\n publicKey: Buffer.from(pqKey.publicKey).toString('base64'),\n signature: Buffer.from(pqSignature).toString('base64'),\n };\n }\n \n return {\n payload,\n meta,\n _signature: signatureMetadata,\n };\n}\n\n/**\n * Verify a signed envelope\n * \n * @example\n * ```typescript\n * const result = await verifySignedEnvelope(sdk, envelope);\n * if (result.valid) {\n * console.log('Verified! Created by:', result.creator);\n * console.log('Payload:', envelope.payload);\n * }\n * ```\n */\nexport async function verifySignedEnvelope<T>(\n sdk: KeySDK,\n envelope: SignedEnvelope<T>,\n options: VerifyEnvelopeOptions = {}\n): Promise<VerificationResult> {\n try {\n // Check age if maxAge specified\n if (options.maxAge) {\n const signatureTime = new Date(envelope._signature.timestamp).getTime();\n const age = Date.now() - signatureTime;\n if (age > options.maxAge) {\n return {\n valid: false,\n error: `Signature too old: ${age}ms > ${options.maxAge}ms`,\n };\n }\n }\n \n // Check expected creator if specified\n if (options.expectedCreator && envelope.meta.creator !== options.expectedCreator) {\n return {\n valid: false,\n error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,\n };\n }\n \n // Rebuild the signed bytes the way the envelope's own version says they\n // were built, so v1 envelopes keep verifying unchanged.\n const message = signingBytes(envelope.payload, envelope.meta);\n const signature = Buffer.from(envelope._signature.signature, 'base64');\n \n // Verify signature\n const keyId = envelope._signature.keyId;\n if (!keyId) {\n return {\n valid: false,\n error: 'No keyId in signature metadata',\n };\n }\n \n const valid = await sdk.verifySignature(keyId, message, signature);\n \n if (!valid) {\n return {\n valid: false,\n error: 'Signature verification failed',\n };\n }\n \n // The post-quantum co-signature. `verified` reports 'both' only when the\n // co-signature was actually checked and is valid; this used to be set\n // whenever a pqSignature field was merely present.\n let verified: 'primary' | 'pq' | 'both' = 'primary';\n const declared = envelope.meta.coSigner as { algorithm?: unknown; keyId?: unknown } | undefined;\n const pq = envelope._signature.pqSignature;\n\n if (declared !== undefined && !pq) {\n return {\n valid: false,\n error: 'Envelope declares a co-signer but carries no co-signature',\n };\n }\n let pqVerified = false;\n if (pq && (declared !== undefined || options.verifyPQ || options.requirePQ)) {\n if (declared !== undefined && (declared.keyId !== pq.keyId || declared.algorithm !== pq.algorithm)) {\n return {\n valid: false,\n error: 'Co-signature does not match the co-signer declared in the signed metadata',\n };\n }\n const pqKey = pq.keyId ? await sdk.getKey(pq.keyId) : null;\n if (!pqKey) {\n return { valid: false, error: `Co-signing key not found: ${String(pq.keyId)}` };\n }\n // The algorithm comes from the verifier's key record, not the envelope.\n if (pqKey.meta.suiteId !== pq.algorithm || !isPostQuantumSuite(pqKey.meta.suiteId)) {\n return {\n valid: false,\n error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,\n };\n }\n const pqValid =\n (await sdk.verifySignature(pq.keyId!, message, Buffer.from(pq.signature, 'base64'))) === true;\n if (!pqValid) {\n return { valid: false, error: 'Post-quantum co-signature verification failed' };\n }\n pqVerified = true;\n verified = 'both';\n }\n\n if (options.requirePQ && !pqVerified) {\n const primaryKey = await sdk.getKey(keyId);\n if (!primaryKey || !isPostQuantumSuite(primaryKey.meta.suiteId)) {\n return {\n valid: false,\n error: 'A post-quantum signature is required and none was verified',\n };\n }\n }\n \n return {\n valid: true,\n verified,\n creator: envelope.meta.creator,\n timestamp: envelope._signature.timestamp,\n contentMatch: true,\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Extract payload from envelope without verification\n * Use only when verification was done previously\n */\nexport function extractPayload<T>(envelope: SignedEnvelope<T>): T {\n return envelope.payload;\n}\n\n/**\n * Get envelope metadata\n */\nexport function getEnvelopeMeta<T>(envelope: SignedEnvelope<T>): EnvelopeMetadata {\n return envelope.meta;\n}\n\n/**\n * Check if envelope has PQ signature\n */\nexport function hasPQSignature<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope._signature.pqSignature;\n}\n\n/**\n * Check if envelope has on-chain anchor\n */\nexport function hasAnchor<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope.meta.anchor;\n}\n\n/**\n * Create a chain of custody by linking envelopes\n * \n * @example\n * ```typescript\n * const original = await createSignedEnvelope(sdk, keyId, data, { creator: 'alice' });\n * const updated = await createEnvelopeChain(\n * sdk,\n * keyId,\n * updatedData,\n * original,\n * { creator: 'bob', contentType: 'revision' }\n * );\n * // updated.meta.parentId === hash(original)\n * ```\n */\nexport async function createEnvelopeChain<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n parentEnvelope: SignedEnvelope<any>,\n options: Omit<CreateEnvelopeOptions, 'parentId'>\n): Promise<SignedEnvelope<T>> {\n // Create a hash of parent envelope as parentId\n const parentJson = JSON.stringify(parentEnvelope);\n const parentHash = Buffer.from(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(parentJson))\n ).toString('hex');\n \n return createSignedEnvelope(sdk, keyId, payload, {\n ...options,\n parentId: parentHash,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { KeySDK } from '@smartledger/keys';\nimport type {\n SignedEnvelope,\n CreateEnvelopeOptions,\n VerifyEnvelopeOptions,\n VerificationResult,\n EnvelopeMetadata,\n SignatureMetadata,\n} from './types.js';\nimport { canonicalize } from './jcs.js';\n\nconst ENVELOPE_VERSION = '3.0.0';\n\n/**\n * How the key SDK turned the signing bytes into what the key signed, by\n * envelope version. Envelopes before 3.0 were signed through @smartledger/keys\n * 3.x, where an ML-DSA key signed SHA3-256 of the bytes; 3.0 envelopes are\n * signed through keys 4.x, where it signs the bytes themselves under a\n * context string. The version is inside the signed metadata, so it cannot be\n * relabelled to pick the weaker rule. (ECDSA signatures are the same either way.)\n */\nfunction signatureScheme(major: 1 | 2 | 3): 'v4' | 'v3-digest' {\n return major === 3 ? 'v4' : 'v3-digest';\n}\n\n/**\n * The major version of an envelope, or null if `meta.version` is not exactly\n * `MAJOR.MINOR.PATCH` with a major this package knows. Nothing is guessed: a\n * missing version, `v3.0.0`, ` 3.0.0` or `3` is refused rather than read as\n * some version, because the version decides which signing rule applies.\n */\nfunction envelopeMajor(meta: EnvelopeMetadata): 1 | 2 | 3 | null {\n const m = typeof meta?.version === 'string' ? /^([1-3])\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$/.exec(meta.version) : null;\n return m ? (Number(m[1]) as 1 | 2 | 3) : null;\n}\n\n/** Suites whose security does not rest on factoring or discrete logarithms. */\nfunction isPostQuantumSuite(suiteId: string): boolean {\n return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);\n}\n\n/**\n * How the bytes a signature covers are built, per envelope version.\n *\n * v1 used `JSON.stringify({ payload, meta })` with no canonicalization, so the\n * signed bytes depended on the insertion order of ordinary string keys. Two\n * semantically identical payloads built in different orders signed differently:\n *\n * { name: 'x', total: 1 } -> {\"payload\":{\"name\":\"x\",\"total\":1},…}\n * { total: 1, name: 'x' } -> {\"payload\":{\"total\":1,\"name\":\"x\"},…}\n *\n * Signing and verifying the same object graph in one process agreed, and a\n * JSON round-trip through V8 happened to be stable, which is why this held up in\n * practice. It fails as soon as the value is rebuilt by a different code path —\n * read back from a database, mapped through a different shape, or produced by a\n * non-JavaScript implementation. `meta` was worse: it is assembled with\n * `...options.metadata`, so a caller's own key order leaked into the signature.\n *\n * v2 canonicalizes with RFC 8785 first. v1 envelopes keep verifying byte for\n * byte: `meta.version` selects the construction, so nothing already signed\n * changes meaning, and no envelope has to be re-signed.\n */\nfunction signingBytes(payload: unknown, meta: EnvelopeMetadata): Uint8Array {\n const isV1 = String(meta.version ?? '').startsWith('1.');\n const serialized = isV1\n ? JSON.stringify({ payload, meta })\n : canonicalize({ payload, meta });\n return new TextEncoder().encode(serialized);\n}\n\n/**\n * Create a signed envelope from payload\n * \n * @example\n * ```typescript\n * import { createKeySDK } from '@smartledger/keys';\n * import { createSignedEnvelope } from '@smartledger/envelope';\n * \n * const sdk = createKeySDK();\n * const key = await sdk.createKey('my-agent', { primarySignatureSuite: 'ml-dsa-87' });\n * \n * const envelope = await createSignedEnvelope(\n * sdk,\n * key.meta.keyId,\n * { message: \"Hello, world!\" },\n * { creator: 'my-agent', contentType: 'greeting' }\n * );\n * ```\n */\nexport async function createSignedEnvelope<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n options: CreateEnvelopeOptions\n): Promise<SignedEnvelope<T>> {\n const now = new Date().toISOString();\n \n // Build metadata\n const meta: EnvelopeMetadata = {\n version: ENVELOPE_VERSION,\n creator: options.creator,\n creatorType: options.creatorType,\n createdAt: now,\n contentType: options.contentType,\n anchor: options.anchor,\n parentId: options.parentId,\n ...options.metadata,\n };\n\n // Dual signing. The co-signer is declared inside `meta`, which the primary\n // signature covers, so the co-signature cannot be stripped: an envelope that\n // declares one and does not carry a valid one fails verification.\n let pqKey: Awaited<ReturnType<KeySDK['getKey']>> = null;\n if (options.dualSign) {\n if (!options.pqKeyId) {\n throw new Error('dualSign requires options.pqKeyId (the post-quantum co-signing key)');\n }\n pqKey = await sdk.getKey(options.pqKeyId);\n if (!pqKey) {\n throw new Error(`Key ${options.pqKeyId} not found`);\n }\n if (!isPostQuantumSuite(pqKey.meta.suiteId)) {\n throw new Error(\n `dualSign needs a post-quantum co-signing key; ${options.pqKeyId} is ${pqKey.meta.suiteId}`\n );\n }\n meta.coSigner = { algorithm: pqKey.meta.suiteId, keyId: options.pqKeyId };\n } else if (meta.coSigner !== undefined) {\n // Reserved: only this function may declare a co-signer.\n throw new Error('metadata.coSigner is reserved; use dualSign with pqKeyId');\n }\n\n const message = signingBytes(payload, meta);\n \n // Sign with primary key\n const signature = await sdk.signWithKey(keyId, message);\n const key = await sdk.getKey(keyId);\n \n if (!key) {\n throw new Error(`Key ${keyId} not found`);\n }\n \n const signatureMetadata: SignatureMetadata = {\n algorithm: key.meta.suiteId,\n keyId,\n publicKey: Buffer.from(key.publicKey).toString('base64'),\n signature: Buffer.from(signature).toString('base64'),\n timestamp: now,\n };\n \n if (pqKey && options.pqKeyId) {\n const pqSignature = await sdk.signWithKey(options.pqKeyId, message);\n signatureMetadata.pqSignature = {\n algorithm: pqKey.meta.suiteId,\n keyId: options.pqKeyId,\n publicKey: Buffer.from(pqKey.publicKey).toString('base64'),\n signature: Buffer.from(pqSignature).toString('base64'),\n };\n }\n \n return {\n payload,\n meta,\n _signature: signatureMetadata,\n };\n}\n\n/**\n * Verify a signed envelope\n * \n * @example\n * ```typescript\n * const result = await verifySignedEnvelope(sdk, envelope);\n * if (result.valid) {\n * console.log('Verified! Created by:', result.creator);\n * console.log('Payload:', envelope.payload);\n * }\n * ```\n */\nexport async function verifySignedEnvelope<T>(\n sdk: KeySDK,\n envelope: SignedEnvelope<T>,\n options: VerifyEnvelopeOptions = {}\n): Promise<VerificationResult> {\n try {\n // Check age if maxAge specified\n if (options.maxAge) {\n const signatureTime = new Date(envelope._signature.timestamp).getTime();\n const age = Date.now() - signatureTime;\n if (age > options.maxAge) {\n return {\n valid: false,\n error: `Signature too old: ${age}ms > ${options.maxAge}ms`,\n };\n }\n }\n \n // Check expected creator if specified\n if (options.expectedCreator && envelope.meta.creator !== options.expectedCreator) {\n return {\n valid: false,\n error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,\n };\n }\n \n // The version decides how the bytes were built and what the key signed,\n // so an envelope without a version this package knows is refused outright.\n const major = envelopeMajor(envelope.meta);\n if (major === null) {\n return {\n valid: false,\n error: `Unsupported envelope version: ${JSON.stringify(envelope.meta?.version)}`,\n };\n }\n\n // Rebuild the signed bytes the way the envelope's own version says they\n // were built, so v1 envelopes keep verifying unchanged.\n const message = signingBytes(envelope.payload, envelope.meta);\n const signature = Buffer.from(envelope._signature.signature, 'base64');\n \n // Verify signature\n const keyId = envelope._signature.keyId;\n if (!keyId) {\n return {\n valid: false,\n error: 'No keyId in signature metadata',\n };\n }\n \n const scheme = signatureScheme(major);\n const valid = await sdk.verifySignature(keyId, message, signature, { scheme });\n \n if (!valid) {\n return {\n valid: false,\n error: 'Signature verification failed',\n };\n }\n \n // The post-quantum co-signature. `verified` reports 'both' only when the\n // co-signature was actually checked and is valid; this used to be set\n // whenever a pqSignature field was merely present.\n let verified: 'primary' | 'pq' | 'both' = 'primary';\n const declared = envelope.meta.coSigner as { algorithm?: unknown; keyId?: unknown } | undefined;\n const pq = envelope._signature.pqSignature;\n\n if (declared !== undefined && !pq) {\n return {\n valid: false,\n error: 'Envelope declares a co-signer but carries no co-signature',\n };\n }\n let pqVerified = false;\n if (pq && (declared !== undefined || options.verifyPQ || options.requirePQ)) {\n if (declared !== undefined && (declared.keyId !== pq.keyId || declared.algorithm !== pq.algorithm)) {\n return {\n valid: false,\n error: 'Co-signature does not match the co-signer declared in the signed metadata',\n };\n }\n const pqKey = pq.keyId ? await sdk.getKey(pq.keyId) : null;\n if (!pqKey) {\n return { valid: false, error: `Co-signing key not found: ${String(pq.keyId)}` };\n }\n // The algorithm comes from the verifier's key record, not the envelope.\n if (pqKey.meta.suiteId !== pq.algorithm || !isPostQuantumSuite(pqKey.meta.suiteId)) {\n return {\n valid: false,\n error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,\n };\n }\n const pqValid =\n (await sdk.verifySignature(pq.keyId!, message, Buffer.from(pq.signature, 'base64'), { scheme })) === true;\n if (!pqValid) {\n return { valid: false, error: 'Post-quantum co-signature verification failed' };\n }\n pqVerified = true;\n verified = 'both';\n }\n\n if (options.requirePQ && !pqVerified) {\n const primaryKey = await sdk.getKey(keyId);\n if (!primaryKey || !isPostQuantumSuite(primaryKey.meta.suiteId)) {\n return {\n valid: false,\n error: 'A post-quantum signature is required and none was verified',\n };\n }\n }\n \n return {\n valid: true,\n verified,\n creator: envelope.meta.creator,\n timestamp: envelope._signature.timestamp,\n contentMatch: true,\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Extract payload from envelope without verification\n * Use only when verification was done previously\n */\nexport function extractPayload<T>(envelope: SignedEnvelope<T>): T {\n return envelope.payload;\n}\n\n/**\n * Get envelope metadata\n */\nexport function getEnvelopeMeta<T>(envelope: SignedEnvelope<T>): EnvelopeMetadata {\n return envelope.meta;\n}\n\n/**\n * Check if envelope has PQ signature\n */\nexport function hasPQSignature<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope._signature.pqSignature;\n}\n\n/**\n * Check if envelope has on-chain anchor\n */\nexport function hasAnchor<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope.meta.anchor;\n}\n\n/**\n * Create a chain of custody by linking envelopes\n * \n * @example\n * ```typescript\n * const original = await createSignedEnvelope(sdk, keyId, data, { creator: 'alice' });\n * const updated = await createEnvelopeChain(\n * sdk,\n * keyId,\n * updatedData,\n * original,\n * { creator: 'bob', contentType: 'revision' }\n * );\n * // updated.meta.parentId === hash(original)\n * ```\n */\nexport async function createEnvelopeChain<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n parentEnvelope: SignedEnvelope<any>,\n options: Omit<CreateEnvelopeOptions, 'parentId'>\n): Promise<SignedEnvelope<T>> {\n // Create a hash of parent envelope as parentId\n const parentJson = JSON.stringify(parentEnvelope);\n const parentHash = Buffer.from(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(parentJson))\n ).toString('hex');\n \n return createSignedEnvelope(sdk, keyId, payload, {\n ...options,\n parentId: parentHash,\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AA8DpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAuE5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAwH7B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAEhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAEhF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAEtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAEjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,EACV,cAAc,EAAE,cAAc,CAAC,GAAG,CAAC,EACnC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,GAC/C,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAW5B"}
|
package/dist/esm/envelope.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { canonicalize } from './jcs.js';
|
|
2
|
-
const ENVELOPE_VERSION = '
|
|
2
|
+
const ENVELOPE_VERSION = '3.0.0';
|
|
3
|
+
/**
|
|
4
|
+
* How the key SDK turned the signing bytes into what the key signed, by
|
|
5
|
+
* envelope version. Envelopes before 3.0 were signed through @smartledger/keys
|
|
6
|
+
* 3.x, where an ML-DSA key signed SHA3-256 of the bytes; 3.0 envelopes are
|
|
7
|
+
* signed through keys 4.x, where it signs the bytes themselves under a
|
|
8
|
+
* context string. The version is inside the signed metadata, so it cannot be
|
|
9
|
+
* relabelled to pick the weaker rule. (ECDSA signatures are the same either way.)
|
|
10
|
+
*/
|
|
11
|
+
function signatureScheme(major) {
|
|
12
|
+
return major === 3 ? 'v4' : 'v3-digest';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The major version of an envelope, or null if `meta.version` is not exactly
|
|
16
|
+
* `MAJOR.MINOR.PATCH` with a major this package knows. Nothing is guessed: a
|
|
17
|
+
* missing version, `v3.0.0`, ` 3.0.0` or `3` is refused rather than read as
|
|
18
|
+
* some version, because the version decides which signing rule applies.
|
|
19
|
+
*/
|
|
20
|
+
function envelopeMajor(meta) {
|
|
21
|
+
const m = typeof meta?.version === 'string' ? /^([1-3])\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.exec(meta.version) : null;
|
|
22
|
+
return m ? Number(m[1]) : null;
|
|
23
|
+
}
|
|
3
24
|
/** Suites whose security does not rest on factoring or discrete logarithms. */
|
|
4
25
|
function isPostQuantumSuite(suiteId) {
|
|
5
26
|
return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);
|
|
@@ -146,6 +167,15 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
146
167
|
error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,
|
|
147
168
|
};
|
|
148
169
|
}
|
|
170
|
+
// The version decides how the bytes were built and what the key signed,
|
|
171
|
+
// so an envelope without a version this package knows is refused outright.
|
|
172
|
+
const major = envelopeMajor(envelope.meta);
|
|
173
|
+
if (major === null) {
|
|
174
|
+
return {
|
|
175
|
+
valid: false,
|
|
176
|
+
error: `Unsupported envelope version: ${JSON.stringify(envelope.meta?.version)}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
149
179
|
// Rebuild the signed bytes the way the envelope's own version says they
|
|
150
180
|
// were built, so v1 envelopes keep verifying unchanged.
|
|
151
181
|
const message = signingBytes(envelope.payload, envelope.meta);
|
|
@@ -158,7 +188,8 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
158
188
|
error: 'No keyId in signature metadata',
|
|
159
189
|
};
|
|
160
190
|
}
|
|
161
|
-
const
|
|
191
|
+
const scheme = signatureScheme(major);
|
|
192
|
+
const valid = await sdk.verifySignature(keyId, message, signature, { scheme });
|
|
162
193
|
if (!valid) {
|
|
163
194
|
return {
|
|
164
195
|
valid: false,
|
|
@@ -196,7 +227,7 @@ export async function verifySignedEnvelope(sdk, envelope, options = {}) {
|
|
|
196
227
|
error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,
|
|
197
228
|
};
|
|
198
229
|
}
|
|
199
|
-
const pqValid = (await sdk.verifySignature(pq.keyId, message, Buffer.from(pq.signature, 'base64'))) === true;
|
|
230
|
+
const pqValid = (await sdk.verifySignature(pq.keyId, message, Buffer.from(pq.signature, 'base64'), { scheme })) === true;
|
|
200
231
|
if (!pqValid) {
|
|
201
232
|
return { valid: false, error: 'Post-quantum co-signature verification failed' };
|
|
202
233
|
}
|
package/dist/esm/envelope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { KeySDK } from '@smartledger/keys';\nimport type {\n SignedEnvelope,\n CreateEnvelopeOptions,\n VerifyEnvelopeOptions,\n VerificationResult,\n EnvelopeMetadata,\n SignatureMetadata,\n} from './types.js';\nimport { canonicalize } from './jcs.js';\n\nconst ENVELOPE_VERSION = '2.0.0';\n\n/** Suites whose security does not rest on factoring or discrete logarithms. */\nfunction isPostQuantumSuite(suiteId: string): boolean {\n return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);\n}\n\n/**\n * How the bytes a signature covers are built, per envelope version.\n *\n * v1 used `JSON.stringify({ payload, meta })` with no canonicalization, so the\n * signed bytes depended on the insertion order of ordinary string keys. Two\n * semantically identical payloads built in different orders signed differently:\n *\n * { name: 'x', total: 1 } -> {\"payload\":{\"name\":\"x\",\"total\":1},…}\n * { total: 1, name: 'x' } -> {\"payload\":{\"total\":1,\"name\":\"x\"},…}\n *\n * Signing and verifying the same object graph in one process agreed, and a\n * JSON round-trip through V8 happened to be stable, which is why this held up in\n * practice. It fails as soon as the value is rebuilt by a different code path —\n * read back from a database, mapped through a different shape, or produced by a\n * non-JavaScript implementation. `meta` was worse: it is assembled with\n * `...options.metadata`, so a caller's own key order leaked into the signature.\n *\n * v2 canonicalizes with RFC 8785 first. v1 envelopes keep verifying byte for\n * byte: `meta.version` selects the construction, so nothing already signed\n * changes meaning, and no envelope has to be re-signed.\n */\nfunction signingBytes(payload: unknown, meta: EnvelopeMetadata): Uint8Array {\n const isV1 = String(meta.version ?? '').startsWith('1.');\n const serialized = isV1\n ? JSON.stringify({ payload, meta })\n : canonicalize({ payload, meta });\n return new TextEncoder().encode(serialized);\n}\n\n/**\n * Create a signed envelope from payload\n * \n * @example\n * ```typescript\n * import { createKeySDK } from '@smartledger/keys';\n * import { createSignedEnvelope } from '@smartledger/envelope';\n * \n * const sdk = createKeySDK();\n * const key = await sdk.createKey('my-agent', { primarySignatureSuite: 'ml-dsa-87' });\n * \n * const envelope = await createSignedEnvelope(\n * sdk,\n * key.meta.keyId,\n * { message: \"Hello, world!\" },\n * { creator: 'my-agent', contentType: 'greeting' }\n * );\n * ```\n */\nexport async function createSignedEnvelope<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n options: CreateEnvelopeOptions\n): Promise<SignedEnvelope<T>> {\n const now = new Date().toISOString();\n \n // Build metadata\n const meta: EnvelopeMetadata = {\n version: ENVELOPE_VERSION,\n creator: options.creator,\n creatorType: options.creatorType,\n createdAt: now,\n contentType: options.contentType,\n anchor: options.anchor,\n parentId: options.parentId,\n ...options.metadata,\n };\n\n // Dual signing. The co-signer is declared inside `meta`, which the primary\n // signature covers, so the co-signature cannot be stripped: an envelope that\n // declares one and does not carry a valid one fails verification.\n let pqKey: Awaited<ReturnType<KeySDK['getKey']>> = null;\n if (options.dualSign) {\n if (!options.pqKeyId) {\n throw new Error('dualSign requires options.pqKeyId (the post-quantum co-signing key)');\n }\n pqKey = await sdk.getKey(options.pqKeyId);\n if (!pqKey) {\n throw new Error(`Key ${options.pqKeyId} not found`);\n }\n if (!isPostQuantumSuite(pqKey.meta.suiteId)) {\n throw new Error(\n `dualSign needs a post-quantum co-signing key; ${options.pqKeyId} is ${pqKey.meta.suiteId}`\n );\n }\n meta.coSigner = { algorithm: pqKey.meta.suiteId, keyId: options.pqKeyId };\n } else if (meta.coSigner !== undefined) {\n // Reserved: only this function may declare a co-signer.\n throw new Error('metadata.coSigner is reserved; use dualSign with pqKeyId');\n }\n\n const message = signingBytes(payload, meta);\n \n // Sign with primary key\n const signature = await sdk.signWithKey(keyId, message);\n const key = await sdk.getKey(keyId);\n \n if (!key) {\n throw new Error(`Key ${keyId} not found`);\n }\n \n const signatureMetadata: SignatureMetadata = {\n algorithm: key.meta.suiteId,\n keyId,\n publicKey: Buffer.from(key.publicKey).toString('base64'),\n signature: Buffer.from(signature).toString('base64'),\n timestamp: now,\n };\n \n if (pqKey && options.pqKeyId) {\n const pqSignature = await sdk.signWithKey(options.pqKeyId, message);\n signatureMetadata.pqSignature = {\n algorithm: pqKey.meta.suiteId,\n keyId: options.pqKeyId,\n publicKey: Buffer.from(pqKey.publicKey).toString('base64'),\n signature: Buffer.from(pqSignature).toString('base64'),\n };\n }\n \n return {\n payload,\n meta,\n _signature: signatureMetadata,\n };\n}\n\n/**\n * Verify a signed envelope\n * \n * @example\n * ```typescript\n * const result = await verifySignedEnvelope(sdk, envelope);\n * if (result.valid) {\n * console.log('Verified! Created by:', result.creator);\n * console.log('Payload:', envelope.payload);\n * }\n * ```\n */\nexport async function verifySignedEnvelope<T>(\n sdk: KeySDK,\n envelope: SignedEnvelope<T>,\n options: VerifyEnvelopeOptions = {}\n): Promise<VerificationResult> {\n try {\n // Check age if maxAge specified\n if (options.maxAge) {\n const signatureTime = new Date(envelope._signature.timestamp).getTime();\n const age = Date.now() - signatureTime;\n if (age > options.maxAge) {\n return {\n valid: false,\n error: `Signature too old: ${age}ms > ${options.maxAge}ms`,\n };\n }\n }\n \n // Check expected creator if specified\n if (options.expectedCreator && envelope.meta.creator !== options.expectedCreator) {\n return {\n valid: false,\n error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,\n };\n }\n \n // Rebuild the signed bytes the way the envelope's own version says they\n // were built, so v1 envelopes keep verifying unchanged.\n const message = signingBytes(envelope.payload, envelope.meta);\n const signature = Buffer.from(envelope._signature.signature, 'base64');\n \n // Verify signature\n const keyId = envelope._signature.keyId;\n if (!keyId) {\n return {\n valid: false,\n error: 'No keyId in signature metadata',\n };\n }\n \n const valid = await sdk.verifySignature(keyId, message, signature);\n \n if (!valid) {\n return {\n valid: false,\n error: 'Signature verification failed',\n };\n }\n \n // The post-quantum co-signature. `verified` reports 'both' only when the\n // co-signature was actually checked and is valid; this used to be set\n // whenever a pqSignature field was merely present.\n let verified: 'primary' | 'pq' | 'both' = 'primary';\n const declared = envelope.meta.coSigner as { algorithm?: unknown; keyId?: unknown } | undefined;\n const pq = envelope._signature.pqSignature;\n\n if (declared !== undefined && !pq) {\n return {\n valid: false,\n error: 'Envelope declares a co-signer but carries no co-signature',\n };\n }\n let pqVerified = false;\n if (pq && (declared !== undefined || options.verifyPQ || options.requirePQ)) {\n if (declared !== undefined && (declared.keyId !== pq.keyId || declared.algorithm !== pq.algorithm)) {\n return {\n valid: false,\n error: 'Co-signature does not match the co-signer declared in the signed metadata',\n };\n }\n const pqKey = pq.keyId ? await sdk.getKey(pq.keyId) : null;\n if (!pqKey) {\n return { valid: false, error: `Co-signing key not found: ${String(pq.keyId)}` };\n }\n // The algorithm comes from the verifier's key record, not the envelope.\n if (pqKey.meta.suiteId !== pq.algorithm || !isPostQuantumSuite(pqKey.meta.suiteId)) {\n return {\n valid: false,\n error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,\n };\n }\n const pqValid =\n (await sdk.verifySignature(pq.keyId!, message, Buffer.from(pq.signature, 'base64'))) === true;\n if (!pqValid) {\n return { valid: false, error: 'Post-quantum co-signature verification failed' };\n }\n pqVerified = true;\n verified = 'both';\n }\n\n if (options.requirePQ && !pqVerified) {\n const primaryKey = await sdk.getKey(keyId);\n if (!primaryKey || !isPostQuantumSuite(primaryKey.meta.suiteId)) {\n return {\n valid: false,\n error: 'A post-quantum signature is required and none was verified',\n };\n }\n }\n \n return {\n valid: true,\n verified,\n creator: envelope.meta.creator,\n timestamp: envelope._signature.timestamp,\n contentMatch: true,\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Extract payload from envelope without verification\n * Use only when verification was done previously\n */\nexport function extractPayload<T>(envelope: SignedEnvelope<T>): T {\n return envelope.payload;\n}\n\n/**\n * Get envelope metadata\n */\nexport function getEnvelopeMeta<T>(envelope: SignedEnvelope<T>): EnvelopeMetadata {\n return envelope.meta;\n}\n\n/**\n * Check if envelope has PQ signature\n */\nexport function hasPQSignature<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope._signature.pqSignature;\n}\n\n/**\n * Check if envelope has on-chain anchor\n */\nexport function hasAnchor<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope.meta.anchor;\n}\n\n/**\n * Create a chain of custody by linking envelopes\n * \n * @example\n * ```typescript\n * const original = await createSignedEnvelope(sdk, keyId, data, { creator: 'alice' });\n * const updated = await createEnvelopeChain(\n * sdk,\n * keyId,\n * updatedData,\n * original,\n * { creator: 'bob', contentType: 'revision' }\n * );\n * // updated.meta.parentId === hash(original)\n * ```\n */\nexport async function createEnvelopeChain<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n parentEnvelope: SignedEnvelope<any>,\n options: Omit<CreateEnvelopeOptions, 'parentId'>\n): Promise<SignedEnvelope<T>> {\n // Create a hash of parent envelope as parentId\n const parentJson = JSON.stringify(parentEnvelope);\n const parentHash = Buffer.from(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(parentJson))\n ).toString('hex');\n \n return createSignedEnvelope(sdk, keyId, payload, {\n ...options,\n parentId: parentHash,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/envelope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { KeySDK } from '@smartledger/keys';\nimport type {\n SignedEnvelope,\n CreateEnvelopeOptions,\n VerifyEnvelopeOptions,\n VerificationResult,\n EnvelopeMetadata,\n SignatureMetadata,\n} from './types.js';\nimport { canonicalize } from './jcs.js';\n\nconst ENVELOPE_VERSION = '3.0.0';\n\n/**\n * How the key SDK turned the signing bytes into what the key signed, by\n * envelope version. Envelopes before 3.0 were signed through @smartledger/keys\n * 3.x, where an ML-DSA key signed SHA3-256 of the bytes; 3.0 envelopes are\n * signed through keys 4.x, where it signs the bytes themselves under a\n * context string. The version is inside the signed metadata, so it cannot be\n * relabelled to pick the weaker rule. (ECDSA signatures are the same either way.)\n */\nfunction signatureScheme(major: 1 | 2 | 3): 'v4' | 'v3-digest' {\n return major === 3 ? 'v4' : 'v3-digest';\n}\n\n/**\n * The major version of an envelope, or null if `meta.version` is not exactly\n * `MAJOR.MINOR.PATCH` with a major this package knows. Nothing is guessed: a\n * missing version, `v3.0.0`, ` 3.0.0` or `3` is refused rather than read as\n * some version, because the version decides which signing rule applies.\n */\nfunction envelopeMajor(meta: EnvelopeMetadata): 1 | 2 | 3 | null {\n const m = typeof meta?.version === 'string' ? /^([1-3])\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$/.exec(meta.version) : null;\n return m ? (Number(m[1]) as 1 | 2 | 3) : null;\n}\n\n/** Suites whose security does not rest on factoring or discrete logarithms. */\nfunction isPostQuantumSuite(suiteId: string): boolean {\n return /^(ml-dsa|slh-dsa|fn-dsa)-/.test(suiteId);\n}\n\n/**\n * How the bytes a signature covers are built, per envelope version.\n *\n * v1 used `JSON.stringify({ payload, meta })` with no canonicalization, so the\n * signed bytes depended on the insertion order of ordinary string keys. Two\n * semantically identical payloads built in different orders signed differently:\n *\n * { name: 'x', total: 1 } -> {\"payload\":{\"name\":\"x\",\"total\":1},…}\n * { total: 1, name: 'x' } -> {\"payload\":{\"total\":1,\"name\":\"x\"},…}\n *\n * Signing and verifying the same object graph in one process agreed, and a\n * JSON round-trip through V8 happened to be stable, which is why this held up in\n * practice. It fails as soon as the value is rebuilt by a different code path —\n * read back from a database, mapped through a different shape, or produced by a\n * non-JavaScript implementation. `meta` was worse: it is assembled with\n * `...options.metadata`, so a caller's own key order leaked into the signature.\n *\n * v2 canonicalizes with RFC 8785 first. v1 envelopes keep verifying byte for\n * byte: `meta.version` selects the construction, so nothing already signed\n * changes meaning, and no envelope has to be re-signed.\n */\nfunction signingBytes(payload: unknown, meta: EnvelopeMetadata): Uint8Array {\n const isV1 = String(meta.version ?? '').startsWith('1.');\n const serialized = isV1\n ? JSON.stringify({ payload, meta })\n : canonicalize({ payload, meta });\n return new TextEncoder().encode(serialized);\n}\n\n/**\n * Create a signed envelope from payload\n * \n * @example\n * ```typescript\n * import { createKeySDK } from '@smartledger/keys';\n * import { createSignedEnvelope } from '@smartledger/envelope';\n * \n * const sdk = createKeySDK();\n * const key = await sdk.createKey('my-agent', { primarySignatureSuite: 'ml-dsa-87' });\n * \n * const envelope = await createSignedEnvelope(\n * sdk,\n * key.meta.keyId,\n * { message: \"Hello, world!\" },\n * { creator: 'my-agent', contentType: 'greeting' }\n * );\n * ```\n */\nexport async function createSignedEnvelope<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n options: CreateEnvelopeOptions\n): Promise<SignedEnvelope<T>> {\n const now = new Date().toISOString();\n \n // Build metadata\n const meta: EnvelopeMetadata = {\n version: ENVELOPE_VERSION,\n creator: options.creator,\n creatorType: options.creatorType,\n createdAt: now,\n contentType: options.contentType,\n anchor: options.anchor,\n parentId: options.parentId,\n ...options.metadata,\n };\n\n // Dual signing. The co-signer is declared inside `meta`, which the primary\n // signature covers, so the co-signature cannot be stripped: an envelope that\n // declares one and does not carry a valid one fails verification.\n let pqKey: Awaited<ReturnType<KeySDK['getKey']>> = null;\n if (options.dualSign) {\n if (!options.pqKeyId) {\n throw new Error('dualSign requires options.pqKeyId (the post-quantum co-signing key)');\n }\n pqKey = await sdk.getKey(options.pqKeyId);\n if (!pqKey) {\n throw new Error(`Key ${options.pqKeyId} not found`);\n }\n if (!isPostQuantumSuite(pqKey.meta.suiteId)) {\n throw new Error(\n `dualSign needs a post-quantum co-signing key; ${options.pqKeyId} is ${pqKey.meta.suiteId}`\n );\n }\n meta.coSigner = { algorithm: pqKey.meta.suiteId, keyId: options.pqKeyId };\n } else if (meta.coSigner !== undefined) {\n // Reserved: only this function may declare a co-signer.\n throw new Error('metadata.coSigner is reserved; use dualSign with pqKeyId');\n }\n\n const message = signingBytes(payload, meta);\n \n // Sign with primary key\n const signature = await sdk.signWithKey(keyId, message);\n const key = await sdk.getKey(keyId);\n \n if (!key) {\n throw new Error(`Key ${keyId} not found`);\n }\n \n const signatureMetadata: SignatureMetadata = {\n algorithm: key.meta.suiteId,\n keyId,\n publicKey: Buffer.from(key.publicKey).toString('base64'),\n signature: Buffer.from(signature).toString('base64'),\n timestamp: now,\n };\n \n if (pqKey && options.pqKeyId) {\n const pqSignature = await sdk.signWithKey(options.pqKeyId, message);\n signatureMetadata.pqSignature = {\n algorithm: pqKey.meta.suiteId,\n keyId: options.pqKeyId,\n publicKey: Buffer.from(pqKey.publicKey).toString('base64'),\n signature: Buffer.from(pqSignature).toString('base64'),\n };\n }\n \n return {\n payload,\n meta,\n _signature: signatureMetadata,\n };\n}\n\n/**\n * Verify a signed envelope\n * \n * @example\n * ```typescript\n * const result = await verifySignedEnvelope(sdk, envelope);\n * if (result.valid) {\n * console.log('Verified! Created by:', result.creator);\n * console.log('Payload:', envelope.payload);\n * }\n * ```\n */\nexport async function verifySignedEnvelope<T>(\n sdk: KeySDK,\n envelope: SignedEnvelope<T>,\n options: VerifyEnvelopeOptions = {}\n): Promise<VerificationResult> {\n try {\n // Check age if maxAge specified\n if (options.maxAge) {\n const signatureTime = new Date(envelope._signature.timestamp).getTime();\n const age = Date.now() - signatureTime;\n if (age > options.maxAge) {\n return {\n valid: false,\n error: `Signature too old: ${age}ms > ${options.maxAge}ms`,\n };\n }\n }\n \n // Check expected creator if specified\n if (options.expectedCreator && envelope.meta.creator !== options.expectedCreator) {\n return {\n valid: false,\n error: `Unexpected creator: ${envelope.meta.creator} !== ${options.expectedCreator}`,\n };\n }\n \n // The version decides how the bytes were built and what the key signed,\n // so an envelope without a version this package knows is refused outright.\n const major = envelopeMajor(envelope.meta);\n if (major === null) {\n return {\n valid: false,\n error: `Unsupported envelope version: ${JSON.stringify(envelope.meta?.version)}`,\n };\n }\n\n // Rebuild the signed bytes the way the envelope's own version says they\n // were built, so v1 envelopes keep verifying unchanged.\n const message = signingBytes(envelope.payload, envelope.meta);\n const signature = Buffer.from(envelope._signature.signature, 'base64');\n \n // Verify signature\n const keyId = envelope._signature.keyId;\n if (!keyId) {\n return {\n valid: false,\n error: 'No keyId in signature metadata',\n };\n }\n \n const scheme = signatureScheme(major);\n const valid = await sdk.verifySignature(keyId, message, signature, { scheme });\n \n if (!valid) {\n return {\n valid: false,\n error: 'Signature verification failed',\n };\n }\n \n // The post-quantum co-signature. `verified` reports 'both' only when the\n // co-signature was actually checked and is valid; this used to be set\n // whenever a pqSignature field was merely present.\n let verified: 'primary' | 'pq' | 'both' = 'primary';\n const declared = envelope.meta.coSigner as { algorithm?: unknown; keyId?: unknown } | undefined;\n const pq = envelope._signature.pqSignature;\n\n if (declared !== undefined && !pq) {\n return {\n valid: false,\n error: 'Envelope declares a co-signer but carries no co-signature',\n };\n }\n let pqVerified = false;\n if (pq && (declared !== undefined || options.verifyPQ || options.requirePQ)) {\n if (declared !== undefined && (declared.keyId !== pq.keyId || declared.algorithm !== pq.algorithm)) {\n return {\n valid: false,\n error: 'Co-signature does not match the co-signer declared in the signed metadata',\n };\n }\n const pqKey = pq.keyId ? await sdk.getKey(pq.keyId) : null;\n if (!pqKey) {\n return { valid: false, error: `Co-signing key not found: ${String(pq.keyId)}` };\n }\n // The algorithm comes from the verifier's key record, not the envelope.\n if (pqKey.meta.suiteId !== pq.algorithm || !isPostQuantumSuite(pqKey.meta.suiteId)) {\n return {\n valid: false,\n error: `Co-signing key ${pq.keyId} is ${pqKey.meta.suiteId}, not the post-quantum ${String(pq.algorithm)} claimed`,\n };\n }\n const pqValid =\n (await sdk.verifySignature(pq.keyId!, message, Buffer.from(pq.signature, 'base64'), { scheme })) === true;\n if (!pqValid) {\n return { valid: false, error: 'Post-quantum co-signature verification failed' };\n }\n pqVerified = true;\n verified = 'both';\n }\n\n if (options.requirePQ && !pqVerified) {\n const primaryKey = await sdk.getKey(keyId);\n if (!primaryKey || !isPostQuantumSuite(primaryKey.meta.suiteId)) {\n return {\n valid: false,\n error: 'A post-quantum signature is required and none was verified',\n };\n }\n }\n \n return {\n valid: true,\n verified,\n creator: envelope.meta.creator,\n timestamp: envelope._signature.timestamp,\n contentMatch: true,\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Extract payload from envelope without verification\n * Use only when verification was done previously\n */\nexport function extractPayload<T>(envelope: SignedEnvelope<T>): T {\n return envelope.payload;\n}\n\n/**\n * Get envelope metadata\n */\nexport function getEnvelopeMeta<T>(envelope: SignedEnvelope<T>): EnvelopeMetadata {\n return envelope.meta;\n}\n\n/**\n * Check if envelope has PQ signature\n */\nexport function hasPQSignature<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope._signature.pqSignature;\n}\n\n/**\n * Check if envelope has on-chain anchor\n */\nexport function hasAnchor<T>(envelope: SignedEnvelope<T>): boolean {\n return !!envelope.meta.anchor;\n}\n\n/**\n * Create a chain of custody by linking envelopes\n * \n * @example\n * ```typescript\n * const original = await createSignedEnvelope(sdk, keyId, data, { creator: 'alice' });\n * const updated = await createEnvelopeChain(\n * sdk,\n * keyId,\n * updatedData,\n * original,\n * { creator: 'bob', contentType: 'revision' }\n * );\n * // updated.meta.parentId === hash(original)\n * ```\n */\nexport async function createEnvelopeChain<T>(\n sdk: KeySDK,\n keyId: string,\n payload: T,\n parentEnvelope: SignedEnvelope<any>,\n options: Omit<CreateEnvelopeOptions, 'parentId'>\n): Promise<SignedEnvelope<T>> {\n // Create a hash of parent envelope as parentId\n const parentJson = JSON.stringify(parentEnvelope);\n const parentHash = Buffer.from(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(parentJson))\n ).toString('hex');\n \n return createSignedEnvelope(sdk, keyId, payload, {\n ...options,\n parentId: parentHash,\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/envelope",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Canonical SignedResponse/SignedEnvelope schema for verifiable AI outputs and content provenance",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
},
|
|
60
60
|
"license": "MIT",
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@smartledger/keys": "^
|
|
62
|
+
"@smartledger/keys": "^4.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@smartledger/keys": "^
|
|
65
|
+
"@smartledger/keys": "^4.0.0",
|
|
66
66
|
"@types/node": "^20.0.0",
|
|
67
67
|
"typescript": "^5.0.0",
|
|
68
68
|
"vitest": "^1.0.4"
|