Skip to content

Commit 15b0d58

Browse files
feat: optional flag to treat bip32 paths as relative
BG-54756
1 parent f4b75c9 commit 15b0d58

19 files changed

Lines changed: 240 additions & 92 deletions

File tree

src/lib/converter/index.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ declare const inputs: {
2727
porCommitment: typeof porCommitment;
2828
witnessUtxo: typeof witnessUtxo;
2929
bip32Derivation: {
30-
decode: (keyVal: import("../interfaces").KeyValue) => import("../interfaces").Bip32Derivation;
30+
decode: (keyVal: import("../interfaces").KeyValue, bip32PathsAbsolute?: boolean | undefined) => import("../interfaces").Bip32Derivation;
3131
encode: (data: import("../interfaces").Bip32Derivation) => import("../interfaces").KeyValue;
3232
check: (data: any) => data is import("../interfaces").Bip32Derivation;
3333
expected: string;
@@ -52,7 +52,7 @@ declare const inputs: {
5252
tapScriptSig: typeof tapScriptSig;
5353
tapLeafScript: typeof tapLeafScript;
5454
tapBip32Derivation: {
55-
decode: (keyVal: import("../interfaces").KeyValue) => import("../interfaces").TapBip32Derivation;
55+
decode: (keyVal: import("../interfaces").KeyValue, bip32PathsAbsolute: boolean) => import("../interfaces").TapBip32Derivation;
5656
encode: (data: import("../interfaces").TapBip32Derivation) => import("../interfaces").KeyValue;
5757
check: (data: any) => data is import("../interfaces").TapBip32Derivation;
5858
expected: string;
@@ -69,7 +69,7 @@ declare const inputs: {
6969
};
7070
declare const outputs: {
7171
bip32Derivation: {
72-
decode: (keyVal: import("../interfaces").KeyValue) => import("../interfaces").Bip32Derivation;
72+
decode: (keyVal: import("../interfaces").KeyValue, bip32PathsAbsolute?: boolean | undefined) => import("../interfaces").Bip32Derivation;
7373
encode: (data: import("../interfaces").Bip32Derivation) => import("../interfaces").KeyValue;
7474
check: (data: any) => data is import("../interfaces").Bip32Derivation;
7575
expected: string;
@@ -91,7 +91,7 @@ declare const outputs: {
9191
};
9292
checkPubkey: (keyVal: import("../interfaces").KeyValue) => Buffer | undefined;
9393
tapBip32Derivation: {
94-
decode: (keyVal: import("../interfaces").KeyValue) => import("../interfaces").TapBip32Derivation;
94+
decode: (keyVal: import("../interfaces").KeyValue, bip32PathsAbsolute: boolean) => import("../interfaces").TapBip32Derivation;
9595
encode: (data: import("../interfaces").TapBip32Derivation) => import("../interfaces").KeyValue;
9696
check: (data: any) => data is import("../interfaces").TapBip32Derivation;
9797
expected: string;

src/lib/converter/shared/bip32Derivation.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="node" />
22
import { Bip32Derivation, KeyValue } from '../../interfaces';
33
export declare function makeConverter(TYPE_BYTE: number, isValidPubkey?: (pubkey: Buffer) => boolean): {
4-
decode: (keyVal: KeyValue) => Bip32Derivation;
4+
decode: (keyVal: KeyValue, bip32PathsAbsolute?: boolean) => Bip32Derivation;
55
encode: (data: Bip32Derivation) => KeyValue;
66
check: (data: any) => data is Bip32Derivation;
77
expected: string;

src/lib/converter/shared/bip32Derivation.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const isValidDERKey = pubkey =>
55
(pubkey.length === 33 && [2, 3].includes(pubkey[0])) ||
66
(pubkey.length === 65 && 4 === pubkey[0]);
77
function makeConverter(TYPE_BYTE, isValidPubkey = isValidDERKey) {
8-
function decode(keyVal) {
8+
function decode(keyVal, bip32PathsAbsolute = true) {
99
if (keyVal.key[0] !== TYPE_BYTE) {
1010
throw new Error(
1111
'Decode Error: could not decode bip32Derivation with key 0x' +
@@ -27,24 +27,34 @@ function makeConverter(TYPE_BYTE, isValidPubkey = isValidDERKey) {
2727
const data = {
2828
masterFingerprint: keyVal.value.slice(0, 4),
2929
pubkey,
30-
path: 'm',
30+
path: '',
3131
};
32+
const path = [];
33+
if (bip32PathsAbsolute) {
34+
path.push('m');
35+
}
3236
for (const i of range(keyVal.value.length / 4 - 1)) {
3337
const val = keyVal.value.readUInt32LE(i * 4 + 4);
3438
const isHard = !!(val & 0x80000000);
3539
const idx = val & 0x7fffffff;
36-
data.path += '/' + idx.toString(10) + (isHard ? "'" : '');
40+
path.push(idx.toString(10) + (isHard ? "'" : ''));
3741
}
42+
data.path = path.join('/');
3843
return data;
3944
}
4045
function encode(data) {
4146
const head = Buffer.from([TYPE_BYTE]);
4247
const key = Buffer.concat([head, data.pubkey]);
43-
const splitPath = data.path.split('/');
44-
const value = Buffer.allocUnsafe(splitPath.length * 4);
48+
const splitPath = data.path ? data.path.split('/') : [];
49+
const isAbsolutePath = splitPath[0] === 'm';
50+
const bufferSize = isAbsolutePath
51+
? splitPath.length * 4
52+
: (splitPath.length + 1) * 4;
53+
const value = Buffer.allocUnsafe(bufferSize);
4554
data.masterFingerprint.copy(value, 0);
4655
let offset = 4;
47-
splitPath.slice(1).forEach(level => {
56+
const writingPath = isAbsolutePath ? splitPath.slice(1) : splitPath;
57+
writingPath.forEach(level => {
4858
const isHard = level.slice(-1) === "'";
4959
let num = 0x7fffffff & parseInt(isHard ? level.slice(0, -1) : level, 10);
5060
if (isHard) num += 0x80000000;

src/lib/converter/shared/tapBip32Derivation.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { KeyValue, TapBip32Derivation } from '../../interfaces';
22
export declare function makeConverter(TYPE_BYTE: number): {
3-
decode: (keyVal: KeyValue) => TapBip32Derivation;
3+
decode: (keyVal: KeyValue, bip32PathsAbsolute: boolean) => TapBip32Derivation;
44
encode: (data: TapBip32Derivation) => KeyValue;
55
check: (data: any) => data is TapBip32Derivation;
66
expected: string;

src/lib/converter/shared/tapBip32Derivation.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ const bip32Derivation = require('./bip32Derivation');
55
const isValidBIP340Key = pubkey => pubkey.length === 32;
66
function makeConverter(TYPE_BYTE) {
77
const parent = bip32Derivation.makeConverter(TYPE_BYTE, isValidBIP340Key);
8-
function decode(keyVal) {
8+
function decode(keyVal, bip32PathsAbsolute = true) {
99
const nHashes = varuint.decode(keyVal.value);
1010
const nHashesLen = varuint.encodingLength(nHashes);
11-
const base = parent.decode({
12-
key: keyVal.key,
13-
value: keyVal.value.slice(nHashesLen + nHashes * 32),
14-
});
11+
const base = parent.decode(
12+
{
13+
key: keyVal.key,
14+
value: keyVal.value.slice(nHashesLen + nHashes * 32),
15+
},
16+
bip32PathsAbsolute,
17+
);
1518
const leafHashes = new Array(nHashes);
1619
for (let i = 0, _offset = nHashesLen; i < nHashes; i++, _offset += 32) {
1720
leafHashes[i] = keyVal.value.slice(_offset, _offset + 32);

src/lib/parser/fromBuffer.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
/// <reference types="node" />
22
import { KeyValue, Transaction, TransactionFromBuffer } from '../interfaces';
33
import { PsbtAttributes } from './index';
4-
export declare function psbtFromBuffer(buffer: Buffer, txGetter: TransactionFromBuffer): PsbtAttributes;
4+
export declare function psbtFromBuffer(buffer: Buffer, txGetter: TransactionFromBuffer, { bip32PathsAbsolute }?: {
5+
bip32PathsAbsolute?: boolean | undefined;
6+
}): PsbtAttributes;
57
interface PsbtFromKeyValsArg {
68
globalMapKeyVals: KeyValue[];
79
inputKeyVals: KeyValue[][];
810
outputKeyVals: KeyValue[][];
911
}
1012
export declare function checkKeyBuffer(type: string, keyBuf: Buffer, keyNum: number): void;
11-
export declare function psbtFromKeyVals(unsignedTx: Transaction, { globalMapKeyVals, inputKeyVals, outputKeyVals }: PsbtFromKeyValsArg): PsbtAttributes;
13+
export declare function psbtFromKeyVals(unsignedTx: Transaction, { globalMapKeyVals, inputKeyVals, outputKeyVals }: PsbtFromKeyValsArg, { bip32PathsAbsolute }?: {
14+
bip32PathsAbsolute?: boolean | undefined;
15+
}): PsbtAttributes;
1216
export {};

src/lib/parser/fromBuffer.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const convert = require('../converter');
44
const tools_1 = require('../converter/tools');
55
const varuint = require('../converter/varint');
66
const typeFields_1 = require('../typeFields');
7-
function psbtFromBuffer(buffer, txGetter) {
7+
function psbtFromBuffer(buffer, txGetter, { bip32PathsAbsolute = true } = {}) {
88
let offset = 0;
99
function varSlice() {
1010
const keyLen = varuint.decode(buffer, offset);
@@ -114,11 +114,15 @@ function psbtFromBuffer(buffer, txGetter) {
114114
}
115115
outputKeyVals.push(output);
116116
}
117-
return psbtFromKeyVals(unsignedTx, {
118-
globalMapKeyVals,
119-
inputKeyVals,
120-
outputKeyVals,
121-
});
117+
return psbtFromKeyVals(
118+
unsignedTx,
119+
{
120+
globalMapKeyVals,
121+
inputKeyVals,
122+
outputKeyVals,
123+
},
124+
{ bip32PathsAbsolute },
125+
);
122126
}
123127
exports.psbtFromBuffer = psbtFromBuffer;
124128
function checkKeyBuffer(type, keyBuf, keyNum) {
@@ -132,6 +136,7 @@ exports.checkKeyBuffer = checkKeyBuffer;
132136
function psbtFromKeyVals(
133137
unsignedTx,
134138
{ globalMapKeyVals, inputKeyVals, outputKeyVals },
139+
{ bip32PathsAbsolute = true } = {},
135140
) {
136141
// That was easy :-)
137142
const globalMap = {
@@ -244,7 +249,7 @@ function psbtFromKeyVals(
244249
input.bip32Derivation = [];
245250
}
246251
input.bip32Derivation.push(
247-
convert.inputs.bip32Derivation.decode(keyVal),
252+
convert.inputs.bip32Derivation.decode(keyVal, bip32PathsAbsolute),
248253
);
249254
break;
250255
case typeFields_1.InputTypes.FINAL_SCRIPTSIG:
@@ -298,7 +303,10 @@ function psbtFromKeyVals(
298303
input.tapBip32Derivation = [];
299304
}
300305
input.tapBip32Derivation.push(
301-
convert.inputs.tapBip32Derivation.decode(keyVal),
306+
convert.inputs.tapBip32Derivation.decode(
307+
keyVal,
308+
bip32PathsAbsolute,
309+
),
302310
);
303311
break;
304312
case typeFields_1.InputTypes.TAP_INTERNAL_KEY:
@@ -357,7 +365,7 @@ function psbtFromKeyVals(
357365
output.bip32Derivation = [];
358366
}
359367
output.bip32Derivation.push(
360-
convert.outputs.bip32Derivation.decode(keyVal),
368+
convert.outputs.bip32Derivation.decode(keyVal, bip32PathsAbsolute),
361369
);
362370
break;
363371
case typeFields_1.OutputTypes.TAP_INTERNAL_KEY:
@@ -381,7 +389,10 @@ function psbtFromKeyVals(
381389
output.tapBip32Derivation = [];
382390
}
383391
output.tapBip32Derivation.push(
384-
convert.outputs.tapBip32Derivation.decode(keyVal),
392+
convert.outputs.tapBip32Derivation.decode(
393+
keyVal,
394+
bip32PathsAbsolute,
395+
),
385396
);
386397
break;
387398
default:

src/lib/psbt.d.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
/// <reference types="node" />
22
import { KeyValue, PsbtGlobal, PsbtGlobalUpdate, PsbtInput, PsbtInputExtended, PsbtInputUpdate, PsbtOutput, PsbtOutputExtended, PsbtOutputUpdate, Transaction, TransactionFromBuffer } from './interfaces';
33
export declare class Psbt {
4-
static fromBase64<T extends typeof Psbt>(this: T, data: string, txFromBuffer: TransactionFromBuffer): InstanceType<T>;
5-
static fromHex<T extends typeof Psbt>(this: T, data: string, txFromBuffer: TransactionFromBuffer): InstanceType<T>;
6-
static fromBuffer<T extends typeof Psbt>(this: T, buffer: Buffer, txFromBuffer: TransactionFromBuffer): InstanceType<T>;
4+
static fromBase64<T extends typeof Psbt>(this: T, data: string, txFromBuffer: TransactionFromBuffer, { bip32PathsAbsolute }?: {
5+
bip32PathsAbsolute?: boolean | undefined;
6+
}): InstanceType<T>;
7+
static fromHex<T extends typeof Psbt>(this: T, data: string, txFromBuffer: TransactionFromBuffer, { bip32PathsAbsolute }?: {
8+
bip32PathsAbsolute?: boolean | undefined;
9+
}): InstanceType<T>;
10+
static fromBuffer<T extends typeof Psbt>(this: T, buffer: Buffer, txFromBuffer: TransactionFromBuffer, { bip32PathsAbsolute }?: {
11+
bip32PathsAbsolute?: boolean | undefined;
12+
}): InstanceType<T>;
713
readonly inputs: PsbtInput[];
814
readonly outputs: PsbtOutput[];
915
readonly globalMap: PsbtGlobal;

src/lib/psbt.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ class Psbt {
1212
unsignedTx: tx,
1313
};
1414
}
15-
static fromBase64(data, txFromBuffer) {
15+
static fromBase64(data, txFromBuffer, { bip32PathsAbsolute = true } = {}) {
1616
const buffer = Buffer.from(data, 'base64');
17-
return this.fromBuffer(buffer, txFromBuffer);
17+
return this.fromBuffer(buffer, txFromBuffer, { bip32PathsAbsolute });
1818
}
19-
static fromHex(data, txFromBuffer) {
19+
static fromHex(data, txFromBuffer, { bip32PathsAbsolute = true } = {}) {
2020
const buffer = Buffer.from(data, 'hex');
21-
return this.fromBuffer(buffer, txFromBuffer);
21+
return this.fromBuffer(buffer, txFromBuffer, { bip32PathsAbsolute });
2222
}
23-
static fromBuffer(buffer, txFromBuffer) {
24-
const results = parser_1.psbtFromBuffer(buffer, txFromBuffer);
23+
static fromBuffer(buffer, txFromBuffer, { bip32PathsAbsolute = true } = {}) {
24+
const results = parser_1.psbtFromBuffer(buffer, txFromBuffer, {
25+
bip32PathsAbsolute,
26+
});
2527
const psbt = new this(results.globalMap.unsignedTx);
2628
Object.assign(psbt, results);
2729
return psbt;

src/tests/fixtures/update.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ exports.fixtures = [
6565
pubkey: b(
6666
'023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73',
6767
),
68-
path: "m/0'/0'/3'",
68+
path: "0'/0'/3'",
6969
},
7070
{
7171
masterFingerprint: b('d90c6a4f'),
@@ -96,7 +96,7 @@ exports.fixtures = [
9696
pubkey: b(
9797
'027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096',
9898
),
99-
path: "m/0'/0'/5'",
99+
path: "0'/0'/5'",
100100
},
101101
],
102102
},

0 commit comments

Comments
 (0)