@btc-vision/transaction 1.1.1 → 1.1.3
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/browser/_version.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/transaction/browser/extensions/UnisatSigner.d.ts +4 -4
- package/browser/transaction/browser/types/Unisat.d.ts +1 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -0
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +2 -0
- package/browser/transaction/builders/TransactionBuilder.d.ts +2 -1
- package/browser/transaction/shared/TweakedTransaction.d.ts +14 -3
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/buffer/BinaryWriter.js +0 -1
- package/build/transaction/browser/extensions/UnisatSigner.d.ts +4 -4
- package/build/transaction/browser/extensions/UnisatSigner.js +103 -20
- package/build/transaction/browser/types/Unisat.d.ts +1 -1
- package/build/transaction/builders/DeploymentTransaction.d.ts +1 -0
- package/build/transaction/builders/DeploymentTransaction.js +17 -0
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +2 -0
- package/build/transaction/builders/SharedInteractionTransaction.js +31 -10
- package/build/transaction/builders/TransactionBuilder.d.ts +2 -1
- package/build/transaction/shared/TweakedTransaction.d.ts +14 -3
- package/build/transaction/shared/TweakedTransaction.js +146 -23
- package/package.json +4 -1
- package/src/_version.ts +1 -1
- package/src/buffer/BinaryWriter.ts +0 -2
- package/src/transaction/browser/extensions/UnisatSigner.ts +139 -28
- package/src/transaction/browser/types/Unisat.ts +1 -1
- package/src/transaction/builders/DeploymentTransaction.ts +25 -0
- package/src/transaction/builders/SharedInteractionTransaction.ts +51 -21
- package/src/transaction/builders/TransactionBuilder.ts +2 -1
- package/src/transaction/shared/TweakedTransaction.ts +210 -113
|
@@ -4,6 +4,7 @@ import { TweakedSigner } from '../../signer/TweakedSigner.js';
|
|
|
4
4
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
5
5
|
import { AddressTypes, AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
6
6
|
import { varuint } from '@btc-vision/bitcoin/src/bufferutils.js';
|
|
7
|
+
import * as bscript from '@btc-vision/bitcoin/src/script.js';
|
|
7
8
|
export var TransactionSequence;
|
|
8
9
|
(function (TransactionSequence) {
|
|
9
10
|
TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
|
|
@@ -27,10 +28,7 @@ export class TweakedTransaction extends Logger {
|
|
|
27
28
|
const inputDecoded = this.inputs[inputIndex];
|
|
28
29
|
if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
|
|
29
30
|
const signatures = input.partialSig.map((sig) => sig.signature);
|
|
30
|
-
const scriptSig = script.compile([
|
|
31
|
-
...signatures,
|
|
32
|
-
inputDecoded.redeemScript,
|
|
33
|
-
]);
|
|
31
|
+
const scriptSig = script.compile([...signatures, inputDecoded.redeemScript]);
|
|
34
32
|
return {
|
|
35
33
|
finalScriptSig: scriptSig,
|
|
36
34
|
finalScriptWitness: undefined,
|
|
@@ -156,22 +154,39 @@ export class TweakedTransaction extends Logger {
|
|
|
156
154
|
getSignerKey() {
|
|
157
155
|
return this.signer;
|
|
158
156
|
}
|
|
159
|
-
async signInput(transaction,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
transaction.signInput(i, signer);
|
|
157
|
+
async signInput(transaction, input, i, signer, reverse = false) {
|
|
158
|
+
const publicKey = signer.publicKey;
|
|
159
|
+
let isTaproot = this.isTaprootInput(input);
|
|
160
|
+
if (reverse) {
|
|
161
|
+
isTaproot = !isTaproot;
|
|
165
162
|
}
|
|
166
|
-
|
|
163
|
+
let signed = false;
|
|
164
|
+
if (isTaproot) {
|
|
167
165
|
try {
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
await this.attemptSignTaproot(transaction, input, i, signer, publicKey);
|
|
167
|
+
signed = true;
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
this.error(`Failed to sign Taproot script path input ${i}: ${e}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (!reverse ? this.canSignNonTaprootInput(input, publicKey) : true) {
|
|
175
|
+
try {
|
|
176
|
+
await this.signNonTaprootInput(signer, transaction, i);
|
|
177
|
+
signed = true;
|
|
170
178
|
}
|
|
171
|
-
|
|
179
|
+
catch (e) {
|
|
180
|
+
this.error(`Failed to sign non-Taproot input ${i}: ${e}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!signed) {
|
|
185
|
+
try {
|
|
186
|
+
await this.signInput(transaction, input, i, signer, true);
|
|
172
187
|
}
|
|
173
188
|
catch {
|
|
174
|
-
throw new Error(
|
|
189
|
+
throw new Error(`Cannot sign input ${i} with the provided signer.`);
|
|
175
190
|
}
|
|
176
191
|
}
|
|
177
192
|
}
|
|
@@ -186,6 +201,10 @@ export class TweakedTransaction extends Logger {
|
|
|
186
201
|
return result;
|
|
187
202
|
}
|
|
188
203
|
async signInputs(transaction) {
|
|
204
|
+
if ('multiSignPsbt' in this.signer) {
|
|
205
|
+
await this.signInputsWalletBased(transaction);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
189
208
|
const txs = transaction.data.inputs;
|
|
190
209
|
const batchSize = 20;
|
|
191
210
|
const batches = this.splitArray(txs, batchSize);
|
|
@@ -205,14 +224,10 @@ export class TweakedTransaction extends Logger {
|
|
|
205
224
|
}
|
|
206
225
|
await Promise.all(promises);
|
|
207
226
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
transaction.finalizeAllInputs();
|
|
211
|
-
this.finalized = true;
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
214
|
-
this.finalized = false;
|
|
227
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
228
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
215
229
|
}
|
|
230
|
+
this.finalized = true;
|
|
216
231
|
}
|
|
217
232
|
internalPubKeyToXOnly() {
|
|
218
233
|
return toXOnly(Buffer.from(this.signer.publicKey));
|
|
@@ -332,7 +347,6 @@ export class TweakedTransaction extends Logger {
|
|
|
332
347
|
}
|
|
333
348
|
if (i === 0 && this.nonWitnessUtxo) {
|
|
334
349
|
input.nonWitnessUtxo = this.nonWitnessUtxo;
|
|
335
|
-
this.log(`Using non-witness utxo for input ${i}`);
|
|
336
350
|
}
|
|
337
351
|
if (utxo.scriptPubKey.address &&
|
|
338
352
|
AddressVerificator.isValidP2TRAddress(utxo.scriptPubKey.address, this.network)) {
|
|
@@ -341,4 +355,113 @@ export class TweakedTransaction extends Logger {
|
|
|
341
355
|
}
|
|
342
356
|
return input;
|
|
343
357
|
}
|
|
358
|
+
async signInputsWalletBased(transaction) {
|
|
359
|
+
const signer = this.signer;
|
|
360
|
+
await signer.multiSignPsbt([transaction]);
|
|
361
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
362
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH);
|
|
363
|
+
}
|
|
364
|
+
this.finalized = true;
|
|
365
|
+
}
|
|
366
|
+
async attemptSignTaproot(transaction, input, i, signer, publicKey) {
|
|
367
|
+
const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
|
|
368
|
+
if (isScriptSpend) {
|
|
369
|
+
await this.signTaprootInput(signer, transaction, i);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
let tweakedSigner;
|
|
373
|
+
if (signer !== this.signer) {
|
|
374
|
+
tweakedSigner = this.getTweakedSigner(true, signer);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
if (!this.tweakedSigner)
|
|
378
|
+
this.tweakSigner();
|
|
379
|
+
tweakedSigner = this.tweakedSigner;
|
|
380
|
+
}
|
|
381
|
+
if (tweakedSigner) {
|
|
382
|
+
await this.signTaprootInput(tweakedSigner, transaction, i);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
this.error(`Failed to obtain tweaked signer for input ${i}.`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
isTaprootScriptSpend(input, publicKey) {
|
|
390
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0) {
|
|
391
|
+
for (const tapLeafScript of input.tapLeafScript) {
|
|
392
|
+
if (this.pubkeyInScript(publicKey, tapLeafScript.script)) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
isTaprootInput(input) {
|
|
400
|
+
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
if (input.witnessUtxo) {
|
|
404
|
+
const script = input.witnessUtxo.script;
|
|
405
|
+
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
canSignNonTaprootInput(input, publicKey) {
|
|
410
|
+
const script = this.getInputRelevantScript(input);
|
|
411
|
+
if (script) {
|
|
412
|
+
return this.pubkeyInScript(publicKey, script);
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
getInputRelevantScript(input) {
|
|
417
|
+
if (input.redeemScript) {
|
|
418
|
+
return input.redeemScript;
|
|
419
|
+
}
|
|
420
|
+
if (input.witnessScript) {
|
|
421
|
+
return input.witnessScript;
|
|
422
|
+
}
|
|
423
|
+
if (input.witnessUtxo) {
|
|
424
|
+
return input.witnessUtxo.script;
|
|
425
|
+
}
|
|
426
|
+
if (input.nonWitnessUtxo) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
pubkeyInScript(pubkey, script) {
|
|
432
|
+
return this.pubkeyPositionInScript(pubkey, script) !== -1;
|
|
433
|
+
}
|
|
434
|
+
pubkeyPositionInScript(pubkey, script) {
|
|
435
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
436
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
437
|
+
const decompiled = bscript.decompile(script);
|
|
438
|
+
if (decompiled === null)
|
|
439
|
+
throw new Error('Unknown script error');
|
|
440
|
+
return decompiled.findIndex((element) => {
|
|
441
|
+
if (typeof element === 'number')
|
|
442
|
+
return false;
|
|
443
|
+
return (element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly));
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
async signTaprootInput(signer, transaction, i, tapLeafHash) {
|
|
447
|
+
if ('signTaprootInput' in signer) {
|
|
448
|
+
try {
|
|
449
|
+
await signer.signTaprootInput(transaction, i, tapLeafHash);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
throw new Error('Failed to sign Taproot input with provided signer.');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
transaction.signTaprootInput(i, signer);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async signNonTaprootInput(signer, transaction, i) {
|
|
460
|
+
if ('signInput' in signer) {
|
|
461
|
+
await signer.signInput(transaction, i);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
transaction.signInput(i, signer);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
344
467
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btc-vision/transaction",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.3",
|
|
5
5
|
"author": "BlobMaster41",
|
|
6
6
|
"description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
|
|
7
7
|
"engines": {
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
"browserBuild": "webpack --mode production",
|
|
64
64
|
"docs": "typedoc --out docs --exclude 'src/tests/*.ts' --tsconfig tsconfig.json --readme README.md --name OPNet --plugin typedoc-material-theme --themeColor '#cb9820' --exclude src/tests/test.ts --exclude src/index.ts src"
|
|
65
65
|
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@btc-vision/bitcoin": "^6.3.0"
|
|
68
|
+
},
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"@babel/core": "^7.26.0",
|
|
68
71
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.1.
|
|
1
|
+
export const version = '1.1.3';
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
crypto as bitCrypto,
|
|
3
|
+
Network,
|
|
4
|
+
networks,
|
|
5
|
+
opcodes,
|
|
6
|
+
Psbt,
|
|
7
|
+
PsbtInput,
|
|
8
|
+
script as bitScript,
|
|
9
|
+
TapScriptSig,
|
|
10
|
+
} from '@btc-vision/bitcoin';
|
|
2
11
|
import { ECPairInterface } from 'ecpair';
|
|
3
12
|
import { EcKeyPair } from '../../../keypair/EcKeyPair.js';
|
|
4
13
|
import { CustomKeypair } from '../BrowserSignerBase.js';
|
|
5
14
|
import { PsbtSignatureOptions, Unisat, UnisatNetwork } from '../types/Unisat.js';
|
|
6
15
|
import { PartialSig } from 'bip174/src/lib/interfaces.js';
|
|
16
|
+
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
7
17
|
|
|
8
18
|
declare global {
|
|
9
19
|
interface Window {
|
|
@@ -122,15 +132,15 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
122
132
|
return this.publicKey;
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
public sign(
|
|
135
|
+
public sign(_hash: Buffer, _lowR?: boolean): Buffer {
|
|
126
136
|
throw new Error('Not implemented: sign');
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
public signSchnorr(
|
|
139
|
+
public signSchnorr(_hash: Buffer): Buffer {
|
|
130
140
|
throw new Error('Not implemented: signSchnorr');
|
|
131
141
|
}
|
|
132
142
|
|
|
133
|
-
public verify(
|
|
143
|
+
public verify(_hash: Buffer, _signature: Buffer): boolean {
|
|
134
144
|
throw new Error('Not implemented: verify');
|
|
135
145
|
}
|
|
136
146
|
|
|
@@ -176,6 +186,80 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
176
186
|
this.combine(transaction, firstSignature, i);
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
public async multiSignPsbt(transactions: Psbt[]): Promise<void> {
|
|
190
|
+
const toSignPsbts: string[] = [];
|
|
191
|
+
const options: PsbtSignatureOptions[] = [];
|
|
192
|
+
|
|
193
|
+
for (const psbt of transactions) {
|
|
194
|
+
const hex = psbt.toHex();
|
|
195
|
+
toSignPsbts.push(hex);
|
|
196
|
+
|
|
197
|
+
const toSignInputs = psbt.data.inputs
|
|
198
|
+
.map((input, i) => {
|
|
199
|
+
let needsToSign = false;
|
|
200
|
+
let viaTaproot = false;
|
|
201
|
+
|
|
202
|
+
if (isTaprootInput(input)) {
|
|
203
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0) {
|
|
204
|
+
for (const tapLeafScript of input.tapLeafScript) {
|
|
205
|
+
if (pubkeyInScript(this.publicKey, tapLeafScript.script)) {
|
|
206
|
+
needsToSign = true;
|
|
207
|
+
viaTaproot = false; // for opnet, we use original keys.
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!needsToSign && input.tapInternalKey) {
|
|
214
|
+
const tapInternalKey = input.tapInternalKey;
|
|
215
|
+
const xOnlyPubKey = toXOnly(this.publicKey);
|
|
216
|
+
|
|
217
|
+
if (tapInternalKey.equals(xOnlyPubKey)) {
|
|
218
|
+
needsToSign = true;
|
|
219
|
+
viaTaproot = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// Non-Taproot input
|
|
224
|
+
const script = getInputRelevantScript(input);
|
|
225
|
+
|
|
226
|
+
if (script && pubkeyInScript(this.publicKey, script)) {
|
|
227
|
+
needsToSign = true;
|
|
228
|
+
viaTaproot = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (needsToSign) {
|
|
233
|
+
return {
|
|
234
|
+
index: i,
|
|
235
|
+
publicKey: this.publicKey.toString('hex'),
|
|
236
|
+
disableTweakSigner: !viaTaproot,
|
|
237
|
+
};
|
|
238
|
+
} else {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
.filter((v) => v !== null);
|
|
243
|
+
|
|
244
|
+
options.push({
|
|
245
|
+
autoFinalized: false,
|
|
246
|
+
toSignInputs: toSignInputs,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const signed = await this.unisat.signPsbt(toSignPsbts[0], options[0]);
|
|
251
|
+
const signedPsbts = Psbt.fromHex(signed); //signed.map((hex) => Psbt.fromHex(hex));
|
|
252
|
+
|
|
253
|
+
/*for (let i = 0; i < signedPsbts.length; i++) {
|
|
254
|
+
const psbtOriginal = transactions[i];
|
|
255
|
+
const psbtSigned = signedPsbts[i];
|
|
256
|
+
|
|
257
|
+
psbtOriginal.combine(psbtSigned);
|
|
258
|
+
}*/
|
|
259
|
+
|
|
260
|
+
transactions[0].combine(signedPsbts);
|
|
261
|
+
}
|
|
262
|
+
|
|
179
263
|
private hasAlreadySignedTapScriptSig(input: TapScriptSig[]): boolean {
|
|
180
264
|
for (let i = 0; i < input.length; i++) {
|
|
181
265
|
const item = input[i];
|
|
@@ -257,30 +341,6 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
257
341
|
return Psbt.fromHex(signed);
|
|
258
342
|
}
|
|
259
343
|
|
|
260
|
-
private async signTweaked(
|
|
261
|
-
transaction: Psbt,
|
|
262
|
-
i: number,
|
|
263
|
-
sighashTypes: number[],
|
|
264
|
-
disableTweakSigner: boolean = false,
|
|
265
|
-
): Promise<Psbt> {
|
|
266
|
-
const opts: PsbtSignatureOptions = {
|
|
267
|
-
autoFinalized: false,
|
|
268
|
-
toSignInputs: [
|
|
269
|
-
{
|
|
270
|
-
index: i,
|
|
271
|
-
publicKey: this.publicKey.toString('hex'),
|
|
272
|
-
sighashTypes,
|
|
273
|
-
disableTweakSigner: disableTweakSigner,
|
|
274
|
-
},
|
|
275
|
-
],
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const psbt = transaction.toHex();
|
|
279
|
-
const signed = await this.unisat.signPsbt(psbt, opts);
|
|
280
|
-
|
|
281
|
-
return Psbt.fromHex(signed);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
344
|
private getNonDuplicateScriptSig(
|
|
285
345
|
scriptSig1: TapScriptSig[],
|
|
286
346
|
scriptSig2: TapScriptSig[],
|
|
@@ -296,3 +356,54 @@ export class UnisatSigner extends CustomKeypair {
|
|
|
296
356
|
return nonDuplicate;
|
|
297
357
|
}
|
|
298
358
|
}
|
|
359
|
+
|
|
360
|
+
// Helper functions
|
|
361
|
+
function isTaprootInput(input: PsbtInput): boolean {
|
|
362
|
+
if (input.tapInternalKey || input.tapKeySig || input.tapScriptSig || input.tapLeafScript) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (input.witnessUtxo) {
|
|
367
|
+
const script = input.witnessUtxo.script;
|
|
368
|
+
return script.length === 34 && script[0] === opcodes.OP_1 && script[1] === 0x20;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function getInputRelevantScript(input: PsbtInput): Buffer | null {
|
|
375
|
+
if (input.redeemScript) {
|
|
376
|
+
return input.redeemScript;
|
|
377
|
+
}
|
|
378
|
+
if (input.witnessScript) {
|
|
379
|
+
return input.witnessScript;
|
|
380
|
+
}
|
|
381
|
+
if (input.witnessUtxo) {
|
|
382
|
+
return input.witnessUtxo.script;
|
|
383
|
+
}
|
|
384
|
+
if (input.nonWitnessUtxo) {
|
|
385
|
+
// Additional logic can be added here if needed
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
|
|
392
|
+
return pubkeyPositionInScript(pubkey, script) !== -1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number {
|
|
396
|
+
const pubkeyHash = bitCrypto.hash160(pubkey);
|
|
397
|
+
const pubkeyXOnly = toXOnly(pubkey);
|
|
398
|
+
|
|
399
|
+
const decompiled = bitScript.decompile(script);
|
|
400
|
+
if (decompiled === null) throw new Error('Unknown script error');
|
|
401
|
+
|
|
402
|
+
return decompiled.findIndex((element) => {
|
|
403
|
+
if (typeof element === 'number') return false;
|
|
404
|
+
return (
|
|
405
|
+
Buffer.isBuffer(element) &&
|
|
406
|
+
(element.equals(pubkey) || element.equals(pubkeyHash) || element.equals(pubkeyXOnly))
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
@@ -83,7 +83,7 @@ export interface Unisat {
|
|
|
83
83
|
|
|
84
84
|
signPsbt(psbtHex: string, psbtOptions: PsbtSignatureOptions): Promise<string>;
|
|
85
85
|
|
|
86
|
-
signPsbts(psbtHex: string[], psbtOptions: PsbtSignatureOptions): Promise<string[]>;
|
|
86
|
+
signPsbts(psbtHex: string[], psbtOptions: PsbtSignatureOptions[]): Promise<string[]>;
|
|
87
87
|
|
|
88
88
|
pushPsbt(psbtHex: string): Promise<string>;
|
|
89
89
|
|
|
@@ -12,6 +12,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
|
|
|
12
12
|
import { SharedInteractionTransaction } from './SharedInteractionTransaction.js';
|
|
13
13
|
import { ECPairInterface } from 'ecpair';
|
|
14
14
|
import { Address } from '../../keypair/Address.js';
|
|
15
|
+
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
15
16
|
|
|
16
17
|
export class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
|
|
17
18
|
public static readonly MAXIMUM_CONTRACT_SIZE = 128 * 1024;
|
|
@@ -203,6 +204,25 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
203
204
|
await this.addRefundOutput(amountSpent);
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
protected override async signInputsWalletBased(transaction: Psbt): Promise<void> {
|
|
208
|
+
const signer: UnisatSigner = this.signer as UnisatSigner;
|
|
209
|
+
|
|
210
|
+
// first, we sign the first input with the script signer.
|
|
211
|
+
await this.signInput(transaction, transaction.data.inputs[0], 0, this.contractSigner);
|
|
212
|
+
|
|
213
|
+
// then, we sign all the remaining inputs with the wallet signer.
|
|
214
|
+
await signer.multiSignPsbt([transaction]);
|
|
215
|
+
|
|
216
|
+
// Then, we finalize every input.
|
|
217
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
218
|
+
if (i === 0) {
|
|
219
|
+
transaction.finalizeInput(i, this.customFinalizer);
|
|
220
|
+
} else {
|
|
221
|
+
transaction.finalizeInput(i);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
/**
|
|
207
227
|
* Sign the inputs
|
|
208
228
|
* @param {Psbt} transaction The transaction to sign
|
|
@@ -215,6 +235,11 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
215
235
|
return;
|
|
216
236
|
}
|
|
217
237
|
|
|
238
|
+
if ('multiSignPsbt' in this.signer) {
|
|
239
|
+
await this.signInputsWalletBased(transaction);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
218
243
|
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
219
244
|
if (i === 0) {
|
|
220
245
|
// multi sig input
|
|
@@ -9,6 +9,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
|
|
|
9
9
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
10
10
|
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
11
11
|
import { toXOnly } from '@btc-vision/bitcoin/src/psbt/bip371.js';
|
|
12
|
+
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Shared interaction transaction
|
|
@@ -186,27 +187,10 @@ export abstract class SharedInteractionTransaction<
|
|
|
186
187
|
return;
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
transaction,
|
|
194
|
-
transaction.data.inputs[i],
|
|
195
|
-
i,
|
|
196
|
-
this.getSignerKey(),
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
transaction.finalizeInput(i, this.customFinalizer);
|
|
200
|
-
} else {
|
|
201
|
-
await this.signInput(
|
|
202
|
-
transaction,
|
|
203
|
-
transaction.data.inputs[i],
|
|
204
|
-
i,
|
|
205
|
-
this.getSignerKey(),
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
transaction.finalizeInput(i);
|
|
209
|
-
}
|
|
190
|
+
if ('multiSignPsbt' in this.signer) {
|
|
191
|
+
await this.signInputsWalletBased(transaction);
|
|
192
|
+
} else {
|
|
193
|
+
await this.signInputsNonWalletBased(transaction);
|
|
210
194
|
}
|
|
211
195
|
}
|
|
212
196
|
|
|
@@ -308,6 +292,52 @@ export abstract class SharedInteractionTransaction<
|
|
|
308
292
|
};
|
|
309
293
|
};
|
|
310
294
|
|
|
295
|
+
// custom for interactions
|
|
296
|
+
protected override async signInputsWalletBased(transaction: Psbt): Promise<void> {
|
|
297
|
+
const signer: UnisatSigner = this.signer as UnisatSigner;
|
|
298
|
+
|
|
299
|
+
// first, we sign the first input with the script signer.
|
|
300
|
+
await this.signInput(transaction, transaction.data.inputs[0], 0, this.scriptSigner);
|
|
301
|
+
|
|
302
|
+
// then, we sign all the remaining inputs with the wallet signer.
|
|
303
|
+
await signer.multiSignPsbt([transaction]);
|
|
304
|
+
|
|
305
|
+
// Then, we finalize every input.
|
|
306
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
307
|
+
if (i === 0) {
|
|
308
|
+
transaction.finalizeInput(i, this.customFinalizer);
|
|
309
|
+
} else {
|
|
310
|
+
transaction.finalizeInput(i);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private async signInputsNonWalletBased(transaction: Psbt): Promise<void> {
|
|
316
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
317
|
+
if (i === 0) {
|
|
318
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.scriptSigner);
|
|
319
|
+
|
|
320
|
+
await this.signInput(
|
|
321
|
+
transaction,
|
|
322
|
+
transaction.data.inputs[i],
|
|
323
|
+
i,
|
|
324
|
+
this.getSignerKey(),
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
transaction.finalizeInput(i, this.customFinalizer);
|
|
328
|
+
} else {
|
|
329
|
+
await this.signInput(
|
|
330
|
+
transaction,
|
|
331
|
+
transaction.data.inputs[i],
|
|
332
|
+
i,
|
|
333
|
+
this.getSignerKey(),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
transaction.finalizeInput(i);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
311
341
|
/**
|
|
312
342
|
* Get the public keys
|
|
313
343
|
* @private
|
|
@@ -22,6 +22,7 @@ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
|
|
|
22
22
|
import { ECPairInterface } from 'ecpair';
|
|
23
23
|
import { AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
24
24
|
import { TweakedTransaction } from '../shared/TweakedTransaction.js';
|
|
25
|
+
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
25
26
|
|
|
26
27
|
initEccLib(ecc);
|
|
27
28
|
|
|
@@ -91,7 +92,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
91
92
|
/**
|
|
92
93
|
* @description The signer of the transaction
|
|
93
94
|
*/
|
|
94
|
-
protected readonly signer: Signer | ECPairInterface;
|
|
95
|
+
protected readonly signer: Signer | ECPairInterface | UnisatSigner;
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* @description The network where the transaction will be broadcasted
|