generated from exp-table/nplate
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbuild.ts
182 lines (158 loc) · 5.6 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { Barretenberg, Fr } from "@aztec/bb.js";
import { writeFileSync } from "fs";
import { cpus } from "node:os";
import {
hashMessage,
Hex,
parseEther,
toHex
} from "viem";
import { PrivateKeyAccount } from "viem/accounts";
import {
hexToUint8Array,
interpolatePolynomial,
kChooseN,
printDebug,
validatePolynomialRoots,
} from "./utils";
import { promptForSigners, promptForThreshold } from "./utils/inquirer";
import { getSafeTransactionHash } from "./utils/safe";
const MAX_SIGNERS = 8;
let barretenberg: Barretenberg;
export async function main() {
//
//// PART 1 :: POLYNOMIAL SET UP
// Here we will set up a polynomial to represent a set of all the valid signer sets on the Safe
///
console.log("\nGenerating polynomial...\n");
// ask the user in the CLI for the signer addresses they'd like to select
const signers: PrivateKeyAccount[] = await promptForSigners();
// ask the user for the signing threshold of the safe
const threshold: number = await promptForThreshold(signers.length);
// get a message hash of sending 1 ether to alice
const safe_message_hash = getSafeTransactionHash({
to: "0x00000000000000000000000000000000000A11c3",
value: parseEther("1"),
}) as Hex;
printDebug("Given the safe message hash", safe_message_hash);
printDebug("And a Threshold of: ", threshold);
printDebug(
"And the following of signers: ",
signers.map(({ address }) => address)
);
// represent the hex addresses as normal ppl numbers
const addresses_bigInt = signers.map(({ address }) => BigInt(address));
// sum up all the unique combinations of addresses
const combinations: bigint[] = kChooseN(addresses_bigInt, threshold);
printDebug(
"Yields the combinations: ",
combinations.map((c) => `0x` + c.toString(16))
);
// the roots of a new polynomial will be all the unique combinations
const roots = combinations;
printDebug(
"And roots: ",
roots.map((c) => `0x` + c.toString(16))
);
const P =
// convert the roots of the polynomial into an array-encoded polynomial
interpolatePolynomial(roots)
// pad the polynomial to up to 70 empty coefficients
// so: [4, 2, 4, 5]
// becomes: [4, 2, 4, 5, 0, 0, 0, ...]
.concat(new Array(100).fill(0n))
.slice(0, 71);
printDebug(
"And the polynomial",
P.map((p) => p.toString(16))
);
// sanity check that f(x) == 0 in all cases
validatePolynomialRoots(P, roots);
console.log("\nPolynomial generated and validated ✨\n");
//
//// PART 2 :: PROVER.TOML SETUP
// Here we will:
// - find the pubkey_x and pubkey_y values of the private key accounts, (derivable from every ethereum transaction)
// - hash the polynomial with pedersen
// - write the prover.toml
///
console.log("\nGenerating prover.toml...\n");
type PubKeyAndSigner = {
should_calculate: number;
pub_key_x: Uint8Array;
pub_key_y: Uint8Array;
signature: Uint8Array;
};
const pubKeyAndSigners: PubKeyAndSigner[] = await Promise.all(
signers.slice(0, threshold).map(async (account) => {
// secp256k1 public keys are encoded with a 1 byte prefix to self-describe their representation
// https://crypto.stackexchange.com/questions/108091/how-to-determine-the-prefix-of-a-secp256k1-compressed-public-key
// so we'll remove that first byte by removing the '0x04' utf-8 characters
const pubKey: string = account.publicKey.slice(4);
const pubKeyX = pubKey.slice(0, 64);
const pubKeyY = pubKey.slice(64);
// we will sign over the message with the PK
const signature = await account.signMessage({
message: safe_message_hash,
});
return {
should_calculate: 1,
pub_key_x: hexToUint8Array(pubKeyX),
pub_key_y: hexToUint8Array(pubKeyY),
signature: Uint8Array.from(
Buffer.from(signature.slice(2).slice(0, 128), "hex")
),
};
})
);
// fill the rest of the prover toml with empty PubKeyAndSigner structs (the no-op flag is `should_calculate`)
const pubKeyAndSignersWithEmpty: PubKeyAndSigner[] = pubKeyAndSigners.concat(
new Array(MAX_SIGNERS - pubKeyAndSigners.length).fill({
...pubKeyAndSigners[0],
should_calculate: 0,
})
);
// we hash that 'ish and write the prover.toml
barretenberg = await Barretenberg.new(Math.floor(cpus.length / 2));
const polynomial_hash = await barretenberg
.pedersenCommit(P.map((p) => new Fr(p)))
.then((point) => point.x);
// write the prover.toml
writeFileSync(
"circuits/Prover.toml",
`polynomial = [\n${
P.map((p) => `\t"0x${p.toString(16)}"`).join(",\n") + "\n"
}]\n` +
`polynomial_hash = "${polynomial_hash}"\n` +
`safe_message_hash = [${hexToUint8Array(
hashMessage(safe_message_hash)
)}]\n` +
`${pubKeyAndSignersWithEmpty
.map(
(v) =>
"\n[[signature_data]]\n" +
`should_calculate = ${v.should_calculate}\n` +
`pub_key_x = [${v.pub_key_x ?? new Array(32).fill(0)}]\n` +
`pub_key_y = [${v.pub_key_y ?? new Array(32).fill(0)}]\n` +
`signature = [${v.signature ?? new Array(64).fill(0)}]
`
)
.join("")}
`
);
// write inputs/*.json
writeFileSync(
"contracts/inputs/polynomial.json",
JSON.stringify({
polynomial: P.map((p) => toHex(p, { size: 32 })),
polynomial_hash: polynomial_hash.toString(),
})
);
console.log("\nWrote Prover.toml ✨\n");
console.log(
"\x1b[1m\x1b[32mrun:\n\n cd ./circuits && nargo build && nargo prove\x1b[0m"
);
}
main()
.catch(console.error)
.finally(async () => await barretenberg.destroy());