@ckb-ccc/core 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/address.advanced-BHxWrbFc.mjs +2 -0
- package/dist/address.advanced-BHxWrbFc.mjs.map +1 -0
- package/dist/advanced.d.mts +1 -1
- package/dist/advanced.mjs +1 -1
- package/{dist.commonjs/advancedBarrel-DRDT4GBS.d.ts → dist/advancedBarrel-Dwyn3WMm.d.mts} +437 -14
- package/dist/advancedBarrel-Dwyn3WMm.d.mts.map +1 -0
- package/dist/advancedBarrel.d.mts +1 -1
- package/dist/advancedBarrel.mjs +1 -1
- package/dist/barrel-DO-IW8Ui.mjs +7 -0
- package/dist/barrel-DO-IW8Ui.mjs.map +1 -0
- package/dist/barrel.d.mts +2 -2
- package/dist/barrel.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist.commonjs/address.advanced-UCU9Hn8t.js +2 -0
- package/dist.commonjs/address.advanced-UCU9Hn8t.js.map +1 -0
- package/dist.commonjs/advanced.d.ts +1 -1
- package/dist.commonjs/advanced.js +1 -1
- package/{dist/advancedBarrel-BD7EAPVd.d.mts → dist.commonjs/advancedBarrel-D97MfkUZ.d.ts} +438 -13
- package/dist.commonjs/advancedBarrel-D97MfkUZ.d.ts.map +1 -0
- package/dist.commonjs/advancedBarrel.d.ts +1 -1
- package/dist.commonjs/advancedBarrel.js +1 -1
- package/dist.commonjs/barrel-D5PRJCB6.js +21 -0
- package/dist.commonjs/barrel-D5PRJCB6.js.map +1 -0
- package/dist.commonjs/barrel.d.ts +2 -2
- package/dist.commonjs/barrel.js +1 -1
- package/dist.commonjs/index.d.ts +2 -2
- package/dist.commonjs/index.js +1 -1
- package/package.json +23 -12
- package/src/address/address.advanced.ts +3 -3
- package/src/ckb/transaction.ts +20 -1
- package/src/client/clientPublicMainnet.advanced.ts +17 -0
- package/src/client/clientPublicTestnet.advanced.ts +17 -0
- package/src/client/knownScript.ts +1 -0
- package/src/codec/entity.ts +12 -8
- package/src/hasher/hasherCkb.ts +7 -1
- package/src/hex/index.ts +82 -2
- package/src/molecule/codec.ts +15 -15
- package/src/num/index.ts +17 -4
- package/src/signer/ckb/index.ts +3 -1
- package/src/signer/ckb/secp256k1Signing.ts +94 -0
- package/src/signer/ckb/signerCkbPrivateKey.ts +6 -11
- package/src/signer/ckb/signerCkbPublicKey.ts +8 -3
- package/src/signer/ckb/signerMultisigCkbPrivateKey.ts +129 -0
- package/src/signer/ckb/signerMultisigCkbReadonly.ts +713 -0
- package/src/signer/signer/index.ts +100 -1
- package/tsdown.config.mts +12 -3
- package/dist/address.advanced-CWGrVD-M.mjs +0 -2
- package/dist/address.advanced-CWGrVD-M.mjs.map +0 -1
- package/dist/advancedBarrel-BD7EAPVd.d.mts.map +0 -1
- package/dist/barrel-DchA_RPN.mjs +0 -7
- package/dist/barrel-DchA_RPN.mjs.map +0 -1
- package/dist.commonjs/address.advanced-COp7SNhZ.js +0 -2
- package/dist.commonjs/address.advanced-COp7SNhZ.js.map +0 -1
- package/dist.commonjs/advancedBarrel-DRDT4GBS.d.ts.map +0 -1
- package/dist.commonjs/barrel-CXC2Uy5L.js +0 -7
- package/dist.commonjs/barrel-CXC2Uy5L.js.map +0 -1
- package/src/signer/ckb/verifyCkbSecp256k1.ts +0 -31
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { Address } from "../../address/index.js";
|
|
2
|
+
import { Bytes, bytesConcat, bytesFrom, BytesLike } from "../../bytes/index.js";
|
|
3
|
+
import {
|
|
4
|
+
Script,
|
|
5
|
+
ScriptLike,
|
|
6
|
+
Since,
|
|
7
|
+
SinceLike,
|
|
8
|
+
Transaction,
|
|
9
|
+
TransactionLike,
|
|
10
|
+
WitnessArgs,
|
|
11
|
+
WitnessArgsLike,
|
|
12
|
+
} from "../../ckb/index.js";
|
|
13
|
+
import {
|
|
14
|
+
CellDepInfo,
|
|
15
|
+
CellDepInfoLike,
|
|
16
|
+
Client,
|
|
17
|
+
KnownScript,
|
|
18
|
+
ScriptInfo,
|
|
19
|
+
ScriptInfoLike,
|
|
20
|
+
} from "../../client/index.js";
|
|
21
|
+
import { codec, Entity } from "../../codec/index.js";
|
|
22
|
+
import { HASH_CKB_SHORT_LENGTH, hashCkbShort } from "../../hasher/index.js";
|
|
23
|
+
import { Hex, hexFrom, HexLike } from "../../hex/index.js";
|
|
24
|
+
import { numFrom, NumLike, numToBytes } from "../../num/index.js";
|
|
25
|
+
import { apply, reduceAsync } from "../../utils/index.js";
|
|
26
|
+
import { SignerMultisig, SignerSignType, SignerType } from "../signer/index.js";
|
|
27
|
+
import {
|
|
28
|
+
recoverMessageSecp256k1,
|
|
29
|
+
SECP256K1_SIGNATURE_LENGTH,
|
|
30
|
+
} from "./secp256k1Signing.js";
|
|
31
|
+
|
|
32
|
+
export type MultisigCkbWitnessLike = (
|
|
33
|
+
| {
|
|
34
|
+
publicKeyHashes: HexLike[];
|
|
35
|
+
publicKeys?: undefined | null;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
publicKeyHashes?: undefined | null;
|
|
39
|
+
publicKeys: HexLike[];
|
|
40
|
+
}
|
|
41
|
+
) & {
|
|
42
|
+
threshold: NumLike;
|
|
43
|
+
mustMatch?: NumLike | null;
|
|
44
|
+
signatures?: HexLike[] | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A class representing multisig information, holding information ingredients and containing utilities.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
@codec({
|
|
52
|
+
encode: (encodable: MultisigCkbWitness) => {
|
|
53
|
+
const { publicKeyHashes, threshold, mustMatch, signatures } =
|
|
54
|
+
MultisigCkbWitness.from(encodable);
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
signatures.some((s) => s.length !== SECP256K1_SIGNATURE_LENGTH * 2 + 2)
|
|
58
|
+
) {
|
|
59
|
+
throw Error("MultisigCkbWitness: invalid signature length");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
publicKeyHashes.some((s) => s.length !== HASH_CKB_SHORT_LENGTH * 2 + 2)
|
|
63
|
+
) {
|
|
64
|
+
throw Error("MultisigCkbWitness: invalid public key hash length");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return bytesConcat(
|
|
68
|
+
"0x00",
|
|
69
|
+
numToBytes(mustMatch ?? 0),
|
|
70
|
+
numToBytes(threshold),
|
|
71
|
+
numToBytes(publicKeyHashes.length),
|
|
72
|
+
...publicKeyHashes,
|
|
73
|
+
...signatures,
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
decode: (raw: Bytes) => {
|
|
77
|
+
if (raw.length < 4) {
|
|
78
|
+
throw Error("MultisigCkbWitness: data length too short");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const [
|
|
82
|
+
_reserved,
|
|
83
|
+
mustMatch,
|
|
84
|
+
threshold,
|
|
85
|
+
publicKeyHashesLength,
|
|
86
|
+
...rawKeyAndSignatures
|
|
87
|
+
] = raw;
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
rawKeyAndSignatures.length <
|
|
91
|
+
publicKeyHashesLength * HASH_CKB_SHORT_LENGTH
|
|
92
|
+
) {
|
|
93
|
+
throw Error("MultisigCkbWitness: invalid public key hashes length");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const signatures = rawKeyAndSignatures.slice(
|
|
97
|
+
publicKeyHashesLength * HASH_CKB_SHORT_LENGTH,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return MultisigCkbWitness.from({
|
|
101
|
+
publicKeyHashes: Array.from(new Array(publicKeyHashesLength), (_, i) =>
|
|
102
|
+
hexFrom(
|
|
103
|
+
rawKeyAndSignatures.slice(
|
|
104
|
+
i * HASH_CKB_SHORT_LENGTH,
|
|
105
|
+
(i + 1) * HASH_CKB_SHORT_LENGTH,
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
threshold: numFrom(threshold),
|
|
110
|
+
mustMatch: numFrom(mustMatch),
|
|
111
|
+
signatures: Array.from(
|
|
112
|
+
new Array(Math.floor(signatures.length / SECP256K1_SIGNATURE_LENGTH)),
|
|
113
|
+
(_, i) =>
|
|
114
|
+
hexFrom(
|
|
115
|
+
signatures.slice(
|
|
116
|
+
i * SECP256K1_SIGNATURE_LENGTH,
|
|
117
|
+
(i + 1) * SECP256K1_SIGNATURE_LENGTH,
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
export class MultisigCkbWitness extends Entity.Base<
|
|
125
|
+
MultisigCkbWitnessLike,
|
|
126
|
+
MultisigCkbWitness
|
|
127
|
+
>() {
|
|
128
|
+
/**
|
|
129
|
+
* @param publicKeyHashes - The public key hashes.
|
|
130
|
+
* @param threshold - The threshold.
|
|
131
|
+
* @param mustMatch - The number of signatures that must match.
|
|
132
|
+
* @param signatures - The signatures.
|
|
133
|
+
*/
|
|
134
|
+
constructor(
|
|
135
|
+
public publicKeyHashes: Hex[],
|
|
136
|
+
public threshold: number,
|
|
137
|
+
public mustMatch: number,
|
|
138
|
+
public signatures: Hex[],
|
|
139
|
+
) {
|
|
140
|
+
super();
|
|
141
|
+
|
|
142
|
+
const keysLength = publicKeyHashes.length;
|
|
143
|
+
|
|
144
|
+
if (threshold <= 0 || threshold > keysLength) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
"threshold should be in range from 1 to public keys length",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (mustMatch < 0 || mustMatch > Math.min(keysLength, threshold)) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"mustMatch should be in range from 0 to min(public keys length, threshold)",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (keysLength > 255) {
|
|
155
|
+
throw new Error("public keys length should be less than 256");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create a MultisigCkbWitness from a MultisigCkbWitnessLike.
|
|
161
|
+
*
|
|
162
|
+
* @param witness - The witness like object.
|
|
163
|
+
* @returns The MultisigCkbWitness.
|
|
164
|
+
*/
|
|
165
|
+
static from(witness: MultisigCkbWitnessLike): MultisigCkbWitness {
|
|
166
|
+
const publicKeyHashes = (() => {
|
|
167
|
+
if (witness.publicKeyHashes) {
|
|
168
|
+
return witness.publicKeyHashes;
|
|
169
|
+
}
|
|
170
|
+
return witness.publicKeys.map((k) => hashCkbShort(k));
|
|
171
|
+
})();
|
|
172
|
+
|
|
173
|
+
return new MultisigCkbWitness(
|
|
174
|
+
publicKeyHashes.map(hexFrom),
|
|
175
|
+
Number(numFrom(witness.threshold)),
|
|
176
|
+
Number(numFrom(witness.mustMatch ?? 0)),
|
|
177
|
+
witness.signatures?.map(hexFrom) ?? [],
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get the threshold of flexible signatures.
|
|
183
|
+
*/
|
|
184
|
+
get flexibleThreshold() {
|
|
185
|
+
return this.threshold - this.mustMatch;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get the script args of the multisig script.
|
|
190
|
+
*
|
|
191
|
+
* @param since - The since value.
|
|
192
|
+
* @returns The script args.
|
|
193
|
+
*/
|
|
194
|
+
scriptArgs(since?: SinceLike | null): Bytes {
|
|
195
|
+
const hash = hashCkbShort(
|
|
196
|
+
MultisigCkbWitness.from({ ...this, signatures: [] }).toBytes(),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (since != null) {
|
|
200
|
+
return bytesConcat(hash, Since.from(since).toBytes());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return bytesFrom(hash);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if the multisig info is equal to another.
|
|
208
|
+
*
|
|
209
|
+
* @param otherLike - The other multisig info.
|
|
210
|
+
* @returns True if the multisig info is equal, false otherwise.
|
|
211
|
+
*/
|
|
212
|
+
eqInfo(otherLike: MultisigCkbWitnessLike): boolean {
|
|
213
|
+
const other = MultisigCkbWitness.from(otherLike);
|
|
214
|
+
return (
|
|
215
|
+
this.publicKeyHashes.length === other.publicKeyHashes.length &&
|
|
216
|
+
this.publicKeyHashes.every((h, i) => h === other.publicKeyHashes[i]) &&
|
|
217
|
+
this.threshold === other.threshold &&
|
|
218
|
+
this.mustMatch === other.mustMatch
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate valid public key hashes and their signatures from the witness.
|
|
224
|
+
* This method filters out invalid signatures, duplicate signatures, and signatures not in the multisig script.
|
|
225
|
+
*
|
|
226
|
+
* @param message - The message signed.
|
|
227
|
+
* @returns A generator of public key hashes, signatures, and whether the signature is required.
|
|
228
|
+
*/
|
|
229
|
+
*generatePublicKeyHashesFromSignatures(message: BytesLike): Generator<{
|
|
230
|
+
pubkeyHash: Hex;
|
|
231
|
+
signature: Hex;
|
|
232
|
+
isRequired: boolean;
|
|
233
|
+
}> {
|
|
234
|
+
const publicKeyHashesFromSignature = new Set<Hex>();
|
|
235
|
+
|
|
236
|
+
for (const signature of this.signatures.filter(
|
|
237
|
+
(sig) => sig !== SignerMultisigCkbReadonly.EmptySignature,
|
|
238
|
+
)) {
|
|
239
|
+
const pubkey = (() => {
|
|
240
|
+
try {
|
|
241
|
+
return recoverMessageSecp256k1(message, signature);
|
|
242
|
+
} catch (_) {
|
|
243
|
+
// Ignore invalid signature
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
})();
|
|
247
|
+
if (pubkey === undefined) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const pubkeyHash = hashCkbShort(pubkey);
|
|
252
|
+
if (publicKeyHashesFromSignature.has(pubkeyHash)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const index = this.publicKeyHashes.indexOf(pubkeyHash);
|
|
257
|
+
if (index === -1) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
publicKeyHashesFromSignature.add(pubkeyHash);
|
|
261
|
+
const isRequired = index < this.mustMatch;
|
|
262
|
+
|
|
263
|
+
yield {
|
|
264
|
+
pubkeyHash,
|
|
265
|
+
signature,
|
|
266
|
+
isRequired,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Calculate the number of matched signatures in the witness.
|
|
273
|
+
*
|
|
274
|
+
* @param message - The message signed.
|
|
275
|
+
* @returns The number of required and flexible signatures.
|
|
276
|
+
*/
|
|
277
|
+
calcMatchedSignaturesCount(message: BytesLike): {
|
|
278
|
+
required: number;
|
|
279
|
+
flexible: number;
|
|
280
|
+
} {
|
|
281
|
+
let required = 0;
|
|
282
|
+
let flexible = 0;
|
|
283
|
+
|
|
284
|
+
for (const { isRequired } of this.generatePublicKeyHashesFromSignatures(
|
|
285
|
+
message,
|
|
286
|
+
)) {
|
|
287
|
+
if (isRequired) {
|
|
288
|
+
required += 1;
|
|
289
|
+
} else {
|
|
290
|
+
flexible += 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return { required, flexible };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A class extending Signer that provides access to a CKB multisig script.
|
|
300
|
+
* This class does not support signing operations.
|
|
301
|
+
* @public
|
|
302
|
+
*/
|
|
303
|
+
export class SignerMultisigCkbReadonly extends SignerMultisig {
|
|
304
|
+
static EmptySignature = hexFrom("00".repeat(SECP256K1_SIGNATURE_LENGTH));
|
|
305
|
+
|
|
306
|
+
get type(): SignerType {
|
|
307
|
+
return SignerType.CKB;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
get signType(): SignerSignType {
|
|
311
|
+
return SignerSignType.Unknown;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public readonly multisigInfo: MultisigCkbWitness;
|
|
315
|
+
|
|
316
|
+
public readonly since?: Since;
|
|
317
|
+
public readonly scriptInfos: Promise<
|
|
318
|
+
{
|
|
319
|
+
script: Script;
|
|
320
|
+
cellDeps: CellDepInfo[];
|
|
321
|
+
}[]
|
|
322
|
+
>;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Creates an instance of SignerMultisigCkbReadonly.
|
|
326
|
+
*
|
|
327
|
+
* @param client - The client instance.
|
|
328
|
+
* @param multisigInfoLike - The multisig information.
|
|
329
|
+
* @param options - The options.
|
|
330
|
+
*/
|
|
331
|
+
constructor(
|
|
332
|
+
client: Client,
|
|
333
|
+
multisigInfoLike: MultisigCkbWitnessLike,
|
|
334
|
+
options?: {
|
|
335
|
+
since?: SinceLike | null;
|
|
336
|
+
scriptInfos?: (KnownScript | ScriptInfoLike)[] | null;
|
|
337
|
+
} | null,
|
|
338
|
+
) {
|
|
339
|
+
super(client);
|
|
340
|
+
|
|
341
|
+
this.multisigInfo = MultisigCkbWitness.from(multisigInfoLike);
|
|
342
|
+
this.since = apply(Since.from, options?.since);
|
|
343
|
+
|
|
344
|
+
const args = this.multisigInfo.scriptArgs(this.since);
|
|
345
|
+
this.scriptInfos = Promise.all(
|
|
346
|
+
(
|
|
347
|
+
options?.scriptInfos ?? [
|
|
348
|
+
KnownScript.Secp256k1MultisigV2,
|
|
349
|
+
KnownScript.Secp256k1MultisigV2Beta,
|
|
350
|
+
KnownScript.Secp256k1Multisig,
|
|
351
|
+
]
|
|
352
|
+
).map(async (v) => {
|
|
353
|
+
if (typeof v !== "string") {
|
|
354
|
+
return ScriptInfo.from(v);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
return await client.getKnownScript(v);
|
|
359
|
+
} catch (_) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
}),
|
|
363
|
+
).then((infos) =>
|
|
364
|
+
infos
|
|
365
|
+
.filter((s) => s !== undefined)
|
|
366
|
+
.map((i) => ({
|
|
367
|
+
script: Script.from({ ...i, args }),
|
|
368
|
+
cellDeps: i.cellDeps,
|
|
369
|
+
})),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get the number of members in the multisig script.
|
|
375
|
+
*
|
|
376
|
+
* @returns The number of members.
|
|
377
|
+
*/
|
|
378
|
+
async getMemberCount() {
|
|
379
|
+
return this.multisigInfo.publicKeyHashes.length;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get the threshold of the multisig script.
|
|
384
|
+
*
|
|
385
|
+
* @returns The threshold.
|
|
386
|
+
*/
|
|
387
|
+
async getMemberThreshold() {
|
|
388
|
+
return this.multisigInfo.threshold;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get the count of required member of the multisig script.
|
|
393
|
+
*
|
|
394
|
+
* @returns The must match count.
|
|
395
|
+
*/
|
|
396
|
+
async getMemberRequiredCount() {
|
|
397
|
+
return this.multisigInfo.mustMatch;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async connect(): Promise<void> {}
|
|
401
|
+
|
|
402
|
+
async isConnected(): Promise<boolean> {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async getInternalAddress(): Promise<string> {
|
|
407
|
+
return this.getRecommendedAddress();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async getAddressObjs(): Promise<Address[]> {
|
|
411
|
+
return (await this.scriptInfos).map(({ script }) =>
|
|
412
|
+
Address.fromScript(script, this.client),
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Decode the witness args at a specific index.
|
|
418
|
+
*
|
|
419
|
+
* @param txLike - The transaction.
|
|
420
|
+
* @param index - The index of the witness args.
|
|
421
|
+
* @returns The decoded MultisigCkbWitness.
|
|
422
|
+
*/
|
|
423
|
+
decodeWitnessArgsAt(
|
|
424
|
+
txLike: TransactionLike,
|
|
425
|
+
index: number,
|
|
426
|
+
): MultisigCkbWitness | undefined {
|
|
427
|
+
const tx = Transaction.from(txLike);
|
|
428
|
+
|
|
429
|
+
return this.decodeWitnessArgs(tx.getWitnessArgsAt(index));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Decode the witness args.
|
|
434
|
+
*
|
|
435
|
+
* @param witnessLike - The witness args like object.
|
|
436
|
+
* @returns The decoded MultisigCkbWitness.
|
|
437
|
+
*/
|
|
438
|
+
decodeWitnessArgs(
|
|
439
|
+
witnessLike?: WitnessArgsLike | null,
|
|
440
|
+
): MultisigCkbWitness | undefined {
|
|
441
|
+
if (!witnessLike) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const witness = WitnessArgs.from(witnessLike);
|
|
445
|
+
|
|
446
|
+
if (witness.lock == null) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const decoded = MultisigCkbWitness.decode(witness.lock);
|
|
452
|
+
if (decoded.eqInfo(this.multisigInfo)) {
|
|
453
|
+
return decoded;
|
|
454
|
+
}
|
|
455
|
+
} catch (_) {
|
|
456
|
+
// Returns undefined for invalid data
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Prepare the witness args at a specific index.
|
|
462
|
+
*
|
|
463
|
+
* @param txLike - The transaction.
|
|
464
|
+
* @param index - The index of the witness args.
|
|
465
|
+
* @param transformer - The transformer function.
|
|
466
|
+
* @returns The prepared transaction.
|
|
467
|
+
*/
|
|
468
|
+
async prepareWitnessArgsAt(
|
|
469
|
+
txLike: TransactionLike,
|
|
470
|
+
index: number,
|
|
471
|
+
transformer?:
|
|
472
|
+
| ((
|
|
473
|
+
witness: MultisigCkbWitness,
|
|
474
|
+
witnessArgs: WitnessArgs,
|
|
475
|
+
) =>
|
|
476
|
+
| MultisigCkbWitnessLike
|
|
477
|
+
| undefined
|
|
478
|
+
| null
|
|
479
|
+
| void
|
|
480
|
+
| Promise<MultisigCkbWitnessLike | undefined | null | void>)
|
|
481
|
+
| null,
|
|
482
|
+
): Promise<Transaction> {
|
|
483
|
+
const tx = Transaction.from(txLike);
|
|
484
|
+
|
|
485
|
+
const witnessArgs = tx.getWitnessArgsAt(index) ?? WitnessArgs.from({});
|
|
486
|
+
const multisigWitness =
|
|
487
|
+
this.decodeWitnessArgs(witnessArgs) ?? this.multisigInfo.clone();
|
|
488
|
+
|
|
489
|
+
const transformed = MultisigCkbWitness.from(
|
|
490
|
+
(await transformer?.(multisigWitness, witnessArgs)) ?? multisigWitness,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
transformed.signatures = transformed.signatures.slice(
|
|
494
|
+
0,
|
|
495
|
+
this.multisigInfo.threshold,
|
|
496
|
+
);
|
|
497
|
+
transformed.signatures.push(
|
|
498
|
+
...Array.from(
|
|
499
|
+
new Array(this.multisigInfo.threshold - transformed.signatures.length),
|
|
500
|
+
() => SignerMultisigCkbReadonly.EmptySignature,
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
witnessArgs.lock = transformed.toHex();
|
|
505
|
+
tx.setWitnessArgsAt(index, witnessArgs);
|
|
506
|
+
|
|
507
|
+
return tx;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Prepare multisig witness, if the existence of multisig witness is detected, nothing happens
|
|
512
|
+
*
|
|
513
|
+
* @param txLike - The transaction to prepare.
|
|
514
|
+
* @param scriptLike - The script to prepare.
|
|
515
|
+
* @returns A promise that resolves to the prepared transaction
|
|
516
|
+
*/
|
|
517
|
+
async prepareTransactionOneScript(
|
|
518
|
+
txLike: TransactionLike,
|
|
519
|
+
script: ScriptLike,
|
|
520
|
+
cellDeps: CellDepInfoLike[],
|
|
521
|
+
) {
|
|
522
|
+
const tx = Transaction.from(txLike);
|
|
523
|
+
const position = await tx.findInputIndexByLock(script, this.client);
|
|
524
|
+
if (position === undefined) {
|
|
525
|
+
return tx;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
await tx.addCellDepInfos(this.client, cellDeps);
|
|
529
|
+
return this.prepareWitnessArgsAt(tx, position);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Prepare transaction for multisig witness and adding related cell deps
|
|
534
|
+
*
|
|
535
|
+
* @param txLike - The transaction to prepare.
|
|
536
|
+
* @returns A promise that resolves to the prepared transaction
|
|
537
|
+
*/
|
|
538
|
+
async prepareTransaction(txLike: TransactionLike): Promise<Transaction> {
|
|
539
|
+
return await reduceAsync(
|
|
540
|
+
await this.scriptInfos,
|
|
541
|
+
(tx, { script, cellDeps }) =>
|
|
542
|
+
this.prepareTransactionOneScript(tx, script, cellDeps),
|
|
543
|
+
Transaction.from(txLike),
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Get the number of valid signatures for matching multisig inputs in the transaction.
|
|
549
|
+
*
|
|
550
|
+
* @remarks
|
|
551
|
+
* Returns `undefined` when the transaction has no inputs locked by any multisig address
|
|
552
|
+
* supported by this signer. This method only counts signatures for matching multisig inputs
|
|
553
|
+
* and does not imply that the transaction should be signed by this multisig.
|
|
554
|
+
*
|
|
555
|
+
* @param txLike - The transaction.
|
|
556
|
+
* @returns The matched multisig signature count, or `undefined` when the transaction is unrelated to any multisig address supported by this signer.
|
|
557
|
+
*/
|
|
558
|
+
async getSignaturesCount(
|
|
559
|
+
txLike: TransactionLike,
|
|
560
|
+
): Promise<number | undefined> {
|
|
561
|
+
const tx = Transaction.from(txLike);
|
|
562
|
+
let minSignaturesCount = undefined;
|
|
563
|
+
|
|
564
|
+
for (const { script } of await this.scriptInfos) {
|
|
565
|
+
const info = await this.getSignInfo(tx, script);
|
|
566
|
+
if (info === undefined) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const multisigWitness = this.decodeWitnessArgsAt(tx, info.position);
|
|
571
|
+
if (!multisigWitness) {
|
|
572
|
+
minSignaturesCount = 0;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const { required, flexible } = multisigWitness.calcMatchedSignaturesCount(
|
|
577
|
+
info.message,
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
minSignaturesCount = Math.min(
|
|
581
|
+
minSignaturesCount ?? 256,
|
|
582
|
+
required + Math.min(flexible, this.multisigInfo.flexibleThreshold),
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return minSignaturesCount;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Check if related multisig inputs in the transaction need more signatures.
|
|
591
|
+
*
|
|
592
|
+
* @remarks
|
|
593
|
+
* Returns `false` when the transaction has no inputs locked by any multisig address
|
|
594
|
+
* supported by this signer.
|
|
595
|
+
* A `false` result therefore means either the related multisig inputs are already fulfilled,
|
|
596
|
+
* or the transaction is unrelated to all multisig addresses supported by this signer.
|
|
597
|
+
*
|
|
598
|
+
* @param txLike - The transaction to check.
|
|
599
|
+
* @returns A promise that resolves to `true` when related multisig inputs still need signatures, and `false` otherwise.
|
|
600
|
+
*/
|
|
601
|
+
async needMoreSignatures(txLike: TransactionLike): Promise<boolean> {
|
|
602
|
+
const count = await this.getSignaturesCount(txLike);
|
|
603
|
+
if (count == null) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
return count < (await this.getMemberThreshold());
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Get the sign info for a script.
|
|
611
|
+
*
|
|
612
|
+
* @param txLike - The transaction.
|
|
613
|
+
* @param script - The script.
|
|
614
|
+
* @returns The sign info.
|
|
615
|
+
*/
|
|
616
|
+
async getSignInfo(
|
|
617
|
+
txLike: TransactionLike,
|
|
618
|
+
script: ScriptLike,
|
|
619
|
+
): Promise<{ message: Hex; position: number } | undefined> {
|
|
620
|
+
const tx = Transaction.from(txLike);
|
|
621
|
+
|
|
622
|
+
const position = await tx.findInputIndexByLock(script, this.client);
|
|
623
|
+
if (position == null) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// === Replace the witness with a dummy one ===
|
|
628
|
+
const witness = tx.getWitnessArgsAt(position) ?? WitnessArgs.from({});
|
|
629
|
+
witness.lock = MultisigCkbWitness.from({
|
|
630
|
+
...this.multisigInfo,
|
|
631
|
+
signatures: Array.from(
|
|
632
|
+
new Array(this.multisigInfo.threshold),
|
|
633
|
+
() => SignerMultisigCkbReadonly.EmptySignature,
|
|
634
|
+
),
|
|
635
|
+
}).toHex();
|
|
636
|
+
|
|
637
|
+
const clonedTx = tx.clone();
|
|
638
|
+
clonedTx.setWitnessArgsAt(position, witness);
|
|
639
|
+
// === Replace the witness with a dummy one ===
|
|
640
|
+
|
|
641
|
+
return clonedTx.getSignHashInfo(script, this.client);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Aggregate transactions.
|
|
646
|
+
*
|
|
647
|
+
* @param txs - The transactions to aggregate.
|
|
648
|
+
* @returns The aggregated transaction.
|
|
649
|
+
*/
|
|
650
|
+
async aggregateTransactions(txs: TransactionLike[]): Promise<Transaction> {
|
|
651
|
+
if (txs.length === 0) {
|
|
652
|
+
throw Error("No transaction to aggregate");
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
let res = Transaction.from(txs[0]);
|
|
656
|
+
|
|
657
|
+
for (const { script } of await this.scriptInfos) {
|
|
658
|
+
const info = await this.getSignInfo(res, script);
|
|
659
|
+
if (info === undefined) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const signatures = new Map<Hex, Hex>();
|
|
664
|
+
let requiredCount = 0;
|
|
665
|
+
for (const txLike of txs) {
|
|
666
|
+
const tx = Transaction.from(txLike);
|
|
667
|
+
const multisigWitness = this.decodeWitnessArgsAt(tx, info.position);
|
|
668
|
+
|
|
669
|
+
if (!multisigWitness) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
for (const {
|
|
674
|
+
pubkeyHash,
|
|
675
|
+
signature,
|
|
676
|
+
isRequired,
|
|
677
|
+
} of multisigWitness.generatePublicKeyHashesFromSignatures(
|
|
678
|
+
info.message,
|
|
679
|
+
)) {
|
|
680
|
+
if (signatures.has(pubkeyHash)) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (isRequired) {
|
|
685
|
+
// A required public key
|
|
686
|
+
requiredCount += 1;
|
|
687
|
+
} else if (
|
|
688
|
+
signatures.size - requiredCount >=
|
|
689
|
+
this.multisigInfo.flexibleThreshold
|
|
690
|
+
) {
|
|
691
|
+
// Not a required public key, and we have too many optional public key
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
signatures.set(pubkeyHash, signature);
|
|
696
|
+
if (signatures.size >= this.multisigInfo.threshold) {
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (signatures.size >= this.multisigInfo.threshold) {
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
res = await this.prepareWitnessArgsAt(res, info.position, (witness) => {
|
|
707
|
+
witness.signatures = Array.from(signatures.values());
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return res;
|
|
712
|
+
}
|
|
713
|
+
}
|