@opeqlabs/sdk 0.0.0-stage → 0.1.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.
@@ -0,0 +1,322 @@
1
+ import { keccak256 } from "./keccak.js";
2
+ import { sha256 } from "./sha256.js";
3
+ import { FACTORY_INIT, ACCOUNT_INIT } from "./evmcode.js";
4
+
5
+ export const N = 16;
6
+ export const W = 256;
7
+ export const CHAINS = 18;
8
+ export const HEIGHT = 10;
9
+ export const LEAVES = 1 << HEIGHT;
10
+ export const SIG_LEN = CHAINS * N;
11
+ export const PATH_LEN = HEIGHT * N;
12
+ export const PROOF_LEN = SIG_LEN + PATH_LEN;
13
+ export const REKEY_RESERVE = 64;
14
+ export const EXEC_LIMIT = LEAVES - REKEY_RESERVE;
15
+ export const GRIND_TRIES = 1 << 17;
16
+
17
+ const T_CHAIN = 0, T_LEAF = 1, T_NODE = 2, T_PRF = 3;
18
+ const enc = new TextEncoder();
19
+
20
+ export const TAG_EXEC = keccak256(enc.encode("opeq/evm/exec/v1"));
21
+ export const CREATE_SELECTOR = keccak256(enc.encode("create(bytes16,bytes16)")).slice(0, 4);
22
+ export const TAG_REKEY = keccak256(enc.encode("opeq/evm/rekey/v1"));
23
+ export const EXECUTE_SELECTOR = keccak256(enc.encode("execute((address,uint256,bytes)[],uint32,uint64,uint32,bytes16,bytes)")).slice(0, 4);
24
+ export const REKEY_SELECTOR = keccak256(enc.encode("rekey(bytes16,bytes16,uint32,uint64,uint32,bytes16,bytes)")).slice(0, 4);
25
+
26
+ export function adrs(kind, a, b, idx) {
27
+ return new Uint8Array([kind, a, b, 0, (idx >>> 24) & 0xff, (idx >>> 16) & 0xff, (idx >>> 8) & 0xff, idx & 0xff]);
28
+ }
29
+
30
+ const cbuf = new Uint8Array(40);
31
+
32
+ export function chainRun(pubSeed, leaf, c, x, start, steps) {
33
+ cbuf.set(pubSeed, 0);
34
+ cbuf.set(adrs(T_CHAIN, c, 0, leaf), 16);
35
+ cbuf.set(x, 24);
36
+ for (let pos = start; pos < start + steps; pos++) {
37
+ cbuf[18] = pos;
38
+ cbuf.set(keccak256(cbuf).subarray(0, N), 24);
39
+ }
40
+ return cbuf.slice(24, 40);
41
+ }
42
+
43
+ export function secret(skSeed, leaf, c) {
44
+ return keccak256(skSeed, adrs(T_PRF, c, 0, leaf)).slice(0, N);
45
+ }
46
+
47
+ export function leafCompress(pubSeed, leaf, pks) {
48
+ return keccak256(pubSeed, adrs(T_LEAF, 0, 0, leaf), ...pks).slice(0, N);
49
+ }
50
+
51
+ export function node(pubSeed, height, index, left, right) {
52
+ return keccak256(pubSeed, adrs(T_NODE, height, 0, index), left, right).slice(0, N);
53
+ }
54
+
55
+ export function digits(d16) {
56
+ const d = Array.from(d16);
57
+ let csum = 0;
58
+ for (const x of d) csum += W - 1 - x;
59
+ return d.concat([csum >> 8, csum & 0xff]);
60
+ }
61
+
62
+ export function verifyCost(d16) {
63
+ return digits(d16).reduce((s, x) => s + (W - 1 - x), 0);
64
+ }
65
+
66
+ export function leafPk(skSeed, pubSeed, leaf) {
67
+ const pks = [];
68
+ for (let c = 0; c < CHAINS; c++) pks.push(chainRun(pubSeed, leaf, c, secret(skSeed, leaf, c), 0, W - 1));
69
+ return leafCompress(pubSeed, leaf, pks);
70
+ }
71
+
72
+ export function rootFromProof(pubSeed, leaf, d16, proof) {
73
+ const ds = digits(d16);
74
+ const pks = [];
75
+ for (let c = 0; c < CHAINS; c++) pks.push(chainRun(pubSeed, leaf, c, proof.subarray(c * N, c * N + N), ds[c], W - 1 - ds[c]));
76
+ let x = leafCompress(pubSeed, leaf, pks);
77
+ for (let h = 0; h < HEIGHT; h++) {
78
+ const sib = proof.subarray(SIG_LEN + h * N, SIG_LEN + h * N + N);
79
+ const idx = leaf >>> (h + 1);
80
+ x = (leaf >>> h) & 1 ? node(pubSeed, h + 1, idx, sib, x) : node(pubSeed, h + 1, idx, x, sib);
81
+ }
82
+ return x;
83
+ }
84
+
85
+ export class EvmTreeKey {
86
+ constructor(skSeed, pubSeed, leaves) {
87
+ this.skSeed = skSeed;
88
+ this.pubSeed = pubSeed;
89
+ this.levels = [leaves];
90
+ for (let h = 0; h < HEIGHT; h++) {
91
+ const prev = this.levels[h], next = [];
92
+ for (let i = 0; i < prev.length / 2; i++) next.push(node(pubSeed, h + 1, i, prev[2 * i], prev[2 * i + 1]));
93
+ this.levels.push(next);
94
+ }
95
+ this.root = this.levels[HEIGHT][0];
96
+ }
97
+
98
+ static generate(skSeed, pubSeed, progress) {
99
+ const leaves = [];
100
+ for (let i = 0; i < LEAVES; i++) {
101
+ leaves.push(leafPk(skSeed, pubSeed, i));
102
+ if (progress && (i & 31) === 31) progress((i + 1) / LEAVES);
103
+ }
104
+ return new EvmTreeKey(skSeed, pubSeed, leaves);
105
+ }
106
+
107
+ sign(leaf, d16) {
108
+ const out = new Uint8Array(PROOF_LEN);
109
+ digits(d16).forEach((d, c) => out.set(chainRun(this.pubSeed, leaf, c, secret(this.skSeed, leaf, c), 0, d), c * N));
110
+ for (let h = 0; h < HEIGHT; h++) out.set(this.levels[h][(leaf >>> h) ^ 1], SIG_LEN + h * N);
111
+ return out;
112
+ }
113
+ }
114
+
115
+ function u32be(v) { return new Uint8Array([(v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff]); }
116
+
117
+ function u64be(v) {
118
+ const b = BigInt(v), out = new Uint8Array(8);
119
+ for (let i = 0; i < 8; i++) out[i] = Number((b >> BigInt(56 - 8 * i)) & 0xffn);
120
+ return out;
121
+ }
122
+
123
+ export const CHAIN_SLOTS = Object.freeze({ 8453: 0, 84532: 1, 31337: 2, 1: 3, 42161: 4, 4663: 5, 421614: 6, 11155111: 7, 99901: 8 });
124
+
125
+ export function chainSlot(chainId) {
126
+ const s = CHAIN_SLOTS[chainId];
127
+ if (s === undefined) throw new Error("chain " + chainId + " has no identity slot");
128
+ return s;
129
+ }
130
+
131
+ export function identitySeeds(master, factory = factoryAddress()) {
132
+ const f = addressBytes(factory);
133
+ return {
134
+ skSeed: sha256(enc.encode("opeq/evm/id/sk/v2"), master, f),
135
+ pubSeed: sha256(enc.encode("opeq/evm/id/pub/v2"), master, f).slice(0, N),
136
+ };
137
+ }
138
+
139
+ export function chainTreeSeeds(master, chainId, account, epoch, pubSeed) {
140
+ if (!Number.isInteger(epoch) || epoch < 1 || epoch > 0xffffffff) throw new Error("chain tree epoch must be 1 or more");
141
+ if (!(pubSeed instanceof Uint8Array) || pubSeed.length !== N) throw new Error("pubSeed must be 16 bytes");
142
+ chainSlot(chainId);
143
+ return { skSeed: sha256(enc.encode("opeq/evm/tree/sk/v3"), master, u64be(chainId), addressBytes(account), u32be(epoch), pubSeed), pubSeed: pubSeed.slice() };
144
+ }
145
+
146
+ export function deriveIdentityTree(master, progress, factory = factoryAddress()) {
147
+ const { skSeed, pubSeed } = identitySeeds(master, factory);
148
+ return EvmTreeKey.generate(skSeed, pubSeed, progress);
149
+ }
150
+
151
+ export function deriveChainTree(master, chainId, account, epoch, pubSeed, progress) {
152
+ const s = chainTreeSeeds(master, chainId, account, epoch, pubSeed);
153
+ return EvmTreeKey.generate(s.skSeed, s.pubSeed, progress);
154
+ }
155
+
156
+ export function word(n) {
157
+ let v = BigInt(n);
158
+ if (v < 0n || v >= 1n << 256n) throw new Error("value does not fit in 256 bits");
159
+ const out = new Uint8Array(32);
160
+ for (let i = 31; i >= 0; i--) { out[i] = Number(v & 0xffn); v >>= 8n; }
161
+ return out;
162
+ }
163
+
164
+ export function leftWord(bytes) {
165
+ if (bytes.length > 32) throw new Error("fixed bytes longer than 32");
166
+ const out = new Uint8Array(32);
167
+ out.set(bytes, 0);
168
+ return out;
169
+ }
170
+
171
+ export function addressBytes(addr) {
172
+ if (typeof addr !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(addr)) throw new Error("not an EVM address");
173
+ return Uint8Array.from(addr.slice(2).match(/../g), (h) => parseInt(h, 16));
174
+ }
175
+
176
+ export function addressWord(addr) {
177
+ const out = new Uint8Array(32);
178
+ out.set(addressBytes(addr), 12);
179
+ return out;
180
+ }
181
+
182
+ export function concat(...parts) {
183
+ const n = parts.reduce((s, p) => s + p.length, 0);
184
+ const out = new Uint8Array(n);
185
+ let o = 0;
186
+ for (const p of parts) { out.set(p, o); o += p.length; }
187
+ return out;
188
+ }
189
+
190
+ export function dynBytes(data) {
191
+ const pad = (32 - (data.length % 32)) % 32;
192
+ return concat(word(data.length), data, new Uint8Array(pad));
193
+ }
194
+
195
+ export function encodeCalls(calls) {
196
+ const bodies = calls.map((c) => concat(addressWord(c.to), word(c.value), word(96), dynBytes(c.data || new Uint8Array(0))));
197
+ const heads = [];
198
+ let off = 32 * calls.length;
199
+ for (const b of bodies) { heads.push(word(off)); off += b.length; }
200
+ return concat(word(calls.length), ...heads, ...bodies);
201
+ }
202
+
203
+ export function callsHash(calls) {
204
+ return keccak256(word(32), encodeCalls(calls));
205
+ }
206
+
207
+ export function execHasher(rnd, chainId, account, epoch, leaf, deadline, calls) {
208
+ if (rnd.length !== N) throw new Error("randomizer must be 16 bytes");
209
+ const head = concat(leftWord(rnd), TAG_EXEC, word(chainId), addressWord(account), word(epoch), word(leaf), word(deadline), callsHash(calls));
210
+ return (salt) => keccak256(head, word(salt)).slice(0, N);
211
+ }
212
+
213
+ export function rekeyHasher(rnd, chainId, account, epoch, leaf, deadline, newPubSeed, newRoot) {
214
+ if (rnd.length !== N) throw new Error("randomizer must be 16 bytes");
215
+ const head = concat(leftWord(rnd), TAG_REKEY, word(chainId), addressWord(account), word(epoch), word(leaf), word(deadline), leftWord(newPubSeed), leftWord(newRoot));
216
+ return (salt) => keccak256(head, word(salt)).slice(0, N);
217
+ }
218
+
219
+ export function grind(hasher, tries = GRIND_TRIES, progress) {
220
+ let best = null;
221
+ for (let salt = 0; salt < tries; salt++) {
222
+ const d = hasher(salt);
223
+ const c = verifyCost(d);
224
+ if (!best || c < best.cost) best = { salt, digest: d, cost: c };
225
+ if (progress && (salt & 8191) === 8191) progress((salt + 1) / tries);
226
+ }
227
+ return best;
228
+ }
229
+
230
+ export function encodeExecute(calls, leaf, deadline, salt, rnd, proof) {
231
+ if (proof.length !== PROOF_LEN) throw new Error("proof must be 448 bytes");
232
+ const callsEnc = encodeCalls(calls);
233
+ const headLen = 32 * 6;
234
+ return concat(
235
+ EXECUTE_SELECTOR,
236
+ word(headLen), word(leaf), word(deadline), word(salt), leftWord(rnd), word(headLen + callsEnc.length),
237
+ callsEnc,
238
+ dynBytes(proof),
239
+ );
240
+ }
241
+
242
+ export function encodeRekey(newPubSeed, newRoot, leaf, deadline, salt, rnd, proof) {
243
+ if (proof.length !== PROOF_LEN) throw new Error("proof must be 448 bytes");
244
+ return concat(
245
+ REKEY_SELECTOR,
246
+ leftWord(newPubSeed), leftWord(newRoot), word(leaf), word(deadline), word(salt), leftWord(rnd), word(32 * 7),
247
+ dynBytes(proof),
248
+ );
249
+ }
250
+
251
+ export function encodeCreate(pubSeed, root) {
252
+ return concat(CREATE_SELECTOR, leftWord(pubSeed), leftWord(root));
253
+ }
254
+
255
+ export const CREATE2_DEPLOYER = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
256
+
257
+ function fromHex(h) {
258
+ return Uint8Array.from(h.match(/../g), (x) => parseInt(x, 16));
259
+ }
260
+
261
+ function toAddr(b) {
262
+ return "0x" + Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
263
+ }
264
+
265
+ function create2(deployer, salt, initHash) {
266
+ return toAddr(keccak256(Uint8Array.of(0xff), addressBytes(deployer), salt, initHash).slice(12));
267
+ }
268
+
269
+ const FACTORY_INIT_BYTES = fromHex(FACTORY_INIT);
270
+ const ACCOUNT_INIT_BYTES = fromHex(ACCOUNT_INIT);
271
+
272
+ export function factoryAddress() {
273
+ return create2(CREATE2_DEPLOYER, new Uint8Array(32), keccak256(FACTORY_INIT_BYTES));
274
+ }
275
+
276
+ export function factoryDeployData() {
277
+ return concat(new Uint8Array(32), FACTORY_INIT_BYTES);
278
+ }
279
+
280
+ export function accountSalt(pubSeed, root) {
281
+ const tag = enc.encode("opeq/evm/id/v1");
282
+ return keccak256(word(96), leftWord(pubSeed), leftWord(root), dynBytes(tag));
283
+ }
284
+
285
+ export function accountAddress(pubSeed, root, factory = factoryAddress()) {
286
+ return create2(factory, accountSalt(pubSeed, root), keccak256(ACCOUNT_INIT_BYTES, leftWord(pubSeed), leftWord(root)));
287
+ }
288
+
289
+ export function bootSeeds(master, chainId, account) {
290
+ chainSlot(chainId);
291
+ const acct = addressBytes(account);
292
+ return {
293
+ pubSeed: sha256(enc.encode("opeq/evm/chain/pub/v2"), master, u64be(chainId), acct).slice(0, N),
294
+ rnd: sha256(enc.encode("opeq/evm/boot/rnd/v2"), master, u64be(chainId), acct).slice(0, N),
295
+ };
296
+ }
297
+
298
+ export function bootstrapCall(master, chainId, account, identity, progress) {
299
+ if (account.toLowerCase() !== accountAddress(identity.pubSeed, identity.root)) throw new Error("refusing to sign a bootstrap for an account that is not this identity's account on the OPEQ factory");
300
+ const { pubSeed, rnd } = bootSeeds(master, chainId, account);
301
+ const tree = deriveChainTree(master, chainId, account, 1, pubSeed, progress);
302
+ const slot = chainSlot(chainId);
303
+ const g = grind(rekeyHasher(rnd, chainId, account, 0, slot, 0, tree.pubSeed, tree.root));
304
+ return { tree, cost: g.cost, calldata: encodeRekey(tree.pubSeed, tree.root, slot, 0, g.salt, rnd, identity.sign(slot, g.digest)) };
305
+ }
306
+
307
+ export function treeFromLeaves(skSeed, pubSeed, bytes) {
308
+ if (bytes.length !== LEAVES * N) throw new Error("stored key tree has the wrong size");
309
+ const leaves = [];
310
+ for (let i = 0; i < LEAVES; i++) leaves.push(bytes.slice(i * N, i * N + N));
311
+ return new EvmTreeKey(skSeed, pubSeed, leaves);
312
+ }
313
+
314
+ export function leavesBytes(tree) {
315
+ const out = new Uint8Array(LEAVES * N);
316
+ tree.levels[0].forEach((l, i) => out.set(l, i * N));
317
+ return out;
318
+ }
319
+
320
+ export function encodeErc20Transfer(to, amount) {
321
+ return concat(keccak256(enc.encode("transfer(address,uint256)")).slice(0, 4), addressWord(to), word(amount));
322
+ }
@@ -0,0 +1,165 @@
1
+ const RC = new Int32Array([
2
+ 0x00000001, 0x00000000, 0x00008082, 0x00000000, 0x0000808a, 0x80000000, 0x80008000, 0x80000000,
3
+ 0x0000808b, 0x00000000, 0x80000001, 0x00000000, 0x80008081, 0x80000000, 0x00008009, 0x80000000,
4
+ 0x0000008a, 0x00000000, 0x00000088, 0x00000000, 0x80008009, 0x00000000, 0x8000000a, 0x00000000,
5
+ 0x8000808b, 0x00000000, 0x0000008b, 0x80000000, 0x00008089, 0x80000000, 0x00008003, 0x80000000,
6
+ 0x00008002, 0x80000000, 0x00000080, 0x80000000, 0x0000800a, 0x00000000, 0x8000000a, 0x80000000,
7
+ 0x80008081, 0x80000000, 0x00008080, 0x80000000, 0x80000001, 0x00000000, 0x80008008, 0x80000000,
8
+ ]);
9
+
10
+ const S = new Int32Array(50);
11
+ const RATE = 136;
12
+
13
+ function permute() {
14
+ let a0l = S[0], a0h = S[1], a1l = S[2], a1h = S[3], a2l = S[4], a2h = S[5], a3l = S[6], a3h = S[7], a4l = S[8], a4h = S[9], a5l = S[10], a5h = S[11], a6l = S[12], a6h = S[13], a7l = S[14], a7h = S[15], a8l = S[16], a8h = S[17], a9l = S[18], a9h = S[19], a10l = S[20], a10h = S[21], a11l = S[22], a11h = S[23], a12l = S[24], a12h = S[25], a13l = S[26], a13h = S[27], a14l = S[28], a14h = S[29], a15l = S[30], a15h = S[31], a16l = S[32], a16h = S[33], a17l = S[34], a17h = S[35], a18l = S[36], a18h = S[37], a19l = S[38], a19h = S[39], a20l = S[40], a20h = S[41], a21l = S[42], a21h = S[43], a22l = S[44], a22h = S[45], a23l = S[46], a23h = S[47], a24l = S[48], a24h = S[49];
15
+ for (let round = 0; round < 48; round += 2) {
16
+ const c0l = a0l ^ a5l ^ a10l ^ a15l ^ a20l, c0h = a0h ^ a5h ^ a10h ^ a15h ^ a20h;
17
+ const c1l = a1l ^ a6l ^ a11l ^ a16l ^ a21l, c1h = a1h ^ a6h ^ a11h ^ a16h ^ a21h;
18
+ const c2l = a2l ^ a7l ^ a12l ^ a17l ^ a22l, c2h = a2h ^ a7h ^ a12h ^ a17h ^ a22h;
19
+ const c3l = a3l ^ a8l ^ a13l ^ a18l ^ a23l, c3h = a3h ^ a8h ^ a13h ^ a18h ^ a23h;
20
+ const c4l = a4l ^ a9l ^ a14l ^ a19l ^ a24l, c4h = a4h ^ a9h ^ a14h ^ a19h ^ a24h;
21
+ const d0l = c4l ^ ((c1l << 1) | (c1h >>> 31)), d0h = c4h ^ ((c1h << 1) | (c1l >>> 31));
22
+ const d1l = c0l ^ ((c2l << 1) | (c2h >>> 31)), d1h = c0h ^ ((c2h << 1) | (c2l >>> 31));
23
+ const d2l = c1l ^ ((c3l << 1) | (c3h >>> 31)), d2h = c1h ^ ((c3h << 1) | (c3l >>> 31));
24
+ const d3l = c2l ^ ((c4l << 1) | (c4h >>> 31)), d3h = c2h ^ ((c4h << 1) | (c4l >>> 31));
25
+ const d4l = c3l ^ ((c0l << 1) | (c0h >>> 31)), d4h = c3h ^ ((c0h << 1) | (c0l >>> 31));
26
+ const t0l = a0l ^ d0l, t0h = a0h ^ d0h;
27
+ const t1l = a1l ^ d1l, t1h = a1h ^ d1h;
28
+ const t2l = a2l ^ d2l, t2h = a2h ^ d2h;
29
+ const t3l = a3l ^ d3l, t3h = a3h ^ d3h;
30
+ const t4l = a4l ^ d4l, t4h = a4h ^ d4h;
31
+ const t5l = a5l ^ d0l, t5h = a5h ^ d0h;
32
+ const t6l = a6l ^ d1l, t6h = a6h ^ d1h;
33
+ const t7l = a7l ^ d2l, t7h = a7h ^ d2h;
34
+ const t8l = a8l ^ d3l, t8h = a8h ^ d3h;
35
+ const t9l = a9l ^ d4l, t9h = a9h ^ d4h;
36
+ const t10l = a10l ^ d0l, t10h = a10h ^ d0h;
37
+ const t11l = a11l ^ d1l, t11h = a11h ^ d1h;
38
+ const t12l = a12l ^ d2l, t12h = a12h ^ d2h;
39
+ const t13l = a13l ^ d3l, t13h = a13h ^ d3h;
40
+ const t14l = a14l ^ d4l, t14h = a14h ^ d4h;
41
+ const t15l = a15l ^ d0l, t15h = a15h ^ d0h;
42
+ const t16l = a16l ^ d1l, t16h = a16h ^ d1h;
43
+ const t17l = a17l ^ d2l, t17h = a17h ^ d2h;
44
+ const t18l = a18l ^ d3l, t18h = a18h ^ d3h;
45
+ const t19l = a19l ^ d4l, t19h = a19h ^ d4h;
46
+ const t20l = a20l ^ d0l, t20h = a20h ^ d0h;
47
+ const t21l = a21l ^ d1l, t21h = a21h ^ d1h;
48
+ const t22l = a22l ^ d2l, t22h = a22h ^ d2h;
49
+ const t23l = a23l ^ d3l, t23h = a23h ^ d3h;
50
+ const t24l = a24l ^ d4l, t24h = a24h ^ d4h;
51
+ const b0l = t0l, b0h = t0h;
52
+ const b10l = (t1l << 1) | (t1h >>> 31), b10h = (t1h << 1) | (t1l >>> 31);
53
+ const b20l = (t2h << 30) | (t2l >>> 2), b20h = (t2l << 30) | (t2h >>> 2);
54
+ const b5l = (t3l << 28) | (t3h >>> 4), b5h = (t3h << 28) | (t3l >>> 4);
55
+ const b15l = (t4l << 27) | (t4h >>> 5), b15h = (t4h << 27) | (t4l >>> 5);
56
+ const b16l = (t5h << 4) | (t5l >>> 28), b16h = (t5l << 4) | (t5h >>> 28);
57
+ const b1l = (t6h << 12) | (t6l >>> 20), b1h = (t6l << 12) | (t6h >>> 20);
58
+ const b11l = (t7l << 6) | (t7h >>> 26), b11h = (t7h << 6) | (t7l >>> 26);
59
+ const b21l = (t8h << 23) | (t8l >>> 9), b21h = (t8l << 23) | (t8h >>> 9);
60
+ const b6l = (t9l << 20) | (t9h >>> 12), b6h = (t9h << 20) | (t9l >>> 12);
61
+ const b7l = (t10l << 3) | (t10h >>> 29), b7h = (t10h << 3) | (t10l >>> 29);
62
+ const b17l = (t11l << 10) | (t11h >>> 22), b17h = (t11h << 10) | (t11l >>> 22);
63
+ const b2l = (t12h << 11) | (t12l >>> 21), b2h = (t12l << 11) | (t12h >>> 21);
64
+ const b12l = (t13l << 25) | (t13h >>> 7), b12h = (t13h << 25) | (t13l >>> 7);
65
+ const b22l = (t14h << 7) | (t14l >>> 25), b22h = (t14l << 7) | (t14h >>> 25);
66
+ const b23l = (t15h << 9) | (t15l >>> 23), b23h = (t15l << 9) | (t15h >>> 23);
67
+ const b8l = (t16h << 13) | (t16l >>> 19), b8h = (t16l << 13) | (t16h >>> 19);
68
+ const b18l = (t17l << 15) | (t17h >>> 17), b18h = (t17h << 15) | (t17l >>> 17);
69
+ const b3l = (t18l << 21) | (t18h >>> 11), b3h = (t18h << 21) | (t18l >>> 11);
70
+ const b13l = (t19l << 8) | (t19h >>> 24), b13h = (t19h << 8) | (t19l >>> 24);
71
+ const b14l = (t20l << 18) | (t20h >>> 14), b14h = (t20h << 18) | (t20l >>> 14);
72
+ const b24l = (t21l << 2) | (t21h >>> 30), b24h = (t21h << 2) | (t21l >>> 30);
73
+ const b9l = (t22h << 29) | (t22l >>> 3), b9h = (t22l << 29) | (t22h >>> 3);
74
+ const b19l = (t23h << 24) | (t23l >>> 8), b19h = (t23l << 24) | (t23h >>> 8);
75
+ const b4l = (t24l << 14) | (t24h >>> 18), b4h = (t24h << 14) | (t24l >>> 18);
76
+ a0l = b0l ^ (~b1l & b2l); a0h = b0h ^ (~b1h & b2h);
77
+ a1l = b1l ^ (~b2l & b3l); a1h = b1h ^ (~b2h & b3h);
78
+ a2l = b2l ^ (~b3l & b4l); a2h = b2h ^ (~b3h & b4h);
79
+ a3l = b3l ^ (~b4l & b0l); a3h = b3h ^ (~b4h & b0h);
80
+ a4l = b4l ^ (~b0l & b1l); a4h = b4h ^ (~b0h & b1h);
81
+ a5l = b5l ^ (~b6l & b7l); a5h = b5h ^ (~b6h & b7h);
82
+ a6l = b6l ^ (~b7l & b8l); a6h = b6h ^ (~b7h & b8h);
83
+ a7l = b7l ^ (~b8l & b9l); a7h = b7h ^ (~b8h & b9h);
84
+ a8l = b8l ^ (~b9l & b5l); a8h = b8h ^ (~b9h & b5h);
85
+ a9l = b9l ^ (~b5l & b6l); a9h = b9h ^ (~b5h & b6h);
86
+ a10l = b10l ^ (~b11l & b12l); a10h = b10h ^ (~b11h & b12h);
87
+ a11l = b11l ^ (~b12l & b13l); a11h = b11h ^ (~b12h & b13h);
88
+ a12l = b12l ^ (~b13l & b14l); a12h = b12h ^ (~b13h & b14h);
89
+ a13l = b13l ^ (~b14l & b10l); a13h = b13h ^ (~b14h & b10h);
90
+ a14l = b14l ^ (~b10l & b11l); a14h = b14h ^ (~b10h & b11h);
91
+ a15l = b15l ^ (~b16l & b17l); a15h = b15h ^ (~b16h & b17h);
92
+ a16l = b16l ^ (~b17l & b18l); a16h = b16h ^ (~b17h & b18h);
93
+ a17l = b17l ^ (~b18l & b19l); a17h = b17h ^ (~b18h & b19h);
94
+ a18l = b18l ^ (~b19l & b15l); a18h = b18h ^ (~b19h & b15h);
95
+ a19l = b19l ^ (~b15l & b16l); a19h = b19h ^ (~b15h & b16h);
96
+ a20l = b20l ^ (~b21l & b22l); a20h = b20h ^ (~b21h & b22h);
97
+ a21l = b21l ^ (~b22l & b23l); a21h = b21h ^ (~b22h & b23h);
98
+ a22l = b22l ^ (~b23l & b24l); a22h = b22h ^ (~b23h & b24h);
99
+ a23l = b23l ^ (~b24l & b20l); a23h = b23h ^ (~b24h & b20h);
100
+ a24l = b24l ^ (~b20l & b21l); a24h = b24h ^ (~b20h & b21h);
101
+ a0l ^= RC[round]; a0h ^= RC[round + 1];
102
+ }
103
+ S[0] = a0l; S[1] = a0h;
104
+ S[2] = a1l; S[3] = a1h;
105
+ S[4] = a2l; S[5] = a2h;
106
+ S[6] = a3l; S[7] = a3h;
107
+ S[8] = a4l; S[9] = a4h;
108
+ S[10] = a5l; S[11] = a5h;
109
+ S[12] = a6l; S[13] = a6h;
110
+ S[14] = a7l; S[15] = a7h;
111
+ S[16] = a8l; S[17] = a8h;
112
+ S[18] = a9l; S[19] = a9h;
113
+ S[20] = a10l; S[21] = a10h;
114
+ S[22] = a11l; S[23] = a11h;
115
+ S[24] = a12l; S[25] = a12h;
116
+ S[26] = a13l; S[27] = a13h;
117
+ S[28] = a14l; S[29] = a14h;
118
+ S[30] = a15l; S[31] = a15h;
119
+ S[32] = a16l; S[33] = a16h;
120
+ S[34] = a17l; S[35] = a17h;
121
+ S[36] = a18l; S[37] = a18h;
122
+ S[38] = a19l; S[39] = a19h;
123
+ S[40] = a20l; S[41] = a20h;
124
+ S[42] = a21l; S[43] = a21h;
125
+ S[44] = a22l; S[45] = a22h;
126
+ S[46] = a23l; S[47] = a23h;
127
+ S[48] = a24l; S[49] = a24h;
128
+ }
129
+
130
+ function absorbByte(pos, v) {
131
+ const lane = pos >> 3, k = pos & 7;
132
+ if (k < 4) S[2 * lane] ^= v << (8 * k);
133
+ else S[2 * lane + 1] ^= v << (8 * (k - 4));
134
+ }
135
+
136
+ export function keccak256(...parts) {
137
+ S.fill(0);
138
+ let pos = 0;
139
+ for (const part of parts) {
140
+ let i = 0;
141
+ const n = part.length;
142
+ while (i < n) {
143
+ if ((pos & 3) === 0 && i + 4 <= n && pos + 4 <= RATE) {
144
+ const w = part[i] | (part[i + 1] << 8) | (part[i + 2] << 16) | (part[i + 3] << 24);
145
+ S[pos >> 2] ^= w;
146
+ i += 4;
147
+ pos += 4;
148
+ } else {
149
+ absorbByte(pos, part[i]);
150
+ i++;
151
+ pos++;
152
+ }
153
+ if (pos === RATE) { permute(); pos = 0; }
154
+ }
155
+ }
156
+ absorbByte(pos, 0x01);
157
+ absorbByte(RATE - 1, 0x80);
158
+ permute();
159
+ const out = new Uint8Array(32);
160
+ for (let i = 0; i < 32; i++) {
161
+ const lane = i >> 3, k = i & 7;
162
+ out[i] = (k < 4 ? S[2 * lane] >>> (8 * k) : S[2 * lane + 1] >>> (8 * (k - 4))) & 0xff;
163
+ }
164
+ return out;
165
+ }
@@ -0,0 +1,80 @@
1
+ import { sha256 } from "./sha256.js";
2
+ import { WORDS } from "./wordlist.js";
3
+ import { N, TreeKey } from "./wots.js";
4
+
5
+ const INDEX = new Map(WORDS.map((w, i) => [w, i]));
6
+
7
+ export function newMaster() {
8
+ return crypto.getRandomValues(new Uint8Array(32));
9
+ }
10
+
11
+ export function toWords(entropy) {
12
+ if (entropy.length !== 32) throw new Error("master seed must be 32 bytes");
13
+ let bits = 0n;
14
+ for (const b of entropy) bits = (bits << 8n) | BigInt(b);
15
+ bits = (bits << 8n) | BigInt(sha256(entropy)[0]);
16
+ const words = [];
17
+ for (let i = 0; i < 24; i++) words.push(WORDS[Number((bits >> BigInt(11 * (23 - i))) & 0x7ffn)]);
18
+ return words;
19
+ }
20
+
21
+ export function fromWords(text) {
22
+ const words = text.toLowerCase().split(/[^a-z]+/).filter(Boolean);
23
+ if (words.length !== 24) throw new Error(`expected 24 words, got ${words.length}`);
24
+ let bits = 0n;
25
+ for (let w of words) {
26
+ if (!INDEX.has(w)) {
27
+ const m = w.length >= 4 ? WORDS.filter((x) => x.startsWith(w.slice(0, 4))) : [];
28
+ if (m.length !== 1) throw new Error(`unknown word: ${w}`);
29
+ w = m[0];
30
+ }
31
+ bits = (bits << 11n) | BigInt(INDEX.get(w));
32
+ }
33
+ const entropy = new Uint8Array(32);
34
+ let e = bits >> 8n;
35
+ for (let i = 31; i >= 0; i--) { entropy[i] = Number(e & 0xffn); e >>= 8n; }
36
+ if (Number(bits & 0xffn) !== sha256(entropy)[0]) throw new Error("checksum mismatch: a word is wrong or out of order");
37
+ return entropy;
38
+ }
39
+
40
+ function epochBytes(epoch) {
41
+ return new Uint8Array([epoch & 0xff, (epoch >>> 8) & 0xff, (epoch >>> 16) & 0xff, epoch >>> 24]);
42
+ }
43
+
44
+ const enc = new TextEncoder();
45
+
46
+ export const LEGACY_PROGRAM = "424ckcA6GGswYmQbrGjQmzdfhk4G9eB2zD1gK8xLw7sN";
47
+ const LEGACY_KEY = new Uint8Array([44, 216, 187, 13, 164, 190, 62, 155, 179, 6, 213, 191, 6, 217, 96, 213, 232, 64, 39, 146, 16, 203, 155, 85, 10, 188, 52, 192, 39, 183, 52, 1]);
48
+
49
+ export function isLegacy(program) {
50
+ if (!(program instanceof Uint8Array) || program.length !== 32) throw new Error("tree derivation needs the 32-byte program id");
51
+ return program.every((b, i) => b === LEGACY_KEY[i]);
52
+ }
53
+
54
+ export function chainSeeded(program, epoch) {
55
+ return !isLegacy(program) && epoch > 0;
56
+ }
57
+
58
+ export function treeSeeds(master, epoch, program, pubSeed) {
59
+ if (!Number.isInteger(epoch) || epoch < 0 || epoch > 0xffffffff) throw new Error("epoch out of range");
60
+ const e = epochBytes(epoch);
61
+ if (chainSeeded(program, epoch)) {
62
+ if (!(pubSeed instanceof Uint8Array) || pubSeed.length !== N) throw new Error("this key tree needs its 16-byte public seed from the chain");
63
+ return { skSeed: sha256(enc.encode("opeq/tree/sk/v3"), program, master, e, pubSeed), pubSeed: pubSeed.slice() };
64
+ }
65
+ if (isLegacy(program)) {
66
+ return {
67
+ skSeed: sha256(enc.encode("opeq/tree/sk/v1"), master, e),
68
+ pubSeed: sha256(enc.encode("opeq/tree/pub/v1"), master, e).slice(0, N),
69
+ };
70
+ }
71
+ return {
72
+ skSeed: sha256(enc.encode("opeq/tree/sk/v2"), program, master, e),
73
+ pubSeed: sha256(enc.encode("opeq/tree/pub/v2"), program, master, e).slice(0, N),
74
+ };
75
+ }
76
+
77
+ export function deriveTree(master, epoch, progress, program, pubSeed) {
78
+ const { skSeed, pubSeed: ps } = treeSeeds(master, epoch, program, pubSeed);
79
+ return TreeKey.generate(skSeed, ps, progress);
80
+ }