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