@crisp-e3/sdk 0.13.0 → 0.15.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/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ type RoundDetails = {
38
38
  startTime: bigint;
39
39
  endTime: bigint;
40
40
  startBlock: bigint;
41
+ snapshotBlock: bigint;
41
42
  committeePublicKey: Uint8Array;
42
43
  emojis: [string, string];
43
44
  tokenAddress: string;
@@ -80,6 +81,15 @@ type MerkleProof = {
80
81
  * Type representing a vote
81
82
  */
82
83
  type Vote = number[];
84
+ /**
85
+ * Type representing a decoded tally: one total per option.
86
+ *
87
+ * @remarks
88
+ * Uses `bigint` because an aggregated tally coefficient is a sum over all ballots,
89
+ * so a total can exceed `Number.MAX_SAFE_INTEGER`. This matches the `BigUint` the
90
+ * CRISP server returns and the `uint256` the CRISP contract returns.
91
+ */
92
+ type TallyResult = bigint[];
83
93
  type ProofData = {
84
94
  publicInputs: string[];
85
95
  proof: Uint8Array;
@@ -139,6 +149,7 @@ type E3StateLiteResponse = {
139
149
  start_time: number;
140
150
  end_time: number;
141
151
  start_block: number;
152
+ snapshot_block: number;
142
153
  committee_public_key: number[];
143
154
  emojis: [string, string];
144
155
  token_address: string;
@@ -403,6 +414,17 @@ declare const getScaledBalance: (balance: bigint, decimals: bigint) => bigint;
403
414
  * encoding, encryption, decryption, and tally decoding.
404
415
  */
405
416
 
417
+ /**
418
+ * Encodes vote choices into a polynomial coefficient array for BFV encryption.
419
+ * Each choice occupies floor(MAX_MSG_NON_ZERO_COEFFS / n) binary coefficients;
420
+ * remaining slots in the first MAX_MSG_NON_ZERO_COEFFS coeffs are zero; then
421
+ * the vector is padded to the BFV degree.
422
+ *
423
+ * @param vote - Array of numeric values per choice (e.g. [10, 5] for 2 options)
424
+ * @returns Array of 0s and 1s representing coefficients
425
+ * @throws If vote has fewer than 2 choices, any value exceeds max for its segment, or degree is too small
426
+ */
427
+ declare const encodeVote: (vote: Vote) => number[];
406
428
  /**
407
429
  * Encrypts an encoded vote using BFV homomorphic encryption.
408
430
  *
@@ -412,14 +434,29 @@ declare const getScaledBalance: (balance: bigint, decimals: bigint) => bigint;
412
434
  */
413
435
  declare const encryptVote: (vote: Vote, publicKey: Uint8Array) => Uint8Array;
414
436
  /**
415
- * Decodes raw tally bytes (or hex string) into vote values per choice.
437
+ * Decodes raw tally bytes (or coefficients) into a total per choice.
416
438
  * Expects the same segment layout as used in encodeVote.
417
439
  *
418
- * @param tallyBytes - Hex string or array of decoded numbers from tally/decryption
440
+ * Mirrors `crisp_utils::decode_tally` (Rust) and `CRISPProgram.decodeTally` (Solidity):
441
+ * only the first MAX_MSG_NON_ZERO_COEFFS coefficients carry the payload, split into
442
+ * `floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)` binary coefficients per choice, MSB first.
443
+ *
444
+ * @param tallyBytes - Hex string, or the polynomial coefficients from tally/decryption
445
+ * @param numChoices - Number of vote options: an integer from 2 to MAX_VOTE_OPTIONS
446
+ * @returns One total per choice
447
+ * @throws If numChoices is outside 2..MAX_VOTE_OPTIONS or not an integer, or there are fewer
448
+ * coefficients than the payload region
449
+ */
450
+ declare const decodeTally: (tallyBytes: string | number[] | bigint[], numChoices: number) => TallyResult;
451
+ /**
452
+ * Decrypts a BFV-encrypted vote and decodes it to vote values.
453
+ *
454
+ * @param ciphertext - Encrypted vote
455
+ * @param secretKey - BFV secret key
419
456
  * @param numChoices - Number of vote options
420
- * @returns Vote array with one value per choice
457
+ * @returns One total per choice
421
458
  */
422
- declare const decodeTally: (tallyBytes: string | number[], numChoices: number) => Vote;
459
+ declare const decryptVote: (ciphertext: Uint8Array, secretKey: Uint8Array, numChoices: number) => TallyResult;
423
460
  /**
424
461
  * Generates a BFV keypair for vote encryption and decryption.
425
462
  *
@@ -594,4 +631,4 @@ declare class CrispSDK {
594
631
  getPreviousCiphertext(e3Id: number, address: string): Promise<Uint8Array | undefined>;
595
632
  }
596
633
 
597
- export { type BroadcastVoteRequest, type BroadcastVoteResponse, CreditMode, CrispSDK, type CurrentRoundResponse, type E3StateLiteResponse, type JsonResponse, MAX_MSG_NON_ZERO_COEFFS, MAX_VOTE_OPTIONS, MERKLE_TREE_MAX_DEPTH, type MaskVoteProofInputs, type NewRoundRequest, type OnChainRoundData, type ProofData, type RoundDetails, SIGNATURE_MESSAGE, SIGNATURE_MESSAGE_HASH, type TokenDetails, type TokenHolder, type Vote, type VoteProofInputs, type VoteResponseStatus, type VoteStatusResponse, type WebResultResponse, broadcastVote, decodeTally, destroyBBApi, encodeSolidityProof, encryptVote, generateBFVKeys, generateMaskVoteProof, generateMerkleProof, generateMerkleTree, generateVoteProof, getAddressFromSignature, getAllRoundResults, getBalanceAt, getCurrentRound, getEligibleAddresses, getMaxVoteValue, getOnChainRoundData, getPreviousCiphertext, getRoundCiphertext, getRoundDetails, getRoundPublicKey, getRoundResult, getRoundStateLite, getRoundTokenDetails, getScaledBalance, getTokenHolderHashes, getTotalSupplyAt, getTreeData, getVoteStatus, getZeroVote, hashLeaf, requestNewRound, validateVote, verifyProof };
634
+ export { type BroadcastVoteRequest, type BroadcastVoteResponse, CreditMode, CrispSDK, type CurrentRoundResponse, type E3StateLiteResponse, type JsonResponse, MAX_MSG_NON_ZERO_COEFFS, MAX_VOTE_OPTIONS, MERKLE_TREE_MAX_DEPTH, type MaskVoteProofInputs, type NewRoundRequest, type OnChainRoundData, type ProofData, type RoundDetails, SIGNATURE_MESSAGE, SIGNATURE_MESSAGE_HASH, type TallyResult, type TokenDetails, type TokenHolder, type Vote, type VoteProofInputs, type VoteResponseStatus, type VoteStatusResponse, type WebResultResponse, broadcastVote, decodeTally, decryptVote, destroyBBApi, encodeSolidityProof, encodeVote, encryptVote, generateBFVKeys, generateMaskVoteProof, generateMerkleProof, generateMerkleTree, generateVoteProof, getAddressFromSignature, getAllRoundResults, getBalanceAt, getCurrentRound, getEligibleAddresses, getMaxVoteValue, getOnChainRoundData, getPreviousCiphertext, getRoundCiphertext, getRoundDetails, getRoundPublicKey, getRoundResult, getRoundStateLite, getRoundTokenDetails, getScaledBalance, getTokenHolderHashes, getTotalSupplyAt, getTreeData, getVoteStatus, getZeroVote, hashLeaf, requestNewRound, validateVote, verifyProof };
package/dist/index.js CHANGED
@@ -172,6 +172,7 @@ var getRoundDetails = async (serverUrl, e3Id) => {
172
172
  startTime: BigInt(data.start_time),
173
173
  endTime: BigInt(data.end_time),
174
174
  startBlock: BigInt(data.start_block),
175
+ snapshotBlock: BigInt(data.snapshot_block),
175
176
  committeePublicKey: new Uint8Array(data.committee_public_key),
176
177
  emojis: data.emojis,
177
178
  numOptions: BigInt(data.num_options),
@@ -185,7 +186,7 @@ var getRoundTokenDetails = async (serverUrl, e3Id) => {
185
186
  return {
186
187
  tokenAddress: roundDetails.tokenAddress,
187
188
  threshold: roundDetails.balanceThreshold,
188
- snapshotBlock: roundDetails.startBlock
189
+ snapshotBlock: roundDetails.snapshotBlock
189
190
  };
190
191
  };
191
192
  var getOnChainRoundData = async (programAddress, e3Id, chainId) => {
@@ -294,7 +295,7 @@ var getMaxVoteValue = (numChoices) => {
294
295
  var getZeroVote = (numChoices) => {
295
296
  return Array(numChoices).fill(0);
296
297
  };
297
- var decodeBytesToNumbers = (data) => {
298
+ var decodeBytesToBigInts = (data) => {
298
299
  if (data.length % 8 !== 0) {
299
300
  throw new Error("Data length must be multiple of 8");
300
301
  }
@@ -304,7 +305,7 @@ var decodeBytesToNumbers = (data) => {
304
305
  for (let i = 0; i < arrayLength; i++) {
305
306
  result.push(view.getBigUint64(i * 8, true));
306
307
  }
307
- return result.map(Number);
308
+ return result;
308
309
  };
309
310
  var numberArrayToBigInt64Array = (numberArray) => {
310
311
  return BigInt64Array.from(numberArray.map(BigInt));
@@ -337,6 +338,9 @@ var encodeVote = (vote) => {
337
338
  if (numChoices < 2) {
338
339
  throw new Error("Vote must have at least two choices");
339
340
  }
341
+ if (numChoices > MAX_VOTE_OPTIONS) {
342
+ throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`);
343
+ }
340
344
  const bfvParams = getZkInputsGenerator().getBFVParams();
341
345
  const degree = bfvParams.degree;
342
346
  if (degree < MAX_MSG_NON_ZERO_COEFFS) {
@@ -370,27 +374,38 @@ var encryptVote = (vote, publicKey) => {
370
374
  return getZkInputsGenerator().encryptVote(publicKey, numberArrayToBigInt64Array(encodedVote));
371
375
  };
372
376
  var decodeTally = (tallyBytes, numChoices) => {
377
+ if (!Number.isInteger(numChoices) || numChoices < 2) {
378
+ throw new Error(`Number of choices (${numChoices}) must be an integer of at least 2`);
379
+ }
380
+ if (numChoices > MAX_VOTE_OPTIONS) {
381
+ throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`);
382
+ }
383
+ let coefficients;
373
384
  if (typeof tallyBytes === "string") {
374
385
  const hexString = tallyBytes.startsWith("0x") ? tallyBytes : `0x${tallyBytes}`;
375
- tallyBytes = decodeBytesToNumbers(hexToBytes2(hexString));
386
+ coefficients = decodeBytesToBigInts(hexToBytes2(hexString));
387
+ } else {
388
+ coefficients = tallyBytes.map(BigInt);
376
389
  }
377
- if (numChoices <= 0) {
378
- throw new Error("Number of choices must be positive");
390
+ if (coefficients.length < MAX_MSG_NON_ZERO_COEFFS) {
391
+ throw new Error(`decoded coefficient count (${coefficients.length}) is less than MAX_MSG_NON_ZERO_COEFFS (${MAX_MSG_NON_ZERO_COEFFS})`);
379
392
  }
380
393
  const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices);
381
394
  const results = [];
382
395
  for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx++) {
383
396
  const segmentStart = choiceIdx * segmentSize;
384
- const segment = tallyBytes.slice(segmentStart, segmentStart + segmentSize);
385
- let value = 0;
386
- for (let i = 0; i < segment.length; i++) {
387
- const weight = 2 ** (segment.length - 1 - i);
388
- value += segment[i] * weight;
397
+ let value = 0n;
398
+ for (let i = 0; i < segmentSize; i++) {
399
+ value += coefficients[segmentStart + i] << BigInt(segmentSize - 1 - i);
389
400
  }
390
401
  results.push(value);
391
402
  }
392
403
  return results;
393
404
  };
405
+ var decryptVote = (ciphertext, secretKey, numChoices) => {
406
+ const decryptedVote = getZkInputsGenerator().decryptVote(secretKey, ciphertext);
407
+ return decodeTally(Array.from(decryptedVote), numChoices);
408
+ };
394
409
  var generateBFVKeys = () => {
395
410
  return getZkInputsGenerator().generateKeys();
396
411
  };
@@ -3625,8 +3640,10 @@ export {
3625
3640
  SIGNATURE_MESSAGE_HASH,
3626
3641
  broadcastVote,
3627
3642
  decodeTally,
3643
+ decryptVote,
3628
3644
  destroyBBApi,
3629
3645
  encodeSolidityProof,
3646
+ encodeVote,
3630
3647
  encryptVote,
3631
3648
  generateBFVKeys,
3632
3649
  generateMaskVoteProof,