threshold-elgamal 1.0.0-beta.8 → 1.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 +47 -10
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/protocol/board-audit.js +1 -1
- package/dist/protocol/types.d.ts +2 -2
- package/dist/protocol/verification.js +1 -1
- package/dist/protocol/voting-shared.js +1 -1
- package/dist/protocol/voting-verification.d.ts +22 -3
- package/dist/protocol/voting-verification.js +21 -0
- package/dist/transport/envelopes.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,6 +41,18 @@ npm install threshold-elgamal
|
|
|
41
41
|
- Authentication signatures require Web Crypto `Ed25519`.
|
|
42
42
|
- Transport share exchange requires Web Crypto `X25519`.
|
|
43
43
|
|
|
44
|
+
## Documentation
|
|
45
|
+
|
|
46
|
+
- Hosted documentation site: [tenemo.github.io/threshold-elgamal](https://tenemo.github.io/threshold-elgamal/)
|
|
47
|
+
- Get started: [tenemo.github.io/threshold-elgamal/guides/getting-started](https://tenemo.github.io/threshold-elgamal/guides/getting-started/)
|
|
48
|
+
- Verifying a public board: [tenemo.github.io/threshold-elgamal/guides/verifying-a-public-board](https://tenemo.github.io/threshold-elgamal/guides/verifying-a-public-board/)
|
|
49
|
+
- Browser and worker usage: [tenemo.github.io/threshold-elgamal/guides/browser-and-worker-usage](https://tenemo.github.io/threshold-elgamal/guides/browser-and-worker-usage/)
|
|
50
|
+
- Published payload examples: [tenemo.github.io/threshold-elgamal/guides/published-payload-examples](https://tenemo.github.io/threshold-elgamal/guides/published-payload-examples/)
|
|
51
|
+
- Honest-majority voting flow: [tenemo.github.io/threshold-elgamal/guides/three-participant-voting-flow](https://tenemo.github.io/threshold-elgamal/guides/three-participant-voting-flow/)
|
|
52
|
+
- Security boundary: [tenemo.github.io/threshold-elgamal/guides/security-and-non-goals](https://tenemo.github.io/threshold-elgamal/guides/security-and-non-goals/)
|
|
53
|
+
- Production voting safety review: [tenemo.github.io/threshold-elgamal/guides/production-voting-safety-review](https://tenemo.github.io/threshold-elgamal/guides/production-voting-safety-review/)
|
|
54
|
+
- API docs: [tenemo.github.io/threshold-elgamal/api](https://tenemo.github.io/threshold-elgamal/api/)
|
|
55
|
+
|
|
44
56
|
## Browser support
|
|
45
57
|
|
|
46
58
|
The shipped cryptographic browser path is fixed:
|
|
@@ -80,6 +92,12 @@ There is no supported `n-of-n` mode and no supported public `k-of-n` configurati
|
|
|
80
92
|
|
|
81
93
|
Transcript verification requires key-derivation confirmations from every qualified participant.
|
|
82
94
|
|
|
95
|
+
## Choose your entry point
|
|
96
|
+
|
|
97
|
+
- Verifying a public board: start with the hosted guide for `tryVerifyElectionCeremony(...)` and `verifyElectionCeremony(...)`.
|
|
98
|
+
- Browser and worker usage: start with the browser guide for key generation, manifest setup, and encrypted transport envelopes.
|
|
99
|
+
- Payload shapes and storage: start with the payload examples guide if you need concrete JSON, posting, or persistence patterns.
|
|
100
|
+
|
|
83
101
|
## Getting started
|
|
84
102
|
|
|
85
103
|
```typescript
|
|
@@ -126,6 +144,34 @@ console.log(majorityThreshold(3)); // 2
|
|
|
126
144
|
console.log(sessionId.length); // 64
|
|
127
145
|
```
|
|
128
146
|
|
|
147
|
+
If your application consumes a complete public board, the shortest safe verifier entry point is:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import {
|
|
151
|
+
tryVerifyElectionCeremony,
|
|
152
|
+
type VerifyElectionCeremonyInput,
|
|
153
|
+
} from "threshold-elgamal";
|
|
154
|
+
|
|
155
|
+
const bundle: VerifyElectionCeremonyInput = {
|
|
156
|
+
manifest,
|
|
157
|
+
sessionId,
|
|
158
|
+
dkgTranscript,
|
|
159
|
+
ballotPayloads,
|
|
160
|
+
ballotClosePayload,
|
|
161
|
+
decryptionSharePayloads,
|
|
162
|
+
tallyPublications,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const result = await tryVerifyElectionCeremony(bundle);
|
|
166
|
+
|
|
167
|
+
if (!result.ok) {
|
|
168
|
+
console.error(result.error.stage, result.error.code, result.error.reason);
|
|
169
|
+
} else {
|
|
170
|
+
console.log(result.verified.perOptionTallies);
|
|
171
|
+
console.log(result.verified.boardAudit.overall.fingerprint);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
129
175
|
The root package also exposes public builders for:
|
|
130
176
|
|
|
131
177
|
- manifest publication
|
|
@@ -140,7 +186,7 @@ The root package also exposes public builders for:
|
|
|
140
186
|
- decryption shares
|
|
141
187
|
- tally publication
|
|
142
188
|
|
|
143
|
-
For
|
|
189
|
+
For concrete integration examples, start with the hosted guides below. The repository integration harness exercises the same workflow, but it is not part of the supported public API.
|
|
144
190
|
|
|
145
191
|
## Security boundary
|
|
146
192
|
|
|
@@ -167,15 +213,6 @@ What it does not claim:
|
|
|
167
213
|
|
|
168
214
|
For a production-threat-model verdict that maps these boundaries to the shipped verifier and tests, read the production voting safety review in the hosted docs.
|
|
169
215
|
|
|
170
|
-
## Documentation
|
|
171
|
-
|
|
172
|
-
- Hosted documentation site: [tenemo.github.io/threshold-elgamal](https://tenemo.github.io/threshold-elgamal/)
|
|
173
|
-
- Get started: [tenemo.github.io/threshold-elgamal/guides/getting-started](https://tenemo.github.io/threshold-elgamal/guides/getting-started/)
|
|
174
|
-
- Honest-majority voting flow: [tenemo.github.io/threshold-elgamal/guides/three-participant-voting-flow](https://tenemo.github.io/threshold-elgamal/guides/three-participant-voting-flow/)
|
|
175
|
-
- Security boundary: [tenemo.github.io/threshold-elgamal/guides/security-and-non-goals](https://tenemo.github.io/threshold-elgamal/guides/security-and-non-goals/)
|
|
176
|
-
- Production voting safety review: [tenemo.github.io/threshold-elgamal/guides/production-voting-safety-review](https://tenemo.github.io/threshold-elgamal/guides/production-voting-safety-review/)
|
|
177
|
-
- API docs: [tenemo.github.io/threshold-elgamal/api](https://tenemo.github.io/threshold-elgamal/api/)
|
|
178
|
-
|
|
179
216
|
## Development
|
|
180
217
|
|
|
181
218
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -17,14 +17,14 @@ export type { DecryptionShare, Share, VerifiedAggregateCiphertext, } from './thr
|
|
|
17
17
|
export { deriveJointPublicKey, deriveTranscriptVerificationKey, verifyDKGTranscript, } from './dkg/verification.js';
|
|
18
18
|
export { decodePedersenShareEnvelope, encodePedersenShareEnvelope, } from './dkg/pedersen-share-codec.js';
|
|
19
19
|
export type { VerifyDKGTranscriptInput, VerifiedDKGTranscript, } from './dkg/verification.js';
|
|
20
|
-
export { generateFeldmanCommitments, verifyFeldmanShare
|
|
20
|
+
export { generateFeldmanCommitments, verifyFeldmanShare } from './vss/feldman.js';
|
|
21
21
|
export { derivePedersenShares, generatePedersenCommitments, verifyPedersenShare, } from './vss/pedersen.js';
|
|
22
22
|
export type { FeldmanCommitments, PedersenCommitments, PedersenShare, } from './vss/types.js';
|
|
23
23
|
export { createBallotClosePayload, createBallotSubmissionPayload, createDecryptionSharePayload, createEncryptedDualSharePayload, createFeldmanCommitmentPayload, createKeyDerivationConfirmationPayload, createManifestAcceptancePayload, createManifestPublicationPayload, createPedersenCommitmentPayload, createPhaseCheckpointPayload, createRegistrationPayload, createTallyPublicationPayload, signProtocolPayload, } from './protocol/builders.js';
|
|
24
24
|
export { canonicalizeElectionManifest, createElectionManifest, deriveSessionId, hashElectionManifest, SHIPPED_PROTOCOL_VERSION, validateElectionManifest, } from './protocol/manifest.js';
|
|
25
25
|
export { hashRosterEntries } from './protocol/verification.js';
|
|
26
26
|
export { hashProtocolTranscript } from './protocol/transcript.js';
|
|
27
|
-
export { verifyElectionCeremony, tryVerifyElectionCeremony, type ElectionVerificationErrorCode, type ElectionVerificationFailure, type ElectionVerificationResult, type ElectionVerificationStage, type VerifiedElectionCeremony,
|
|
27
|
+
export { verifyElectionCeremony, tryVerifyElectionCeremony, type ElectionVerificationErrorCode, type ElectionVerificationFailure, type ElectionVerificationResult, type ElectionVerificationStage, type VerifiedElectionCeremony, } from './protocol/voting-verification.js';
|
|
28
28
|
export { verifyBallotSubmissionPayloadsByOption } from './protocol/voting-ballots.js';
|
|
29
29
|
export { scoreVotingDomain } from './protocol/voting-codecs.js';
|
|
30
|
-
export type { BallotClosePayload, BallotSubmissionPayload, DecryptionSharePayload, ElectionManifest, EncodedCiphertext, EncodedCompactProof, EncodedDisjunctiveProof, EncryptedDualSharePayload, FeldmanCommitmentPayload, KeyDerivationConfirmation, ManifestAcceptancePayload, ManifestPublicationPayload, PedersenCommitmentPayload, PhaseCheckpointPayload, ProtocolMessageType, ProtocolPayload, RegistrationPayload, SignedPayload, TallyPublicationPayload, } from './protocol/types.js';
|
|
30
|
+
export type { BallotClosePayload, BallotSubmissionPayload, DecryptionSharePayload, ElectionManifest, EncodedCiphertext, EncodedCompactProof, EncodedDisjunctiveProof, EncryptedDualSharePayload, FeldmanCommitmentPayload, KeyDerivationConfirmation, ManifestAcceptancePayload, ManifestPublicationPayload, PedersenCommitmentPayload, PhaseCheckpointPayload, ProtocolMessageType, ProtocolPayload, RegistrationPayload, SignedPayload, TallyPublicationPayload, VerifyElectionCeremonyInput, } from './protocol/types.js';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { exportTransportPublicKey, generateTransportKeyPair, } from './transport
|
|
|
12
12
|
export { combineDecryptionShares } from './threshold/decrypt.js';
|
|
13
13
|
export { deriveJointPublicKey, deriveTranscriptVerificationKey, verifyDKGTranscript, } from './dkg/verification.js';
|
|
14
14
|
export { decodePedersenShareEnvelope, encodePedersenShareEnvelope, } from './dkg/pedersen-share-codec.js';
|
|
15
|
-
export { generateFeldmanCommitments, verifyFeldmanShare
|
|
15
|
+
export { generateFeldmanCommitments, verifyFeldmanShare } from './vss/feldman.js';
|
|
16
16
|
export { derivePedersenShares, generatePedersenCommitments, verifyPedersenShare, } from './vss/pedersen.js';
|
|
17
17
|
export { createBallotClosePayload, createBallotSubmissionPayload, createDecryptionSharePayload, createEncryptedDualSharePayload, createFeldmanCommitmentPayload, createKeyDerivationConfirmationPayload, createManifestAcceptancePayload, createManifestPublicationPayload, createPedersenCommitmentPayload, createPhaseCheckpointPayload, createRegistrationPayload, createTallyPublicationPayload, signProtocolPayload, } from './protocol/builders.js';
|
|
18
18
|
export { canonicalizeElectionManifest, createElectionManifest, deriveSessionId, hashElectionManifest, SHIPPED_PROTOCOL_VERSION, validateElectionManifest, } from './protocol/manifest.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InvalidPayloadError } from '../core/index.js';
|
|
2
2
|
import { compareProtocolPayloads } from './ordering.js';
|
|
3
3
|
import { classifySlotConflict, payloadSlotKey } from './payloads.js';
|
|
4
|
-
import { formatSessionFingerprint, hashProtocolTranscript
|
|
4
|
+
import { formatSessionFingerprint, hashProtocolTranscript } from './transcript.js';
|
|
5
5
|
const compareSignedPayloads = (left, right) => {
|
|
6
6
|
const payloadOrder = compareProtocolPayloads(left.payload, right.payload);
|
|
7
7
|
if (payloadOrder !== 0) {
|
package/dist/protocol/types.d.ts
CHANGED
|
@@ -191,8 +191,8 @@ export type VerifyDecryptionSharePayloadsByOptionInput = {
|
|
|
191
191
|
readonly manifest: ElectionManifest;
|
|
192
192
|
readonly sessionId: string;
|
|
193
193
|
};
|
|
194
|
-
/** Input bundle for
|
|
195
|
-
export type
|
|
194
|
+
/** Input bundle for full ceremony verification across all published options. */
|
|
195
|
+
export type VerifyElectionCeremonyInput = {
|
|
196
196
|
readonly manifest: ElectionManifest;
|
|
197
197
|
readonly sessionId: string;
|
|
198
198
|
readonly dkgTranscript: readonly SignedPayload[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InvalidPayloadError, assertValidParticipantIndex, sha256, utf8ToBytes, } from '../core/index.js';
|
|
2
2
|
import { bytesToHex } from '../serialize/index.js';
|
|
3
|
-
import { importAuthPublicKey, verifyPayloadSignature
|
|
3
|
+
import { importAuthPublicKey, verifyPayloadSignature } from '../transport/auth.js';
|
|
4
4
|
import { assertSupportedTransportPublicKeyEncoding } from '../transport/key-agreement.js';
|
|
5
5
|
import { canonicalizeJson } from './canonical-json.js';
|
|
6
6
|
import { canonicalUnsignedPayloadBytes } from './payloads.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertPositiveParticipantIndex, InvalidPayloadError, RISTRETTO_GROUP, } from '../core/index.js';
|
|
2
|
-
import { importAuthPublicKey, verifyPayloadSignature
|
|
2
|
+
import { importAuthPublicKey, verifyPayloadSignature } from '../transport/auth.js';
|
|
3
3
|
import { hashElectionManifest, SHIPPED_PROTOCOL_VERSION, validateElectionManifest, } from './manifest.js';
|
|
4
4
|
import { canonicalUnsignedPayloadBytes } from './payloads.js';
|
|
5
5
|
import { scoreVotingDomain } from './voting-codecs.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type VerifiedDKGTranscript } from '../dkg/verification.js';
|
|
2
2
|
import { type BoardAudit } from './board-audit.js';
|
|
3
|
-
import type { BallotClosePayload, BallotSubmissionPayload, DecryptionSharePayload, ElectionManifest,
|
|
3
|
+
import type { BallotClosePayload, BallotSubmissionPayload, DecryptionSharePayload, ElectionManifest, VerifyElectionCeremonyInput, VerifiedPublishedOptionVotingResult, TallyPublicationPayload } from './types.js';
|
|
4
4
|
/** Stable high-level failure codes for full ceremony verification. */
|
|
5
5
|
export type ElectionVerificationErrorCode = 'MANIFEST_INVALID' | 'BOARD_INVALID' | 'DKG_INVALID' | 'SIGNATURE_INVALID' | 'BALLOT_INVALID' | 'DECRYPTION_INVALID' | 'TALLY_INVALID';
|
|
6
6
|
/** Named verification stage used by the high-level ceremony verifier. */
|
|
@@ -38,8 +38,6 @@ export type VerifiedElectionCeremony = {
|
|
|
38
38
|
readonly dkg: VerifiedDKGTranscript;
|
|
39
39
|
readonly options: readonly VerifiedPublishedOptionVotingResult[];
|
|
40
40
|
};
|
|
41
|
-
/** Input bundle for full ceremony verification across all published options. */
|
|
42
|
-
export type VerifyElectionCeremonyInput = VerifyPublishedVotingResultsInput;
|
|
43
41
|
/** Non-throwing result shape for full ceremony verification. */
|
|
44
42
|
export type ElectionVerificationResult = {
|
|
45
43
|
readonly ok: true;
|
|
@@ -55,6 +53,15 @@ export type ElectionVerificationResult = {
|
|
|
55
53
|
*
|
|
56
54
|
* @param input Full public ceremony input bundle.
|
|
57
55
|
* @returns Detailed verified ceremony output.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const verified = await verifyElectionCeremony(bundle);
|
|
60
|
+
*
|
|
61
|
+
* console.log(verified.boardAudit.overall.fingerprint);
|
|
62
|
+
* console.log(verified.countedParticipantIndices);
|
|
63
|
+
* console.log(verified.perOptionTallies);
|
|
64
|
+
* ```
|
|
58
65
|
*/
|
|
59
66
|
export declare const verifyElectionCeremony: (input: VerifyElectionCeremonyInput) => Promise<VerifiedElectionCeremony>;
|
|
60
67
|
/**
|
|
@@ -63,5 +70,17 @@ export declare const verifyElectionCeremony: (input: VerifyElectionCeremonyInput
|
|
|
63
70
|
*
|
|
64
71
|
* @param input Full public ceremony input bundle.
|
|
65
72
|
* @returns Verified ceremony output or a stable structured failure.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const result = await tryVerifyElectionCeremony(bundle);
|
|
77
|
+
*
|
|
78
|
+
* if (!result.ok) {
|
|
79
|
+
* console.error(result.error.stage, result.error.code, result.error.reason);
|
|
80
|
+
* return;
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* console.log(result.verified.qualifiedParticipantIndices);
|
|
84
|
+
* ```
|
|
66
85
|
*/
|
|
67
86
|
export declare const tryVerifyElectionCeremony: (input: VerifyElectionCeremonyInput) => Promise<ElectionVerificationResult>;
|
|
@@ -55,6 +55,15 @@ const findOptionDecryptionShares = (decryptionShares, optionIndex) => {
|
|
|
55
55
|
*
|
|
56
56
|
* @param input Full public ceremony input bundle.
|
|
57
57
|
* @returns Detailed verified ceremony output.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const verified = await verifyElectionCeremony(bundle);
|
|
62
|
+
*
|
|
63
|
+
* console.log(verified.boardAudit.overall.fingerprint);
|
|
64
|
+
* console.log(verified.countedParticipantIndices);
|
|
65
|
+
* console.log(verified.perOptionTallies);
|
|
66
|
+
* ```
|
|
58
67
|
*/
|
|
59
68
|
export const verifyElectionCeremony = async (input) => {
|
|
60
69
|
let context;
|
|
@@ -249,6 +258,18 @@ export const verifyElectionCeremony = async (input) => {
|
|
|
249
258
|
*
|
|
250
259
|
* @param input Full public ceremony input bundle.
|
|
251
260
|
* @returns Verified ceremony output or a stable structured failure.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* const result = await tryVerifyElectionCeremony(bundle);
|
|
265
|
+
*
|
|
266
|
+
* if (!result.ok) {
|
|
267
|
+
* console.error(result.error.stage, result.error.code, result.error.reason);
|
|
268
|
+
* return;
|
|
269
|
+
* }
|
|
270
|
+
*
|
|
271
|
+
* console.log(result.verified.qualifiedParticipantIndices);
|
|
272
|
+
* ```
|
|
252
273
|
*/
|
|
253
274
|
export const tryVerifyElectionCeremony = async (input) => {
|
|
254
275
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toBufferSource } from '../core/bytes.js';
|
|
2
2
|
import { getWebCrypto, hkdfSha256, randomBytes } from '../core/index.js';
|
|
3
|
-
import { bytesToHex, encodeForChallenge, hexToBytes
|
|
3
|
+
import { bytesToHex, encodeForChallenge, hexToBytes } from '../serialize/index.js';
|
|
4
4
|
import { deriveTransportSharedSecret, exportTransportPrivateKey, exportTransportPublicKey, generateTransportKeyPair, importTransportPrivateKey, importTransportPublicKey, } from './key-agreement.js';
|
|
5
5
|
const envelopeKeySalt = (rosterHash) => new TextEncoder().encode(rosterHash);
|
|
6
6
|
export const encodeEnvelopeContext = (context) => encodeForChallenge(context.sessionId, BigInt(context.phase), BigInt(context.dealerIndex), BigInt(context.recipientIndex), context.envelopeId, context.payloadType, context.protocolVersion, context.suite);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "threshold-elgamal",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A browser-native TypeScript ElGamal library for Ristretto255-based research prototypes, shipping additive ElGamal, threshold decryption, protocol helpers, board auditing, transport primitives, and log-driven DKG state machines.",
|
|
5
5
|
"author": "Piotr Piech <piotr@piech.dev>",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"coverage:badge": "pnpm run coverage:node && tsx ./tools/generate-coverage-badge.ts",
|
|
24
24
|
"smoke:pack": "tsx ./tools/ci/verify-packed-package.ts",
|
|
25
25
|
"prebuild": "pnpm run lint && tsc && knip && tsx ./tools/ci/verify-vectors.ts && pnpm run test",
|
|
26
|
-
"build": "pnpm exec del-cli dist && tsc --project tsconfig.build.json",
|
|
26
|
+
"build": "pnpm exec del-cli dist && tsc --project tsconfig.build.json && tsx ./tools/build/rewrite-dist-relative-imports.ts",
|
|
27
27
|
"vectors:threshold": "tsx ./tools/generate-threshold-vectors.ts",
|
|
28
28
|
"vectors:protocol": "tsx ./tools/generate-protocol-vectors.ts",
|
|
29
29
|
"bench:micro": "tsx ./tools/benchmarks/microbench.ts",
|