@ledgerhq/hw-app-btc 6.2.0 → 6.9.1-6.9.1-taproot.0.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/lib/Btc.d.ts +7 -3
- package/lib/Btc.d.ts.map +1 -1
- package/lib/Btc.js +99 -30
- package/lib/Btc.js.map +1 -1
- package/lib/BtcNew.d.ts +70 -0
- package/lib/BtcNew.d.ts.map +1 -0
- package/lib/BtcNew.js +372 -0
- package/lib/BtcNew.js.map +1 -0
- package/lib/BtcOld.d.ts +114 -0
- package/lib/BtcOld.d.ts.map +1 -0
- package/lib/BtcOld.js +138 -0
- package/lib/BtcOld.js.map +1 -0
- package/lib/bip32.d.ts +8 -0
- package/lib/bip32.d.ts.map +1 -1
- package/lib/bip32.js +32 -3
- package/lib/bip32.js.map +1 -1
- package/lib/buffertools.d.ts +28 -0
- package/lib/buffertools.d.ts.map +1 -0
- package/lib/buffertools.js +100 -0
- package/lib/buffertools.js.map +1 -0
- package/lib/createTransaction.d.ts.map +1 -1
- package/lib/createTransaction.js +16 -16
- package/lib/createTransaction.js.map +1 -1
- package/lib/finalizeInput.js +1 -1
- package/lib/finalizeInput.js.map +1 -1
- package/lib/getAppAndVersion.js +1 -1
- package/lib/getAppAndVersion.js.map +1 -1
- package/lib/getTrustedInput.js +6 -6
- package/lib/getTrustedInput.js.map +1 -1
- package/lib/getTrustedInputBIP143.js +2 -2
- package/lib/getTrustedInputBIP143.js.map +1 -1
- package/lib/getWalletPublicKey.d.ts +1 -1
- package/lib/getWalletPublicKey.d.ts.map +1 -1
- package/lib/getWalletPublicKey.js +1 -1
- package/lib/getWalletPublicKey.js.map +1 -1
- package/lib/hashPublicKey.d.ts +1 -1
- package/lib/hashPublicKey.d.ts.map +1 -1
- package/lib/hashPublicKey.js +1 -1
- package/lib/hashPublicKey.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -0
- package/lib/newops/appClient.d.ts +14 -0
- package/lib/newops/appClient.d.ts.map +1 -0
- package/lib/newops/appClient.js +242 -0
- package/lib/newops/appClient.js.map +1 -0
- package/lib/newops/clientCommands.d.ts +61 -0
- package/lib/newops/clientCommands.d.ts.map +1 -0
- package/lib/newops/clientCommands.js +331 -0
- package/lib/newops/clientCommands.js.map +1 -0
- package/lib/newops/merkelizedPsbt.d.ts +15 -0
- package/lib/newops/merkelizedPsbt.d.ts.map +1 -0
- package/lib/newops/merkelizedPsbt.js +91 -0
- package/lib/newops/merkelizedPsbt.js.map +1 -0
- package/lib/newops/merkle.d.ts +29 -0
- package/lib/newops/merkle.d.ts.map +1 -0
- package/lib/newops/merkle.js +133 -0
- package/lib/newops/merkle.js.map +1 -0
- package/lib/newops/merkleMap.d.ts +15 -0
- package/lib/newops/merkleMap.d.ts.map +1 -0
- package/lib/newops/merkleMap.js +37 -0
- package/lib/newops/merkleMap.js.map +1 -0
- package/lib/newops/policy.d.ts +14 -0
- package/lib/newops/policy.d.ts.map +1 -0
- package/lib/newops/policy.js +40 -0
- package/lib/newops/policy.js.map +1 -0
- package/lib/newops/psbtExtractor.d.ts +4 -0
- package/lib/newops/psbtExtractor.d.ts.map +1 -0
- package/lib/newops/psbtExtractor.js +36 -0
- package/lib/newops/psbtExtractor.js.map +1 -0
- package/lib/newops/psbtFinalizer.d.ts +7 -0
- package/lib/newops/psbtFinalizer.d.ts.map +1 -0
- package/lib/newops/psbtFinalizer.js +111 -0
- package/lib/newops/psbtFinalizer.js.map +1 -0
- package/lib/newops/psbtv2.d.ts +129 -0
- package/lib/newops/psbtv2.d.ts.map +1 -0
- package/lib/newops/psbtv2.js +478 -0
- package/lib/newops/psbtv2.js.map +1 -0
- package/lib/serializeTransaction.js +4 -4
- package/lib/serializeTransaction.js.map +1 -1
- package/lib/signP2SHTransaction.js +5 -5
- package/lib/signP2SHTransaction.js.map +1 -1
- package/lib/signTransaction.js +1 -1
- package/lib/signTransaction.js.map +1 -1
- package/lib/splitTransaction.js +7 -7
- package/lib/splitTransaction.js.map +1 -1
- package/lib/startUntrustedHashTransactionInput.js +2 -2
- package/lib/startUntrustedHashTransactionInput.js.map +1 -1
- package/lib/varint.d.ts.map +1 -1
- package/lib/varint.js +1 -0
- package/lib/varint.js.map +1 -1
- package/lib-es/Btc.d.ts +7 -3
- package/lib-es/Btc.d.ts.map +1 -1
- package/lib-es/Btc.js +92 -26
- package/lib-es/Btc.js.map +1 -1
- package/lib-es/BtcNew.d.ts +70 -0
- package/lib-es/BtcNew.d.ts.map +1 -0
- package/lib-es/BtcNew.js +370 -0
- package/lib-es/BtcNew.js.map +1 -0
- package/lib-es/BtcOld.d.ts +114 -0
- package/lib-es/BtcOld.d.ts.map +1 -0
- package/lib-es/BtcOld.js +136 -0
- package/lib-es/BtcOld.js.map +1 -0
- package/lib-es/bip32.d.ts +8 -0
- package/lib-es/bip32.d.ts.map +1 -1
- package/lib-es/bip32.js +26 -2
- package/lib-es/bip32.js.map +1 -1
- package/lib-es/buffertools.d.ts +28 -0
- package/lib-es/buffertools.d.ts.map +1 -0
- package/lib-es/buffertools.js +94 -0
- package/lib-es/buffertools.js.map +1 -0
- package/lib-es/createTransaction.d.ts.map +1 -1
- package/lib-es/getWalletPublicKey.d.ts +1 -1
- package/lib-es/getWalletPublicKey.d.ts.map +1 -1
- package/lib-es/getWalletPublicKey.js.map +1 -1
- package/lib-es/hashPublicKey.d.ts +1 -1
- package/lib-es/hashPublicKey.d.ts.map +1 -1
- package/lib-es/index.d.ts +3 -0
- package/lib-es/index.d.ts.map +1 -0
- package/lib-es/index.js +3 -0
- package/lib-es/index.js.map +1 -0
- package/lib-es/newops/appClient.d.ts +14 -0
- package/lib-es/newops/appClient.d.ts.map +1 -0
- package/lib-es/newops/appClient.js +239 -0
- package/lib-es/newops/appClient.js.map +1 -0
- package/lib-es/newops/clientCommands.d.ts +61 -0
- package/lib-es/newops/clientCommands.d.ts.map +1 -0
- package/lib-es/newops/clientCommands.js +328 -0
- package/lib-es/newops/clientCommands.js.map +1 -0
- package/lib-es/newops/merkelizedPsbt.d.ts +15 -0
- package/lib-es/newops/merkelizedPsbt.d.ts.map +1 -0
- package/lib-es/newops/merkelizedPsbt.js +88 -0
- package/lib-es/newops/merkelizedPsbt.js.map +1 -0
- package/lib-es/newops/merkle.d.ts +29 -0
- package/lib-es/newops/merkle.d.ts.map +1 -0
- package/lib-es/newops/merkle.js +129 -0
- package/lib-es/newops/merkle.js.map +1 -0
- package/lib-es/newops/merkleMap.d.ts +15 -0
- package/lib-es/newops/merkleMap.d.ts.map +1 -0
- package/lib-es/newops/merkleMap.js +34 -0
- package/lib-es/newops/merkleMap.js.map +1 -0
- package/lib-es/newops/policy.d.ts +14 -0
- package/lib-es/newops/policy.d.ts.map +1 -0
- package/lib-es/newops/policy.js +36 -0
- package/lib-es/newops/policy.js.map +1 -0
- package/lib-es/newops/psbtExtractor.d.ts +4 -0
- package/lib-es/newops/psbtExtractor.d.ts.map +1 -0
- package/lib-es/newops/psbtExtractor.js +32 -0
- package/lib-es/newops/psbtExtractor.js.map +1 -0
- package/lib-es/newops/psbtFinalizer.d.ts +7 -0
- package/lib-es/newops/psbtFinalizer.d.ts.map +1 -0
- package/lib-es/newops/psbtFinalizer.js +107 -0
- package/lib-es/newops/psbtFinalizer.js.map +1 -0
- package/lib-es/newops/psbtv2.d.ts +129 -0
- package/lib-es/newops/psbtv2.d.ts.map +1 -0
- package/lib-es/newops/psbtv2.js +475 -0
- package/lib-es/newops/psbtv2.js.map +1 -0
- package/lib-es/varint.d.ts.map +1 -1
- package/lib-es/varint.js +1 -0
- package/lib-es/varint.js.map +1 -1
- package/package.json +7 -4
- package/src/Btc.ts +42 -25
- package/src/BtcNew.ts +326 -0
- package/src/BtcOld.ts +156 -0
- package/src/bip32.ts +34 -2
- package/src/buffertools.ts +102 -0
- package/src/createTransaction.ts +2 -2
- package/src/getWalletPublicKey.ts +6 -1
- package/src/hashPublicKey.ts +1 -1
- package/src/index.ts +2 -0
- package/src/newops/appClient.ts +178 -0
- package/src/newops/clientCommands.ts +312 -0
- package/src/newops/merkelizedPsbt.ts +55 -0
- package/src/newops/merkle.ts +123 -0
- package/src/newops/merkleMap.ts +39 -0
- package/src/newops/policy.ts +52 -0
- package/src/newops/psbtExtractor.ts +33 -0
- package/src/newops/psbtFinalizer.ts +110 -0
- package/src/newops/psbtv2.ts +548 -0
- package/src/varint.ts +2 -0
- package/tests/Btc.integration.test.ts +89 -0
- package/tests/Btc.test.ts +6 -0
- package/tests/newops/BtcNew.test.ts +646 -0
- package/tests/newops/common.ts +25 -0
- package/tests/newops/merkle.test.ts +97 -0
- package/tests/trustedInputs.test.ts +4 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import varuint from "varuint-bitcoin";
|
|
2
|
+
|
|
3
|
+
export class BufferWriter {
|
|
4
|
+
private bufs: Buffer[] = [];
|
|
5
|
+
|
|
6
|
+
write(alloc: number, fn: (b: Buffer) => void): void {
|
|
7
|
+
const b = Buffer.alloc(alloc);
|
|
8
|
+
fn(b);
|
|
9
|
+
this.bufs.push(b);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
writeUInt8(i: number): void {
|
|
13
|
+
this.write(1, (b) => b.writeUInt8(i, 0));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
writeInt32(i: number): void {
|
|
17
|
+
this.write(4, (b) => b.writeInt32LE(i, 0));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
writeUInt32(i: number): void {
|
|
21
|
+
this.write(4, (b) => b.writeUInt32LE(i, 0));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
writeUInt64(i: bigint): void {
|
|
25
|
+
this.write(8, (b) => b.writeBigUInt64LE(i, 0));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
writeVarInt(i: number): void {
|
|
29
|
+
this.bufs.push(varuint.encode(i));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
writeSlice(slice: Buffer): void {
|
|
33
|
+
this.bufs.push(Buffer.from(slice));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
writeVarSlice(slice: Buffer): void {
|
|
37
|
+
this.writeVarInt(slice.length);
|
|
38
|
+
this.writeSlice(slice);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
buffer(): Buffer {
|
|
42
|
+
return Buffer.concat(this.bufs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class BufferReader {
|
|
47
|
+
constructor(public buffer: Buffer, public offset: number = 0) {}
|
|
48
|
+
|
|
49
|
+
available(): number {
|
|
50
|
+
return this.buffer.length - this.offset;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
readUInt8(): number {
|
|
54
|
+
const result = this.buffer.readUInt8(this.offset);
|
|
55
|
+
this.offset++;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
readInt32(): number {
|
|
60
|
+
const result = this.buffer.readInt32LE(this.offset);
|
|
61
|
+
this.offset += 4;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readUInt32(): number {
|
|
66
|
+
const result = this.buffer.readUInt32LE(this.offset);
|
|
67
|
+
this.offset += 4;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
readUInt64(): bigint {
|
|
72
|
+
const result = this.buffer.readBigUInt64LE(this.offset);
|
|
73
|
+
this.offset += 8;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
readVarInt(): number {
|
|
78
|
+
const vi = varuint.decode(this.buffer, this.offset);
|
|
79
|
+
this.offset += varuint.decode.bytes;
|
|
80
|
+
return vi;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
readSlice(n: number): Buffer {
|
|
84
|
+
if (this.buffer.length < this.offset + n) {
|
|
85
|
+
throw new Error("Cannot read slice out of bounds");
|
|
86
|
+
}
|
|
87
|
+
const result = this.buffer.slice(this.offset, this.offset + n);
|
|
88
|
+
this.offset += n;
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
readVarSlice(): Buffer {
|
|
93
|
+
return this.readSlice(this.readVarInt());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
readVector(): Buffer[] {
|
|
97
|
+
const count = this.readVarInt();
|
|
98
|
+
const vector: Buffer[] = [];
|
|
99
|
+
for (let i = 0; i < count; i++) vector.push(this.readVarSlice());
|
|
100
|
+
return vector;
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/createTransaction.ts
CHANGED
|
@@ -62,7 +62,7 @@ export type CreateTransactionArg = {
|
|
|
62
62
|
export async function createTransaction(
|
|
63
63
|
transport: Transport,
|
|
64
64
|
arg: CreateTransactionArg
|
|
65
|
-
) {
|
|
65
|
+
): Promise<string> {
|
|
66
66
|
const signTx = { ...defaultsSignTransaction, ...arg };
|
|
67
67
|
const {
|
|
68
68
|
inputs,
|
|
@@ -85,7 +85,7 @@ export async function createTransaction(
|
|
|
85
85
|
try {
|
|
86
86
|
const a = await getAppAndVersion(transport);
|
|
87
87
|
useTrustedInputForSegwit = shouldUseTrustedInputForSegwit(a);
|
|
88
|
-
} catch (e) {
|
|
88
|
+
} catch (e: any) {
|
|
89
89
|
if (e.statusCode === 0x6d00) {
|
|
90
90
|
useTrustedInputForSegwit = false;
|
|
91
91
|
} else {
|
|
@@ -4,7 +4,12 @@ import { bip32asBuffer } from "./bip32";
|
|
|
4
4
|
/**
|
|
5
5
|
* address format is one of legacy | p2sh | bech32 | cashaddr
|
|
6
6
|
*/
|
|
7
|
-
export type AddressFormat =
|
|
7
|
+
export type AddressFormat =
|
|
8
|
+
| "legacy"
|
|
9
|
+
| "p2sh"
|
|
10
|
+
| "bech32"
|
|
11
|
+
| "bech32m"
|
|
12
|
+
| "cashaddr";
|
|
8
13
|
const addressFormatMap = {
|
|
9
14
|
legacy: 0,
|
|
10
15
|
p2sh: 1,
|
package/src/hashPublicKey.ts
CHANGED
package/src/index.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
2
|
+
import { pathElementsToBuffer } from "../bip32";
|
|
3
|
+
import { PsbtV2 } from "./psbtv2";
|
|
4
|
+
import { MerkelizedPsbt } from "./merkelizedPsbt";
|
|
5
|
+
import { ClientCommandInterpreter } from "./clientCommands";
|
|
6
|
+
import { WalletPolicy } from "./policy";
|
|
7
|
+
import { createVarint } from "../varint";
|
|
8
|
+
import { hashLeaf, Merkle } from "./merkle";
|
|
9
|
+
|
|
10
|
+
const CLA_BTC = 0xe1;
|
|
11
|
+
const CLA_FRAMEWORK = 0xf8;
|
|
12
|
+
|
|
13
|
+
enum BitcoinIns {
|
|
14
|
+
GET_PUBKEY = 0x00,
|
|
15
|
+
// GET_ADDRESS = 0x01,
|
|
16
|
+
REGISTER_WALLET = 0x02,
|
|
17
|
+
GET_WALLET_ADDRESS = 0x03,
|
|
18
|
+
SIGN_PSBT = 0x04,
|
|
19
|
+
GET_MASTER_FINGERPRINT = 0x05,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
enum FrameworkIns {
|
|
23
|
+
CONTINUE_INTERRUPTED = 0x01,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AppClient {
|
|
27
|
+
transport: Transport;
|
|
28
|
+
|
|
29
|
+
constructor(transport: Transport) {
|
|
30
|
+
this.transport = transport;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async makeRequest(
|
|
34
|
+
ins: BitcoinIns,
|
|
35
|
+
data: Buffer,
|
|
36
|
+
cci?: ClientCommandInterpreter
|
|
37
|
+
): Promise<Buffer> {
|
|
38
|
+
let response: Buffer = await this.transport.send(CLA_BTC, ins, 0, 0, data, [
|
|
39
|
+
0x9000,
|
|
40
|
+
0xe000,
|
|
41
|
+
]);
|
|
42
|
+
while (response.readUInt16BE(response.length - 2) === 0xe000) {
|
|
43
|
+
if (!cci) {
|
|
44
|
+
throw new Error("Unexpected SW_INTERRUPTED_EXECUTION");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hwRequest = response.slice(0, -2);
|
|
48
|
+
const commandResponse = cci.execute(hwRequest);
|
|
49
|
+
|
|
50
|
+
response = await this.transport.send(
|
|
51
|
+
CLA_FRAMEWORK,
|
|
52
|
+
FrameworkIns.CONTINUE_INTERRUPTED,
|
|
53
|
+
0,
|
|
54
|
+
0,
|
|
55
|
+
commandResponse,
|
|
56
|
+
[0x9000, 0xe000]
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return response.slice(0, -2); // drop the status word (can only be 0x9000 at this point)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getPubkey(display: boolean, pathElements: number[]): Promise<string> {
|
|
63
|
+
if (pathElements.length > 6) {
|
|
64
|
+
throw new Error("Path too long. At most 6 levels allowed.");
|
|
65
|
+
}
|
|
66
|
+
const response = await this.makeRequest(
|
|
67
|
+
BitcoinIns.GET_PUBKEY,
|
|
68
|
+
Buffer.concat([
|
|
69
|
+
Buffer.of(display ? 1 : 0),
|
|
70
|
+
pathElementsToBuffer(pathElements),
|
|
71
|
+
])
|
|
72
|
+
);
|
|
73
|
+
return response.toString("ascii");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getWalletAddress(
|
|
77
|
+
walletPolicy: WalletPolicy,
|
|
78
|
+
walletHMAC: Buffer | null,
|
|
79
|
+
change: number,
|
|
80
|
+
addressIndex: number,
|
|
81
|
+
display: boolean
|
|
82
|
+
): Promise<string> {
|
|
83
|
+
if (change !== 0 && change !== 1)
|
|
84
|
+
throw new Error("Change can only be 0 or 1");
|
|
85
|
+
if (addressIndex < 0 || !Number.isInteger(addressIndex))
|
|
86
|
+
throw new Error("Invalid address index");
|
|
87
|
+
|
|
88
|
+
if (walletHMAC != null && walletHMAC.length != 32) {
|
|
89
|
+
throw new Error("Invalid HMAC length");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const clientInterpreter = new ClientCommandInterpreter();
|
|
93
|
+
clientInterpreter.addKnownList(
|
|
94
|
+
walletPolicy.keys.map((k) => Buffer.from(k, "ascii"))
|
|
95
|
+
);
|
|
96
|
+
clientInterpreter.addKnownPreimage(walletPolicy.serialize());
|
|
97
|
+
|
|
98
|
+
const addressIndexBuffer = Buffer.alloc(4);
|
|
99
|
+
addressIndexBuffer.writeUInt32BE(addressIndex, 0);
|
|
100
|
+
|
|
101
|
+
const response = await this.makeRequest(
|
|
102
|
+
BitcoinIns.GET_WALLET_ADDRESS,
|
|
103
|
+
Buffer.concat([
|
|
104
|
+
Buffer.of(display ? 1 : 0),
|
|
105
|
+
walletPolicy.getWalletId(),
|
|
106
|
+
walletHMAC || Buffer.alloc(32, 0),
|
|
107
|
+
Buffer.of(change),
|
|
108
|
+
addressIndexBuffer,
|
|
109
|
+
]),
|
|
110
|
+
clientInterpreter
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return response.toString("ascii");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async signPsbt(
|
|
117
|
+
psbt: PsbtV2,
|
|
118
|
+
walletPolicy: WalletPolicy,
|
|
119
|
+
walletHMAC: Buffer | null
|
|
120
|
+
): Promise<Map<number, Buffer>> {
|
|
121
|
+
const merkelizedPsbt = new MerkelizedPsbt(psbt);
|
|
122
|
+
|
|
123
|
+
if (walletHMAC != null && walletHMAC.length != 32) {
|
|
124
|
+
throw new Error("Invalid HMAC length");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const clientInterpreter = new ClientCommandInterpreter();
|
|
128
|
+
|
|
129
|
+
// prepare ClientCommandInterpreter
|
|
130
|
+
clientInterpreter.addKnownList(
|
|
131
|
+
walletPolicy.keys.map((k) => Buffer.from(k, "ascii"))
|
|
132
|
+
);
|
|
133
|
+
clientInterpreter.addKnownPreimage(walletPolicy.serialize());
|
|
134
|
+
|
|
135
|
+
clientInterpreter.addKnownMapping(merkelizedPsbt.globalMerkleMap);
|
|
136
|
+
for (const map of merkelizedPsbt.inputMerkleMaps) {
|
|
137
|
+
clientInterpreter.addKnownMapping(map);
|
|
138
|
+
}
|
|
139
|
+
for (const map of merkelizedPsbt.outputMerkleMaps) {
|
|
140
|
+
clientInterpreter.addKnownMapping(map);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
clientInterpreter.addKnownList(merkelizedPsbt.inputMapCommitments);
|
|
144
|
+
const inputMapsRoot = new Merkle(
|
|
145
|
+
merkelizedPsbt.inputMapCommitments.map((m) => hashLeaf(m))
|
|
146
|
+
).getRoot();
|
|
147
|
+
clientInterpreter.addKnownList(merkelizedPsbt.outputMapCommitments);
|
|
148
|
+
const outputMapsRoot = new Merkle(
|
|
149
|
+
merkelizedPsbt.outputMapCommitments.map((m) => hashLeaf(m))
|
|
150
|
+
).getRoot();
|
|
151
|
+
|
|
152
|
+
await this.makeRequest(
|
|
153
|
+
BitcoinIns.SIGN_PSBT,
|
|
154
|
+
Buffer.concat([
|
|
155
|
+
merkelizedPsbt.getGlobalKeysValuesRoot(),
|
|
156
|
+
createVarint(merkelizedPsbt.getGlobalInputCount()),
|
|
157
|
+
inputMapsRoot,
|
|
158
|
+
createVarint(merkelizedPsbt.getGlobalOutputCount()),
|
|
159
|
+
outputMapsRoot,
|
|
160
|
+
walletPolicy.getWalletId(),
|
|
161
|
+
walletHMAC || Buffer.alloc(32, 0),
|
|
162
|
+
]),
|
|
163
|
+
clientInterpreter
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const yielded = clientInterpreter.getYielded();
|
|
167
|
+
|
|
168
|
+
const ret: Map<number, Buffer> = new Map();
|
|
169
|
+
for (const inputAndSig of yielded) {
|
|
170
|
+
ret[inputAndSig[0]] = inputAndSig.slice(1);
|
|
171
|
+
}
|
|
172
|
+
return ret;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async getMasterFingerprint(): Promise<Buffer> {
|
|
176
|
+
return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.of());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { crypto } from "bitcoinjs-lib";
|
|
2
|
+
import { createVarint } from "../varint";
|
|
3
|
+
import { hashLeaf, Merkle } from "./merkle";
|
|
4
|
+
import { MerkleMap } from "./merkleMap";
|
|
5
|
+
|
|
6
|
+
enum ClientCommandCode {
|
|
7
|
+
YIELD = 0x10,
|
|
8
|
+
GET_PREIMAGE = 0x40,
|
|
9
|
+
GET_MERKLE_LEAF_PROOF = 0x41,
|
|
10
|
+
GET_MERKLE_LEAF_INDEX = 0x42,
|
|
11
|
+
GET_MORE_ELEMENTS = 0xa0,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
abstract class ClientCommand {
|
|
15
|
+
abstract code: ClientCommandCode;
|
|
16
|
+
abstract execute(request: Buffer): Buffer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class YieldCommand extends ClientCommand {
|
|
20
|
+
private results: Buffer[];
|
|
21
|
+
|
|
22
|
+
code = ClientCommandCode.YIELD;
|
|
23
|
+
|
|
24
|
+
constructor(results: Buffer[]) {
|
|
25
|
+
super();
|
|
26
|
+
this.results = results;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
execute(request: Buffer): Buffer {
|
|
30
|
+
this.results.push(Buffer.from(request.subarray(1)));
|
|
31
|
+
return Buffer.from("");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class GetPreimageCommand extends ClientCommand {
|
|
36
|
+
private known_preimages: Map<string, Buffer>;
|
|
37
|
+
private queue: Buffer[];
|
|
38
|
+
|
|
39
|
+
code = ClientCommandCode.GET_PREIMAGE;
|
|
40
|
+
|
|
41
|
+
constructor(known_preimages: Map<string, Buffer>, queue: Buffer[]) {
|
|
42
|
+
super();
|
|
43
|
+
this.known_preimages = known_preimages;
|
|
44
|
+
this.queue = queue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
execute(request: Buffer): Buffer {
|
|
48
|
+
const req = request.subarray(1);
|
|
49
|
+
|
|
50
|
+
// we expect no more data to read
|
|
51
|
+
if (req.length != 1 + 32) {
|
|
52
|
+
throw new Error("Invalid request, unexpected trailing data");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (req[0] != 0) {
|
|
56
|
+
throw new Error("Unsupported request, the first byte should be 0");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// read the hash
|
|
60
|
+
const hash = Buffer.alloc(32);
|
|
61
|
+
for (let i = 0; i < 32; i++) {
|
|
62
|
+
hash[i] = req[1 + i];
|
|
63
|
+
}
|
|
64
|
+
const req_hash_hex = hash.toString("hex");
|
|
65
|
+
|
|
66
|
+
const known_preimage = this.known_preimages.get(req_hash_hex);
|
|
67
|
+
if (known_preimage != undefined) {
|
|
68
|
+
const preimage_len_varint = createVarint(known_preimage.length);
|
|
69
|
+
|
|
70
|
+
// We can send at most 255 - len(preimage_len_out) - 1 bytes in a single message;
|
|
71
|
+
// the rest will be stored in the queue for GET_MORE_ELEMENTS
|
|
72
|
+
const max_payload_size = 255 - preimage_len_varint.length - 1;
|
|
73
|
+
|
|
74
|
+
const payload_size = Math.min(max_payload_size, known_preimage.length);
|
|
75
|
+
|
|
76
|
+
if (payload_size < known_preimage.length) {
|
|
77
|
+
for (let i = payload_size; i < known_preimage.length; i++) {
|
|
78
|
+
this.queue.push(Buffer.from([known_preimage[i]]));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Buffer.concat([
|
|
83
|
+
preimage_len_varint,
|
|
84
|
+
Buffer.from([payload_size]),
|
|
85
|
+
known_preimage.subarray(0, payload_size),
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw Error(`Requested unknown preimage for: ${req_hash_hex}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class GetMerkleLeafProofCommand extends ClientCommand {
|
|
94
|
+
private known_trees: Map<string, Merkle>;
|
|
95
|
+
private queue: Buffer[];
|
|
96
|
+
|
|
97
|
+
code = ClientCommandCode.GET_MERKLE_LEAF_PROOF;
|
|
98
|
+
|
|
99
|
+
constructor(known_trees: Map<string, Merkle>, queue: Buffer[]) {
|
|
100
|
+
super();
|
|
101
|
+
this.known_trees = known_trees;
|
|
102
|
+
this.queue = queue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
execute(request: Buffer): Buffer {
|
|
106
|
+
const req = request.subarray(1);
|
|
107
|
+
|
|
108
|
+
if (req.length != 32 + 4 + 4) {
|
|
109
|
+
throw new Error("Invalid request, unexpected trailing data");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// read the hash
|
|
113
|
+
const hash = Buffer.alloc(32);
|
|
114
|
+
for (let i = 0; i < 32; i++) {
|
|
115
|
+
hash[i] = req.readUInt8(i);
|
|
116
|
+
}
|
|
117
|
+
const hash_hex = hash.toString("hex");
|
|
118
|
+
|
|
119
|
+
const tree_size = req.readUInt32BE(32);
|
|
120
|
+
const leaf_index = req.readUInt32BE(32 + 4);
|
|
121
|
+
|
|
122
|
+
const mt = this.known_trees.get(hash_hex);
|
|
123
|
+
if (!mt) {
|
|
124
|
+
throw Error(`Requested Merkle leaf proof for unknown tree: ${hash_hex}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (leaf_index >= tree_size || mt.size() != tree_size) {
|
|
128
|
+
throw Error("Invalid index or tree size.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (this.queue.length != 0) {
|
|
132
|
+
throw Error(
|
|
133
|
+
"This command should not execute when the queue is not empty."
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const proof = mt.getProof(leaf_index);
|
|
138
|
+
|
|
139
|
+
const n_response_elements = Math.min(
|
|
140
|
+
Math.floor((255 - 32 - 1 - 1) / 32),
|
|
141
|
+
proof.length
|
|
142
|
+
);
|
|
143
|
+
const n_leftover_elements = proof.length - n_response_elements;
|
|
144
|
+
|
|
145
|
+
// Add to the queue any proof elements that do not fit the response
|
|
146
|
+
if (n_leftover_elements > 0) {
|
|
147
|
+
this.queue.push(...proof.slice(-n_leftover_elements));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Buffer.concat([
|
|
151
|
+
mt.getLeafHash(leaf_index),
|
|
152
|
+
Buffer.from([proof.length]),
|
|
153
|
+
Buffer.from([n_response_elements]),
|
|
154
|
+
...proof.slice(0, n_response_elements),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class GetMerkleLeafIndexCommand extends ClientCommand {
|
|
160
|
+
private known_trees: Map<string, Merkle>;
|
|
161
|
+
|
|
162
|
+
code = ClientCommandCode.GET_MERKLE_LEAF_INDEX;
|
|
163
|
+
|
|
164
|
+
constructor(known_trees: Map<string, Merkle>) {
|
|
165
|
+
super();
|
|
166
|
+
this.known_trees = known_trees;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
execute(request: Buffer): Buffer {
|
|
170
|
+
const req = request.subarray(1);
|
|
171
|
+
|
|
172
|
+
if (req.length != 32 + 32) {
|
|
173
|
+
throw new Error("Invalid request, unexpected trailing data");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// read the root hash
|
|
177
|
+
const root_hash = Buffer.alloc(32);
|
|
178
|
+
for (let i = 0; i < 32; i++) {
|
|
179
|
+
root_hash[i] = req.readUInt8(i);
|
|
180
|
+
}
|
|
181
|
+
const root_hash_hex = root_hash.toString("hex");
|
|
182
|
+
|
|
183
|
+
// read the leaf hash
|
|
184
|
+
const leef_hash = Buffer.alloc(32);
|
|
185
|
+
for (let i = 0; i < 32; i++) {
|
|
186
|
+
leef_hash[i] = req.readUInt8(32 + i);
|
|
187
|
+
}
|
|
188
|
+
const leef_hash_hex = leef_hash.toString("hex");
|
|
189
|
+
|
|
190
|
+
const mt = this.known_trees.get(root_hash_hex);
|
|
191
|
+
if (!mt) {
|
|
192
|
+
throw Error(
|
|
193
|
+
`Requested Merkle leaf index for unknown root: ${root_hash_hex}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let leaf_index = 0;
|
|
198
|
+
let found = 0;
|
|
199
|
+
for (let i = 0; i < mt.size(); i++) {
|
|
200
|
+
if (mt.getLeafHash(i).toString("hex") == leef_hash_hex) {
|
|
201
|
+
found = 1;
|
|
202
|
+
leaf_index = i;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return Buffer.concat([Buffer.from([found]), createVarint(leaf_index)]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class GetMoreElementsCommand extends ClientCommand {
|
|
211
|
+
queue: Buffer[];
|
|
212
|
+
|
|
213
|
+
code = ClientCommandCode.GET_MORE_ELEMENTS;
|
|
214
|
+
|
|
215
|
+
constructor(queue: Buffer[]) {
|
|
216
|
+
super();
|
|
217
|
+
this.queue = queue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
execute(request: Buffer): Buffer {
|
|
221
|
+
if (request.length != 1) {
|
|
222
|
+
throw new Error("Invalid request, unexpected trailing data");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.queue.length === 0) {
|
|
226
|
+
throw new Error("No elements to get");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// all elements should have the same length
|
|
230
|
+
const element_len = this.queue[0].length;
|
|
231
|
+
if (this.queue.some((el) => el.length != element_len)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"The queue contains elements with different byte length, which is not expected"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const max_elements = Math.floor(253 / element_len);
|
|
238
|
+
const n_returned_elements = Math.min(max_elements, this.queue.length);
|
|
239
|
+
|
|
240
|
+
const returned_elements = this.queue.splice(0, n_returned_elements);
|
|
241
|
+
|
|
242
|
+
return Buffer.concat([
|
|
243
|
+
Buffer.from([n_returned_elements]),
|
|
244
|
+
Buffer.from([element_len]),
|
|
245
|
+
...returned_elements,
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export class ClientCommandInterpreter {
|
|
251
|
+
private roots: Map<string, Merkle> = new Map();
|
|
252
|
+
private preimages: Map<string, Buffer> = new Map();
|
|
253
|
+
|
|
254
|
+
private yielded: Buffer[] = [];
|
|
255
|
+
|
|
256
|
+
private queue: Buffer[] = [];
|
|
257
|
+
|
|
258
|
+
private commands: Map<ClientCommandCode, ClientCommand> = new Map();
|
|
259
|
+
|
|
260
|
+
constructor() {
|
|
261
|
+
const commands = [
|
|
262
|
+
new YieldCommand(this.yielded),
|
|
263
|
+
new GetPreimageCommand(this.preimages, this.queue),
|
|
264
|
+
new GetMerkleLeafIndexCommand(this.roots),
|
|
265
|
+
new GetMerkleLeafProofCommand(this.roots, this.queue),
|
|
266
|
+
new GetMoreElementsCommand(this.queue),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
for (const cmd of commands) {
|
|
270
|
+
if (this.commands.has(cmd.code)) {
|
|
271
|
+
throw new Error(`Multiple commands with code ${cmd.code}`);
|
|
272
|
+
}
|
|
273
|
+
this.commands.set(cmd.code, cmd);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getYielded(): Buffer[] {
|
|
278
|
+
return this.yielded;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
addKnownPreimage(preimage: Buffer): void {
|
|
282
|
+
this.preimages.set(crypto.sha256(preimage).toString("hex"), preimage);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
addKnownList(elements: Buffer[]): void {
|
|
286
|
+
for (const el of elements) {
|
|
287
|
+
const preimage = Buffer.concat([Buffer.from([0]), el]);
|
|
288
|
+
this.addKnownPreimage(preimage);
|
|
289
|
+
}
|
|
290
|
+
const mt = new Merkle(elements.map((el) => hashLeaf(el)));
|
|
291
|
+
this.roots.set(mt.getRoot().toString("hex"), mt);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
addKnownMapping(mm: MerkleMap): void {
|
|
295
|
+
this.addKnownList(mm.keys);
|
|
296
|
+
this.addKnownList(mm.values);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
execute(request: Buffer): Buffer {
|
|
300
|
+
if (request.length == 0) {
|
|
301
|
+
throw new Error("Unexpected empty command");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const cmdCode = request[0];
|
|
305
|
+
const cmd = this.commands.get(cmdCode);
|
|
306
|
+
if (!cmd) {
|
|
307
|
+
throw new Error(`Unexpected command code ${cmdCode}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return cmd.execute(request);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MerkleMap } from "./merkleMap";
|
|
2
|
+
import { PsbtV2 } from "./psbtv2";
|
|
3
|
+
|
|
4
|
+
export class MerkelizedPsbt extends PsbtV2 {
|
|
5
|
+
public globalMerkleMap: MerkleMap;
|
|
6
|
+
public inputMerkleMaps: MerkleMap[] = [];
|
|
7
|
+
public outputMerkleMaps: MerkleMap[] = [];
|
|
8
|
+
public inputMapCommitments: Buffer[];
|
|
9
|
+
public outputMapCommitments: Buffer[];
|
|
10
|
+
constructor(psbt: PsbtV2) {
|
|
11
|
+
super();
|
|
12
|
+
psbt.copy(this);
|
|
13
|
+
this.globalMerkleMap = MerkelizedPsbt.createMerkleMap(this.globalMap);
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < this.getGlobalInputCount(); i++) {
|
|
16
|
+
this.inputMerkleMaps.push(
|
|
17
|
+
MerkelizedPsbt.createMerkleMap(this.inputMaps[i])
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
this.inputMapCommitments = [...this.inputMerkleMaps.values()].map((v) =>
|
|
21
|
+
v.commitment()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < this.getGlobalOutputCount(); i++) {
|
|
25
|
+
this.outputMerkleMaps.push(
|
|
26
|
+
MerkelizedPsbt.createMerkleMap(this.outputMaps[i])
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
this.outputMapCommitments = [...this.outputMerkleMaps.values()].map((v) =>
|
|
30
|
+
v.commitment()
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
// These public functions are for MerkelizedPsbt.
|
|
34
|
+
getGlobalSize(): number {
|
|
35
|
+
return this.globalMap.size;
|
|
36
|
+
}
|
|
37
|
+
getGlobalKeysValuesRoot(): Buffer {
|
|
38
|
+
return this.globalMerkleMap.commitment();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static createMerkleMap(map: Map<string, Buffer>): MerkleMap {
|
|
42
|
+
const sortedKeysStrings = [...map.keys()].sort();
|
|
43
|
+
const values = sortedKeysStrings.map((k) => {
|
|
44
|
+
const v = map.get(k);
|
|
45
|
+
if (!v) {
|
|
46
|
+
throw new Error("No value for key " + k);
|
|
47
|
+
}
|
|
48
|
+
return v;
|
|
49
|
+
});
|
|
50
|
+
const sortedKeys = sortedKeysStrings.map((k) => Buffer.from(k, "hex"));
|
|
51
|
+
|
|
52
|
+
const merkleMap = new MerkleMap(sortedKeys, values);
|
|
53
|
+
return merkleMap;
|
|
54
|
+
}
|
|
55
|
+
}
|