@leather.io/bitcoin 0.8.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/.eslintrc.yml +4 -0
- package/.turbo/turbo-build.log +17 -0
- package/CHANGELOG.md +283 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +516 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
- package/src/bip322/bip322-utils.spec.ts +18 -0
- package/src/bip322/bip322-utils.ts +85 -0
- package/src/bip322/sign-message-bip322-bitcoinjs.ts +93 -0
- package/src/bip322/sign-message-bip322.spec.ts +168 -0
- package/src/bip322/sign-message-bip322.ts +54 -0
- package/src/bitcoin-signer.ts +19 -0
- package/src/bitcoin.network.ts +57 -0
- package/src/bitcoin.utils.ts +277 -0
- package/src/index.ts +8 -0
- package/src/p2tr-address-gen.spec.ts +37 -0
- package/src/p2tr-address-gen.ts +57 -0
- package/src/p2wpkh-address-gen.spec.ts +45 -0
- package/src/p2wpkh-address-gen.ts +68 -0
- package/src/p2wsh-p2sh-address-gen.spec.ts +180 -0
- package/src/p2wsh-p2sh-address-gen.ts +64 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.js +5 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
2
|
+
|
|
3
|
+
import { hashBip322Message } from './bip322-utils';
|
|
4
|
+
|
|
5
|
+
describe('Message hashing', () => {
|
|
6
|
+
test('empty string', () =>
|
|
7
|
+
expect(hashBip322Message(utf8ToBytes(''))).toEqual(
|
|
8
|
+
hexToBytes('c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1')
|
|
9
|
+
));
|
|
10
|
+
|
|
11
|
+
const helloWorld = 'Hello World';
|
|
12
|
+
|
|
13
|
+
test(helloWorld, () =>
|
|
14
|
+
expect(hashBip322Message(utf8ToBytes(helloWorld))).toEqual(
|
|
15
|
+
hexToBytes('f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a')
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import ecc from '@bitcoinerlab/secp256k1';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
+
import { hexToBytes, utf8ToBytes } from '@stacks/common';
|
|
4
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
5
|
+
import { ECPairFactory } from 'ecpair';
|
|
6
|
+
import { encode } from 'varuint-bitcoin';
|
|
7
|
+
|
|
8
|
+
import { PaymentTypes } from '@leather.io/rpc';
|
|
9
|
+
import { isString } from '@leather.io/utils';
|
|
10
|
+
|
|
11
|
+
import { toXOnly } from '../bitcoin.utils';
|
|
12
|
+
|
|
13
|
+
const bip322MessageTag = 'BIP0322-signed-message';
|
|
14
|
+
|
|
15
|
+
const ECPair = ECPairFactory(ecc);
|
|
16
|
+
bitcoin.initEccLib(ecc);
|
|
17
|
+
|
|
18
|
+
export function ecPairFromPrivateKey(key: Uint8Array) {
|
|
19
|
+
return ECPair.fromPrivateKey(Buffer.from(key));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// See tagged hashes section of BIP-340
|
|
23
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#design
|
|
24
|
+
const messageTagHash = Uint8Array.from([
|
|
25
|
+
...sha256(utf8ToBytes(bip322MessageTag)),
|
|
26
|
+
...sha256(utf8ToBytes(bip322MessageTag)),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export function hashBip322Message(message: Uint8Array | string) {
|
|
30
|
+
return sha256(
|
|
31
|
+
Uint8Array.from([...messageTagHash, ...(isString(message) ? utf8ToBytes(message) : message)])
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const bip322TransactionToSignValues = {
|
|
36
|
+
prevoutHash: hexToBytes('0000000000000000000000000000000000000000000000000000000000000000'),
|
|
37
|
+
prevoutIndex: 0xffffffff,
|
|
38
|
+
sequence: 0,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function encodeVarString(b: Buffer) {
|
|
42
|
+
return Buffer.concat([encode(b.byteLength), b]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const supportedMessageSigningPaymentTypes: PaymentTypes[] = ['p2wpkh', 'p2tr'];
|
|
46
|
+
|
|
47
|
+
export function isSupportedMessageSigningPaymentType(paymentType: string) {
|
|
48
|
+
return supportedMessageSigningPaymentTypes.includes(paymentType as PaymentTypes);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encode witness data for a BIP322 message
|
|
53
|
+
* TODO: Refactor to remove `Buffer` use
|
|
54
|
+
*/
|
|
55
|
+
export function encodeMessageWitnessData(witnessArray: Buffer[]) {
|
|
56
|
+
const len = encode(witnessArray.length);
|
|
57
|
+
return Buffer.concat([len, ...witnessArray.map(witness => encodeVarString(witness))]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
|
|
61
|
+
return bitcoin.crypto.taggedHash('TapTweak', Buffer.concat(h ? [pubKey, h] : [pubKey]));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer {
|
|
65
|
+
// @ts-expect-error privateKey exists on signer
|
|
66
|
+
let privateKey: Uint8Array | undefined = signer.privateKey!;
|
|
67
|
+
if (!privateKey) {
|
|
68
|
+
throw new Error('Private key is required for tweaking signer!');
|
|
69
|
+
}
|
|
70
|
+
if (signer.publicKey[0] === 3) {
|
|
71
|
+
privateKey = ecc.privateNegate(privateKey);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const tweakedPrivateKey = ecc.privateAdd(
|
|
75
|
+
privateKey,
|
|
76
|
+
tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)
|
|
77
|
+
);
|
|
78
|
+
if (!tweakedPrivateKey) {
|
|
79
|
+
throw new Error('Invalid tweaked private key!');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
|
|
83
|
+
network: opts.network,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { base64 } from '@scure/base';
|
|
2
|
+
import * as btc from '@scure/btc-signer';
|
|
3
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
4
|
+
|
|
5
|
+
import { BitcoinNetworkModes } from '@leather.io/models';
|
|
6
|
+
|
|
7
|
+
import { getBitcoinJsLibNetworkConfigByMode } from '../bitcoin.network';
|
|
8
|
+
import {
|
|
9
|
+
bip322TransactionToSignValues,
|
|
10
|
+
ecPairFromPrivateKey,
|
|
11
|
+
encodeMessageWitnessData,
|
|
12
|
+
hashBip322Message,
|
|
13
|
+
tweakSigner,
|
|
14
|
+
} from './bip322-utils';
|
|
15
|
+
|
|
16
|
+
export function createNativeSegwitBitcoinJsSigner(privateKey: Buffer) {
|
|
17
|
+
return ecPairFromPrivateKey(privateKey);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createTaprootBitcoinJsSigner(privateKey: Buffer) {
|
|
21
|
+
return tweakSigner(ecPairFromPrivateKey(privateKey));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createToSpendTx(address: string, message: string, network: BitcoinNetworkModes) {
|
|
25
|
+
const { prevoutHash, prevoutIndex, sequence } = bip322TransactionToSignValues;
|
|
26
|
+
|
|
27
|
+
const script = bitcoin.address.toOutputScript(
|
|
28
|
+
address,
|
|
29
|
+
getBitcoinJsLibNetworkConfigByMode(network)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const hash = hashBip322Message(message);
|
|
33
|
+
const commands = [0, Buffer.from(hash)];
|
|
34
|
+
const scriptSig = bitcoin.script.compile(commands);
|
|
35
|
+
|
|
36
|
+
const virtualToSpend = new bitcoin.Transaction();
|
|
37
|
+
virtualToSpend.version = 0;
|
|
38
|
+
virtualToSpend.addInput(Buffer.from(prevoutHash), prevoutIndex, sequence, scriptSig);
|
|
39
|
+
virtualToSpend.addOutput(script, 0);
|
|
40
|
+
return { virtualToSpend, script };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createToSignTx(toSpendTxHex: Buffer, script: Buffer, network: BitcoinNetworkModes) {
|
|
44
|
+
const virtualToSign = new bitcoin.Psbt({ network: getBitcoinJsLibNetworkConfigByMode(network) });
|
|
45
|
+
virtualToSign.setVersion(0);
|
|
46
|
+
const prevTxHash = toSpendTxHex;
|
|
47
|
+
const prevOutIndex = 0;
|
|
48
|
+
const toSignScriptSig = bitcoin.script.compile([bitcoin.script.OPS.OP_RETURN]);
|
|
49
|
+
|
|
50
|
+
virtualToSign.addInput({
|
|
51
|
+
hash: prevTxHash,
|
|
52
|
+
index: prevOutIndex,
|
|
53
|
+
sequence: 0,
|
|
54
|
+
witnessUtxo: { script, value: 0 },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
virtualToSign.addOutput({ script: toSignScriptSig, value: 0 });
|
|
58
|
+
return virtualToSign;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface SignBip322MessageSimple {
|
|
62
|
+
address: string;
|
|
63
|
+
message: string;
|
|
64
|
+
network: BitcoinNetworkModes;
|
|
65
|
+
signPsbt(psbt: bitcoin.Psbt): Promise<btc.Transaction>;
|
|
66
|
+
}
|
|
67
|
+
export async function signBip322MessageSimple(args: SignBip322MessageSimple) {
|
|
68
|
+
const { address, message, network, signPsbt } = args;
|
|
69
|
+
|
|
70
|
+
const { virtualToSpend, script } = createToSpendTx(address, message, network);
|
|
71
|
+
|
|
72
|
+
const virtualToSign = createToSignTx(virtualToSpend.getHash(), script, network);
|
|
73
|
+
|
|
74
|
+
const signedTx = await signPsbt(virtualToSign);
|
|
75
|
+
|
|
76
|
+
const asBitcoinJsTransaction = bitcoin.Psbt.fromBuffer(Buffer.from(signedTx.toPSBT()));
|
|
77
|
+
|
|
78
|
+
asBitcoinJsTransaction.finalizeInput(0);
|
|
79
|
+
|
|
80
|
+
// sign the tx
|
|
81
|
+
// section 5.1
|
|
82
|
+
// github.com/LegReq/bip0322-signatures/blob/master/BIP0322_signing.ipynb
|
|
83
|
+
const toSignTx = asBitcoinJsTransaction.extractTransaction();
|
|
84
|
+
|
|
85
|
+
const result = encodeMessageWitnessData(toSignTx.ins[0].witness);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
virtualToSpend,
|
|
89
|
+
virtualToSign: toSignTx,
|
|
90
|
+
unencodedSig: result,
|
|
91
|
+
signature: base64.encode(result),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as secp from '@noble/secp256k1';
|
|
2
|
+
import * as btc from '@scure/btc-signer';
|
|
3
|
+
import { bytesToHex } from '@stacks/common';
|
|
4
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
5
|
+
|
|
6
|
+
import { ecdsaPublicKeyToSchnorr } from '../bitcoin.utils';
|
|
7
|
+
import {
|
|
8
|
+
createNativeSegwitBitcoinJsSigner,
|
|
9
|
+
createTaprootBitcoinJsSigner,
|
|
10
|
+
createToSpendTx,
|
|
11
|
+
signBip322MessageSimple,
|
|
12
|
+
} from './sign-message-bip322-bitcoinjs';
|
|
13
|
+
|
|
14
|
+
describe(createToSpendTx.name, () => {
|
|
15
|
+
test('bitcoinjs example', () => {
|
|
16
|
+
const result = createToSpendTx(
|
|
17
|
+
'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l',
|
|
18
|
+
'generatedWithBitcoinJs',
|
|
19
|
+
'mainnet'
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(result.script.toString('hex')).toEqual('00142b05d564e6a7a33c087f16e0f730d1440123799d');
|
|
23
|
+
|
|
24
|
+
expect(result.virtualToSpend.toHex()).toEqual(
|
|
25
|
+
'00000000010000000000000000000000000000000000000000000000000000000000000000ffffffff220020093bbd44da65116318b960749b3d6172ab9775b5d1923a7c71e18845c6524852000000000100000000000000001600142b05d564e6a7a33c087f16e0f730d1440123799d00000000'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
//
|
|
31
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#test-vectors
|
|
32
|
+
describe(signBip322MessageSimple.name, () => {
|
|
33
|
+
const testVectorKey = btc.WIF().decode('L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k');
|
|
34
|
+
|
|
35
|
+
describe('Message signing, Native Segwit', () => {
|
|
36
|
+
const nativeSegwitAddress = btc.getAddress('wpkh', testVectorKey);
|
|
37
|
+
const payment = btc.p2wpkh(secp.getPublicKey(testVectorKey, true));
|
|
38
|
+
|
|
39
|
+
async function signPsbt(psbt: bitcoin.Psbt) {
|
|
40
|
+
psbt.signAllInputs(createNativeSegwitBitcoinJsSigner(Buffer.from(testVectorKey)));
|
|
41
|
+
return btc.Transaction.fromPSBT(psbt.toBuffer());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!nativeSegwitAddress) throw new Error('nativeSegwitAddress is undefined');
|
|
45
|
+
|
|
46
|
+
test('Addresses against native segwit test vectors', () => {
|
|
47
|
+
expect(nativeSegwitAddress).toEqual('bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l');
|
|
48
|
+
expect(payment.address).toEqual('bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('Signature: "" (empty string)', async () => {
|
|
52
|
+
const {
|
|
53
|
+
virtualToSpend: emptyStringToSpend,
|
|
54
|
+
virtualToSign: emptyStringToSign,
|
|
55
|
+
signature: emptyStringSig,
|
|
56
|
+
} = await signBip322MessageSimple({
|
|
57
|
+
address: nativeSegwitAddress,
|
|
58
|
+
message: '',
|
|
59
|
+
network: 'mainnet',
|
|
60
|
+
signPsbt,
|
|
61
|
+
});
|
|
62
|
+
expect(emptyStringToSpend.getId()).toEqual(
|
|
63
|
+
'c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7'
|
|
64
|
+
);
|
|
65
|
+
expect(emptyStringToSign.getId()).toEqual(
|
|
66
|
+
'1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Signature
|
|
70
|
+
// Bip322 says:
|
|
71
|
+
// AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=
|
|
72
|
+
expect(emptyStringSig).toEqual(
|
|
73
|
+
'AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy'
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const helloWorld = 'Hello World';
|
|
78
|
+
test(`Signature: "${helloWorld}"`, async () => {
|
|
79
|
+
const { virtualToSpend, virtualToSign, unencodedSig, signature } =
|
|
80
|
+
await signBip322MessageSimple({
|
|
81
|
+
address: nativeSegwitAddress,
|
|
82
|
+
message: helloWorld,
|
|
83
|
+
network: 'mainnet',
|
|
84
|
+
signPsbt,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// section 3
|
|
88
|
+
expect(virtualToSpend.getId()).toEqual(
|
|
89
|
+
'b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b'
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// sectuion 4.3 expectedid
|
|
93
|
+
expect(virtualToSign.getId()).toEqual(
|
|
94
|
+
'88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// sectioun 5.2 witness
|
|
98
|
+
expect(virtualToSign.ins[0].witness.map(bytesToHex).join(' ')).toEqual(
|
|
99
|
+
'3045022100ecf2ca796ab7dde538a26bfb09a6c487a7b3fff33f397db6a20eb9af77c0ee8c022062e67e44c8070f49c3a37f5940a8850842daf7cca35e6af61a6c7c91f1e1a1a301 02c7f12003196442943d8588e01aee840423cc54fc1521526a3b85c2b0cbd58872'
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(unencodedSig.toString('hex')).toEqual(
|
|
103
|
+
'02483045022100ecf2ca796ab7dde538a26bfb09a6c487a7b3fff33f397db6a20eb9af77c0ee8c022062e67e44c8070f49c3a37f5940a8850842daf7cca35e6af61a6c7c91f1e1a1a3012102c7f12003196442943d8588e01aee840423cc54fc1521526a3b85c2b0cbd58872'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Signature
|
|
107
|
+
// Bip322 says:
|
|
108
|
+
// AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=
|
|
109
|
+
expect(signature).toEqual(
|
|
110
|
+
'AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy'
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Message Signing, Taproot', () => {
|
|
116
|
+
const taprootAddress = btc.getAddress('tr', testVectorKey);
|
|
117
|
+
|
|
118
|
+
if (!taprootAddress) throw new Error('Could not generate taproot address');
|
|
119
|
+
|
|
120
|
+
const payment = btc.p2tr(
|
|
121
|
+
ecdsaPublicKeyToSchnorr(secp.getPublicKey(Buffer.from(testVectorKey), true))
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
async function signPsbt(psbt: bitcoin.Psbt) {
|
|
125
|
+
psbt.data.inputs.forEach(
|
|
126
|
+
input => (input.tapInternalKey = Buffer.from(payment.tapInternalKey))
|
|
127
|
+
);
|
|
128
|
+
psbt.signAllInputs(createTaprootBitcoinJsSigner(Buffer.from(testVectorKey)));
|
|
129
|
+
return btc.Transaction.fromPSBT(psbt.toBuffer());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
test('Addresses against taproot test vectors', () => {
|
|
133
|
+
expect(taprootAddress).toEqual(
|
|
134
|
+
'bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3'
|
|
135
|
+
);
|
|
136
|
+
expect(payment.address).toEqual(
|
|
137
|
+
'bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3'
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Taproot signatures verified with verifymessage request to node
|
|
142
|
+
test('Signature: "" (empty string)', async () => {
|
|
143
|
+
const { signature } = await signBip322MessageSimple({
|
|
144
|
+
address: taprootAddress,
|
|
145
|
+
message: '',
|
|
146
|
+
network: 'mainnet',
|
|
147
|
+
signPsbt,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(signature).toEqual(
|
|
151
|
+
'AUD4DxC7li8RxVkoC/H27LIZnaBD/ZCyZOjjVTzyQf7wa1kYMuyv1uX7XshysTXVR05HbexLSChXuGSoZcqJl6zF'
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('Signature: "WearLeather"', async () => {
|
|
156
|
+
const { signature } = await signBip322MessageSimple({
|
|
157
|
+
address: taprootAddress,
|
|
158
|
+
message: 'WearLeather',
|
|
159
|
+
network: 'mainnet',
|
|
160
|
+
signPsbt,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(signature).toEqual(
|
|
164
|
+
'AUDjK8SJX34boek3m3EKXI94AMBZynJUmdqgO7i4z6JKG6gkUgp+brkWl0ylzWb+8enM4s4B4TWel0iCmcQrKNWS'
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as btc from '@scure/btc-signer';
|
|
2
|
+
import { hexToBytes } from '@stacks/common';
|
|
3
|
+
|
|
4
|
+
import { hashBip322Message } from './bip322-utils';
|
|
5
|
+
|
|
6
|
+
// TODO: Complete this fn
|
|
7
|
+
// Ran into difficiulties with btc-signer vs bitcoinjs-lib
|
|
8
|
+
// Using that library to unblock for now, but we should go
|
|
9
|
+
// back and replace it when possible.
|
|
10
|
+
// ts-unused-exports:disable-next-line
|
|
11
|
+
export function _UNSAFE_signBip322MessageSimple(script: Uint8Array, message: string) {
|
|
12
|
+
// nVersion = 0
|
|
13
|
+
// nLockTime = 0
|
|
14
|
+
// vin[0].prevout.hash = 0000...000
|
|
15
|
+
// vin[0].prevout.n = 0xFFFFFFFF
|
|
16
|
+
// vin[0].nSequence = 0
|
|
17
|
+
// vin[0].scriptSig = OP_0 PUSH32[ message_hash ]
|
|
18
|
+
// vin[0].scriptWitness = []
|
|
19
|
+
// vout[0].nValue = 0
|
|
20
|
+
// vout[0].scriptPubKey = message_challenge
|
|
21
|
+
|
|
22
|
+
const prevoutHash = hexToBytes(
|
|
23
|
+
'0000000000000000000000000000000000000000000000000000000000000000'
|
|
24
|
+
);
|
|
25
|
+
const prevoutIndex = 0xffffffff;
|
|
26
|
+
const sequence = 0;
|
|
27
|
+
|
|
28
|
+
const hash = hashBip322Message(message);
|
|
29
|
+
|
|
30
|
+
const commands = [btc.OP.OP_0, hash];
|
|
31
|
+
|
|
32
|
+
const virtualToSpend = new btc.Transaction({
|
|
33
|
+
version: 0,
|
|
34
|
+
lockTime: 0,
|
|
35
|
+
allowUnknowInput: true,
|
|
36
|
+
allowUnknowOutput: true,
|
|
37
|
+
disableScriptCheck: true,
|
|
38
|
+
allowLegacyWitnessUtxo: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
virtualToSpend.addInput({
|
|
42
|
+
txid: prevoutHash,
|
|
43
|
+
index: prevoutIndex,
|
|
44
|
+
sequence,
|
|
45
|
+
witnessScript: btc.Script.encode(commands),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
virtualToSpend.addOutput({
|
|
49
|
+
script,
|
|
50
|
+
amount: 0n,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return { virtualToSpend };
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HDKey } from '@scure/bip32';
|
|
2
|
+
import * as btc from '@scure/btc-signer';
|
|
3
|
+
import { SigHash } from '@scure/btc-signer/transaction';
|
|
4
|
+
|
|
5
|
+
import type { BitcoinNetworkModes } from '@leather.io/models';
|
|
6
|
+
import { SignatureHash } from '@leather.io/rpc';
|
|
7
|
+
|
|
8
|
+
export type AllowedSighashTypes = SignatureHash | SigHash;
|
|
9
|
+
|
|
10
|
+
export interface Signer<Payment> {
|
|
11
|
+
network: BitcoinNetworkModes;
|
|
12
|
+
payment: Payment;
|
|
13
|
+
keychain: HDKey;
|
|
14
|
+
derivationPath: string;
|
|
15
|
+
address: string;
|
|
16
|
+
publicKey: Uint8Array;
|
|
17
|
+
sign(tx: btc.Transaction): void;
|
|
18
|
+
signIndex(tx: btc.Transaction, index: number, allowedSighash?: AllowedSighashTypes[]): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as bitcoinJs from 'bitcoinjs-lib';
|
|
2
|
+
|
|
3
|
+
import { BitcoinNetworkModes } from '@leather.io/models';
|
|
4
|
+
|
|
5
|
+
// See this PR https://github.com/paulmillr/@scure/btc-signer/pull/15
|
|
6
|
+
// Atttempting to add these directly to the library
|
|
7
|
+
export interface BtcSignerNetwork {
|
|
8
|
+
bech32: string;
|
|
9
|
+
pubKeyHash: number;
|
|
10
|
+
scriptHash: number;
|
|
11
|
+
wif: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const bitcoinMainnet: BtcSignerNetwork = {
|
|
15
|
+
bech32: 'bc',
|
|
16
|
+
pubKeyHash: 0x00,
|
|
17
|
+
scriptHash: 0x05,
|
|
18
|
+
wif: 0x80,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const bitcoinTestnet: BtcSignerNetwork = {
|
|
22
|
+
bech32: 'tb',
|
|
23
|
+
pubKeyHash: 0x6f,
|
|
24
|
+
scriptHash: 0xc4,
|
|
25
|
+
wif: 0xef,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const bitcoinRegtest: BtcSignerNetwork = {
|
|
29
|
+
bech32: 'bcrt',
|
|
30
|
+
pubKeyHash: 0x6f,
|
|
31
|
+
scriptHash: 0xc4,
|
|
32
|
+
wif: 0xef,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const btcSignerLibNetworks: Record<BitcoinNetworkModes, BtcSignerNetwork> = {
|
|
36
|
+
mainnet: bitcoinMainnet,
|
|
37
|
+
testnet: bitcoinTestnet,
|
|
38
|
+
regtest: bitcoinRegtest,
|
|
39
|
+
// Signet originally was going to have its own prefix but authors decided to
|
|
40
|
+
// copy testnet
|
|
41
|
+
signet: bitcoinTestnet,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function getBtcSignerLibNetworkConfigByMode(network: BitcoinNetworkModes) {
|
|
45
|
+
return btcSignerLibNetworks[network];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bitcoinJsLibNetworks: Record<BitcoinNetworkModes, bitcoinJs.Network> = {
|
|
49
|
+
mainnet: bitcoinJs.networks.bitcoin,
|
|
50
|
+
testnet: bitcoinJs.networks.testnet,
|
|
51
|
+
regtest: bitcoinJs.networks.regtest,
|
|
52
|
+
signet: bitcoinJs.networks.testnet,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function getBitcoinJsLibNetworkConfigByMode(network: BitcoinNetworkModes) {
|
|
56
|
+
return bitcoinJsLibNetworks[network];
|
|
57
|
+
}
|