@crisp-e3/sdk 0.0.1-test
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/LICENSE.md +165 -0
- package/README.md +18 -0
- package/dist/src/ERC20Votes.json +847 -0
- package/dist/src/constants.d.ts +18 -0
- package/dist/src/constants.js +23 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +12 -0
- package/dist/src/signature.d.ts +8 -0
- package/dist/src/signature.js +36 -0
- package/dist/src/state.d.ts +12 -0
- package/dist/src/state.js +48 -0
- package/dist/src/token.d.ts +23 -0
- package/dist/src/token.js +95 -0
- package/dist/src/types.d.ts +193 -0
- package/dist/src/types.js +16 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.js +71 -0
- package/dist/src/vote.d.ts +71 -0
- package/dist/src/vote.js +222 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +51 -0
package/dist/src/vote.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-3.0-only
|
|
2
|
+
//
|
|
3
|
+
// This file is provided WITHOUT ANY WARRANTY;
|
|
4
|
+
// without even the implied warranty of MERCHANTABILITY
|
|
5
|
+
// or FITNESS FOR A PARTICULAR PURPOSE.
|
|
6
|
+
import { ZKInputsGenerator } from '@crisp-e3/zk-inputs';
|
|
7
|
+
import { VotingMode } from './types';
|
|
8
|
+
import { toBinary } from './utils';
|
|
9
|
+
import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS, MESSAGE } from './constants';
|
|
10
|
+
import { extractSignature } from './signature';
|
|
11
|
+
import { Noir } from '@noir-lang/noir_js';
|
|
12
|
+
import { UltraHonkBackend } from '@aztec/bb.js';
|
|
13
|
+
import circuit from '../../../circuits/target/crisp_circuit.json';
|
|
14
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
15
|
+
/**
|
|
16
|
+
* This utility function calculates the first valid index for vote options
|
|
17
|
+
* based on the total voting power and degree.
|
|
18
|
+
* @dev This is needed to calculate the decoded plaintext
|
|
19
|
+
* @dev Also, we will need to check in the circuit that anything within these indices is
|
|
20
|
+
* either 0 or 1.
|
|
21
|
+
* @param totalVotingPower The maximum vote amount (if a single voter had all of the power)
|
|
22
|
+
* @param degree The degree of the polynomial
|
|
23
|
+
*/
|
|
24
|
+
export const calculateValidIndicesForPlaintext = (totalVotingPower, degree) => {
|
|
25
|
+
// Sanity check: degree must be even and positive
|
|
26
|
+
if (degree <= 0 || degree % 2 !== 0) {
|
|
27
|
+
throw new Error('Degree must be a positive even number');
|
|
28
|
+
}
|
|
29
|
+
// Calculate the number of bits needed to represent the total voting power
|
|
30
|
+
const bitsNeeded = totalVotingPower.toString(2).length;
|
|
31
|
+
const halfLength = Math.floor(degree / 2);
|
|
32
|
+
// Check if bits needed exceed half the degree
|
|
33
|
+
if (bitsNeeded > halfLength) {
|
|
34
|
+
throw new Error('Total voting power exceeds maximum representable votes for the given degree');
|
|
35
|
+
}
|
|
36
|
+
// For "yes": right-align in first half
|
|
37
|
+
// Start index = (half length) - (bits needed)
|
|
38
|
+
const yesIndex = halfLength - bitsNeeded;
|
|
39
|
+
// For "no": right-align in second half
|
|
40
|
+
// Start index = (full length) - (bits needed)
|
|
41
|
+
const noIndex = degree - bitsNeeded;
|
|
42
|
+
return {
|
|
43
|
+
yesIndex: yesIndex,
|
|
44
|
+
noIndex: noIndex,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Encode a vote based on the voting mode
|
|
49
|
+
* @param vote The vote to encode
|
|
50
|
+
* @param votingMode The voting mode to use for encoding
|
|
51
|
+
* @param votingPower The voting power of the voter
|
|
52
|
+
* @param bfvParams The BFV parameters to use for encoding
|
|
53
|
+
* @returns The encoded vote as a string
|
|
54
|
+
*/
|
|
55
|
+
export const encodeVote = (vote, votingMode, votingPower, bfvParams) => {
|
|
56
|
+
validateVote(votingMode, vote, votingPower);
|
|
57
|
+
switch (votingMode) {
|
|
58
|
+
case VotingMode.GOVERNANCE:
|
|
59
|
+
const voteArray = [];
|
|
60
|
+
const length = bfvParams?.degree || DEFAULT_BFV_PARAMS.degree;
|
|
61
|
+
const halfLength = length / 2;
|
|
62
|
+
const yesBinary = toBinary(vote.yes).split('');
|
|
63
|
+
const noBinary = toBinary(vote.no).split('');
|
|
64
|
+
// Fill first half with 'yes' binary representation (pad with leading 0s if needed)
|
|
65
|
+
for (let i = 0; i < halfLength; i++) {
|
|
66
|
+
const offset = halfLength - yesBinary.length;
|
|
67
|
+
voteArray.push(i < offset ? '0' : yesBinary[i - offset]);
|
|
68
|
+
}
|
|
69
|
+
// Fill second half with 'no' binary representation (pad with leading 0s if needed)
|
|
70
|
+
for (let i = 0; i < length - halfLength; i++) {
|
|
71
|
+
const offset = length - halfLength - noBinary.length;
|
|
72
|
+
voteArray.push(i < offset ? '0' : noBinary[i - offset]);
|
|
73
|
+
}
|
|
74
|
+
return voteArray;
|
|
75
|
+
default:
|
|
76
|
+
throw new Error('Unsupported voting mode');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Given an encoded tally, decode it into its decimal representation
|
|
81
|
+
* @param tally The encoded tally to decode
|
|
82
|
+
* @param votingMode The voting mode
|
|
83
|
+
*/
|
|
84
|
+
export const decodeTally = (tally, votingMode) => {
|
|
85
|
+
switch (votingMode) {
|
|
86
|
+
case VotingMode.GOVERNANCE:
|
|
87
|
+
const halfLength = tally.length / 2;
|
|
88
|
+
// Split the tally into two halves
|
|
89
|
+
const yesBinary = tally.slice(0, halfLength);
|
|
90
|
+
const noBinary = tally.slice(halfLength, tally.length);
|
|
91
|
+
let yes = 0n;
|
|
92
|
+
let no = 0n;
|
|
93
|
+
// Convert each half back to decimal
|
|
94
|
+
for (let i = 0; i < halfLength; i += 1) {
|
|
95
|
+
const weight = 2n ** BigInt(halfLength - 1 - i);
|
|
96
|
+
yes += BigInt(yesBinary[i]) * weight;
|
|
97
|
+
no += BigInt(noBinary[i]) * weight;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
yes,
|
|
101
|
+
no,
|
|
102
|
+
};
|
|
103
|
+
default:
|
|
104
|
+
throw new Error('Unsupported voting mode');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Validate whether a vote is valid for a given voting mode
|
|
109
|
+
* @param votingMode The voting mode to validate against
|
|
110
|
+
* @param vote The vote to validate
|
|
111
|
+
* @param votingPower The voting power of the voter
|
|
112
|
+
*/
|
|
113
|
+
export const validateVote = (votingMode, vote, votingPower) => {
|
|
114
|
+
switch (votingMode) {
|
|
115
|
+
case VotingMode.GOVERNANCE:
|
|
116
|
+
if (vote.yes > 0n && vote.no > 0n) {
|
|
117
|
+
throw new Error('Invalid vote for GOVERNANCE mode: cannot spread votes between options');
|
|
118
|
+
}
|
|
119
|
+
if (vote.yes > votingPower || vote.no > votingPower) {
|
|
120
|
+
throw new Error('Invalid vote for GOVERNANCE mode: vote exceeds voting power');
|
|
121
|
+
}
|
|
122
|
+
if (vote.yes > MAXIMUM_VOTE_VALUE || vote.no > MAXIMUM_VOTE_VALUE) {
|
|
123
|
+
throw new Error('Invalid vote for GOVERNANCE mode: vote exceeds maximum allowed value');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* This is a wrapper around enclave-e3/sdk encryption functions as CRISP circuit will require some more
|
|
129
|
+
* input values which generic Greco do not need.
|
|
130
|
+
* @param encodedVote The encoded vote as string array
|
|
131
|
+
* @param publicKey The public key to use for encryption
|
|
132
|
+
* @param previousCiphertext The previous ciphertext to use for addition operation
|
|
133
|
+
* @param bfvParams The BFV parameters to use for encryption
|
|
134
|
+
* @param merkleData The merkle proof data
|
|
135
|
+
* @param message The message that was signed
|
|
136
|
+
* @param signature The signature of the message
|
|
137
|
+
* @param balance The voter's balance
|
|
138
|
+
* @param slotAddress The voter's slot address
|
|
139
|
+
* @param isFirstVote Whether this is the first vote for this slot
|
|
140
|
+
* @returns The CRISP circuit inputs
|
|
141
|
+
*/
|
|
142
|
+
export const encryptVoteAndGenerateCRISPInputs = async ({ encodedVote, publicKey, previousCiphertext, bfvParams = DEFAULT_BFV_PARAMS, merkleData, message, signature, balance, slotAddress, isFirstVote, }) => {
|
|
143
|
+
if (encodedVote.length !== bfvParams.degree) {
|
|
144
|
+
throw new RangeError(`encodedVote length ${encodedVote.length} does not match BFV degree ${bfvParams.degree}`);
|
|
145
|
+
}
|
|
146
|
+
const zkInputsGenerator = new ZKInputsGenerator(bfvParams.degree, bfvParams.plaintextModulus, bfvParams.moduli);
|
|
147
|
+
const vote = BigInt64Array.from(encodedVote.map(BigInt));
|
|
148
|
+
const crispInputs = (await zkInputsGenerator.generateInputs(previousCiphertext, publicKey, vote));
|
|
149
|
+
const { hashed_message, pub_key_x, pub_key_y, signature: extractedSignature } = await extractSignature(message, signature);
|
|
150
|
+
return {
|
|
151
|
+
...crispInputs,
|
|
152
|
+
hashed_message: Array.from(hashed_message).map((b) => b.toString()),
|
|
153
|
+
public_key_x: Array.from(pub_key_x).map((b) => b.toString()),
|
|
154
|
+
public_key_y: Array.from(pub_key_y).map((b) => b.toString()),
|
|
155
|
+
signature: Array.from(extractedSignature).map((b) => b.toString()),
|
|
156
|
+
merkle_proof_length: merkleData.length.toString(),
|
|
157
|
+
merkle_proof_indices: merkleData.indices.map((i) => i.toString()),
|
|
158
|
+
merkle_proof_siblings: merkleData.proof.siblings.map((s) => s.toString()),
|
|
159
|
+
merkle_root: merkleData.proof.root.toString(),
|
|
160
|
+
slot_address: slotAddress,
|
|
161
|
+
balance: balance.toString(),
|
|
162
|
+
is_first_vote: isFirstVote,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* A function to generate the data required to mask a vote
|
|
167
|
+
* @param voter The voter's address
|
|
168
|
+
* @param publicKey The voter's public key
|
|
169
|
+
* @param previousCiphertext The previous ciphertext
|
|
170
|
+
* @param bfvParams The BFV parameters
|
|
171
|
+
* @param merkleRoot The merkle root of the census tree
|
|
172
|
+
* @param slotAddress The voter's slot address
|
|
173
|
+
* @param isFirstVote Whether this is the first vote for this slot
|
|
174
|
+
* @returns The CRISP circuit inputs for a mask vote
|
|
175
|
+
*/
|
|
176
|
+
export const generateMaskVote = async (publicKey, previousCiphertext, bfvParams = DEFAULT_BFV_PARAMS, merkleRoot, slotAddress, isFirstVote) => {
|
|
177
|
+
const plaintextVote = {
|
|
178
|
+
yes: 0n,
|
|
179
|
+
no: 0n,
|
|
180
|
+
};
|
|
181
|
+
const encodedVote = encodeVote(plaintextVote, VotingMode.GOVERNANCE, 0n, bfvParams);
|
|
182
|
+
const zkInputsGenerator = new ZKInputsGenerator(bfvParams.degree, bfvParams.plaintextModulus, bfvParams.moduli);
|
|
183
|
+
const vote = BigInt64Array.from(encodedVote.map(BigInt));
|
|
184
|
+
const crispInputs = (await zkInputsGenerator.generateInputs(previousCiphertext, publicKey, vote));
|
|
185
|
+
// hardhat default private key
|
|
186
|
+
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
187
|
+
const account = privateKeyToAccount(privateKey);
|
|
188
|
+
const signature = await account.signMessage({ message: MESSAGE });
|
|
189
|
+
const { hashed_message, pub_key_x, pub_key_y, signature: extractedSignature } = await extractSignature(MESSAGE, signature);
|
|
190
|
+
return {
|
|
191
|
+
...crispInputs,
|
|
192
|
+
hashed_message: Array.from(hashed_message).map((b) => b.toString()),
|
|
193
|
+
public_key_x: Array.from(pub_key_x).map((b) => b.toString()),
|
|
194
|
+
public_key_y: Array.from(pub_key_y).map((b) => b.toString()),
|
|
195
|
+
signature: Array.from(extractedSignature).map((b) => b.toString()),
|
|
196
|
+
merkle_proof_indices: Array.from({ length: 20 }, () => '0'),
|
|
197
|
+
merkle_proof_siblings: Array.from({ length: 20 }, () => '0'),
|
|
198
|
+
merkle_proof_length: '1',
|
|
199
|
+
merkle_root: merkleRoot.toString(),
|
|
200
|
+
slot_address: slotAddress,
|
|
201
|
+
balance: '0',
|
|
202
|
+
is_first_vote: isFirstVote,
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
export const generateProof = async (crispInputs) => {
|
|
206
|
+
const noir = new Noir(circuit);
|
|
207
|
+
const backend = new UltraHonkBackend(circuit.bytecode);
|
|
208
|
+
const { witness } = await noir.execute(crispInputs);
|
|
209
|
+
const proof = await backend.generateProof(witness);
|
|
210
|
+
return proof;
|
|
211
|
+
};
|
|
212
|
+
export const generateProofWithReturnValue = async (crispInputs) => {
|
|
213
|
+
const noir = new Noir(circuit);
|
|
214
|
+
const backend = new UltraHonkBackend(circuit.bytecode);
|
|
215
|
+
const { witness, returnValue } = await noir.execute(crispInputs);
|
|
216
|
+
const proof = await backend.generateProof(witness);
|
|
217
|
+
return { returnValue, proof };
|
|
218
|
+
};
|
|
219
|
+
export const verifyProof = async (proof) => {
|
|
220
|
+
const backend = new UltraHonkBackend(circuit.bytecode);
|
|
221
|
+
return await backend.verifyProof(proof);
|
|
222
|
+
};
|