@0xsequence/wallet-primitives 0.0.0-20250520201059

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.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +7 -0
  3. package/LICENSE +202 -0
  4. package/dist/address.d.ts +5 -0
  5. package/dist/address.d.ts.map +1 -0
  6. package/dist/address.js +7 -0
  7. package/dist/address.js.map +1 -0
  8. package/dist/attestation.d.ts +24 -0
  9. package/dist/attestation.d.ts.map +1 -0
  10. package/dist/attestation.js +77 -0
  11. package/dist/attestation.js.map +1 -0
  12. package/dist/config.d.ts +85 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +381 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/constants.d.ts +173 -0
  17. package/dist/constants.d.ts.map +1 -0
  18. package/dist/constants.js +31 -0
  19. package/dist/constants.js.map +1 -0
  20. package/dist/context.d.ts +9 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +8 -0
  23. package/dist/context.js.map +1 -0
  24. package/dist/erc-6492.d.ts +19 -0
  25. package/dist/erc-6492.d.ts.map +1 -0
  26. package/dist/erc-6492.js +64 -0
  27. package/dist/erc-6492.js.map +1 -0
  28. package/dist/extensions/index.d.ts +9 -0
  29. package/dist/extensions/index.d.ts.map +1 -0
  30. package/dist/extensions/index.js +7 -0
  31. package/dist/extensions/index.js.map +1 -0
  32. package/dist/extensions/passkeys.d.ts +31 -0
  33. package/dist/extensions/passkeys.d.ts.map +1 -0
  34. package/dist/extensions/passkeys.js +224 -0
  35. package/dist/extensions/passkeys.js.map +1 -0
  36. package/dist/extensions/recovery.d.ts +310 -0
  37. package/dist/extensions/recovery.d.ts.map +1 -0
  38. package/dist/extensions/recovery.js +444 -0
  39. package/dist/extensions/recovery.js.map +1 -0
  40. package/dist/generic-tree.d.ts +14 -0
  41. package/dist/generic-tree.d.ts.map +1 -0
  42. package/dist/generic-tree.js +34 -0
  43. package/dist/generic-tree.js.map +1 -0
  44. package/dist/index.d.ts +16 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +16 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/network.d.ts +15 -0
  49. package/dist/network.d.ts.map +1 -0
  50. package/dist/network.js +24 -0
  51. package/dist/network.js.map +1 -0
  52. package/dist/payload.d.ts +108 -0
  53. package/dist/payload.d.ts.map +1 -0
  54. package/dist/payload.js +627 -0
  55. package/dist/payload.js.map +1 -0
  56. package/dist/permission.d.ts +73 -0
  57. package/dist/permission.d.ts.map +1 -0
  58. package/dist/permission.js +188 -0
  59. package/dist/permission.js.map +1 -0
  60. package/dist/session-config.d.ts +113 -0
  61. package/dist/session-config.d.ts.map +1 -0
  62. package/dist/session-config.js +554 -0
  63. package/dist/session-config.js.map +1 -0
  64. package/dist/session-signature.d.ts +24 -0
  65. package/dist/session-signature.d.ts.map +1 -0
  66. package/dist/session-signature.js +141 -0
  67. package/dist/session-signature.js.map +1 -0
  68. package/dist/signature.d.ts +108 -0
  69. package/dist/signature.d.ts.map +1 -0
  70. package/dist/signature.js +1079 -0
  71. package/dist/signature.js.map +1 -0
  72. package/dist/utils.d.ts +45 -0
  73. package/dist/utils.d.ts.map +1 -0
  74. package/dist/utils.js +100 -0
  75. package/dist/utils.js.map +1 -0
  76. package/eslint.config.mjs +4 -0
  77. package/package.json +27 -0
  78. package/src/address.ts +19 -0
  79. package/src/attestation.ts +114 -0
  80. package/src/config.ts +521 -0
  81. package/src/constants.ts +39 -0
  82. package/src/context.ts +16 -0
  83. package/src/erc-6492.ts +97 -0
  84. package/src/extensions/index.ts +14 -0
  85. package/src/extensions/passkeys.ts +283 -0
  86. package/src/extensions/recovery.ts +542 -0
  87. package/src/generic-tree.ts +55 -0
  88. package/src/index.ts +15 -0
  89. package/src/network.ts +37 -0
  90. package/src/payload.ts +825 -0
  91. package/src/permission.ts +252 -0
  92. package/src/session-config.ts +681 -0
  93. package/src/session-signature.ts +197 -0
  94. package/src/signature.ts +1398 -0
  95. package/src/utils.ts +114 -0
  96. package/tsconfig.json +10 -0
@@ -0,0 +1,1398 @@
1
+ import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider, Secp256k1, Signature } from 'ox'
2
+ import {
3
+ Config,
4
+ Leaf,
5
+ NestedLeaf,
6
+ SapientSignerLeaf,
7
+ SignerLeaf,
8
+ SubdigestLeaf,
9
+ AnyAddressSubdigestLeaf,
10
+ Topology,
11
+ hashConfiguration,
12
+ isNestedLeaf,
13
+ isNode,
14
+ isNodeLeaf,
15
+ isSapientSignerLeaf,
16
+ isSignerLeaf,
17
+ isSubdigestLeaf,
18
+ isAnyAddressSubdigestLeaf,
19
+ isTopology,
20
+ } from './config.js'
21
+ import { RECOVER_SAPIENT_SIGNATURE, RECOVER_SAPIENT_SIGNATURE_COMPACT, IS_VALID_SIGNATURE } from './constants.js'
22
+ import { wrap, decode } from './erc-6492.js'
23
+ import { fromConfigUpdate, hash, Parented } from './payload.js'
24
+ import { minBytesFor, packRSY, unpackRSY } from './utils.js'
25
+
26
+ export const FLAG_SIGNATURE_HASH = 0
27
+ export const FLAG_ADDRESS = 1
28
+ export const FLAG_SIGNATURE_ERC1271 = 2
29
+ export const FLAG_NODE = 3
30
+ export const FLAG_BRANCH = 4
31
+ export const FLAG_SUBDIGEST = 5
32
+ export const FLAG_NESTED = 6
33
+ export const FLAG_SIGNATURE_ETH_SIGN = 7
34
+ export const FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST = 8
35
+ export const FLAG_SIGNATURE_SAPIENT = 9
36
+ export const FLAG_SIGNATURE_SAPIENT_COMPACT = 10
37
+
38
+ export type RSY = {
39
+ r: bigint
40
+ s: bigint
41
+ yParity: number
42
+ }
43
+
44
+ export type SignatureOfSignerLeafEthSign = {
45
+ type: 'eth_sign'
46
+ } & RSY
47
+
48
+ export type SignatureOfSignerLeafHash = {
49
+ type: 'hash'
50
+ } & RSY
51
+
52
+ export type SignatureOfSignerLeafErc1271 = {
53
+ type: 'erc1271'
54
+ address: `0x${string}`
55
+ data: Hex.Hex
56
+ }
57
+
58
+ export type SignatureOfSignerLeaf =
59
+ | SignatureOfSignerLeafEthSign
60
+ | SignatureOfSignerLeafHash
61
+ | SignatureOfSignerLeafErc1271
62
+
63
+ export type SignatureOfSapientSignerLeaf = {
64
+ address: `0x${string}`
65
+ data: Hex.Hex
66
+ type: 'sapient' | 'sapient_compact'
67
+ }
68
+
69
+ export type SignedSignerLeaf = SignerLeaf & {
70
+ signed: true
71
+ signature: SignatureOfSignerLeaf
72
+ }
73
+
74
+ export type SignedSapientSignerLeaf = SapientSignerLeaf & {
75
+ signed: true
76
+ signature: SignatureOfSapientSignerLeaf
77
+ }
78
+
79
+ export type RawSignerLeaf = {
80
+ type: 'unrecovered-signer'
81
+ weight: bigint
82
+ signature: SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf
83
+ }
84
+
85
+ export type RawNestedLeaf = {
86
+ type: 'nested'
87
+ tree: RawTopology
88
+ weight: bigint
89
+ threshold: bigint
90
+ }
91
+
92
+ export type RawLeaf = Leaf | RawSignerLeaf | RawNestedLeaf
93
+
94
+ export type RawNode = [RawTopology, RawTopology]
95
+
96
+ export type RawTopology = RawNode | RawLeaf
97
+
98
+ export type RawConfig = {
99
+ threshold: bigint
100
+ checkpoint: bigint
101
+ topology: RawTopology
102
+ checkpointer?: Address.Address
103
+ }
104
+
105
+ export type RawSignature = {
106
+ noChainId: boolean
107
+ checkpointerData?: Bytes.Bytes
108
+ configuration: RawConfig
109
+ suffix?: RawSignature[]
110
+ erc6492?: { to: Address.Address; data: Bytes.Bytes }
111
+ }
112
+
113
+ export function isSignatureOfSapientSignerLeaf(signature: any): signature is SignatureOfSapientSignerLeaf {
114
+ return (
115
+ 'type' in signature &&
116
+ (signature.type === 'sapient_compact' || signature.type === 'sapient') &&
117
+ typeof signature === 'object' &&
118
+ 'address' in signature &&
119
+ 'data' in signature
120
+ )
121
+ }
122
+
123
+ export function isRawSignature(signature: any): signature is RawSignature {
124
+ return (
125
+ typeof signature === 'object' &&
126
+ signature &&
127
+ typeof signature.noChainId === 'boolean' &&
128
+ (signature.checkpointerData === undefined || Bytes.validate(signature.checkpointerData)) &&
129
+ isRawConfig(signature.configuration) &&
130
+ (signature.suffix === undefined ||
131
+ (Array.isArray(signature.suffix) &&
132
+ signature.suffix.every(
133
+ (signature: any) => isRawSignature(signature) && signature.checkpointerData === undefined,
134
+ )))
135
+ )
136
+ }
137
+
138
+ export function isRawConfig(configuration: any): configuration is RawConfig {
139
+ return (
140
+ configuration &&
141
+ typeof configuration === 'object' &&
142
+ typeof configuration.threshold === 'bigint' &&
143
+ typeof configuration.checkpoint === 'bigint' &&
144
+ isRawTopology(configuration.topology) &&
145
+ (configuration.checkpointer === undefined || Address.validate(configuration.checkpointer))
146
+ )
147
+ }
148
+
149
+ export function isRawSignerLeaf(cand: any): cand is RawSignerLeaf {
150
+ return typeof cand === 'object' && 'weight' in cand && 'signature' in cand
151
+ }
152
+
153
+ export function isSignedSignerLeaf(cand: any): cand is SignedSignerLeaf {
154
+ return isSignerLeaf(cand) && 'signature' in cand
155
+ }
156
+
157
+ export function isSignedSapientSignerLeaf(cand: any): cand is SignedSapientSignerLeaf {
158
+ return isSapientSignerLeaf(cand) && 'signature' in cand
159
+ }
160
+
161
+ export function isRawNode(cand: any): cand is RawNode {
162
+ return (
163
+ Array.isArray(cand) &&
164
+ cand.length === 2 &&
165
+ (isRawTopology(cand[0]) || isTopology(cand[0])) &&
166
+ (isRawTopology(cand[1]) || isTopology(cand[1]))
167
+ )
168
+ }
169
+
170
+ export function isRawTopology(cand: any): cand is RawTopology {
171
+ return isRawNode(cand) || isRawLeaf(cand)
172
+ }
173
+
174
+ export function isRawLeaf(cand: any): cand is RawLeaf {
175
+ return typeof cand === 'object' && 'weight' in cand && !('tree' in cand)
176
+ }
177
+
178
+ export function isRawNestedLeaf(cand: any): cand is RawNestedLeaf {
179
+ return typeof cand === 'object' && 'tree' in cand && 'weight' in cand && 'threshold' in cand
180
+ }
181
+
182
+ export function decodeSignature(erc6492Signature: Bytes.Bytes): RawSignature {
183
+ const { signature, erc6492 } = decode(erc6492Signature)
184
+
185
+ if (signature.length < 1) {
186
+ throw new Error('Signature is empty')
187
+ }
188
+
189
+ const flag = signature[0]!
190
+ let index = 1
191
+
192
+ const noChainId = (flag & 0x02) === 0x02
193
+
194
+ let checkpointerAddress: Address.Address | undefined
195
+ let checkpointerData: Bytes.Bytes | undefined
196
+
197
+ // bit [6] => checkpointer address + data
198
+ if ((flag & 0x40) === 0x40) {
199
+ if (index + 20 > signature.length) {
200
+ throw new Error('Not enough bytes for checkpointer address')
201
+ }
202
+ checkpointerAddress = Bytes.toHex(signature.slice(index, index + 20))
203
+ index += 20
204
+
205
+ if (index + 3 > signature.length) {
206
+ throw new Error('Not enough bytes for checkpointer data size')
207
+ }
208
+ const checkpointerDataSize = Bytes.toNumber(signature.slice(index, index + 3))
209
+ index += 3
210
+
211
+ if (index + checkpointerDataSize > signature.length) {
212
+ throw new Error('Not enough bytes for checkpointer data')
213
+ }
214
+ checkpointerData = signature.slice(index, index + checkpointerDataSize)
215
+ index += checkpointerDataSize
216
+ }
217
+
218
+ // bits [2..4] => checkpoint size
219
+ const checkpointSize = (flag & 0x1c) >> 2
220
+ if (index + checkpointSize > signature.length) {
221
+ throw new Error('Not enough bytes for checkpoint')
222
+ }
223
+ const checkpoint = Bytes.toBigInt(signature.slice(index, index + checkpointSize))
224
+ index += checkpointSize
225
+
226
+ // bit [5] => threshold size offset
227
+ const thresholdSize = ((flag & 0x20) >> 5) + 1
228
+ if (index + thresholdSize > signature.length) {
229
+ throw new Error('Not enough bytes for threshold')
230
+ }
231
+ const threshold = Bytes.toBigInt(signature.slice(index, index + thresholdSize))
232
+ index += thresholdSize
233
+
234
+ // If bit 1 is set => chained signature
235
+ if ((flag & 0x01) === 0x01) {
236
+ const subsignatures: Array<RawSignature & { checkpointerData: undefined }> = []
237
+
238
+ while (index < signature.length) {
239
+ if (index + 3 > signature.length) {
240
+ throw new Error('Not enough bytes for chained subsignature size')
241
+ }
242
+ const subsignatureSize = Bytes.toNumber(signature.subarray(index, index + 3))
243
+ index += 3
244
+
245
+ if (index + subsignatureSize > signature.length) {
246
+ throw new Error('Not enough bytes for chained subsignature')
247
+ }
248
+ const subsignature = decodeSignature(signature.subarray(index, index + subsignatureSize))
249
+ index += subsignatureSize
250
+
251
+ if (subsignature.checkpointerData) {
252
+ throw new Error('Chained subsignature has checkpointer data')
253
+ }
254
+
255
+ subsignatures.push({ ...subsignature, checkpointerData: undefined })
256
+ }
257
+
258
+ if (subsignatures.length === 0) {
259
+ throw new Error('Chained signature has no subsignatures')
260
+ }
261
+
262
+ return { ...subsignatures[0]!, suffix: subsignatures.slice(1), erc6492 }
263
+ }
264
+
265
+ const { nodes, leftover } = parseBranch(signature.slice(index))
266
+ if (leftover.length !== 0) {
267
+ throw new Error('Leftover bytes in signature')
268
+ }
269
+
270
+ const topology = foldNodes(nodes)
271
+
272
+ return {
273
+ noChainId,
274
+ checkpointerData,
275
+ configuration: { threshold, checkpoint, topology, checkpointer: checkpointerAddress },
276
+ erc6492,
277
+ }
278
+ }
279
+
280
+ export function parseBranch(signature: Bytes.Bytes): {
281
+ nodes: RawTopology[]
282
+ leftover: Bytes.Bytes
283
+ } {
284
+ const nodes: RawTopology[] = []
285
+ let index = 0
286
+
287
+ while (index < signature.length) {
288
+ const firstByte = signature[index]!
289
+ index++
290
+
291
+ const flag = (firstByte & 0xf0) >> 4
292
+
293
+ // FLAG_SIGNATURE_HASH = 0 => bottom nibble is weight
294
+ // Then read 64 bytes => r, yParityAndS => top bit => yParity => s is the rest => v=27+yParity
295
+ if (flag === FLAG_SIGNATURE_HASH) {
296
+ let weight = firstByte & 0x0f
297
+ if (weight === 0) {
298
+ if (index >= signature.length) {
299
+ throw new Error('Not enough bytes for dynamic weight')
300
+ }
301
+ weight = signature[index]!
302
+ index++
303
+ }
304
+ if (index + 64 > signature.length) {
305
+ throw new Error('Not enough bytes for hash signature (r + yParityAndS)')
306
+ }
307
+ const unpackedRSY = unpackRSY(signature.slice(index, index + 64))
308
+ index += 64
309
+
310
+ nodes.push({
311
+ type: 'unrecovered-signer',
312
+ weight: BigInt(weight),
313
+ signature: {
314
+ type: 'hash',
315
+ ...unpackedRSY,
316
+ },
317
+ } as RawSignerLeaf)
318
+ continue
319
+ }
320
+
321
+ // FLAG_ADDRESS = 1 => bottom nibble is weight => read 20 bytes => no signature
322
+ if (flag === FLAG_ADDRESS) {
323
+ let weight = firstByte & 0x0f
324
+ if (weight === 0) {
325
+ if (index >= signature.length) {
326
+ throw new Error('Not enough bytes for address weight')
327
+ }
328
+ weight = signature[index]!
329
+ index++
330
+ }
331
+ if (index + 20 > signature.length) {
332
+ throw new Error('Not enough bytes for address leaf')
333
+ }
334
+ const addr = Bytes.toHex(signature.slice(index, index + 20))
335
+ index += 20
336
+
337
+ nodes.push({
338
+ type: 'signer',
339
+ address: addr,
340
+ weight: BigInt(weight),
341
+ } as SignerLeaf)
342
+ continue
343
+ }
344
+
345
+ // FLAG_SIGNATURE_ERC1271 = 2 => bottom 2 bits => weight, next 2 bits => sizeSize
346
+ if (flag === FLAG_SIGNATURE_ERC1271) {
347
+ let weight = firstByte & 0x03
348
+ if (weight === 0) {
349
+ if (index >= signature.length) {
350
+ throw new Error('Not enough bytes for ERC1271 weight')
351
+ }
352
+ weight = signature[index]!
353
+ index++
354
+ }
355
+ if (index + 20 > signature.length) {
356
+ throw new Error('Not enough bytes for ERC1271 signer address')
357
+ }
358
+ const signer = Bytes.toHex(signature.slice(index, index + 20))
359
+ index += 20
360
+
361
+ const sizeSize = (firstByte & 0x0c) >> 2
362
+ if (index + sizeSize > signature.length) {
363
+ throw new Error('Not enough bytes for ERC1271 sizeSize')
364
+ }
365
+ const dataSize = Bytes.toNumber(signature.slice(index, index + sizeSize))
366
+ index += sizeSize
367
+
368
+ if (index + dataSize > signature.length) {
369
+ throw new Error('Not enough bytes for ERC1271 data')
370
+ }
371
+ const subSignature = signature.slice(index, index + dataSize)
372
+ index += dataSize
373
+
374
+ nodes.push({
375
+ type: 'unrecovered-signer',
376
+ weight: BigInt(weight),
377
+ signature: {
378
+ type: 'erc1271',
379
+ address: signer,
380
+ data: Bytes.toHex(subSignature),
381
+ },
382
+ } as RawSignerLeaf)
383
+ continue
384
+ }
385
+
386
+ // FLAG_NODE = 3 => read 32 bytes as a node hash
387
+ if (flag === FLAG_NODE) {
388
+ if (index + 32 > signature.length) {
389
+ throw new Error('Not enough bytes for node leaf')
390
+ }
391
+ const node = signature.slice(index, index + 32)
392
+ index += 32
393
+
394
+ nodes.push(Bytes.toHex(node))
395
+ continue
396
+ }
397
+
398
+ // FLAG_BRANCH = 4 => next nibble => sizeSize => read size => parse sub-branch
399
+ if (flag === FLAG_BRANCH) {
400
+ const sizeSize = firstByte & 0x0f
401
+ if (index + sizeSize > signature.length) {
402
+ throw new Error('Not enough bytes for branch sizeSize')
403
+ }
404
+ const size = Bytes.toNumber(signature.slice(index, index + sizeSize))
405
+ index += sizeSize
406
+
407
+ if (index + size > signature.length) {
408
+ throw new Error('Not enough bytes in sub-branch')
409
+ }
410
+ const branchBytes = signature.slice(index, index + size)
411
+ index += size
412
+
413
+ const { nodes: subNodes, leftover } = parseBranch(branchBytes)
414
+ if (leftover.length > 0) {
415
+ throw new Error('Leftover bytes in sub-branch')
416
+ }
417
+ const subTree = foldNodes(subNodes)
418
+ nodes.push(subTree)
419
+ continue
420
+ }
421
+
422
+ // FLAG_SUBDIGEST = 5 => read 32 bytes => push subdigest leaf
423
+ if (flag === FLAG_SUBDIGEST) {
424
+ if (index + 32 > signature.length) {
425
+ throw new Error('Not enough bytes for subdigest')
426
+ }
427
+ const hardcoded = signature.slice(index, index + 32)
428
+ index += 32
429
+ nodes.push({
430
+ type: 'subdigest',
431
+ digest: Bytes.toHex(hardcoded),
432
+ } as SubdigestLeaf)
433
+ continue
434
+ }
435
+
436
+ // FLAG_NESTED = 6 => read externalWeight + internalThreshold, then read 3 bytes => parse subtree
437
+ if (flag === FLAG_NESTED) {
438
+ // bits [3..2] => external weight
439
+ let externalWeight = (firstByte & 0x0c) >> 2
440
+ if (externalWeight === 0) {
441
+ if (index >= signature.length) {
442
+ throw new Error('Not enough bytes for nested weight')
443
+ }
444
+ externalWeight = signature[index]!
445
+ index++
446
+ }
447
+
448
+ // bits [1..0] => internal threshold
449
+ let internalThreshold = firstByte & 0x03
450
+ if (internalThreshold === 0) {
451
+ if (index + 2 > signature.length) {
452
+ throw new Error('Not enough bytes for nested threshold')
453
+ }
454
+ internalThreshold = Bytes.toNumber(signature.slice(index, index + 2))
455
+ index += 2
456
+ }
457
+
458
+ if (index + 3 > signature.length) {
459
+ throw new Error('Not enough bytes for nested sub-tree size')
460
+ }
461
+ const size = Bytes.toNumber(signature.slice(index, index + 3))
462
+ index += 3
463
+
464
+ if (index + size > signature.length) {
465
+ throw new Error('Not enough bytes for nested sub-tree')
466
+ }
467
+ const nestedTreeBytes = signature.slice(index, index + size)
468
+ index += size
469
+
470
+ const { nodes: subNodes, leftover } = parseBranch(nestedTreeBytes)
471
+ if (leftover.length > 0) {
472
+ throw new Error('Leftover bytes in nested sub-tree')
473
+ }
474
+ const subTree = foldNodes(subNodes)
475
+
476
+ nodes.push({
477
+ type: 'nested',
478
+ tree: subTree,
479
+ weight: BigInt(externalWeight),
480
+ threshold: BigInt(internalThreshold),
481
+ } as RawNestedLeaf)
482
+ continue
483
+ }
484
+
485
+ // FLAG_SIGNATURE_ETH_SIGN = 7 => parse it same as hash, but interpret the subdigest as an Ethereum Signed Message
486
+ if (flag === FLAG_SIGNATURE_ETH_SIGN) {
487
+ let weight = firstByte & 0x0f
488
+ if (weight === 0) {
489
+ if (index >= signature.length) {
490
+ throw new Error('Not enough bytes for dynamic weight in eth_sign')
491
+ }
492
+ weight = signature[index]!
493
+ index++
494
+ }
495
+ if (index + 64 > signature.length) {
496
+ throw new Error('Not enough bytes for eth_sign signature')
497
+ }
498
+ const unpackedRSY = unpackRSY(signature.slice(index, index + 64))
499
+ index += 64
500
+
501
+ nodes.push({
502
+ type: 'unrecovered-signer',
503
+ weight: BigInt(weight),
504
+ signature: {
505
+ type: 'eth_sign',
506
+ ...unpackedRSY,
507
+ },
508
+ } as RawSignerLeaf)
509
+ continue
510
+ }
511
+
512
+ // FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST = 8 => read 32 bytes => push any address subdigest leaf
513
+ if (flag === FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST) {
514
+ if (index + 32 > signature.length) {
515
+ throw new Error('Not enough bytes for any address subdigest')
516
+ }
517
+ const anyAddressSubdigest = signature.slice(index, index + 32)
518
+ index += 32
519
+ nodes.push({
520
+ type: 'any-address-subdigest',
521
+ digest: Bytes.toHex(anyAddressSubdigest),
522
+ } as AnyAddressSubdigestLeaf)
523
+ continue
524
+ }
525
+
526
+ if (flag === FLAG_SIGNATURE_SAPIENT || flag === FLAG_SIGNATURE_SAPIENT_COMPACT) {
527
+ let addrWeight = firstByte & 0x03
528
+ if (addrWeight === 0) {
529
+ if (index >= signature.length) {
530
+ throw new Error('Not enough bytes for sapient weight')
531
+ }
532
+ addrWeight = signature[index]!
533
+ index++
534
+ }
535
+ if (index + 20 > signature.length) {
536
+ throw new Error('Not enough bytes for sapient signer address')
537
+ }
538
+ const address = Bytes.toHex(signature.slice(index, index + 20))
539
+ index += 20
540
+
541
+ const sizeSize = (firstByte & 0x0c) >> 2
542
+ if (index + sizeSize > signature.length) {
543
+ throw new Error('Not enough bytes for sapient signature size')
544
+ }
545
+ const dataSize = Bytes.toNumber(signature.slice(index, index + sizeSize))
546
+ index += sizeSize
547
+
548
+ if (index + dataSize > signature.length) {
549
+ throw new Error('Not enough bytes for sapient signature data')
550
+ }
551
+ const subSignature = signature.slice(index, index + dataSize)
552
+ index += dataSize
553
+
554
+ nodes.push({
555
+ type: 'unrecovered-signer',
556
+ weight: BigInt(addrWeight),
557
+ signature: {
558
+ address,
559
+ data: Bytes.toHex(subSignature),
560
+ type: flag === FLAG_SIGNATURE_SAPIENT ? 'sapient' : 'sapient_compact',
561
+ },
562
+ } as RawSignerLeaf)
563
+ continue
564
+ }
565
+
566
+ throw new Error(`Invalid signature flag: 0x${flag.toString(16)}`)
567
+ }
568
+
569
+ return { nodes, leftover: signature.slice(index) }
570
+ }
571
+
572
+ export function fillLeaves(
573
+ topology: Topology,
574
+ signatureFor: (
575
+ leaf: SignerLeaf | SapientSignerLeaf,
576
+ ) => SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf | undefined,
577
+ ): Topology {
578
+ if (isNode(topology)) {
579
+ return [fillLeaves(topology[0]!, signatureFor), fillLeaves(topology[1]!, signatureFor)] as Topology
580
+ }
581
+
582
+ if (isSignerLeaf(topology)) {
583
+ const signature = signatureFor(topology)
584
+ if (!signature) {
585
+ return topology
586
+ }
587
+ return { ...topology, signature } as SignedSignerLeaf
588
+ }
589
+
590
+ if (isSapientSignerLeaf(topology)) {
591
+ const signature = signatureFor(topology)
592
+ if (!signature) {
593
+ return topology
594
+ }
595
+ return { ...topology, signature } as SignedSapientSignerLeaf
596
+ }
597
+
598
+ if (isSubdigestLeaf(topology)) {
599
+ return topology
600
+ }
601
+
602
+ if (isAnyAddressSubdigestLeaf(topology)) {
603
+ return topology
604
+ }
605
+
606
+ if (isNestedLeaf(topology)) {
607
+ return { ...topology, tree: fillLeaves(topology.tree, signatureFor) } as NestedLeaf
608
+ }
609
+
610
+ if (isNodeLeaf(topology)) {
611
+ return topology
612
+ }
613
+
614
+ throw new Error('Invalid topology')
615
+ }
616
+
617
+ export function encodeChainedSignature(signatures: RawSignature[]): Uint8Array {
618
+ let flag = 0x01
619
+
620
+ let sigForCheckpointer = signatures[signatures.length - 1]
621
+
622
+ if (sigForCheckpointer?.configuration.checkpointer) {
623
+ flag |= 0x40
624
+ }
625
+
626
+ let output = Bytes.fromNumber(flag)
627
+ if (sigForCheckpointer?.configuration.checkpointer) {
628
+ output = Bytes.concat(output, Bytes.padLeft(Bytes.fromHex(sigForCheckpointer.configuration.checkpointer), 20))
629
+ const checkpointerDataSize = sigForCheckpointer.checkpointerData?.length ?? 0
630
+ if (checkpointerDataSize > 16777215) {
631
+ throw new Error('Checkpointer data too large')
632
+ }
633
+ const checkpointerDataSizeBytes = Bytes.padLeft(Bytes.fromNumber(checkpointerDataSize), 3)
634
+ output = Bytes.concat(output, checkpointerDataSizeBytes, sigForCheckpointer.checkpointerData ?? Bytes.fromArray([]))
635
+ }
636
+
637
+ for (let i = 0; i < signatures.length; i++) {
638
+ const signature = signatures[i]!
639
+ const encoded = encodeSignature(signature, true, i === signatures.length - 1)
640
+ if (encoded.length > 16777215) {
641
+ throw new Error('Chained signature too large')
642
+ }
643
+ const encodedSize = Bytes.padLeft(Bytes.fromNumber(encoded.length), 3)
644
+ output = Bytes.concat(output, encodedSize, encoded)
645
+ }
646
+
647
+ return output
648
+ }
649
+
650
+ export function encodeSignature(
651
+ signature: RawSignature,
652
+ skipCheckpointerData?: boolean,
653
+ skipCheckpointerAddress?: boolean,
654
+ ): Uint8Array {
655
+ const { noChainId, checkpointerData, configuration: config, suffix, erc6492 } = signature
656
+
657
+ if (suffix?.length) {
658
+ const chainedSig = encodeChainedSignature([{ ...signature, suffix: undefined, erc6492: undefined }, ...suffix])
659
+ return erc6492 ? wrap(chainedSig, erc6492) : chainedSig
660
+ }
661
+
662
+ let flag = 0
663
+
664
+ if (noChainId) {
665
+ flag |= 0x02
666
+ }
667
+
668
+ const bytesForCheckpoint = minBytesFor(config.checkpoint)
669
+ if (bytesForCheckpoint > 7) {
670
+ throw new Error('Checkpoint too large')
671
+ }
672
+ flag |= bytesForCheckpoint << 2
673
+
674
+ let bytesForThreshold = minBytesFor(config.threshold)
675
+ bytesForThreshold = bytesForThreshold === 0 ? 1 : bytesForThreshold
676
+ if (bytesForThreshold > 2) {
677
+ throw new Error('Threshold too large')
678
+ }
679
+ flag |= bytesForThreshold == 2 ? 0x20 : 0x00
680
+
681
+ if (config.checkpointer && !skipCheckpointerAddress) {
682
+ flag |= 0x40
683
+ }
684
+
685
+ let output = Bytes.fromNumber(flag)
686
+
687
+ if (config.checkpointer && !skipCheckpointerAddress) {
688
+ output = Bytes.concat(output, Bytes.padLeft(Bytes.fromHex(config.checkpointer), 20))
689
+ if (!skipCheckpointerData) {
690
+ const checkpointerDataSize = checkpointerData?.length ?? 0
691
+ if (checkpointerDataSize > 16777215) {
692
+ throw new Error('Checkpointer data too large')
693
+ }
694
+
695
+ const checkpointerDataSizeBytes = Bytes.padLeft(Bytes.fromNumber(checkpointerDataSize), 3)
696
+ output = Bytes.concat(output, checkpointerDataSizeBytes, checkpointerData ?? Bytes.fromArray([]))
697
+ }
698
+ }
699
+
700
+ const checkpointBytes = Bytes.padLeft(Bytes.fromNumber(config.checkpoint), bytesForCheckpoint)
701
+ output = Bytes.concat(output, checkpointBytes)
702
+
703
+ const thresholdBytes = Bytes.padLeft(Bytes.fromNumber(config.threshold), bytesForThreshold)
704
+ output = Bytes.concat(output, thresholdBytes)
705
+
706
+ const topologyBytes = encodeTopology(config.topology, signature)
707
+ output = Bytes.concat(output, topologyBytes)
708
+
709
+ return erc6492 ? wrap(output, erc6492) : output
710
+ }
711
+
712
+ export function encodeTopology(
713
+ topology: Topology | RawTopology,
714
+ options: {
715
+ noChainId?: boolean
716
+ checkpointerData?: Uint8Array
717
+ } = {},
718
+ ): Uint8Array {
719
+ if (isNode(topology) || isRawNode(topology)) {
720
+ const encoded0 = encodeTopology(topology[0]!, options)
721
+ const encoded1 = encodeTopology(topology[1]!, options)
722
+ const isBranching = isNode(topology[1]!) || isRawNode(topology[1]!)
723
+
724
+ if (isBranching) {
725
+ let encoded1Size = minBytesFor(BigInt(encoded1.length))
726
+ if (encoded1Size > 15) {
727
+ throw new Error('Branch too large')
728
+ }
729
+
730
+ const flag = (FLAG_BRANCH << 4) | encoded1Size
731
+ return Bytes.concat(
732
+ encoded0,
733
+ Bytes.fromNumber(flag),
734
+ Bytes.padLeft(Bytes.fromNumber(encoded1.length), encoded1Size),
735
+ encoded1,
736
+ )
737
+ } else {
738
+ return Bytes.concat(encoded0, encoded1)
739
+ }
740
+ }
741
+
742
+ if (isNestedLeaf(topology) || isRawNestedLeaf(topology)) {
743
+ const nested = encodeTopology(topology.tree, options)
744
+
745
+ // - XX00 : Weight (00 = dynamic, 01 = 1, 10 = 2, 11 = 3)
746
+ // - 00XX : Threshold (00 = dynamic, 01 = 1, 10 = 2, 11 = 3)
747
+ let flag = FLAG_NESTED << 4
748
+
749
+ let weightBytes = Bytes.fromArray([])
750
+ if (topology.weight <= 3n && topology.weight > 0n) {
751
+ flag |= Number(topology.weight) << 2
752
+ } else if (topology.weight <= 255n) {
753
+ weightBytes = Bytes.fromNumber(Number(topology.weight))
754
+ } else {
755
+ throw new Error('Weight too large')
756
+ }
757
+
758
+ let thresholdBytes = Bytes.fromArray([])
759
+ if (topology.threshold <= 3n && topology.threshold > 0n) {
760
+ flag |= Number(topology.threshold)
761
+ } else if (topology.threshold <= 65535n) {
762
+ thresholdBytes = Bytes.padLeft(Bytes.fromNumber(Number(topology.threshold)), 2)
763
+ } else {
764
+ throw new Error('Threshold too large')
765
+ }
766
+
767
+ if (nested.length > 16777215) {
768
+ throw new Error('Nested tree too large')
769
+ }
770
+
771
+ return Bytes.concat(
772
+ Bytes.fromNumber(flag),
773
+ weightBytes,
774
+ thresholdBytes,
775
+ Bytes.padLeft(Bytes.fromNumber(nested.length), 3),
776
+ nested,
777
+ )
778
+ }
779
+
780
+ if (isNodeLeaf(topology)) {
781
+ return Bytes.concat(Bytes.fromNumber(FLAG_NODE << 4), Bytes.fromHex(topology))
782
+ }
783
+
784
+ if (isSignedSignerLeaf(topology) || isRawSignerLeaf(topology)) {
785
+ if (topology.signature.type === 'hash' || topology.signature.type === 'eth_sign') {
786
+ let flag = (topology.signature.type === 'hash' ? FLAG_SIGNATURE_HASH : FLAG_SIGNATURE_ETH_SIGN) << 4
787
+ let weightBytes = Bytes.fromArray([])
788
+ if (topology.weight <= 15n && topology.weight > 0n) {
789
+ flag |= Number(topology.weight)
790
+ } else if (topology.weight <= 255n) {
791
+ weightBytes = Bytes.fromNumber(Number(topology.weight))
792
+ } else {
793
+ throw new Error('Weight too large')
794
+ }
795
+
796
+ const packedRSY = packRSY(topology.signature)
797
+ return Bytes.concat(Bytes.fromNumber(flag), weightBytes, packedRSY)
798
+ } else if (topology.signature.type === 'erc1271') {
799
+ let flag = FLAG_SIGNATURE_ERC1271 << 4
800
+
801
+ let bytesForSignatureSize = minBytesFor(BigInt(topology.signature.data.length))
802
+ if (bytesForSignatureSize > 3) {
803
+ throw new Error('Signature too large')
804
+ }
805
+
806
+ flag |= bytesForSignatureSize << 2
807
+
808
+ let weightBytes = Bytes.fromArray([])
809
+ if (topology.weight <= 3n && topology.weight > 0n) {
810
+ flag |= Number(topology.weight)
811
+ } else if (topology.weight <= 255n) {
812
+ weightBytes = Bytes.fromNumber(Number(topology.weight))
813
+ } else {
814
+ throw new Error('Weight too large')
815
+ }
816
+
817
+ return Bytes.concat(
818
+ Bytes.fromNumber(flag),
819
+ weightBytes,
820
+ Bytes.padLeft(Bytes.fromHex(topology.signature.address), 20),
821
+ Bytes.padLeft(Bytes.fromNumber(Bytes.fromHex(topology.signature.data).length), bytesForSignatureSize),
822
+ Bytes.fromHex(topology.signature.data),
823
+ )
824
+ } else if (topology.signature.type === 'sapient' || topology.signature.type === 'sapient_compact') {
825
+ let flag = (topology.signature.type === 'sapient' ? FLAG_SIGNATURE_SAPIENT : FLAG_SIGNATURE_SAPIENT_COMPACT) << 4
826
+
827
+ const signatureBytes = Bytes.fromHex(topology.signature.data)
828
+ let bytesForSignatureSize = minBytesFor(BigInt(signatureBytes.length))
829
+ if (bytesForSignatureSize > 3) {
830
+ throw new Error('Signature too large')
831
+ }
832
+
833
+ flag |= bytesForSignatureSize << 2
834
+
835
+ let weightBytes = Bytes.fromArray([])
836
+ if (topology.weight <= 3n && topology.weight > 0n) {
837
+ flag |= Number(topology.weight)
838
+ } else if (topology.weight <= 255n) {
839
+ weightBytes = Bytes.fromNumber(Number(topology.weight))
840
+ } else {
841
+ throw new Error('Weight too large')
842
+ }
843
+
844
+ return Bytes.concat(
845
+ Bytes.fromNumber(flag),
846
+ weightBytes,
847
+ Bytes.padLeft(Bytes.fromHex(topology.signature.address), 20),
848
+ Bytes.padLeft(Bytes.fromNumber(signatureBytes.length), bytesForSignatureSize),
849
+ signatureBytes,
850
+ )
851
+ } else {
852
+ throw new Error(`Invalid signature type: ${topology.signature.type}`)
853
+ }
854
+ }
855
+
856
+ if (isSignerLeaf(topology)) {
857
+ let flag = FLAG_ADDRESS << 4
858
+ let weightBytes = Bytes.fromArray([])
859
+ if (topology.weight <= 15n && topology.weight > 0n) {
860
+ flag |= Number(topology.weight)
861
+ } else if (topology.weight <= 255n) {
862
+ weightBytes = Bytes.fromNumber(Number(topology.weight))
863
+ } else {
864
+ throw new Error('Weight too large')
865
+ }
866
+
867
+ return Bytes.concat(Bytes.fromNumber(flag), weightBytes, Bytes.padLeft(Bytes.fromHex(topology.address), 20))
868
+ }
869
+
870
+ if (isSapientSignerLeaf(topology)) {
871
+ // Encode as node directly
872
+ const hash = hashConfiguration(topology)
873
+ return Bytes.concat(Bytes.fromNumber(FLAG_NODE << 4), hash)
874
+ }
875
+
876
+ if (isSubdigestLeaf(topology)) {
877
+ return Bytes.concat(Bytes.fromNumber(FLAG_SUBDIGEST << 4), Bytes.fromHex(topology.digest))
878
+ }
879
+
880
+ if (isAnyAddressSubdigestLeaf(topology)) {
881
+ return Bytes.concat(Bytes.fromNumber(FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST << 4), Bytes.fromHex(topology.digest))
882
+ }
883
+
884
+ throw new Error('Invalid topology')
885
+ }
886
+
887
+ function foldNodes(nodes: RawTopology[]): RawTopology {
888
+ if (nodes.length === 0) {
889
+ throw new Error('Empty signature tree')
890
+ }
891
+
892
+ if (nodes.length === 1) {
893
+ return nodes[0]!
894
+ }
895
+
896
+ let tree: RawTopology = nodes[0]!
897
+ for (let i = 1; i < nodes.length; i++) {
898
+ tree = [tree, nodes[i]!] as RawNode
899
+ }
900
+ return tree
901
+ }
902
+
903
+ export function rawSignatureToJson(signature: RawSignature): string {
904
+ return JSON.stringify(rawSignatureToJsonParsed(signature))
905
+ }
906
+
907
+ function rawSignatureToJsonParsed(signature: RawSignature): any {
908
+ return {
909
+ noChainId: signature.noChainId,
910
+ checkpointerData: signature.checkpointerData ? Bytes.toHex(signature.checkpointerData) : undefined,
911
+ configuration: {
912
+ threshold: signature.configuration.threshold.toString(),
913
+ checkpoint: signature.configuration.checkpoint.toString(),
914
+ topology: rawTopologyToJson(signature.configuration.topology),
915
+ checkpointer: signature.configuration.checkpointer,
916
+ },
917
+ suffix: signature.suffix ? signature.suffix.map((sig) => rawSignatureToJsonParsed(sig)) : undefined,
918
+ }
919
+ }
920
+
921
+ function rawTopologyToJson(top: RawTopology): any {
922
+ if (Array.isArray(top)) {
923
+ return [rawTopologyToJson(top[0]), rawTopologyToJson(top[1])]
924
+ }
925
+ if (typeof top === 'object' && top !== null) {
926
+ if ('type' in top) {
927
+ switch (top.type) {
928
+ case 'signer':
929
+ return {
930
+ type: 'signer',
931
+ address: top.address,
932
+ weight: top.weight.toString(),
933
+ }
934
+ case 'sapient-signer':
935
+ return {
936
+ type: 'sapient-signer',
937
+ address: top.address,
938
+ weight: top.weight.toString(),
939
+ imageHash: top.imageHash,
940
+ }
941
+ case 'subdigest':
942
+ return {
943
+ type: 'subdigest',
944
+ digest: top.digest,
945
+ }
946
+ case 'any-address-subdigest':
947
+ return {
948
+ type: 'any-address-subdigest',
949
+ digest: top.digest,
950
+ }
951
+ case 'nested':
952
+ return {
953
+ type: 'nested',
954
+ tree: rawTopologyToJson(top.tree),
955
+ weight: top.weight.toString(),
956
+ threshold: top.threshold.toString(),
957
+ }
958
+ case 'unrecovered-signer':
959
+ return {
960
+ type: 'unrecovered-signer',
961
+ weight: top.weight.toString(),
962
+ signature: rawSignatureOfLeafToJson(top.signature),
963
+ }
964
+ default:
965
+ throw new Error('Invalid raw topology type')
966
+ }
967
+ }
968
+ }
969
+ if (typeof top === 'string') {
970
+ return top
971
+ }
972
+ throw new Error('Invalid raw topology format')
973
+ }
974
+
975
+ function rawSignatureOfLeafToJson(sig: SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf): any {
976
+ if (sig.type === 'eth_sign' || sig.type === 'hash') {
977
+ return {
978
+ type: sig.type,
979
+ r: Hex.fromNumber(sig.r, { size: 32 }),
980
+ s: Hex.fromNumber(sig.s, { size: 32 }),
981
+ yParity: sig.yParity,
982
+ }
983
+ }
984
+ if (sig.type === 'erc1271') {
985
+ return {
986
+ type: sig.type,
987
+ address: sig.address,
988
+ data: sig.data,
989
+ }
990
+ }
991
+ if (sig.type === 'sapient' || sig.type === 'sapient_compact') {
992
+ return {
993
+ type: sig.type,
994
+ address: sig.address,
995
+ data: sig.data,
996
+ }
997
+ }
998
+ throw new Error('Unknown signature type in raw signature')
999
+ }
1000
+
1001
+ export function rawSignatureFromJson(json: string): RawSignature {
1002
+ const parsed = JSON.parse(json)
1003
+ return rawSignatureFromParsed(parsed)
1004
+ }
1005
+
1006
+ function rawSignatureFromParsed(parsed: any): RawSignature {
1007
+ return {
1008
+ noChainId: parsed.noChainId,
1009
+ checkpointerData: parsed.checkpointerData ? Bytes.fromHex(parsed.checkpointerData) : undefined,
1010
+ configuration: {
1011
+ threshold: BigInt(parsed.configuration.threshold),
1012
+ checkpoint: BigInt(parsed.configuration.checkpoint),
1013
+ topology: rawTopologyFromJson(parsed.configuration.topology),
1014
+ checkpointer: parsed.configuration.checkpointer,
1015
+ },
1016
+ suffix: parsed.suffix ? parsed.suffix.map((sig: any) => rawSignatureFromParsed(sig)) : undefined,
1017
+ }
1018
+ }
1019
+
1020
+ function rawTopologyFromJson(obj: any): RawTopology {
1021
+ if (Array.isArray(obj)) {
1022
+ if (obj.length !== 2) {
1023
+ throw new Error('Invalid raw topology node')
1024
+ }
1025
+ return [rawTopologyFromJson(obj[0]), rawTopologyFromJson(obj[1])]
1026
+ }
1027
+ if (typeof obj === 'object' && obj !== null) {
1028
+ if ('type' in obj) {
1029
+ switch (obj.type) {
1030
+ case 'signer':
1031
+ return {
1032
+ type: 'signer',
1033
+ address: obj.address,
1034
+ weight: BigInt(obj.weight),
1035
+ }
1036
+ case 'sapient-signer':
1037
+ return {
1038
+ type: 'sapient-signer',
1039
+ address: obj.address,
1040
+ weight: BigInt(obj.weight),
1041
+ imageHash: obj.imageHash,
1042
+ }
1043
+ case 'subdigest':
1044
+ return {
1045
+ type: 'subdigest',
1046
+ digest: obj.digest,
1047
+ }
1048
+ case 'any-address-subdigest':
1049
+ return {
1050
+ type: 'any-address-subdigest',
1051
+ digest: obj.digest,
1052
+ }
1053
+ case 'nested':
1054
+ return {
1055
+ type: 'nested',
1056
+ tree: rawTopologyFromJson(obj.tree),
1057
+ weight: BigInt(obj.weight),
1058
+ threshold: BigInt(obj.threshold),
1059
+ }
1060
+ case 'unrecovered-signer':
1061
+ return {
1062
+ type: 'unrecovered-signer',
1063
+ weight: BigInt(obj.weight),
1064
+ signature: rawSignatureOfLeafFromJson(obj.signature),
1065
+ }
1066
+ default:
1067
+ throw new Error('Invalid raw topology type')
1068
+ }
1069
+ }
1070
+ }
1071
+ if (typeof obj === 'string') {
1072
+ return obj as Hex.Hex
1073
+ }
1074
+ throw new Error('Invalid raw topology format')
1075
+ }
1076
+
1077
+ function rawSignatureOfLeafFromJson(obj: any): SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf {
1078
+ switch (obj.type) {
1079
+ case 'eth_sign':
1080
+ case 'hash':
1081
+ return {
1082
+ type: obj.type,
1083
+ r: Hex.toBigInt(obj.r),
1084
+ s: Hex.toBigInt(obj.s),
1085
+ yParity: obj.yParity,
1086
+ }
1087
+ case 'erc1271':
1088
+ return {
1089
+ type: 'erc1271',
1090
+ address: obj.address,
1091
+ data: obj.data,
1092
+ }
1093
+ case 'sapient':
1094
+ case 'sapient_compact':
1095
+ return {
1096
+ type: obj.type,
1097
+ address: obj.address,
1098
+ data: obj.data,
1099
+ }
1100
+ default:
1101
+ throw new Error('Invalid signature type in raw signature')
1102
+ }
1103
+ }
1104
+
1105
+ export async function recover(
1106
+ signature: RawSignature,
1107
+ wallet: Address.Address,
1108
+ chainId: bigint,
1109
+ payload: Parented,
1110
+ options?: {
1111
+ provider?: Provider.Provider | { provider: Provider.Provider; block: number } | 'assume-valid' | 'assume-invalid'
1112
+ },
1113
+ ): Promise<{ configuration: Config; weight: bigint }> {
1114
+ if (signature.suffix?.length) {
1115
+ let invalid = false
1116
+
1117
+ let { configuration, weight } = await recover(
1118
+ { ...signature, suffix: undefined },
1119
+ wallet,
1120
+ chainId,
1121
+ payload,
1122
+ options,
1123
+ )
1124
+
1125
+ invalid ||= weight < configuration.threshold
1126
+
1127
+ for (const subsignature of signature.suffix) {
1128
+ const recovered = await recover(
1129
+ subsignature,
1130
+ wallet,
1131
+ subsignature.noChainId ? 0n : chainId,
1132
+ fromConfigUpdate(Bytes.toHex(hashConfiguration(configuration))),
1133
+ options,
1134
+ )
1135
+
1136
+ invalid ||= recovered.weight < recovered.configuration.threshold
1137
+ invalid ||= recovered.configuration.checkpoint >= configuration.checkpoint
1138
+
1139
+ configuration = recovered.configuration
1140
+ weight = recovered.weight
1141
+ }
1142
+
1143
+ return { configuration, weight: invalid ? 0n : weight }
1144
+ }
1145
+
1146
+ const { topology, weight } = await recoverTopology(
1147
+ signature.configuration.topology,
1148
+ wallet,
1149
+ chainId,
1150
+ payload,
1151
+ options,
1152
+ )
1153
+
1154
+ return { configuration: { ...signature.configuration, topology }, weight }
1155
+ }
1156
+
1157
+ async function recoverTopology(
1158
+ topology: RawTopology,
1159
+ wallet: Address.Address,
1160
+ chainId: bigint,
1161
+ payload: Parented,
1162
+ options?: {
1163
+ provider?: Provider.Provider | { provider: Provider.Provider; block: number } | 'assume-valid' | 'assume-invalid'
1164
+ throw?: boolean
1165
+ },
1166
+ ): Promise<{ topology: Topology; weight: bigint }> {
1167
+ const digest = hash(wallet, chainId, payload)
1168
+
1169
+ if (isRawSignerLeaf(topology)) {
1170
+ switch (topology.signature.type) {
1171
+ case 'eth_sign':
1172
+ case 'hash':
1173
+ return {
1174
+ topology: {
1175
+ type: 'signer',
1176
+ address: Secp256k1.recoverAddress({
1177
+ payload:
1178
+ topology.signature.type === 'eth_sign'
1179
+ ? Hash.keccak256(
1180
+ AbiParameters.encodePacked(
1181
+ ['string', 'bytes32'],
1182
+ ['\x19Ethereum Signed Message:\n32', Bytes.toHex(digest)],
1183
+ ),
1184
+ )
1185
+ : digest,
1186
+ signature: topology.signature,
1187
+ }),
1188
+ weight: topology.weight,
1189
+ signed: true,
1190
+ signature: topology.signature,
1191
+ },
1192
+ weight: topology.weight,
1193
+ }
1194
+
1195
+ case 'erc1271':
1196
+ switch (options?.provider) {
1197
+ case undefined:
1198
+ case 'assume-invalid':
1199
+ if (options?.throw !== false) {
1200
+ throw new Error(`unable to validate signer ${topology.signature.address} erc-1271 signature`)
1201
+ } else {
1202
+ return {
1203
+ topology: { type: 'signer', address: topology.signature.address, weight: topology.weight },
1204
+ weight: 0n,
1205
+ }
1206
+ }
1207
+
1208
+ case 'assume-valid':
1209
+ return {
1210
+ topology: {
1211
+ type: 'signer',
1212
+ address: topology.signature.address,
1213
+ weight: topology.weight,
1214
+ signed: true,
1215
+ signature: topology.signature,
1216
+ },
1217
+ weight: topology.weight,
1218
+ }
1219
+
1220
+ default:
1221
+ const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider
1222
+ const block = 'block' in options!.provider ? options!.provider.block : undefined
1223
+
1224
+ const call = {
1225
+ to: topology.signature.address,
1226
+ data: AbiFunction.encodeData(IS_VALID_SIGNATURE, [Bytes.toHex(digest), topology.signature.data]),
1227
+ }
1228
+
1229
+ const response = await provider.request({
1230
+ method: 'eth_call',
1231
+ params: block === undefined ? [call] : [call, Hex.fromNumber(block)],
1232
+ })
1233
+ const decodedResult = AbiFunction.decodeResult(IS_VALID_SIGNATURE, response)
1234
+
1235
+ if (Hex.isEqual(decodedResult, AbiFunction.getSelector(IS_VALID_SIGNATURE))) {
1236
+ return {
1237
+ topology: {
1238
+ type: 'signer',
1239
+ address: topology.signature.address,
1240
+ weight: topology.weight,
1241
+ signed: true,
1242
+ signature: topology.signature,
1243
+ },
1244
+ weight: topology.weight,
1245
+ }
1246
+ } else {
1247
+ if (options?.throw !== false) {
1248
+ throw new Error(`invalid signer ${topology.signature.address} erc-1271 signature`)
1249
+ } else {
1250
+ return {
1251
+ topology: { type: 'signer', address: topology.signature.address, weight: topology.weight },
1252
+ weight: 0n,
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+
1258
+ case 'sapient':
1259
+ case 'sapient_compact':
1260
+ switch (options?.provider) {
1261
+ case undefined:
1262
+ case 'assume-invalid':
1263
+ case 'assume-valid':
1264
+ throw new Error(`unable to validate sapient signer ${topology.signature.address} signature`)
1265
+
1266
+ default:
1267
+ const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider
1268
+ const block = 'block' in options!.provider ? options!.provider.block : undefined
1269
+
1270
+ const call = {
1271
+ to: topology.signature.address,
1272
+ data:
1273
+ topology.signature.type === 'sapient'
1274
+ ? AbiFunction.encodeData(RECOVER_SAPIENT_SIGNATURE, [
1275
+ encode(chainId, payload),
1276
+ topology.signature.data,
1277
+ ])
1278
+ : AbiFunction.encodeData(RECOVER_SAPIENT_SIGNATURE_COMPACT, [
1279
+ Bytes.toHex(digest),
1280
+ topology.signature.data,
1281
+ ]),
1282
+ }
1283
+
1284
+ const response = await provider.request({
1285
+ method: 'eth_call',
1286
+ params: block === undefined ? [call] : [call, Hex.fromNumber(block)],
1287
+ })
1288
+
1289
+ return {
1290
+ topology: {
1291
+ type: 'sapient-signer',
1292
+ address: topology.signature.address,
1293
+ weight: topology.weight,
1294
+ imageHash: response,
1295
+ signed: true,
1296
+ signature: topology.signature,
1297
+ },
1298
+ weight: topology.weight,
1299
+ }
1300
+ }
1301
+ }
1302
+ } else if (isRawNestedLeaf(topology)) {
1303
+ const { topology: tree, weight } = await recoverTopology(topology.tree, wallet, chainId, payload, options)
1304
+ return { topology: { ...topology, tree }, weight: weight >= topology.threshold ? topology.weight : 0n }
1305
+ } else if (isSignerLeaf(topology)) {
1306
+ return { topology, weight: 0n }
1307
+ } else if (isSapientSignerLeaf(topology)) {
1308
+ return { topology, weight: 0n }
1309
+ } else if (isSubdigestLeaf(topology)) {
1310
+ return {
1311
+ topology,
1312
+ weight: Bytes.isEqual(Bytes.fromHex(topology.digest), digest)
1313
+ ? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
1314
+ : 0n,
1315
+ }
1316
+ } else if (isAnyAddressSubdigestLeaf(topology)) {
1317
+ const anyAddressOpHash = hash('0x0000000000000000000000000000000000000000', chainId, payload)
1318
+ return {
1319
+ topology,
1320
+ weight: Bytes.isEqual(Bytes.fromHex(topology.digest), anyAddressOpHash)
1321
+ ? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
1322
+ : 0n,
1323
+ }
1324
+ } else if (isNodeLeaf(topology)) {
1325
+ return { topology, weight: 0n }
1326
+ } else {
1327
+ const [left, right] = await Promise.all(
1328
+ topology.map((topology) => recoverTopology(topology, wallet, chainId, payload, options)),
1329
+ )
1330
+ return { topology: [left!.topology, right!.topology], weight: left!.weight + right!.weight }
1331
+ }
1332
+ }
1333
+
1334
+ function encode(
1335
+ chainId: bigint,
1336
+ payload: Parented,
1337
+ ): Exclude<AbiFunction.encodeData.Args<typeof RECOVER_SAPIENT_SIGNATURE>, []>[0][0] {
1338
+ switch (payload.type) {
1339
+ case 'call':
1340
+ return {
1341
+ kind: 0,
1342
+ noChainId: !chainId,
1343
+ calls: payload.calls.map((call) => ({
1344
+ ...call,
1345
+ data: call.data,
1346
+ behaviorOnError: call.behaviorOnError === 'ignore' ? 0n : call.behaviorOnError === 'revert' ? 1n : 2n,
1347
+ })),
1348
+ space: payload.space,
1349
+ nonce: payload.nonce,
1350
+ message: '0x',
1351
+ imageHash: '0x',
1352
+ digest: '0x',
1353
+ parentWallets: payload.parentWallets ?? [],
1354
+ }
1355
+
1356
+ case 'message':
1357
+ return {
1358
+ kind: 1,
1359
+ noChainId: !chainId,
1360
+ calls: [],
1361
+ space: 0n,
1362
+ nonce: 0n,
1363
+ message: payload.message,
1364
+ imageHash: '0x',
1365
+ digest: '0x',
1366
+ parentWallets: payload.parentWallets ?? [],
1367
+ }
1368
+
1369
+ case 'config-update':
1370
+ return {
1371
+ kind: 2,
1372
+ noChainId: !chainId,
1373
+ calls: [],
1374
+ space: 0n,
1375
+ nonce: 0n,
1376
+ message: '0x',
1377
+ imageHash: payload.imageHash,
1378
+ digest: '0x',
1379
+ parentWallets: payload.parentWallets ?? [],
1380
+ }
1381
+
1382
+ case 'digest':
1383
+ return {
1384
+ kind: 3,
1385
+ noChainId: !chainId,
1386
+ calls: [],
1387
+ space: 0n,
1388
+ nonce: 0n,
1389
+ message: '0x',
1390
+ imageHash: '0x',
1391
+ digest: payload.digest,
1392
+ parentWallets: payload.parentWallets ?? [],
1393
+ }
1394
+
1395
+ default:
1396
+ throw new Error('Invalid payload type')
1397
+ }
1398
+ }