@crisp-e3/sdk 0.14.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
@@ -81,6 +81,15 @@ type MerkleProof = {
81
81
  * Type representing a vote
82
82
  */
83
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[];
84
93
  type ProofData = {
85
94
  publicInputs: string[];
86
95
  proof: Uint8Array;
@@ -405,6 +414,17 @@ declare const getScaledBalance: (balance: bigint, decimals: bigint) => bigint;
405
414
  * encoding, encryption, decryption, and tally decoding.
406
415
  */
407
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[];
408
428
  /**
409
429
  * Encrypts an encoded vote using BFV homomorphic encryption.
410
430
  *
@@ -414,14 +434,29 @@ declare const getScaledBalance: (balance: bigint, decimals: bigint) => bigint;
414
434
  */
415
435
  declare const encryptVote: (vote: Vote, publicKey: Uint8Array) => Uint8Array;
416
436
  /**
417
- * Decodes raw tally bytes (or hex string) into vote values per choice.
437
+ * Decodes raw tally bytes (or coefficients) into a total per choice.
418
438
  * Expects the same segment layout as used in encodeVote.
419
439
  *
420
- * @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
421
456
  * @param numChoices - Number of vote options
422
- * @returns Vote array with one value per choice
457
+ * @returns One total per choice
423
458
  */
424
- declare const decodeTally: (tallyBytes: string | number[], numChoices: number) => Vote;
459
+ declare const decryptVote: (ciphertext: Uint8Array, secretKey: Uint8Array, numChoices: number) => TallyResult;
425
460
  /**
426
461
  * Generates a BFV keypair for vote encryption and decryption.
427
462
  *
@@ -596,4 +631,4 @@ declare class CrispSDK {
596
631
  getPreviousCiphertext(e3Id: number, address: string): Promise<Uint8Array | undefined>;
597
632
  }
598
633
 
599
- 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
@@ -295,7 +295,7 @@ var getMaxVoteValue = (numChoices) => {
295
295
  var getZeroVote = (numChoices) => {
296
296
  return Array(numChoices).fill(0);
297
297
  };
298
- var decodeBytesToNumbers = (data) => {
298
+ var decodeBytesToBigInts = (data) => {
299
299
  if (data.length % 8 !== 0) {
300
300
  throw new Error("Data length must be multiple of 8");
301
301
  }
@@ -305,7 +305,7 @@ var decodeBytesToNumbers = (data) => {
305
305
  for (let i = 0; i < arrayLength; i++) {
306
306
  result.push(view.getBigUint64(i * 8, true));
307
307
  }
308
- return result.map(Number);
308
+ return result;
309
309
  };
310
310
  var numberArrayToBigInt64Array = (numberArray) => {
311
311
  return BigInt64Array.from(numberArray.map(BigInt));
@@ -338,6 +338,9 @@ var encodeVote = (vote) => {
338
338
  if (numChoices < 2) {
339
339
  throw new Error("Vote must have at least two choices");
340
340
  }
341
+ if (numChoices > MAX_VOTE_OPTIONS) {
342
+ throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`);
343
+ }
341
344
  const bfvParams = getZkInputsGenerator().getBFVParams();
342
345
  const degree = bfvParams.degree;
343
346
  if (degree < MAX_MSG_NON_ZERO_COEFFS) {
@@ -371,27 +374,38 @@ var encryptVote = (vote, publicKey) => {
371
374
  return getZkInputsGenerator().encryptVote(publicKey, numberArrayToBigInt64Array(encodedVote));
372
375
  };
373
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;
374
384
  if (typeof tallyBytes === "string") {
375
385
  const hexString = tallyBytes.startsWith("0x") ? tallyBytes : `0x${tallyBytes}`;
376
- tallyBytes = decodeBytesToNumbers(hexToBytes2(hexString));
386
+ coefficients = decodeBytesToBigInts(hexToBytes2(hexString));
387
+ } else {
388
+ coefficients = tallyBytes.map(BigInt);
377
389
  }
378
- if (numChoices <= 0) {
379
- 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})`);
380
392
  }
381
393
  const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices);
382
394
  const results = [];
383
395
  for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx++) {
384
396
  const segmentStart = choiceIdx * segmentSize;
385
- const segment = tallyBytes.slice(segmentStart, segmentStart + segmentSize);
386
- let value = 0;
387
- for (let i = 0; i < segment.length; i++) {
388
- const weight = 2 ** (segment.length - 1 - i);
389
- 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);
390
400
  }
391
401
  results.push(value);
392
402
  }
393
403
  return results;
394
404
  };
405
+ var decryptVote = (ciphertext, secretKey, numChoices) => {
406
+ const decryptedVote = getZkInputsGenerator().decryptVote(secretKey, ciphertext);
407
+ return decodeTally(Array.from(decryptedVote), numChoices);
408
+ };
395
409
  var generateBFVKeys = () => {
396
410
  return getZkInputsGenerator().generateKeys();
397
411
  };
@@ -3626,8 +3640,10 @@ export {
3626
3640
  SIGNATURE_MESSAGE_HASH,
3627
3641
  broadcastVote,
3628
3642
  decodeTally,
3643
+ decryptVote,
3629
3644
  destroyBBApi,
3630
3645
  encodeSolidityProof,
3646
+ encodeVote,
3631
3647
  encryptVote,
3632
3648
  generateBFVKeys,
3633
3649
  generateMaskVoteProof,