threshold-elgamal 1.0.9 → 2.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.
- package/README.md +33 -35
- package/dist/core/bigint.d.ts +2 -1
- package/dist/core/bigint.js +6 -1
- package/dist/core/bytes.js +4 -0
- package/dist/core/crypto.js +5 -0
- package/dist/core/errors.d.ts +50 -11
- package/dist/core/errors.js +50 -11
- package/dist/core/groups.d.ts +6 -1
- package/dist/core/groups.js +13 -2
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/public.d.ts +13 -0
- package/dist/core/public.js +12 -0
- package/dist/core/random.js +4 -0
- package/dist/core/ristretto.d.ts +7 -0
- package/dist/core/ristretto.js +7 -0
- package/dist/core/types.d.ts +14 -6
- package/dist/core/validation.d.ts +45 -12
- package/dist/core/validation.js +52 -12
- package/dist/dkg/pedersen-share-codec.d.ts +12 -2
- package/dist/dkg/pedersen-share-codec.js +19 -2
- package/dist/dkg/public.d.ts +12 -0
- package/dist/dkg/public.js +11 -0
- package/dist/dkg/verification.d.ts +33 -14
- package/dist/dkg/verification.js +16 -12
- package/dist/elgamal/additive.d.ts +15 -3
- package/dist/elgamal/additive.js +27 -23
- package/dist/elgamal/bsgs.js +7 -0
- package/dist/elgamal/public.d.ts +11 -0
- package/dist/elgamal/public.js +10 -0
- package/dist/elgamal/types.d.ts +6 -0
- package/dist/index.d.ts +27 -25
- package/dist/index.js +20 -19
- package/dist/proofs/disjunctive.d.ts +11 -16
- package/dist/proofs/disjunctive.js +12 -17
- package/dist/proofs/dleq.d.ts +17 -13
- package/dist/proofs/dleq.js +11 -12
- package/dist/proofs/helpers.d.ts +1 -1
- package/dist/proofs/helpers.js +5 -4
- package/dist/proofs/nonces.js +6 -0
- package/dist/proofs/public.d.ts +12 -0
- package/dist/proofs/public.js +11 -0
- package/dist/proofs/schnorr.d.ts +10 -11
- package/dist/proofs/schnorr.js +10 -11
- package/dist/proofs/types.d.ts +30 -5
- package/dist/protocol/board-audit.d.ts +8 -3
- package/dist/protocol/board-audit.js +8 -2
- package/dist/protocol/builders.d.ts +77 -13
- package/dist/protocol/builders.js +83 -13
- package/dist/protocol/canonical-json.js +4 -0
- package/dist/protocol/manifest.d.ts +32 -15
- package/dist/protocol/manifest.js +65 -18
- package/dist/protocol/ordering.js +6 -0
- package/dist/protocol/payloads.d.ts +3 -3
- package/dist/protocol/payloads.js +7 -3
- package/dist/protocol/public.d.ts +17 -0
- package/dist/protocol/public.js +16 -0
- package/dist/protocol/score-range.d.ts +6 -0
- package/dist/protocol/score-range.js +24 -0
- package/dist/protocol/transcript.js +4 -0
- package/dist/protocol/types.d.ts +110 -17
- package/dist/protocol/verification.d.ts +14 -7
- package/dist/protocol/verification.js +7 -5
- package/dist/protocol/voting-ballot-aggregation.d.ts +10 -2
- package/dist/protocol/voting-ballot-aggregation.js +6 -0
- package/dist/protocol/voting-ballots.d.ts +3 -5
- package/dist/protocol/voting-ballots.js +6 -5
- package/dist/protocol/voting-codecs.d.ts +8 -3
- package/dist/protocol/voting-codecs.js +18 -2
- package/dist/protocol/voting-decryption.d.ts +3 -5
- package/dist/protocol/voting-decryption.js +7 -5
- package/dist/protocol/voting-shared.d.ts +2 -1
- package/dist/protocol/voting-shared.js +9 -2
- package/dist/protocol/voting-verification.d.ts +27 -30
- package/dist/protocol/voting-verification.js +13 -26
- package/dist/serialize/encoding.js +4 -0
- package/dist/threshold/decrypt.d.ts +13 -5
- package/dist/threshold/decrypt.js +20 -5
- package/dist/threshold/public.d.ts +11 -0
- package/dist/threshold/public.js +10 -0
- package/dist/threshold/types.d.ts +23 -4
- package/dist/transport/auth.d.ts +10 -16
- package/dist/transport/auth.js +16 -16
- package/dist/transport/complaints.d.ts +2 -4
- package/dist/transport/complaints.js +8 -4
- package/dist/transport/envelopes.d.ts +5 -7
- package/dist/transport/envelopes.js +9 -7
- package/dist/transport/key-agreement.d.ts +15 -23
- package/dist/transport/key-agreement.js +21 -23
- package/dist/transport/types.d.ts +16 -2
- package/dist/vss/feldman.d.ts +11 -1
- package/dist/vss/feldman.js +11 -1
- package/dist/vss/pedersen.d.ts +13 -0
- package/dist/vss/pedersen.js +13 -0
- package/dist/vss/public.d.ts +10 -0
- package/dist/vss/public.js +9 -0
- package/dist/vss/types.d.ts +4 -0
- package/package.json +43 -14
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public payload-builder façade for the supported ceremony.
|
|
3
|
+
*
|
|
4
|
+
* Application-facing code normally uses this module instead of hand-assembling
|
|
5
|
+
* protocol payload objects and signatures.
|
|
6
|
+
*/
|
|
1
7
|
import { encodeScalar } from '../core/ristretto.js';
|
|
2
8
|
import { signPayloadBytes } from '../transport/auth.js';
|
|
3
9
|
import { attachProtocolVersion, signedProtocolPayloadBytes } from './payloads.js';
|
|
@@ -7,7 +13,12 @@ import { assertUniqueSortedIndices, BALLOT_CLOSE_PHASE, BALLOT_SUBMISSION_PHASE,
|
|
|
7
13
|
const isEncodedDisjunctiveProof = (proof) => typeof proof.branches[0]?.challenge === 'string';
|
|
8
14
|
const isEncodedCompactProof = (proof) => typeof proof.challenge === 'string';
|
|
9
15
|
const isEncodedFeldmanProofEntry = (proof) => typeof proof.challenge === 'string';
|
|
10
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Signs one canonical protocol payload with a participant auth key.
|
|
18
|
+
*
|
|
19
|
+
* All specialized payload builders in this module eventually route through
|
|
20
|
+
* this helper after normalizing the payload shape.
|
|
21
|
+
*/
|
|
11
22
|
export const signProtocolPayload = async (privateKey, payload) => {
|
|
12
23
|
const signedPayload = attachProtocolVersion(payload);
|
|
13
24
|
return {
|
|
@@ -15,7 +26,12 @@ export const signProtocolPayload = async (privateKey, payload) => {
|
|
|
15
26
|
signature: await signPayloadBytes(privateKey, signedProtocolPayloadBytes(signedPayload)),
|
|
16
27
|
};
|
|
17
28
|
};
|
|
18
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Creates a signed `manifest-publication` payload.
|
|
31
|
+
*
|
|
32
|
+
* This is the first public payload in the supported ceremony and anchors the
|
|
33
|
+
* explicit manifest on the board.
|
|
34
|
+
*/
|
|
19
35
|
export const createManifestPublicationPayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
20
36
|
protocolVersion: input.protocolVersion,
|
|
21
37
|
sessionId: input.sessionId,
|
|
@@ -25,7 +41,12 @@ export const createManifestPublicationPayload = async (privateKey, input) => sig
|
|
|
25
41
|
messageType: 'manifest-publication',
|
|
26
42
|
manifest: input.manifest,
|
|
27
43
|
});
|
|
28
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Creates a signed `registration` payload for one participant.
|
|
46
|
+
*
|
|
47
|
+
* Registrations publish the participant's auth key and transport key and are
|
|
48
|
+
* the source of truth for the accepted roster.
|
|
49
|
+
*/
|
|
29
50
|
export const createRegistrationPayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
30
51
|
protocolVersion: input.protocolVersion,
|
|
31
52
|
sessionId: input.sessionId,
|
|
@@ -37,7 +58,12 @@ export const createRegistrationPayload = async (privateKey, input) => signProtoc
|
|
|
37
58
|
authPublicKey: input.authPublicKey,
|
|
38
59
|
transportPublicKey: input.transportPublicKey,
|
|
39
60
|
});
|
|
40
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Creates a signed `manifest-acceptance` payload.
|
|
63
|
+
*
|
|
64
|
+
* This records that a participant accepted the frozen manifest and the roster
|
|
65
|
+
* hash under which they were assigned an index.
|
|
66
|
+
*/
|
|
41
67
|
export const createManifestAcceptancePayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
42
68
|
protocolVersion: input.protocolVersion,
|
|
43
69
|
sessionId: input.sessionId,
|
|
@@ -53,7 +79,12 @@ export const createManifestAcceptancePayload = async (privateKey, input) => sign
|
|
|
53
79
|
accountIdHash: input.accountIdHash,
|
|
54
80
|
}),
|
|
55
81
|
});
|
|
56
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Creates a signed `phase-checkpoint` payload over one DKG phase snapshot.
|
|
84
|
+
*
|
|
85
|
+
* Checkpoints let observers replay the DKG in coarse-grained epochs while
|
|
86
|
+
* preserving deterministic transcript commitments.
|
|
87
|
+
*/
|
|
57
88
|
export const createPhaseCheckpointPayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
58
89
|
protocolVersion: input.protocolVersion,
|
|
59
90
|
sessionId: input.sessionId,
|
|
@@ -65,20 +96,32 @@ export const createPhaseCheckpointPayload = async (privateKey, input) => signPro
|
|
|
65
96
|
checkpointTranscriptHash: await hashProtocolPhaseSnapshot(input.transcript.map((entry) => entry.payload), input.checkpointPhase),
|
|
66
97
|
qualifiedParticipantIndices: [...input.qualifiedParticipantIndices],
|
|
67
98
|
});
|
|
68
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Creates a signed `pedersen-commitment` payload for DKG phase 1.
|
|
101
|
+
*/
|
|
69
102
|
export const createPedersenCommitmentPayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
70
103
|
...input,
|
|
71
104
|
phase: 1,
|
|
72
105
|
messageType: 'pedersen-commitment',
|
|
73
106
|
commitments: [...input.commitments],
|
|
74
107
|
});
|
|
75
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Creates a signed `encrypted-dual-share` payload for DKG phase 1.
|
|
110
|
+
*
|
|
111
|
+
* The envelope contents are produced separately by the transport layer and are
|
|
112
|
+
* merely wrapped and signed here.
|
|
113
|
+
*/
|
|
76
114
|
export const createEncryptedDualSharePayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
77
115
|
...input,
|
|
78
116
|
phase: 1,
|
|
79
117
|
messageType: 'encrypted-dual-share',
|
|
80
118
|
});
|
|
81
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Creates a signed `feldman-commitment` payload for DKG phase 3.
|
|
121
|
+
*
|
|
122
|
+
* This payload exposes the public Feldman commitments and their Schnorr proofs
|
|
123
|
+
* after the complaint phase has been resolved.
|
|
124
|
+
*/
|
|
82
125
|
export const createFeldmanCommitmentPayload = async (privateKey, input) => {
|
|
83
126
|
const payload = {
|
|
84
127
|
...input,
|
|
@@ -97,13 +140,25 @@ export const createFeldmanCommitmentPayload = async (privateKey, input) => {
|
|
|
97
140
|
};
|
|
98
141
|
return signProtocolPayload(privateKey, payload);
|
|
99
142
|
};
|
|
100
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Creates a signed `key-derivation-confirmation` payload for DKG phase 4.
|
|
145
|
+
*
|
|
146
|
+
* Trustees use this to confirm the final derived joint key and transcript
|
|
147
|
+
* digest they believe the DKG produced.
|
|
148
|
+
*/
|
|
101
149
|
export const createKeyDerivationConfirmationPayload = async (privateKey, input) => signProtocolPayload(privateKey, {
|
|
102
150
|
...input,
|
|
103
151
|
phase: 4,
|
|
104
152
|
messageType: 'key-derivation-confirmation',
|
|
105
153
|
});
|
|
106
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Creates a signed `ballot-submission` payload for one participant and one
|
|
156
|
+
* option slot.
|
|
157
|
+
*
|
|
158
|
+
* Higher-level application code is expected to create the ciphertext and
|
|
159
|
+
* disjunctive proof first, then hand them to this builder for encoding and
|
|
160
|
+
* signing.
|
|
161
|
+
*/
|
|
107
162
|
export const createBallotSubmissionPayload = async (privateKey, input) => {
|
|
108
163
|
const payload = {
|
|
109
164
|
...input,
|
|
@@ -116,7 +171,12 @@ export const createBallotSubmissionPayload = async (privateKey, input) => {
|
|
|
116
171
|
};
|
|
117
172
|
return signProtocolPayload(privateKey, payload);
|
|
118
173
|
};
|
|
119
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Creates the organizer-signed `ballot-close` payload.
|
|
176
|
+
*
|
|
177
|
+
* This freezes which complete ballots are counted for the tally and therefore
|
|
178
|
+
* determines the accepted aggregate seen by every later decryption step.
|
|
179
|
+
*/
|
|
120
180
|
export const createBallotClosePayload = async (privateKey, input) => {
|
|
121
181
|
const countedParticipantIndices = [...input.countedParticipantIndices].sort((left, right) => left - right);
|
|
122
182
|
assertUniqueSortedIndices(countedParticipantIndices, 'Ballot close participant');
|
|
@@ -127,7 +187,12 @@ export const createBallotClosePayload = async (privateKey, input) => {
|
|
|
127
187
|
countedParticipantIndices,
|
|
128
188
|
});
|
|
129
189
|
};
|
|
130
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Creates a signed `decryption-share` payload for one option slot.
|
|
192
|
+
*
|
|
193
|
+
* The caller is responsible for computing the share and DLEQ proof first; this
|
|
194
|
+
* helper encodes and signs the published object.
|
|
195
|
+
*/
|
|
131
196
|
export const createDecryptionSharePayload = async (privateKey, input) => {
|
|
132
197
|
const payload = {
|
|
133
198
|
...input,
|
|
@@ -139,7 +204,12 @@ export const createDecryptionSharePayload = async (privateKey, input) => {
|
|
|
139
204
|
};
|
|
140
205
|
return signProtocolPayload(privateKey, payload);
|
|
141
206
|
};
|
|
142
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* Creates a signed `tally-publication` payload for one option slot.
|
|
209
|
+
*
|
|
210
|
+
* This is the final organizer-facing publication step after the accepted
|
|
211
|
+
* decryption shares have been combined into a bounded plaintext tally.
|
|
212
|
+
*/
|
|
143
213
|
export const createTallyPublicationPayload = async (privateKey, input) => {
|
|
144
214
|
const decryptionParticipantIndices = [
|
|
145
215
|
...input.decryptionParticipantIndices,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON serialization for manifests, protocol payloads, and
|
|
3
|
+
* other transcript-bound objects.
|
|
4
|
+
*/
|
|
1
5
|
import { InvalidPayloadError } from '../core/index.js';
|
|
2
6
|
import { bigintToFixedHex } from '../serialize/encoding.js';
|
|
3
7
|
const canonicalize = (value, options) => {
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
import type { ElectionManifest } from './types.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Default protocol namespace used by the built-in helpers and verifier.
|
|
4
|
+
*
|
|
5
|
+
* The current verifier only accepts this namespace, so applications that want
|
|
6
|
+
* verifier-compatible payloads should treat this value as fixed.
|
|
7
|
+
*/
|
|
3
8
|
export declare const SHIPPED_PROTOCOL_VERSION = "v1";
|
|
4
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Validates that a protocol version string is present and non-empty.
|
|
11
|
+
*
|
|
12
|
+
* Builders use this to normalize explicit overrides before attaching them to
|
|
13
|
+
* published payloads.
|
|
14
|
+
*/
|
|
5
15
|
export declare const assertValidProtocolVersion: (protocolVersion: string, label?: string) => string;
|
|
6
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Validates that a protocol version matches the verifier namespace.
|
|
18
|
+
*
|
|
19
|
+
* Verifier-facing paths call this when they need to reject protocol variants
|
|
20
|
+
* that are outside the package's supported public workflow.
|
|
21
|
+
*/
|
|
7
22
|
export declare const assertSupportedProtocolVersion: (protocolVersion: string, label?: string) => string;
|
|
8
23
|
/**
|
|
9
|
-
* Validates the supported election-manifest invariants for the
|
|
10
|
-
*
|
|
24
|
+
* Validates the supported election-manifest invariants for the score-voting
|
|
25
|
+
* workflow.
|
|
11
26
|
*
|
|
12
|
-
*
|
|
27
|
+
* The manifest is intentionally minimal: it fixes the frozen roster hash and
|
|
28
|
+
* option list together with one explicit global score range, while participant
|
|
29
|
+
* count and threshold are derived later from the accepted registration roster.
|
|
13
30
|
*/
|
|
14
31
|
export declare const validateElectionManifest: (manifest: ElectionManifest) => ElectionManifest;
|
|
15
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Creates the explicit election manifest after validating the supported
|
|
34
|
+
* invariants.
|
|
35
|
+
*/
|
|
16
36
|
export declare const createElectionManifest: (manifest: ElectionManifest) => ElectionManifest;
|
|
17
37
|
/**
|
|
18
38
|
* Canonically serializes an election manifest.
|
|
@@ -24,18 +44,15 @@ export declare const canonicalizeElectionManifest: (manifest: ElectionManifest)
|
|
|
24
44
|
/**
|
|
25
45
|
* Hashes a canonical election manifest with SHA-256.
|
|
26
46
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
47
|
+
* The resulting digest is the manifest anchor reused by every later payload,
|
|
48
|
+
* proof context, and verifier stage.
|
|
29
49
|
*/
|
|
30
50
|
export declare const hashElectionManifest: (manifest: ElectionManifest) => Promise<string>;
|
|
31
51
|
/**
|
|
32
52
|
* Derives a globally unique session identifier from the frozen setup values.
|
|
33
53
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @param timestamp Timestamp string included in the derivation.
|
|
38
|
-
* @param protocolVersion Protocol version namespace for the derived session.
|
|
39
|
-
* @returns Lowercase hexadecimal SHA-256 digest.
|
|
54
|
+
* Applications normally compute this once after freezing the manifest and
|
|
55
|
+
* roster so every later payload can bind itself to one concrete ceremony
|
|
56
|
+
* instance.
|
|
40
57
|
*/
|
|
41
58
|
export declare const deriveSessionId: (manifestHash: string, rosterHash: string, randomNonce: string, timestamp: string, protocolVersion?: string) => Promise<string>;
|
|
@@ -1,20 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest validation, hashing, and session-derivation helpers.
|
|
3
|
+
*
|
|
4
|
+
* This is the entry point for freezing the ceremony root that every later DKG,
|
|
5
|
+
* ballot, decryption-share, and tally payload binds to.
|
|
6
|
+
*/
|
|
1
7
|
import { bytesToHex } from '../core/bytes.js';
|
|
2
8
|
import { InvalidPayloadError, sha256, utf8ToBytes } from '../core/index.js';
|
|
3
9
|
import { encodeForChallenge } from '../serialize/encoding.js';
|
|
4
10
|
import { canonicalizeJson } from './canonical-json.js';
|
|
5
|
-
|
|
11
|
+
import { validateSupportedScoreRange } from './score-range.js';
|
|
12
|
+
/**
|
|
13
|
+
* Default protocol namespace used by the built-in helpers and verifier.
|
|
14
|
+
*
|
|
15
|
+
* The current verifier only accepts this namespace, so applications that want
|
|
16
|
+
* verifier-compatible payloads should treat this value as fixed.
|
|
17
|
+
*/
|
|
6
18
|
export const SHIPPED_PROTOCOL_VERSION = 'v1';
|
|
7
19
|
const assertNonEmptyString = (value, label) => {
|
|
8
20
|
if (value.trim() === '') {
|
|
9
21
|
throw new InvalidPayloadError(`${label} must be a non-empty string`);
|
|
10
22
|
}
|
|
11
23
|
};
|
|
12
|
-
|
|
24
|
+
const validateScoreRange = (scoreRange) => {
|
|
25
|
+
if (typeof scoreRange !== 'object' ||
|
|
26
|
+
scoreRange === null ||
|
|
27
|
+
Array.isArray(scoreRange)) {
|
|
28
|
+
throw new InvalidPayloadError('Election manifest scoreRange must be an object with min and max bounds');
|
|
29
|
+
}
|
|
30
|
+
const scoreRangeRecord = scoreRange;
|
|
31
|
+
if (typeof scoreRangeRecord.min !== 'number' ||
|
|
32
|
+
typeof scoreRangeRecord.max !== 'number') {
|
|
33
|
+
throw new InvalidPayloadError('Election manifest scoreRange requires numeric min and max bounds');
|
|
34
|
+
}
|
|
35
|
+
return validateSupportedScoreRange(scoreRange, {
|
|
36
|
+
comparisonMax: 'scoreRange.max',
|
|
37
|
+
min: 'Election manifest scoreRange.min',
|
|
38
|
+
max: 'Election manifest scoreRange.max',
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Validates that a protocol version string is present and non-empty.
|
|
43
|
+
*
|
|
44
|
+
* Builders use this to normalize explicit overrides before attaching them to
|
|
45
|
+
* published payloads.
|
|
46
|
+
*/
|
|
13
47
|
export const assertValidProtocolVersion = (protocolVersion, label = 'Protocol version') => {
|
|
14
48
|
assertNonEmptyString(protocolVersion, label);
|
|
15
49
|
return protocolVersion;
|
|
16
50
|
};
|
|
17
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Validates that a protocol version matches the verifier namespace.
|
|
53
|
+
*
|
|
54
|
+
* Verifier-facing paths call this when they need to reject protocol variants
|
|
55
|
+
* that are outside the package's supported public workflow.
|
|
56
|
+
*/
|
|
18
57
|
export const assertSupportedProtocolVersion = (protocolVersion, label = 'Protocol version') => {
|
|
19
58
|
assertValidProtocolVersion(protocolVersion, label);
|
|
20
59
|
if (protocolVersion !== SHIPPED_PROTOCOL_VERSION) {
|
|
@@ -23,14 +62,19 @@ export const assertSupportedProtocolVersion = (protocolVersion, label = 'Protoco
|
|
|
23
62
|
return protocolVersion;
|
|
24
63
|
};
|
|
25
64
|
/**
|
|
26
|
-
* Validates the supported election-manifest invariants for the
|
|
27
|
-
*
|
|
65
|
+
* Validates the supported election-manifest invariants for the score-voting
|
|
66
|
+
* workflow.
|
|
28
67
|
*
|
|
29
|
-
*
|
|
68
|
+
* The manifest is intentionally minimal: it fixes the frozen roster hash and
|
|
69
|
+
* option list together with one explicit global score range, while participant
|
|
70
|
+
* count and threshold are derived later from the accepted registration roster.
|
|
30
71
|
*/
|
|
31
72
|
export const validateElectionManifest = (manifest) => {
|
|
32
73
|
const manifestRecord = manifest;
|
|
33
74
|
assertNonEmptyString(manifest.rosterHash, 'Roster hash');
|
|
75
|
+
if (!('scoreRange' in manifestRecord)) {
|
|
76
|
+
throw new InvalidPayloadError('Election manifest requires an explicit scoreRange');
|
|
77
|
+
}
|
|
34
78
|
for (const legacyField of [
|
|
35
79
|
'participantCount',
|
|
36
80
|
'reconstructionThreshold',
|
|
@@ -48,7 +92,7 @@ export const validateElectionManifest = (manifest) => {
|
|
|
48
92
|
'requiresAllOptions',
|
|
49
93
|
]) {
|
|
50
94
|
if (legacyField in manifestRecord) {
|
|
51
|
-
throw new InvalidPayloadError(`Legacy manifest field "${legacyField}" is not supported
|
|
95
|
+
throw new InvalidPayloadError(`Legacy manifest field "${legacyField}" is not supported by the public manifest`);
|
|
52
96
|
}
|
|
53
97
|
}
|
|
54
98
|
if (manifest.optionList.length === 0) {
|
|
@@ -62,9 +106,15 @@ export const validateElectionManifest = (manifest) => {
|
|
|
62
106
|
}
|
|
63
107
|
seenOptions.add(option);
|
|
64
108
|
}
|
|
65
|
-
return
|
|
109
|
+
return {
|
|
110
|
+
...manifest,
|
|
111
|
+
scoreRange: validateScoreRange(manifest.scoreRange),
|
|
112
|
+
};
|
|
66
113
|
};
|
|
67
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Creates the explicit election manifest after validating the supported
|
|
116
|
+
* invariants.
|
|
117
|
+
*/
|
|
68
118
|
export const createElectionManifest = (manifest) => validateElectionManifest(manifest);
|
|
69
119
|
/**
|
|
70
120
|
* Canonically serializes an election manifest.
|
|
@@ -76,18 +126,15 @@ export const canonicalizeElectionManifest = (manifest) => canonicalizeJson(valid
|
|
|
76
126
|
/**
|
|
77
127
|
* Hashes a canonical election manifest with SHA-256.
|
|
78
128
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
129
|
+
* The resulting digest is the manifest anchor reused by every later payload,
|
|
130
|
+
* proof context, and verifier stage.
|
|
81
131
|
*/
|
|
82
132
|
export const hashElectionManifest = async (manifest) => bytesToHex(await sha256(utf8ToBytes(canonicalizeElectionManifest(manifest))));
|
|
83
133
|
/**
|
|
84
134
|
* Derives a globally unique session identifier from the frozen setup values.
|
|
85
135
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @param timestamp Timestamp string included in the derivation.
|
|
90
|
-
* @param protocolVersion Protocol version namespace for the derived session.
|
|
91
|
-
* @returns Lowercase hexadecimal SHA-256 digest.
|
|
136
|
+
* Applications normally compute this once after freezing the manifest and
|
|
137
|
+
* roster so every later payload can bind itself to one concrete ceremony
|
|
138
|
+
* instance.
|
|
92
139
|
*/
|
|
93
|
-
export const deriveSessionId = async (manifestHash, rosterHash, randomNonce, timestamp, protocolVersion
|
|
140
|
+
export const deriveSessionId = async (manifestHash, rosterHash, randomNonce, timestamp, protocolVersion) => bytesToHex(await sha256(encodeForChallenge(assertValidProtocolVersion(protocolVersion ?? SHIPPED_PROTOCOL_VERSION, 'Session protocol version'), manifestHash, rosterHash, randomNonce, timestamp)));
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic ordering helpers for protocol payloads.
|
|
3
|
+
*
|
|
4
|
+
* Transcript hashing and board audit both rely on this ordering so all
|
|
5
|
+
* observers hash the same payload set in the same way.
|
|
6
|
+
*/
|
|
1
7
|
import { bytesToHex } from '../core/bytes.js';
|
|
2
8
|
import { canonicalUnsignedPayloadBytes, payloadSlotKey } from './payloads.js';
|
|
3
9
|
const compareStrings = (left, right) => {
|
|
@@ -21,9 +21,9 @@ export declare const canonicalUnsignedPayloadBytes: (payload: ProtocolPayload, b
|
|
|
21
21
|
/**
|
|
22
22
|
* Serializes the payload bytes that are covered by the outer Ed25519 signature.
|
|
23
23
|
*
|
|
24
|
-
* The signature preimage is domain-separated and
|
|
25
|
-
* payload signatures are not re-used across transcript families or
|
|
26
|
-
*
|
|
24
|
+
* The signature preimage is domain-separated and protocol-bound so that
|
|
25
|
+
* protocol payload signatures are not re-used across transcript families or
|
|
26
|
+
* protocol namespaces.
|
|
27
27
|
*
|
|
28
28
|
* @param payload Unsigned protocol payload.
|
|
29
29
|
* @param bigintByteLength Fixed byte width used for any bigint fields.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical protocol-payload attachment, serialization, and slot-classification
|
|
3
|
+
* helpers shared by payload builders, board audit, and verification.
|
|
4
|
+
*/
|
|
1
5
|
import { bytesToHex } from '../core/bytes.js';
|
|
2
6
|
import { utf8ToBytes } from '../core/index.js';
|
|
3
7
|
import { encodeForChallenge } from '../serialize/encoding.js';
|
|
@@ -48,9 +52,9 @@ export const canonicalUnsignedPayloadBytes = (payload, bigintByteLength) => utf8
|
|
|
48
52
|
/**
|
|
49
53
|
* Serializes the payload bytes that are covered by the outer Ed25519 signature.
|
|
50
54
|
*
|
|
51
|
-
* The signature preimage is domain-separated and
|
|
52
|
-
* payload signatures are not re-used across transcript families or
|
|
53
|
-
*
|
|
55
|
+
* The signature preimage is domain-separated and protocol-bound so that
|
|
56
|
+
* protocol payload signatures are not re-used across transcript families or
|
|
57
|
+
* protocol namespaces.
|
|
54
58
|
*
|
|
55
59
|
* @param payload Unsigned protocol payload.
|
|
56
60
|
* @param bigintByteLength Fixed byte width used for any bigint fields.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level protocol helpers for transcript hashing, generic signed payloads,
|
|
3
|
+
* registration-roster hashing, signature verification, ballot proof
|
|
4
|
+
* verification, and protocol payload types.
|
|
5
|
+
*
|
|
6
|
+
* Use this module when you want protocol helpers grouped by subsystem instead
|
|
7
|
+
* of importing them from the root package.
|
|
8
|
+
*
|
|
9
|
+
* @module threshold-elgamal/protocol
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
export { signProtocolPayload } from './builders.js';
|
|
13
|
+
export { hashProtocolTranscript } from './transcript.js';
|
|
14
|
+
export { hashRosterEntries, verifySignedProtocolPayloads, type RosterEntry, type VerifiedProtocolSignatures, } from './verification.js';
|
|
15
|
+
export { verifyBallotSubmissionPayloadsByOption } from './voting-ballots.js';
|
|
16
|
+
export { scoreRangeDomain } from './voting-codecs.js';
|
|
17
|
+
export type { EncodedCiphertext, EncodedCompactProof, EncodedDisjunctiveProof, ProtocolMessageType, ProtocolPayload, ScoreRange, } from './types.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level protocol helpers for transcript hashing, generic signed payloads,
|
|
3
|
+
* registration-roster hashing, signature verification, ballot proof
|
|
4
|
+
* verification, and protocol payload types.
|
|
5
|
+
*
|
|
6
|
+
* Use this module when you want protocol helpers grouped by subsystem instead
|
|
7
|
+
* of importing them from the root package.
|
|
8
|
+
*
|
|
9
|
+
* @module threshold-elgamal/protocol
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
export { signProtocolPayload } from './builders.js';
|
|
13
|
+
export { hashProtocolTranscript } from './transcript.js';
|
|
14
|
+
export { hashRosterEntries, verifySignedProtocolPayloads, } from './verification.js';
|
|
15
|
+
export { verifyBallotSubmissionPayloadsByOption } from './voting-ballots.js';
|
|
16
|
+
export { scoreRangeDomain } from './voting-codecs.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InvalidPayloadError } from '../core/index.js';
|
|
2
|
+
const MAX_SUPPORTED_SCORE_RANGE_MAX = 100;
|
|
3
|
+
const assertSafeInteger = (value, label) => {
|
|
4
|
+
if (!Number.isSafeInteger(value)) {
|
|
5
|
+
throw new InvalidPayloadError(`${label} must be a safe integer`);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export const validateSupportedScoreRange = (scoreRange, labels) => {
|
|
9
|
+
assertSafeInteger(scoreRange.min, labels.min);
|
|
10
|
+
assertSafeInteger(scoreRange.max, labels.max);
|
|
11
|
+
if (scoreRange.min < 0) {
|
|
12
|
+
throw new InvalidPayloadError(`${labels.min} must be non-negative`);
|
|
13
|
+
}
|
|
14
|
+
if (scoreRange.max < 0) {
|
|
15
|
+
throw new InvalidPayloadError(`${labels.max} must be non-negative`);
|
|
16
|
+
}
|
|
17
|
+
if (scoreRange.min > scoreRange.max) {
|
|
18
|
+
throw new InvalidPayloadError(`${labels.min} must not exceed ${labels.comparisonMax ?? labels.max}`);
|
|
19
|
+
}
|
|
20
|
+
if (scoreRange.max > MAX_SUPPORTED_SCORE_RANGE_MAX) {
|
|
21
|
+
throw new InvalidPayloadError(`${labels.max} must not exceed ${MAX_SUPPORTED_SCORE_RANGE_MAX}`);
|
|
22
|
+
}
|
|
23
|
+
return scoreRange;
|
|
24
|
+
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript hashing and fingerprint helpers for full protocol logs and
|
|
3
|
+
* individual phase snapshots.
|
|
4
|
+
*/
|
|
1
5
|
import { bytesToHex } from '../core/bytes.js';
|
|
2
6
|
import { sha256, utf8ToBytes } from '../core/index.js';
|
|
3
7
|
import { canonicalizeJson } from './canonical-json.js';
|