@ledgerhq/hw-app-btc 6.11.1 → 6.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/README.md +106 -48
- package/lib/Btc.d.ts.map +1 -1
- package/lib/Btc.js +5 -3
- package/lib/Btc.js.map +1 -1
- package/lib/BtcNew.d.ts.map +1 -1
- package/lib/BtcNew.js +38 -167
- package/lib/BtcNew.js.map +1 -1
- package/lib/newops/accounttype.d.ts +110 -0
- package/lib/newops/accounttype.d.ts.map +1 -0
- package/lib/newops/accounttype.js +236 -0
- package/lib/newops/accounttype.js.map +1 -0
- package/lib/newops/appClient.js +4 -4
- package/lib/newops/appClient.js.map +1 -1
- package/lib/newops/clientCommands.d.ts.map +1 -1
- package/lib/newops/clientCommands.js +14 -9
- package/lib/newops/clientCommands.js.map +1 -1
- package/lib/newops/merkle.js +2 -2
- package/lib/newops/merkle.js.map +1 -1
- package/lib/newops/psbtExtractor.js +2 -2
- package/lib/newops/psbtExtractor.js.map +1 -1
- package/lib/newops/psbtv2.js +4 -4
- package/lib/newops/psbtv2.js.map +1 -1
- package/lib-es/Btc.d.ts.map +1 -1
- package/lib-es/Btc.js +5 -3
- package/lib-es/Btc.js.map +1 -1
- package/lib-es/BtcNew.d.ts.map +1 -1
- package/lib-es/BtcNew.js +41 -170
- package/lib-es/BtcNew.js.map +1 -1
- package/lib-es/newops/accounttype.d.ts +110 -0
- package/lib-es/newops/accounttype.d.ts.map +1 -0
- package/lib-es/newops/accounttype.js +233 -0
- package/lib-es/newops/accounttype.js.map +1 -0
- package/lib-es/newops/appClient.js +4 -4
- package/lib-es/newops/appClient.js.map +1 -1
- package/lib-es/newops/clientCommands.d.ts.map +1 -1
- package/lib-es/newops/clientCommands.js +14 -9
- package/lib-es/newops/clientCommands.js.map +1 -1
- package/lib-es/newops/merkle.js +2 -2
- package/lib-es/newops/merkle.js.map +1 -1
- package/lib-es/newops/psbtExtractor.js +2 -2
- package/lib-es/newops/psbtExtractor.js.map +1 -1
- package/lib-es/newops/psbtv2.js +4 -4
- package/lib-es/newops/psbtv2.js.map +1 -1
- package/package.json +3 -3
- package/src/Btc.ts +34 -3
- package/src/BtcNew.ts +64 -166
- package/src/newops/accounttype.ts +373 -0
- package/src/newops/appClient.ts +4 -4
- package/src/newops/clientCommands.ts +15 -9
- package/src/newops/merkle.ts +2 -2
- package/src/newops/psbtExtractor.ts +2 -2
- package/src/newops/psbtv2.ts +4 -4
- package/tests/Btc.test.ts +68 -39
- package/tests/newops/BtcNew.test.ts +23 -10
- package/tests/newops/integrationtools.ts +71 -41
- package/tests/newops/merkle.test.ts +1 -1
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
WalletPolicy
|
|
10
10
|
} from "../../src/newops/policy";
|
|
11
11
|
import { PsbtV2 } from "../../src/newops/psbtv2";
|
|
12
|
-
import {
|
|
12
|
+
import { splitTransaction } from "../../src/splitTransaction";
|
|
13
|
+
import { StandardPurpose, addressFormatFromDescriptorTemplate, creatDummyXpub, masterFingerprint, runSignTransaction, TestingClient } from "./integrationtools";
|
|
13
14
|
import { CoreInput, CoreTx, p2pkh, p2tr, p2wpkh, wrappedP2wpkh, wrappedP2wpkhTwoInputs } from "./testtx";
|
|
14
15
|
|
|
15
16
|
test("getWalletPublicKey p2pkh", async () => {
|
|
@@ -40,7 +41,7 @@ test("getWalletXpub normal path", async () => {
|
|
|
40
41
|
await testGetWalletXpub("m/44'/0'/0'");
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
function testPaths(type:
|
|
44
|
+
function testPaths(type: StandardPurpose): { ins: string[], out?: string } {
|
|
44
45
|
const basePath = `m/${type}/1'/0'/`;
|
|
45
46
|
const ins = [
|
|
46
47
|
basePath + "0/0",
|
|
@@ -55,21 +56,21 @@ function testPaths(type: AccountType): { ins: string[], out?: string } {
|
|
|
55
56
|
|
|
56
57
|
test("Sign p2pkh", async () => {
|
|
57
58
|
const changePubkey = "037ed58c914720772c59f7a1e7e76fba0ef95d7c5667119798586301519b9ad2cf";
|
|
58
|
-
await runSignTransactionTest(p2pkh,
|
|
59
|
+
await runSignTransactionTest(p2pkh, StandardPurpose.p2pkh, changePubkey);
|
|
59
60
|
});
|
|
60
61
|
test("Sign p2wpkh wrapped", async () => {
|
|
61
62
|
let changePubkey = "03efc6b990c1626d08bd176aab0e545a4f55c627c7ddee878d12bbbc46a126177a";
|
|
62
|
-
await runSignTransactionTest(wrappedP2wpkh,
|
|
63
|
+
await runSignTransactionTest(wrappedP2wpkh, StandardPurpose.p2wpkhInP2sh, changePubkey);
|
|
63
64
|
changePubkey = "031175a985c56e310ce3496a819229b427a2172920fd20b5972dda62758c6def09";
|
|
64
|
-
await runSignTransactionTest(wrappedP2wpkhTwoInputs,
|
|
65
|
+
await runSignTransactionTest(wrappedP2wpkhTwoInputs, StandardPurpose.p2wpkhInP2sh, changePubkey);
|
|
65
66
|
});
|
|
66
67
|
test("Sign p2wpkh", async () => {
|
|
67
|
-
await runSignTransactionTest(p2wpkh,
|
|
68
|
+
await runSignTransactionTest(p2wpkh, StandardPurpose.p2wpkh);
|
|
68
69
|
});
|
|
69
70
|
test("Sign p2tr", async () => {
|
|
70
71
|
// This tx uses locktime, so this test verifies that locktime is propagated to/from
|
|
71
72
|
// the psbt correctly.
|
|
72
|
-
await runSignTransactionTest(p2tr,
|
|
73
|
+
await runSignTransactionTest(p2tr, StandardPurpose.p2tr);
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
test("Sign p2tr with sigHashType", async () => {
|
|
@@ -79,16 +80,28 @@ test("Sign p2tr with sigHashType", async () => {
|
|
|
79
80
|
const sig = input.txinwitness![0] + "83";
|
|
80
81
|
input.txinwitness = [sig];
|
|
81
82
|
})
|
|
82
|
-
const tx = await runSignTransactionNoVerification(testTx,
|
|
83
|
+
const tx = await runSignTransactionNoVerification(testTx, StandardPurpose.p2tr);
|
|
83
84
|
// The verification of the sighashtype is done in MockClient.signPsbt
|
|
84
85
|
})
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
test("Sign p2tr sequence 0", async() => {
|
|
88
|
+
const testTx = JSON.parse(JSON.stringify(p2tr));
|
|
89
|
+
testTx.vin.forEach((input: CoreInput, index: number) => {
|
|
90
|
+
input.sequence = 0;
|
|
91
|
+
})
|
|
92
|
+
const tx = await runSignTransactionNoVerification(testTx, StandardPurpose.p2tr);
|
|
93
|
+
const txObj = splitTransaction(tx, true);
|
|
94
|
+
txObj.inputs.forEach((input, index) => {
|
|
95
|
+
expect(input.sequence.toString("hex")).toEqual("00000000");
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
async function runSignTransactionTest(testTx: CoreTx, accountType: StandardPurpose, changePubkey?: string) {
|
|
87
100
|
const tx = await runSignTransactionNoVerification(testTx, accountType, changePubkey);
|
|
88
101
|
expect(tx).toEqual(testTx.hex);
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
async function runSignTransactionNoVerification(testTx: CoreTx, accountType:
|
|
104
|
+
async function runSignTransactionNoVerification(testTx: CoreTx, accountType: StandardPurpose, changePubkey?: string): Promise<string> {
|
|
92
105
|
const [client, transport] = await createClient();
|
|
93
106
|
const accountXpub = "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT";
|
|
94
107
|
client.mockGetPubkeyResponse(`m/${accountType}/1'/0'`, accountXpub);
|
|
@@ -9,26 +9,26 @@ import { AddressFormat } from "../../src/getWalletPublicKey";
|
|
|
9
9
|
import { AppClient } from "../../src/newops/appClient";
|
|
10
10
|
import {
|
|
11
11
|
DefaultDescriptorTemplate,
|
|
12
|
-
WalletPolicy
|
|
12
|
+
WalletPolicy,
|
|
13
13
|
} from "../../src/newops/policy";
|
|
14
14
|
import { Transaction } from "../../src/types";
|
|
15
15
|
import { CoreInput, CoreTx, spentTxs } from "./testtx";
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
export async function runSignTransaction(
|
|
19
18
|
testTx: CoreTx,
|
|
20
|
-
testPaths: { ins: string[]
|
|
19
|
+
testPaths: { ins: string[]; out?: string },
|
|
21
20
|
client: TestingClient,
|
|
22
|
-
transport: Transport
|
|
21
|
+
transport: Transport
|
|
22
|
+
): Promise<string> {
|
|
23
23
|
const btcNew = new BtcNew(client);
|
|
24
24
|
// btc is needed to perform some functions like splitTransaction.
|
|
25
25
|
const btc = new Btc(transport);
|
|
26
26
|
const accountType = getAccountType(testTx.vin[0], btc);
|
|
27
27
|
const additionals: string[] = [];
|
|
28
|
-
if (accountType ==
|
|
28
|
+
if (accountType == StandardPurpose.p2wpkh) {
|
|
29
29
|
additionals.push("bech32");
|
|
30
30
|
}
|
|
31
|
-
if (accountType ==
|
|
31
|
+
if (accountType == StandardPurpose.p2tr) {
|
|
32
32
|
additionals.push("bech32m");
|
|
33
33
|
}
|
|
34
34
|
const associatedKeysets: string[] = [];
|
|
@@ -37,30 +37,38 @@ export async function runSignTransaction(
|
|
|
37
37
|
const path = testPaths.ins[index];
|
|
38
38
|
associatedKeysets.push(path);
|
|
39
39
|
const inputData = createInput(input, btc);
|
|
40
|
-
const pubkey = getPubkey(
|
|
40
|
+
const pubkey = getPubkey(
|
|
41
|
+
index,
|
|
42
|
+
accountType,
|
|
43
|
+
testTx,
|
|
44
|
+
inputData[0],
|
|
45
|
+
inputData[1]
|
|
46
|
+
);
|
|
41
47
|
const mockXpub = creatDummyXpub(pubkey);
|
|
42
48
|
client.mockGetPubkeyResponse(path, mockXpub);
|
|
43
49
|
yieldSigs.set(index, getSignature(input, accountType));
|
|
44
50
|
return inputData;
|
|
45
51
|
});
|
|
46
52
|
const sig0 = yieldSigs.get(0)!;
|
|
47
|
-
let sigHashType: number | undefined = sig0.readUInt8(sig0.length - 1)
|
|
53
|
+
let sigHashType: number | undefined = sig0.readUInt8(sig0.length - 1);
|
|
48
54
|
if (sigHashType == 0x01) {
|
|
49
55
|
sigHashType = undefined;
|
|
50
56
|
}
|
|
51
57
|
client.mockSignPsbt(yieldSigs);
|
|
52
58
|
const outputWriter = new BufferWriter();
|
|
53
59
|
outputWriter.writeVarInt(testTx.vout.length);
|
|
54
|
-
testTx.vout.forEach(output => {
|
|
55
|
-
outputWriter.writeUInt64(
|
|
60
|
+
testTx.vout.forEach((output) => {
|
|
61
|
+
outputWriter.writeUInt64(
|
|
62
|
+
BigInt(Number.parseFloat((output.value * 100000000).toFixed(8)))
|
|
63
|
+
);
|
|
56
64
|
outputWriter.writeVarSlice(Buffer.from(output.scriptPubKey.hex, "hex"));
|
|
57
65
|
});
|
|
58
|
-
const outputScriptHex = outputWriter.buffer().toString("hex");
|
|
66
|
+
const outputScriptHex = outputWriter.buffer().toString("hex");
|
|
59
67
|
let callbacks = "";
|
|
60
68
|
function logCallback(message: string) {
|
|
61
69
|
callbacks += new Date().toISOString() + " " + message + "\n";
|
|
62
70
|
}
|
|
63
|
-
const arg: CreateTransactionArg = {
|
|
71
|
+
const arg: CreateTransactionArg = {
|
|
64
72
|
inputs,
|
|
65
73
|
additionals,
|
|
66
74
|
associatedKeysets,
|
|
@@ -68,21 +76,22 @@ export async function runSignTransaction(
|
|
|
68
76
|
outputScriptHex,
|
|
69
77
|
lockTime: testTx.locktime,
|
|
70
78
|
sigHashType,
|
|
71
|
-
segwit: accountType !=
|
|
79
|
+
segwit: accountType != StandardPurpose.p2pkh,
|
|
72
80
|
onDeviceSignatureGranted: () => logCallback("CALLBACK: signature granted"),
|
|
73
|
-
onDeviceSignatureRequested: () =>
|
|
74
|
-
|
|
81
|
+
onDeviceSignatureRequested: () =>
|
|
82
|
+
logCallback("CALLBACK: signature requested"),
|
|
83
|
+
onDeviceStreaming: (arg) => logCallback("CALLBACK: " + JSON.stringify(arg)),
|
|
75
84
|
};
|
|
76
85
|
logCallback("Start createPaymentTransactionNew");
|
|
77
86
|
const tx = await btcNew.createPaymentTransactionNew(arg);
|
|
78
87
|
logCallback("Done createPaymentTransactionNew");
|
|
79
88
|
// console.log(callbacks);
|
|
80
89
|
return tx;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
}
|
|
84
91
|
|
|
85
|
-
export function addressFormatFromDescriptorTemplate(
|
|
92
|
+
export function addressFormatFromDescriptorTemplate(
|
|
93
|
+
descTemp: DefaultDescriptorTemplate
|
|
94
|
+
): AddressFormat {
|
|
86
95
|
if (descTemp == "tr(@0)") return "bech32m";
|
|
87
96
|
if (descTemp == "pkh(@0)") return "legacy";
|
|
88
97
|
if (descTemp == "wpkh(@0)") return "bech32";
|
|
@@ -90,42 +99,57 @@ export function addressFormatFromDescriptorTemplate(descTemp: DefaultDescriptorT
|
|
|
90
99
|
throw new Error();
|
|
91
100
|
}
|
|
92
101
|
|
|
93
|
-
export enum
|
|
102
|
+
export enum StandardPurpose {
|
|
94
103
|
p2tr = "86'",
|
|
95
104
|
p2wpkh = "84'",
|
|
96
105
|
p2wpkhInP2sh = "49'",
|
|
97
|
-
p2pkh = "44'"
|
|
106
|
+
p2pkh = "44'",
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
function getPubkey(
|
|
109
|
+
function getPubkey(
|
|
110
|
+
inputIndex: number,
|
|
111
|
+
accountType: StandardPurpose,
|
|
112
|
+
testTx: CoreTx,
|
|
113
|
+
spentTx: Transaction,
|
|
114
|
+
spentOutputIndex: number
|
|
115
|
+
): Buffer {
|
|
101
116
|
const scriptSig = Buffer.from(testTx.vin[inputIndex].scriptSig.hex, "hex");
|
|
102
|
-
if (accountType ==
|
|
117
|
+
if (accountType == StandardPurpose.p2pkh) {
|
|
103
118
|
return scriptSig.slice(scriptSig.length - 33);
|
|
104
119
|
}
|
|
105
|
-
if (accountType ==
|
|
120
|
+
if (accountType == StandardPurpose.p2tr) {
|
|
106
121
|
return spentTx.outputs![spentOutputIndex].script.slice(2, 34); // 32 bytes x-only pubkey
|
|
107
122
|
}
|
|
108
|
-
if (
|
|
123
|
+
if (
|
|
124
|
+
accountType == StandardPurpose.p2wpkh ||
|
|
125
|
+
accountType == StandardPurpose.p2wpkhInP2sh
|
|
126
|
+
) {
|
|
109
127
|
return Buffer.from(testTx.vin[inputIndex].txinwitness![1], "hex");
|
|
110
128
|
}
|
|
111
129
|
throw new Error();
|
|
112
130
|
}
|
|
113
131
|
|
|
114
|
-
function getSignature(
|
|
132
|
+
function getSignature(
|
|
133
|
+
testTxInput: CoreInput,
|
|
134
|
+
accountType: StandardPurpose
|
|
135
|
+
): Buffer {
|
|
115
136
|
const scriptSig = Buffer.from(testTxInput.scriptSig.hex, "hex");
|
|
116
|
-
if (accountType ==
|
|
137
|
+
if (accountType == StandardPurpose.p2pkh) {
|
|
117
138
|
return scriptSig.slice(1, scriptSig.length - 34);
|
|
118
139
|
}
|
|
119
|
-
if (accountType ==
|
|
140
|
+
if (accountType == StandardPurpose.p2tr) {
|
|
120
141
|
return Buffer.from(testTxInput.txinwitness![0], "hex");
|
|
121
142
|
}
|
|
122
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
accountType == StandardPurpose.p2wpkh ||
|
|
145
|
+
accountType == StandardPurpose.p2wpkhInP2sh
|
|
146
|
+
) {
|
|
123
147
|
return Buffer.from(testTxInput.txinwitness![0], "hex");
|
|
124
148
|
}
|
|
125
149
|
throw new Error();
|
|
126
150
|
}
|
|
127
151
|
|
|
128
|
-
function getAccountType(coreInput: CoreInput, btc: Btc):
|
|
152
|
+
function getAccountType(coreInput: CoreInput, btc: Btc): StandardPurpose {
|
|
129
153
|
const spentTx = spentTxs[coreInput.txid];
|
|
130
154
|
if (!spentTx) {
|
|
131
155
|
throw new Error("Spent tx " + coreInput.txid + " unavailable.");
|
|
@@ -134,25 +158,31 @@ function getAccountType(coreInput: CoreInput, btc: Btc): AccountType {
|
|
|
134
158
|
const spentOutput = splitSpentTx.outputs![coreInput.vout];
|
|
135
159
|
const script = spentOutput.script;
|
|
136
160
|
if (script.length == 34 && script[0] == 0x51) {
|
|
137
|
-
return
|
|
161
|
+
return StandardPurpose.p2tr;
|
|
138
162
|
}
|
|
139
163
|
if (script.length == 22 && script[0] == 0x00) {
|
|
140
|
-
return
|
|
164
|
+
return StandardPurpose.p2wpkh;
|
|
141
165
|
}
|
|
142
166
|
if (script.length == 23) {
|
|
143
|
-
return
|
|
167
|
+
return StandardPurpose.p2wpkhInP2sh;
|
|
144
168
|
}
|
|
145
|
-
return
|
|
169
|
+
return StandardPurpose.p2pkh;
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
export function creatDummyXpub(pubkey: Buffer): string {
|
|
149
|
-
const xpubDecoded = bs58check.decode(
|
|
150
|
-
|
|
173
|
+
const xpubDecoded = bs58check.decode(
|
|
174
|
+
"tpubDHcN44A4UHqdHJZwBxgTbu8Cy87ZrZkN8tQnmJGhcijHqe4rztuvGcD4wo36XSviLmiqL5fUbDnekYaQ7LzAnaqauBb9RsyahsTTFHdeJGd"
|
|
175
|
+
);
|
|
176
|
+
const pubkey33 =
|
|
177
|
+
pubkey.length == 33 ? pubkey : Buffer.concat([Buffer.from([2]), pubkey]);
|
|
151
178
|
xpubDecoded.fill(pubkey33, xpubDecoded.length - 33);
|
|
152
179
|
return bs58check.encode(xpubDecoded);
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
function createInput(
|
|
182
|
+
function createInput(
|
|
183
|
+
coreInput: CoreInput,
|
|
184
|
+
btc: Btc
|
|
185
|
+
): [Transaction, number, string | null, number] {
|
|
156
186
|
const spentTx = spentTxs[coreInput.txid];
|
|
157
187
|
if (!spentTx) {
|
|
158
188
|
throw new Error("Spent tx " + coreInput.txid + " unavailable.");
|
|
@@ -161,14 +191,14 @@ function createInput(coreInput: CoreInput, btc: Btc): [Transaction, number, stri
|
|
|
161
191
|
return [splitSpentTx, coreInput.vout, null, coreInput.sequence];
|
|
162
192
|
}
|
|
163
193
|
|
|
164
|
-
export const masterFingerprint = Buffer.
|
|
194
|
+
export const masterFingerprint = Buffer.from([1, 2, 3, 4]);
|
|
165
195
|
export class TestingClient extends AppClient {
|
|
166
|
-
mockGetPubkeyResponse(_pathElements: string, _response: string): void {
|
|
196
|
+
mockGetPubkeyResponse(_pathElements: string, _response: string): void {}
|
|
167
197
|
mockGetWalletAddressResponse(
|
|
168
198
|
_walletPolicy: WalletPolicy,
|
|
169
199
|
_change: number,
|
|
170
200
|
_addressIndex: number,
|
|
171
201
|
_response: string
|
|
172
|
-
): void {
|
|
173
|
-
mockSignPsbt(_yieldSigs: Map<number, Buffer>): void {
|
|
202
|
+
): void {}
|
|
203
|
+
mockSignPsbt(_yieldSigs: Map<number, Buffer>): void {}
|
|
174
204
|
}
|