sealed-lattice 0.0.10 → 0.0.11

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.
Files changed (31) hide show
  1. package/README.md +7 -3
  2. package/dist/index.d.ts +18 -1
  3. package/dist/index.js +39 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/crypto/index.js +450 -0
  6. package/dist/internal/election-foundation/board/index.js +322 -0
  7. package/dist/internal/election-foundation/closing/index.js +255 -0
  8. package/dist/internal/election-foundation/common/digests.js +1 -0
  9. package/dist/internal/election-foundation/common/signatures.js +1 -0
  10. package/dist/internal/election-foundation/common/verification-helpers.js +9 -0
  11. package/dist/internal/election-foundation/finality/index.js +305 -0
  12. package/dist/internal/election-foundation/index.js +15 -0
  13. package/dist/internal/election-foundation/lifecycle/capabilities.js +233 -0
  14. package/dist/internal/election-foundation/lifecycle/labels.js +186 -0
  15. package/dist/internal/election-foundation/lifecycle/lifecycle.js +57 -0
  16. package/dist/internal/election-foundation/lifecycle/poll-spec.js +126 -0
  17. package/dist/internal/election-foundation/lifecycle/profiles.js +13 -0
  18. package/dist/internal/election-foundation/lifecycle/refusal.js +9 -0
  19. package/dist/internal/election-foundation/lifecycle/thresholds.js +105 -0
  20. package/dist/internal/election-foundation/ordering/index.js +115 -0
  21. package/dist/internal/election-foundation/recovery/index.js +241 -0
  22. package/dist/internal/election-foundation/roster/index.js +447 -0
  23. package/dist/internal/election-foundation/target-phase/index.js +366 -0
  24. package/dist/internal/transcript-core-bridge.js +194 -0
  25. package/dist/internal/types.d.ts +720 -0
  26. package/dist/internal/types.js +24 -0
  27. package/dist/kernel.d.ts +6 -0
  28. package/dist/kernel.js +4 -0
  29. package/dist/kernel.js.map +1 -0
  30. package/dist/sealed-lattice-kernel.wasm +0 -0
  31. package/package.json +54 -41
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This package is the only published npm surface in the workspace.
4
4
 
5
- The current public runtime facade is intentionally empty. The package exists so
6
- packaging, documentation, smoke checks, and release workflow can stabilize
7
- before broader protocol-facing APIs are introduced.
5
+ The current public runtime facade exposes the safe transcript core fixture
6
+ verifier plus the threshold, lifecycle, poll specification, capability,
7
+ board-consistency, target-finality, roster-manifest, cast receipt, close
8
+ record, first-come ordering, and recovery-epoch helpers. It does not expose raw
9
+ hashing, object mutation, generic cryptography, ballots, replay-attestation
10
+ shell checks, semantic target acceptance, decryption-share shell checks,
11
+ decryption, or protocol internals.
package/dist/index.d.ts CHANGED
@@ -1 +1,18 @@
1
- export {};
1
+ import type { ActionCurrentForRecoveryEpochInput, ActionCurrentForRecoveryEpochResult, BoardConsistencyInput, BoardConsistencyVerification, CastReceiptVerification, CastReceiptVerificationInput, CapabilityContext, CapabilityDecision, CloseRecordVerification, CloseRecordVerificationInput, FirstComeOrderingInput, FirstComeOrderingVerification, LifecycleLabelInput, LifecycleLabels, LifecycleTransition, PollSpecInput, PollSpecValidation, ProtocolAction, RecoveryEpochVerification, RecoveryEpochVerificationInput, ThresholdProfile, ThresholdProfileInput, TranscriptCoreFixture, TranscriptCoreVerificationResult, RosterManifestTranscriptInput, RosterManifestTranscriptVerification, TargetFinalityVerification, TargetFinalityVerificationInput } from './internal/types.js';
2
+ export type { AcceptedTargetFinalityCheckpoint, ActionContext, ActionCurrentForRecoveryEpochInput, ActionCurrentForRecoveryEpochResult, AppendOnlyConsistencyProof, BaseClaimProfile, BoardConsistencyInput, BoardConsistencyVerification, CanonicalError, CanonicalErrorCode, CanonicalSignedRootObject, CapabilityContext, CapabilityDecision, CastReceipt, CastReceiptVerification, CastReceiptVerificationInput, CloseRecord, CloseRecordKind, CloseRecordVerification, CloseRecordVerificationInput, ConflictingHeadEvidence, ConflictingManifestEvidence, DuplicateBallotPolicy, ElectionManifest, EvaluationProofMode, FailureStatusLabel, FirstComeOrderingInput, FirstComeOrderingVerification, GoldenTranscriptCoreFixture, GoldenTranscriptCoreFixtureVerification, HeBackendCorruptionModel, InclusionProof, LifecycleLabelInput, LifecycleLabels, LifecycleState, LifecycleTransition, MalformedObjectFixture, MalformedObjectFixtureVerification, ManifestOpaqueBindings, ManifestPolicyDigests, MheSecurityStage, MlDsaSignatureMode, MlDsaSignatureProfile, ModeStatusLabel, PollSpec, PollSpecInput, PollSpecValidation, PollSpecValidationError, PollSpecValidationErrorCode, PrimaryStatusLabel, ProtocolAction, ProtocolDigest, ProtocolObjectType, ProtocolRefusalCode, ProtocolSignatureEnvelope, ProtocolVerificationStatusLabel, ReceiverKeyRegistration, RecoveryEpochMapEntry, RecoveryEpochUpdate, RecoveryEpochVerification, RecoveryEpochVerificationInput, RecoveryState, RefusalReason, RefusalRecord, RegistrationEntry, ResultClaimLabel, RosterManifestTranscriptInput, RosterManifestTranscriptVerification, RosterProfileKind, ScoreDomain, SignatureVerificationResult, SignedBoardHead, SignedObjectType, SignerRole, StructuredProtocolVerificationResult, TargetFinalityPolicy, TargetFinalityRecord, TargetFinalityVerification, TargetFinalityVerificationInput, ThresholdProfile, ThresholdProfileInput, ThresholdWarning, TiePolicy, TranscriptCoreAnalysis, TranscriptCoreFixture, TranscriptCoreFixtureVerification, TranscriptCoreMheSecurityStage, TranscriptCoreReplayFixture, TranscriptCoreStatusLabel, TranscriptCoreVerificationLabel, TranscriptCoreVerificationResult, TrusteeSetupEntry, ValidatedFirstComeCandidate, WitnessCheckpoint, WitnessPolicy, } from './internal/types.js';
3
+ export declare const deriveThresholdProfile: (input: ThresholdProfileInput) => ThresholdProfile;
4
+ export declare function validatePollSpec(input: PollSpecInput): PollSpecValidation;
5
+ export declare function validatePollSpec(input: unknown): PollSpecValidation;
6
+ export declare const isValidLifecycleTransition: (transition: LifecycleTransition) => boolean;
7
+ export declare const deriveLifecycleLabels: (input: LifecycleLabelInput) => LifecycleLabels;
8
+ export declare const evaluateActionCapability: (action: ProtocolAction, context: CapabilityContext) => CapabilityDecision;
9
+ export declare const verifyBoardConsistency: (input: BoardConsistencyInput) => BoardConsistencyVerification;
10
+ export declare const verifyCastReceiptShell: (input: CastReceiptVerificationInput) => CastReceiptVerification;
11
+ export declare const verifyCloseRecordShell: (input: CloseRecordVerificationInput) => CloseRecordVerification;
12
+ export declare const verifyTargetFinality: (input: TargetFinalityVerificationInput) => TargetFinalityVerification;
13
+ export declare const deriveValidatedFirstComeOrder: (input: FirstComeOrderingInput) => FirstComeOrderingVerification;
14
+ export declare const verifyFirstComePolicy: (input: FirstComeOrderingInput) => FirstComeOrderingVerification;
15
+ export declare const verifyRosterManifestTranscript: (input: RosterManifestTranscriptInput) => RosterManifestTranscriptVerification;
16
+ export declare const isActionCurrentForRecoveryEpoch: (input: ActionCurrentForRecoveryEpochInput) => ActionCurrentForRecoveryEpochResult;
17
+ export declare const verifyRecoveryEpochUpdate: (input: RecoveryEpochVerificationInput) => RecoveryEpochVerification;
18
+ export declare const verifyTranscriptCoreFixture: (fixture: TranscriptCoreFixture) => Promise<TranscriptCoreVerificationResult>;
package/dist/index.js CHANGED
@@ -1,2 +1,40 @@
1
- export {};
1
+ import { deriveValidatedFirstComeOrder as deriveValidatedFirstComeOrderInternal, deriveLifecycleLabels as deriveLifecycleLabelsInternal, deriveThresholdProfile as deriveThresholdProfileInternal, evaluateActionCapability as evaluateActionCapabilityInternal, verifyCastReceiptShell as verifyCastReceiptShellInternal, verifyCloseRecordShell as verifyCloseRecordShellInternal, isValidLifecycleTransition as isValidLifecycleTransitionInternal, isActionCurrentForRecoveryEpoch as isActionCurrentForRecoveryEpochInternal, validatePollSpecFromUnknown as validatePollSpecFromUnknownInternal, verifyBoardConsistency as verifyBoardConsistencyInternal, verifyFirstComePolicy as verifyFirstComePolicyInternal, verifyRecoveryEpochUpdate as verifyRecoveryEpochUpdateInternal, verifyRosterManifestTranscript as verifyRosterManifestTranscriptInternal, verifyTargetFinality as verifyTargetFinalityInternal, } from './internal/election-foundation/index.js';
2
+ import { loadTranscriptCoreKernel } from './kernel.js';
3
+ export const deriveThresholdProfile = (input) => deriveThresholdProfileInternal(input);
4
+ export function validatePollSpec(input) {
5
+ return validatePollSpecFromUnknownInternal(input);
6
+ }
7
+ export const isValidLifecycleTransition = (transition) => isValidLifecycleTransitionInternal(transition);
8
+ export const deriveLifecycleLabels = (input) => deriveLifecycleLabelsInternal(input);
9
+ export const evaluateActionCapability = (action, context) => evaluateActionCapabilityInternal(action, context);
10
+ export const verifyBoardConsistency = (input) => verifyBoardConsistencyInternal(input);
11
+ export const verifyCastReceiptShell = (input) => verifyCastReceiptShellInternal(input);
12
+ export const verifyCloseRecordShell = (input) => verifyCloseRecordShellInternal(input);
13
+ export const verifyTargetFinality = (input) => verifyTargetFinalityInternal(input);
14
+ export const deriveValidatedFirstComeOrder = (input) => deriveValidatedFirstComeOrderInternal(input);
15
+ export const verifyFirstComePolicy = (input) => verifyFirstComePolicyInternal(input);
16
+ export const verifyRosterManifestTranscript = (input) => verifyRosterManifestTranscriptInternal(input);
17
+ export const isActionCurrentForRecoveryEpoch = (input) => isActionCurrentForRecoveryEpochInternal(input);
18
+ export const verifyRecoveryEpochUpdate = (input) => verifyRecoveryEpochUpdateInternal(input);
19
+ export const verifyTranscriptCoreFixture = async (fixture) => {
20
+ const kernel = await loadTranscriptCoreKernel();
21
+ const verification = kernel.verifyFixture(fixture);
22
+ if ('expectedErrorCode' in verification) {
23
+ return {
24
+ caseName: verification.caseName,
25
+ label: 'TranscriptCoreRejected',
26
+ statusLabels: [],
27
+ rejection: {
28
+ code: verification.expectedErrorCode,
29
+ },
30
+ };
31
+ }
32
+ return {
33
+ caseName: verification.caseName,
34
+ label: 'TranscriptCoreVerified',
35
+ objectHash512: verification.objectHash512,
36
+ chunkRoot: verification.chunkRoot,
37
+ statusLabels: verification.statusLabels,
38
+ };
39
+ };
2
40
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,450 @@
1
+ import { shake256 } from '@noble/hashes/sha3.js';
2
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
3
+ import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
4
+ const textEncoder = new TextEncoder();
5
+ const hash512PreimagePrefix = textEncoder.encode('sealed.vote/v1/hash512');
6
+ const mlDsaContextByteLimit = 255;
7
+ const supportedMlDsaContextString = 'sealed-lattice:v1';
8
+ const mlDsa65PublicKeyByteLength = ml_dsa65.lengths.publicKey;
9
+ const mlDsa65SecretKeyByteLength = ml_dsa65.lengths.secretKey;
10
+ const mlDsa65SignatureByteLength = ml_dsa65.lengths.signature;
11
+ export const protocolDigestNamespaceValues = [
12
+ 'BoardEntryDigest',
13
+ 'BoardRootDigest',
14
+ 'BoardPolicyDigest',
15
+ 'PollSpecDigest',
16
+ 'PublicKeyDigest',
17
+ 'RegistrationEntryDigest',
18
+ 'ReceiverKeyRegistrationDigest',
19
+ 'TrusteeSetupEntryDigest',
20
+ 'ElectionManifestDigest',
21
+ 'RosterDigest',
22
+ 'BoardHeadDigest',
23
+ 'RecoveryEpochUpdateDigest',
24
+ 'ActionContextDigest',
25
+ 'BallotPackageDigest',
26
+ 'BallotSetDigest',
27
+ 'CastReceiptDigest',
28
+ 'CloseRecordDigest',
29
+ 'WitnessCheckpointDigest',
30
+ 'ConflictingHeadEvidenceDigest',
31
+ 'InclusionProofDigest',
32
+ 'FirstComeOrderDigest',
33
+ 'DuplicateBallotPolicyDigest',
34
+ 'FirstComePolicyDigest',
35
+ 'TargetFinalityPolicyDigest',
36
+ 'WitnessPolicyDigest',
37
+ 'RecoveryPolicyDigest',
38
+ 'SignedRootDigest',
39
+ 'ProtocolSignatureEnvelopeDigest',
40
+ 'ProviderBuildDigest',
41
+ 'ThresholdProfileDigest',
42
+ 'HEParamDigest',
43
+ 'CiphertextRoot',
44
+ 'PlaintextRoot',
45
+ 'EvalKeyRoot',
46
+ 'TopKCircuitDigest',
47
+ 'RotSetDigest',
48
+ 'TargetLayoutDigest',
49
+ 'PublicSlotMaskDigest',
50
+ 'AggregateDerivationComponentDigest',
51
+ 'AggregateContributionDigest',
52
+ 'AggregateReadyRecordDigest',
53
+ 'AggregateSelectionPolicyDigest',
54
+ 'PostVotingClosedContextDigest',
55
+ 'EvaluationContextDigest',
56
+ 'TopKEvaluationRecordDigest',
57
+ 'TargetFinalityRecordDigest',
58
+ 'EvaluationReplayAttestationDigest',
59
+ 'TargetAcceptedRecordDigest',
60
+ 'TargetPreimageDigest',
61
+ 'TopKDecryptionShareDigest',
62
+ 'VerifiedTopKResultDigest',
63
+ 'EvaluationProofRoot',
64
+ 'CPADProfileDigest',
65
+ 'ThresholdDecryptionProfileDigest',
66
+ 'BridgeProofRecordDigest',
67
+ 'BridgeProofProfileId',
68
+ 'ProofPrimeParamDigest',
69
+ 'ProofPrimeCiphertextRoot',
70
+ 'ProofPrimePublicKeyRoot',
71
+ 'ProofPrimeToQDataKeyConsistencyDigest',
72
+ 'DerivedAggregateCiphertextRoot',
73
+ 'CanonicalCiphertextConventionDigest',
74
+ 'BFVBatchEncoderDigest',
75
+ 'BridgeLayoutDigest',
76
+ 'AggregateShareCommitmentDigest',
77
+ 'ShareCommitmentDigest',
78
+ 'BrakerskiProfileDigest',
79
+ 'BrakerskiDeltaDigest',
80
+ 'BrakerskiShareVerificationKeyRoot',
81
+ 'TargetDecryptionPreparationRecordDigest',
82
+ 'BrakerskiPreprocessRecordDigest',
83
+ 'BrakerskiPreprocessTokenDigest',
84
+ 'BrakerskiPreprocessUseRecordDigest',
85
+ 'QTargetDigest',
86
+ 'MobileProfileCertDigest',
87
+ 'BridgeMobileCertDigest',
88
+ 'BridgeBatchingCertDigest',
89
+ 'AggregateBridgeProverCertDigest',
90
+ 'EncryptedEnvelopeRoot',
91
+ ];
92
+ const emptySignatureVerificationResult = (code, message, objectDigest) => ({
93
+ ok: false,
94
+ statusLabels: [],
95
+ acceptedDigests: [],
96
+ refusedObjects: [
97
+ {
98
+ code,
99
+ message,
100
+ objectDigest,
101
+ },
102
+ ],
103
+ });
104
+ const successfulSignatureVerification = (signatureDigest) => ({
105
+ ok: true,
106
+ statusLabels: [],
107
+ acceptedDigests: [signatureDigest],
108
+ refusedObjects: [],
109
+ });
110
+ const isNonNegativeInteger = (value) => Number.isInteger(value) && value >= 0;
111
+ const isLowercaseHex = (value) => /^[0-9a-f]*$/u.test(value) && value.length % 2 === 0;
112
+ const isPlainObject = (value) => typeof value === 'object' &&
113
+ value !== null &&
114
+ !Array.isArray(value) &&
115
+ Object.getPrototypeOf(value) === Object.prototype;
116
+ const normalizeHex = (value) => value.toLowerCase();
117
+ const normalizeCanonicalValue = (value) => {
118
+ if (value === null) {
119
+ return null;
120
+ }
121
+ if (typeof value === 'string' || typeof value === 'boolean') {
122
+ return value;
123
+ }
124
+ if (typeof value === 'number') {
125
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
126
+ throw new TypeError('Canonical numeric fields must be integers.');
127
+ }
128
+ return value;
129
+ }
130
+ if (Array.isArray(value)) {
131
+ return value.map((entry) => normalizeCanonicalValue(entry));
132
+ }
133
+ if (isPlainObject(value)) {
134
+ const normalized = {};
135
+ for (const key of Object.keys(value).sort()) {
136
+ const entry = value[key];
137
+ if (entry === undefined) {
138
+ throw new TypeError('Canonical objects cannot contain undefined.');
139
+ }
140
+ normalized[key] = normalizeCanonicalValue(entry);
141
+ }
142
+ return normalized;
143
+ }
144
+ throw new TypeError('Unsupported canonical value.');
145
+ };
146
+ export const canonicalJson = (value) => JSON.stringify(normalizeCanonicalValue(value));
147
+ const appendVarUint = (output, value) => {
148
+ if (!Number.isSafeInteger(value) || value < 0) {
149
+ throw new TypeError('Varuint values must be non-negative safe integers.');
150
+ }
151
+ let remainingValue = value;
152
+ for (;;) {
153
+ let byte = remainingValue & 0x7f;
154
+ remainingValue = Math.floor(remainingValue / 128);
155
+ if (remainingValue !== 0) {
156
+ byte |= 0x80;
157
+ }
158
+ output.push(byte);
159
+ if (remainingValue === 0) {
160
+ break;
161
+ }
162
+ }
163
+ };
164
+ const appendBytes = (output, value) => {
165
+ appendVarUint(output, value.byteLength);
166
+ output.push(...value);
167
+ };
168
+ export const hash512 = (domain, parts) => {
169
+ const preimage = Array.from(hash512PreimagePrefix);
170
+ appendBytes(preimage, textEncoder.encode(domain));
171
+ appendVarUint(preimage, parts.length);
172
+ for (const part of parts) {
173
+ appendBytes(preimage, part);
174
+ }
175
+ return shake256(Uint8Array.from(preimage), { dkLen: 64 });
176
+ };
177
+ export const hash512Hex = (domain, parts) => bytesToHex(hash512(domain, parts));
178
+ const pascalCaseToKebabCase = (value) => value
179
+ .replace(/([A-Z]+)([A-Z][a-z])/gu, '$1-$2')
180
+ .replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
181
+ .toLowerCase();
182
+ export const resolveProtocolDigestDomain = (namespace) => {
183
+ if (namespace.startsWith('sealed-lattice-root/')) {
184
+ return namespace;
185
+ }
186
+ if (!/^[A-Z][A-Za-z0-9]*$/u.test(namespace)) {
187
+ throw new TypeError('Protocol digest namespace must be a reserved PascalCase name or an explicit sealed-lattice-root domain.');
188
+ }
189
+ return `sealed-lattice-root/${pascalCaseToKebabCase(namespace)}-v1`;
190
+ };
191
+ export const deriveProtocolDigest = (namespace, value) => hash512Hex(resolveProtocolDigestDomain(namespace), [
192
+ textEncoder.encode(canonicalJson(value)),
193
+ ]);
194
+ export const derivePolicyDigest = (namespace, policy) => deriveProtocolDigest(namespace, policy);
195
+ const canonicalSignedRootMessage = (signedRoot) => textEncoder.encode(canonicalJson(signedRoot));
196
+ const decodeHexField = (value, expectedByteLength, fieldName) => {
197
+ if (!isLowercaseHex(value)) {
198
+ throw new Error(`${fieldName} must be lowercase canonical hex.`);
199
+ }
200
+ const bytes = hexToBytes(value);
201
+ if (bytes.byteLength !== expectedByteLength) {
202
+ throw new Error(`${fieldName} must be ${String(expectedByteLength)} bytes.`);
203
+ }
204
+ return bytes;
205
+ };
206
+ export const deriveMlDsaContextByteLength = (contextString) => textEncoder.encode(contextString).byteLength;
207
+ export const createMlDsaSignatureProfileFixture = (overrides = {}) => {
208
+ const contextString = overrides.contextString ?? 'sealed-lattice:v1';
209
+ const contextStringByteLength = overrides.contextStringByteLength ??
210
+ deriveMlDsaContextByteLength(contextString);
211
+ return {
212
+ algorithm: 'ML-DSA-65',
213
+ mode: overrides.mode ?? 'PureMLDSA',
214
+ providerName: overrides.providerName ?? 'deterministic-fixture',
215
+ providerVersion: overrides.providerVersion ?? '1',
216
+ providerBuildHash: overrides.providerBuildHash ??
217
+ deriveProtocolDigest('ProviderBuildDigest', {
218
+ providerName: 'deterministic-fixture',
219
+ providerVersion: '1',
220
+ }),
221
+ fips204Version: overrides.fips204Version ?? 'FIPS 204',
222
+ errataStatus: overrides.errataStatus ?? 'none',
223
+ contextString,
224
+ contextStringByteLength,
225
+ };
226
+ };
227
+ export const deriveMlDsaPublicKeyDigest = (publicKeyBytesHex) => deriveProtocolDigest('PublicKeyDigest', {
228
+ algorithm: 'ML-DSA-65',
229
+ publicKeyBytesHex: normalizeHex(publicKeyBytesHex),
230
+ });
231
+ export const createMlDsaKeyPairFixture = (seedLabel) => {
232
+ const seed = deriveProtocolDigest('ProviderBuildDigest', {
233
+ purpose: 'ml-dsa-fixture-seed',
234
+ seedLabel,
235
+ }).slice(0, 64);
236
+ const keyPair = ml_dsa65.keygen(hexToBytes(seed));
237
+ const publicKeyBytesHex = bytesToHex(keyPair.publicKey);
238
+ return {
239
+ publicKeyBytesHex,
240
+ publicKeyDigest: deriveMlDsaPublicKeyDigest(publicKeyBytesHex),
241
+ secretKeyBytesHex: bytesToHex(keyPair.secretKey),
242
+ };
243
+ };
244
+ export const deriveCanonicalSignedRootDigest = (signedRoot) => deriveProtocolDigest('SignedRootDigest', signedRoot);
245
+ export const deriveProtocolSignatureDigest = (signature) => deriveProtocolDigest('ProtocolSignatureEnvelopeDigest', {
246
+ profile: signature.profile,
247
+ publicKeyBytesHex: normalizeHex(signature.publicKeyBytesHex),
248
+ publicKeyDigest: signature.publicKeyDigest,
249
+ signatureBytesHex: normalizeHex(signature.signatureBytesHex),
250
+ signedRoot: signature.signedRoot,
251
+ });
252
+ export const createProtocolSignatureFixture = (input) => {
253
+ const secretKey = decodeHexField(normalizeHex(input.secretKeyBytesHex), mlDsa65SecretKeyByteLength, 'secretKeyBytesHex');
254
+ const message = canonicalSignedRootMessage(input.signedRoot);
255
+ const signatureBytes = ml_dsa65.sign(message, secretKey, {
256
+ context: textEncoder.encode(input.profile.contextString),
257
+ extraEntropy: false,
258
+ });
259
+ const signature = {
260
+ profile: input.profile,
261
+ publicKeyBytesHex: normalizeHex(input.publicKeyBytesHex),
262
+ publicKeyDigest: input.publicKeyDigest,
263
+ signatureBytesHex: bytesToHex(signatureBytes),
264
+ signedRoot: input.signedRoot,
265
+ };
266
+ return {
267
+ ...signature,
268
+ signatureDigest: deriveProtocolSignatureDigest(signature),
269
+ };
270
+ };
271
+ const validateProfile = (signature) => {
272
+ const byteLength = deriveMlDsaContextByteLength(signature.profile.contextString);
273
+ if (signature.profile.algorithm !== 'ML-DSA-65') {
274
+ return emptySignatureVerificationResult('InvalidSignature', 'Signature profile must use ML-DSA-65.', signature.signatureDigest);
275
+ }
276
+ if (signature.profile.mode !== 'PureMLDSA') {
277
+ return emptySignatureVerificationResult('InvalidSignature', 'Only PureMLDSA signatures are supported by this verifier.', signature.signatureDigest);
278
+ }
279
+ if (signature.profile.providerName.length === 0 ||
280
+ signature.profile.providerVersion.length === 0 ||
281
+ signature.profile.providerBuildHash.length === 0 ||
282
+ signature.profile.fips204Version.length === 0 ||
283
+ signature.profile.errataStatus.length === 0) {
284
+ return emptySignatureVerificationResult('InvalidSignature', 'Signature profile metadata must be fully bound.', signature.signatureDigest);
285
+ }
286
+ if (byteLength > mlDsaContextByteLimit) {
287
+ return emptySignatureVerificationResult('InvalidMlDsaContext', 'ML-DSA context strings must be at most 255 bytes.', signature.signatureDigest);
288
+ }
289
+ if (signature.profile.contextStringByteLength !== byteLength) {
290
+ return emptySignatureVerificationResult('InvalidMlDsaContext', 'ML-DSA context string byte length does not match the profile.', signature.signatureDigest);
291
+ }
292
+ if (signature.profile.contextString !== supportedMlDsaContextString) {
293
+ return emptySignatureVerificationResult('InvalidMlDsaContext', 'ML-DSA context string does not match the supported protocol context.', signature.signatureDigest);
294
+ }
295
+ return undefined;
296
+ };
297
+ const validateSignatureMaterial = (signature) => {
298
+ try {
299
+ decodeHexField(normalizeHex(signature.publicKeyBytesHex), mlDsa65PublicKeyByteLength, 'publicKeyBytesHex');
300
+ decodeHexField(normalizeHex(signature.signatureBytesHex), mlDsa65SignatureByteLength, 'signatureBytesHex');
301
+ }
302
+ catch {
303
+ return emptySignatureVerificationResult('InvalidSignature', 'Signature envelope contains malformed ML-DSA key or signature bytes.', signature.signatureDigest);
304
+ }
305
+ const expectedPublicKeyDigest = deriveMlDsaPublicKeyDigest(normalizeHex(signature.publicKeyBytesHex));
306
+ if (signature.publicKeyDigest !== expectedPublicKeyDigest) {
307
+ return emptySignatureVerificationResult('WrongPublicKey', 'Signature public key digest does not match the ML-DSA public key bytes.', signature.signatureDigest);
308
+ }
309
+ return undefined;
310
+ };
311
+ const validateSignedRootShape = (signedRoot) => {
312
+ const signedRootRecord = signedRoot;
313
+ const requiredFields = [
314
+ 'objectType',
315
+ 'objectVersion',
316
+ 'ceremonyId',
317
+ 'manifestHash',
318
+ 'boardHeadHash',
319
+ 'objectRoot',
320
+ 'chunkMerkleRoot',
321
+ 'byteLength',
322
+ 'signerRole',
323
+ 'signerIdentity',
324
+ 'recoveryEpoch',
325
+ 'deviceEpoch',
326
+ 'contextDigest',
327
+ ];
328
+ const missingField = requiredFields.find((fieldName) => !Object.prototype.hasOwnProperty.call(signedRootRecord, fieldName));
329
+ if (missingField !== undefined) {
330
+ return emptySignatureVerificationResult('InvalidSignedRoot', `Signed roots must bind ${missingField}.`);
331
+ }
332
+ const objectRootPresent = typeof signedRoot.objectRoot === 'string';
333
+ const chunkMerkleRootPresent = typeof signedRoot.chunkMerkleRoot === 'string';
334
+ if (!objectRootPresent && !chunkMerkleRootPresent) {
335
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signed roots must bind an object root or chunk Merkle root.');
336
+ }
337
+ if (objectRootPresent && chunkMerkleRootPresent) {
338
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signed roots must bind exactly one object root or chunk Merkle root.');
339
+ }
340
+ if ((signedRoot.objectRoot !== null && !objectRootPresent) ||
341
+ (signedRoot.chunkMerkleRoot !== null && !chunkMerkleRootPresent) ||
342
+ (signedRoot.manifestHash !== null &&
343
+ typeof signedRoot.manifestHash !== 'string') ||
344
+ (signedRoot.boardHeadHash !== null &&
345
+ typeof signedRoot.boardHeadHash !== 'string')) {
346
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signed-root digest bindings must be canonical digest strings or null.');
347
+ }
348
+ if (!isNonNegativeInteger(signedRoot.objectVersion) ||
349
+ !isNonNegativeInteger(signedRoot.byteLength) ||
350
+ !isNonNegativeInteger(signedRoot.recoveryEpoch) ||
351
+ !isNonNegativeInteger(signedRoot.deviceEpoch)) {
352
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signed root version, byte length, and epochs must be non-negative integers.');
353
+ }
354
+ if (signedRoot.ceremonyId.length === 0 ||
355
+ signedRoot.signerIdentity.length === 0 ||
356
+ signedRoot.contextDigest.length === 0) {
357
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signed roots must bind ceremony, signer identity, and context digest.');
358
+ }
359
+ return undefined;
360
+ };
361
+ const validateExpectation = (signature, expectation) => {
362
+ const { signedRoot } = signature;
363
+ if (expectation.objectType !== undefined &&
364
+ signedRoot.objectType !== expectation.objectType) {
365
+ return emptySignatureVerificationResult('WrongObjectType', 'Signature root object type does not match the expected object.', signature.signatureDigest);
366
+ }
367
+ if (expectation.objectVersion !== undefined &&
368
+ signedRoot.objectVersion !== expectation.objectVersion) {
369
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root object version does not match the expected version.', signature.signatureDigest);
370
+ }
371
+ if (expectation.signerRole !== undefined &&
372
+ signedRoot.signerRole !== expectation.signerRole) {
373
+ return emptySignatureVerificationResult('WrongSignerRole', 'Signature root signer role does not match the expected role.', signature.signatureDigest);
374
+ }
375
+ if (expectation.signerIdentity !== undefined &&
376
+ signedRoot.signerIdentity !== expectation.signerIdentity) {
377
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root signer identity does not match the expected identity.', signature.signatureDigest);
378
+ }
379
+ if (expectation.ceremonyId !== undefined &&
380
+ signedRoot.ceremonyId !== expectation.ceremonyId) {
381
+ return emptySignatureVerificationResult('WrongCeremony', 'Signature root ceremony does not match the expected ceremony.', signature.signatureDigest);
382
+ }
383
+ if (expectation.publicKeyDigest !== undefined &&
384
+ signature.publicKeyDigest !== expectation.publicKeyDigest) {
385
+ return emptySignatureVerificationResult('WrongPublicKey', 'Signature public key digest does not match the expected key.', signature.signatureDigest);
386
+ }
387
+ if (expectation.manifestHash !== undefined &&
388
+ signedRoot.manifestHash !== expectation.manifestHash) {
389
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root manifest digest does not match the expected manifest.', signature.signatureDigest);
390
+ }
391
+ if (expectation.objectRoot !== undefined &&
392
+ signedRoot.objectRoot !== expectation.objectRoot) {
393
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root object digest does not match the signed object.', signature.signatureDigest);
394
+ }
395
+ if (expectation.boardHeadHash !== undefined &&
396
+ signedRoot.boardHeadHash !== expectation.boardHeadHash) {
397
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root board-head digest does not match the expected head.', signature.signatureDigest);
398
+ }
399
+ if (expectation.contextDigest !== undefined &&
400
+ signedRoot.contextDigest !== expectation.contextDigest) {
401
+ return emptySignatureVerificationResult('InvalidSignedRoot', 'Signature root context digest does not match the expected context.', signature.signatureDigest);
402
+ }
403
+ return undefined;
404
+ };
405
+ const verifySignedObjectSignatureInner = (signature, expectation = {}) => {
406
+ const profileFailure = validateProfile(signature);
407
+ if (profileFailure !== undefined) {
408
+ return profileFailure;
409
+ }
410
+ const materialFailure = validateSignatureMaterial(signature);
411
+ if (materialFailure !== undefined) {
412
+ return materialFailure;
413
+ }
414
+ const shapeFailure = validateSignedRootShape(signature.signedRoot);
415
+ if (shapeFailure !== undefined) {
416
+ return shapeFailure;
417
+ }
418
+ const expectationFailure = validateExpectation(signature, expectation);
419
+ if (expectationFailure !== undefined) {
420
+ return expectationFailure;
421
+ }
422
+ const expectedSignatureDigest = deriveProtocolSignatureDigest({
423
+ profile: signature.profile,
424
+ publicKeyBytesHex: normalizeHex(signature.publicKeyBytesHex),
425
+ publicKeyDigest: signature.publicKeyDigest,
426
+ signatureBytesHex: normalizeHex(signature.signatureBytesHex),
427
+ signedRoot: signature.signedRoot,
428
+ });
429
+ if (signature.signatureDigest !== expectedSignatureDigest) {
430
+ return emptySignatureVerificationResult('InvalidSignature', 'Signature digest does not verify for the canonical signed root.', signature.signatureDigest);
431
+ }
432
+ const publicKeyBytes = decodeHexField(normalizeHex(signature.publicKeyBytesHex), mlDsa65PublicKeyByteLength, 'publicKeyBytesHex');
433
+ const signatureBytes = decodeHexField(normalizeHex(signature.signatureBytesHex), mlDsa65SignatureByteLength, 'signatureBytesHex');
434
+ const signatureValid = ml_dsa65.verify(signatureBytes, canonicalSignedRootMessage(signature.signedRoot), publicKeyBytes, {
435
+ context: textEncoder.encode(signature.profile.contextString),
436
+ });
437
+ if (!signatureValid) {
438
+ return emptySignatureVerificationResult('InvalidSignature', 'ML-DSA signature does not verify for the canonical signed root.', signature.signatureDigest);
439
+ }
440
+ return successfulSignatureVerification(signature.signatureDigest);
441
+ };
442
+ export const verifySignedObjectSignature = (signature, expectation = {}) => {
443
+ try {
444
+ return verifySignedObjectSignatureInner(signature, expectation);
445
+ }
446
+ catch {
447
+ return emptySignatureVerificationResult('InvalidSignature', 'Signature envelope is not a canonical ML-DSA signed-root envelope.', signature
448
+ ?.signatureDigest);
449
+ }
450
+ };