Skip to content

Commit f1a1d61

Browse files
Merge pull request #6266 from BitGo/BTC-2183.outputProprietaryKeyVals-functions
feat(utxo-lib): Added util functions to add proprietary key vals to output of PSBT
2 parents eb46a79 + 405a5f6 commit f1a1d61

File tree

6 files changed

+264
-47
lines changed

6 files changed

+264
-47
lines changed

modules/utxo-lib/src/bitgo/Musig2.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@ import { Tuple } from './types';
1212
import { calculateTapTweak, tapTweakPubkey } from '../taproot';
1313
import { Transaction } from '../index';
1414
import { PsbtInput } from 'bip174/src/lib/interfaces';
15-
import {
16-
getPsbtInputProprietaryKeyVals,
17-
ProprietaryKeySubtype,
18-
ProprietaryKeyValue,
19-
PSBT_PROPRIETARY_IDENTIFIER,
20-
} from './PsbtUtil';
15+
import { getPsbtInputProprietaryKeyVals, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from './PsbtUtil';
16+
import { ProprietaryKeyValue } from './ProprietaryKeyValUtils';
2117

2218
/**
2319
* Participant key value object.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { PsbtInput, PsbtOutput } from 'bip174/src/lib/interfaces';
2+
import { decodeProprietaryKey, encodeProprietaryKey, ProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal';
3+
import { UtxoPsbt } from './UtxoPsbt';
4+
5+
/**
6+
* Psbt proprietary keydata object search fields.
7+
* <compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>
8+
*/
9+
export interface ProprietaryKeySearch {
10+
identifier: string;
11+
subtype?: number;
12+
keydata?: Buffer;
13+
identifierEncoding?: BufferEncoding;
14+
}
15+
/**
16+
* Psbt proprietary keydata object.
17+
* <compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>
18+
* => <bytes valuedata>
19+
*/
20+
export interface ProprietaryKeyValue {
21+
key: ProprietaryKey;
22+
value: Buffer;
23+
}
24+
25+
export function getProprietaryKeyValuesFromUnknownKeyValues(
26+
psbtField: PsbtInput | PsbtOutput,
27+
keySearch?: ProprietaryKeySearch
28+
): ProprietaryKeyValue[] {
29+
if (!psbtField.unknownKeyVals?.length) {
30+
return [];
31+
}
32+
33+
if (keySearch && keySearch.subtype === undefined && Buffer.isBuffer(keySearch.keydata)) {
34+
throw new Error('invalid proprietary key search filter combination. subtype is required');
35+
}
36+
const keyVals = psbtField.unknownKeyVals.map(({ key, value }, i) => {
37+
return { key: decodeProprietaryKey(key), value };
38+
});
39+
return keyVals.filter((keyVal) => {
40+
return (
41+
keySearch === undefined ||
42+
(keySearch.identifier === keyVal.key.identifier &&
43+
(keySearch.subtype === undefined ||
44+
(keySearch.subtype === keyVal.key.subtype &&
45+
(!Buffer.isBuffer(keySearch.keydata) || keySearch.keydata.equals(keyVal.key.keydata)))))
46+
);
47+
});
48+
}
49+
50+
export function deleteProprietaryKeyValuesFromUnknownKeyValues(
51+
psbtField: PsbtInput | PsbtOutput,
52+
keysToDelete?: ProprietaryKeySearch
53+
): void {
54+
if (!psbtField.unknownKeyVals?.length) {
55+
return;
56+
}
57+
58+
if (keysToDelete && keysToDelete.subtype === undefined && Buffer.isBuffer(keysToDelete.keydata)) {
59+
throw new Error('invalid proprietary key search filter combination. subtype is required');
60+
}
61+
psbtField.unknownKeyVals = psbtField.unknownKeyVals.filter((keyValue, i) => {
62+
const key = decodeProprietaryKey(keyValue.key);
63+
return !(
64+
keysToDelete === undefined ||
65+
(keysToDelete.identifier === key.identifier &&
66+
(keysToDelete.subtype === undefined ||
67+
(keysToDelete.subtype === key.subtype &&
68+
(!Buffer.isBuffer(keysToDelete.keydata) || keysToDelete.keydata.equals(key.keydata)))))
69+
);
70+
});
71+
}
72+
73+
export function updateProprietaryKeyValuesFromUnknownKeyValues(
74+
keyValueData: ProprietaryKeyValue,
75+
psbtField: PsbtInput | PsbtOutput
76+
): void {
77+
if (!psbtField.unknownKeyVals?.length) {
78+
return;
79+
}
80+
81+
const key = encodeProprietaryKey(keyValueData.key);
82+
const { value } = keyValueData;
83+
const ukvIndex = psbtField.unknownKeyVals.findIndex((ukv) => ukv.key.equals(key));
84+
if (ukvIndex <= -1) {
85+
throw new Error(`The Key-Value pair does not exist within the PSBT.`);
86+
}
87+
psbtField.unknownKeyVals[ukvIndex] = { key, value };
88+
}
89+
90+
export function addProprietaryKeyValuesFromUnknownKeyValues(
91+
psbt: UtxoPsbt,
92+
entry: string,
93+
index: number,
94+
keyValueData: ProprietaryKeyValue
95+
): void {
96+
if (index < 0) {
97+
throw new Error('Not a valid index within the PSBT to add proprietary key value.');
98+
}
99+
if (entry === 'input') {
100+
psbt.addUnknownKeyValToInput(index, {
101+
key: encodeProprietaryKey(keyValueData.key),
102+
value: keyValueData.value,
103+
});
104+
} else if (entry === 'output') {
105+
psbt.addUnknownKeyValToOutput(index, {
106+
key: encodeProprietaryKey(keyValueData.key),
107+
value: keyValueData.value,
108+
});
109+
} else {
110+
throw new Error("Not a valid PSBT entry, only valid for 'input' or 'output'.");
111+
}
112+
}

modules/utxo-lib/src/bitgo/PsbtUtil.ts

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { decodeProprietaryKey, ProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal';
2-
import { PsbtInput } from 'bip174/src/lib/interfaces';
1+
import { PsbtInput, PsbtOutput } from 'bip174/src/lib/interfaces';
32
import { Psbt } from 'bitcoinjs-lib/src/psbt';
43

4+
import {
5+
getProprietaryKeyValuesFromUnknownKeyValues,
6+
ProprietaryKeySearch,
7+
ProprietaryKeyValue,
8+
} from './ProprietaryKeyValUtils';
9+
510
/**
611
* bitgo proprietary key identifier
712
*/
@@ -17,27 +22,6 @@ export enum ProprietaryKeySubtype {
1722
MUSIG2_PARTIAL_SIG = 0x03,
1823
}
1924

20-
/**
21-
* Psbt proprietary keydata object.
22-
* <compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>
23-
* => <bytes valuedata>
24-
*/
25-
export interface ProprietaryKeyValue {
26-
key: ProprietaryKey;
27-
value: Buffer;
28-
}
29-
30-
/**
31-
* Psbt proprietary keydata object search fields.
32-
* <compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>
33-
*/
34-
export interface ProprietaryKeySearch {
35-
identifier: string;
36-
subtype?: number;
37-
keydata?: Buffer;
38-
identifierEncoding?: BufferEncoding;
39-
}
40-
4125
/**
4226
* Search any data from psbt proprietary key value against keydata.
4327
* Default identifierEncoding is utf-8 for identifier.
@@ -49,21 +33,17 @@ export function getPsbtInputProprietaryKeyVals(
4933
if (!input.unknownKeyVals?.length) {
5034
return [];
5135
}
52-
if (keySearch && keySearch.subtype === undefined && Buffer.isBuffer(keySearch.keydata)) {
53-
throw new Error('invalid proprietary key search filter combination. subtype is required');
36+
return getProprietaryKeyValuesFromUnknownKeyValues(input, keySearch);
37+
}
38+
39+
export function getPsbtOutputProprietaryKeyVals(
40+
output: PsbtOutput,
41+
keySearch?: ProprietaryKeySearch
42+
): ProprietaryKeyValue[] {
43+
if (!output.unknownKeyVals?.length) {
44+
return [];
5445
}
55-
const keyVals = input.unknownKeyVals.map(({ key, value }, i) => {
56-
return { key: decodeProprietaryKey(key), value };
57-
});
58-
return keyVals.filter((keyVal) => {
59-
return (
60-
keySearch === undefined ||
61-
(keySearch.identifier === keyVal.key.identifier &&
62-
(keySearch.subtype === undefined ||
63-
(keySearch.subtype === keyVal.key.subtype &&
64-
(!Buffer.isBuffer(keySearch.keydata) || keySearch.keydata.equals(keyVal.key.keydata)))))
65-
);
66-
});
46+
return getProprietaryKeyValuesFromUnknownKeyValues(output, keySearch);
6747
}
6848

6949
/**

modules/utxo-lib/src/bitgo/UtxoPsbt.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,10 @@ import { getTaprootOutputKey } from '../taproot';
5757
import {
5858
getPsbtInputProprietaryKeyVals,
5959
getPsbtInputSignatureCount,
60-
ProprietaryKeySearch,
6160
ProprietaryKeySubtype,
62-
ProprietaryKeyValue,
6361
PSBT_PROPRIETARY_IDENTIFIER,
6462
} from './PsbtUtil';
63+
import { ProprietaryKeySearch, ProprietaryKeyValue } from './ProprietaryKeyValUtils';
6564

6665
type SignatureParams = {
6766
/** When true, and add the second (last) nonce and signature for a taproot key
@@ -1076,6 +1075,8 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
10761075
}
10771076

10781077
/**
1078+
* @deprecated Please use the new method addProprietaryKeyVals(psbt, entry, index, keyValueData)
1079+
*
10791080
* Adds proprietary key value pair to PSBT input.
10801081
* Default identifierEncoding is utf-8 for identifier.
10811082
*/
@@ -1087,6 +1088,9 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
10871088
}
10881089

10891090
/**
1091+
* @deprecated Please use the new method addProprietaryKeyValuesToUnknownKeyValues(psbt, entry, index, keyValueData)
1092+
* or updateProprietaryKeyValuesToUnknownKeyValues(keyValueData)
1093+
*
10901094
* Adds or updates (if exists) proprietary key value pair to PSBT input.
10911095
* Default identifierEncoding is utf-8 for identifier.
10921096
*/
@@ -1109,7 +1113,10 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11091113
}
11101114

11111115
/**
1112-
* To search any data from proprietary key value against keydata.
1116+
* @deprecated Please use getProprietaryKeyValuesFromUnknownKeyValues(psbtField, keySearch). The new method
1117+
* allows for the retrieval of either input or output proprietary key values.
1118+
*
1119+
* To search any data from proprietary key value against keydata in the inputs.
11131120
* Default identifierEncoding is utf-8 for identifier.
11141121
*/
11151122
getProprietaryKeyVals(inputIndex: number, keySearch?: ProprietaryKeySearch): ProprietaryKeyValue[] {
@@ -1118,7 +1125,10 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11181125
}
11191126

11201127
/**
1121-
* To delete any data from proprietary key value.
1128+
* @deprecated Please use deleteProprietaryKeyValues(type, index, keysToDelete?). The new method
1129+
* allows for the deletion of either input or output proprietary key values.
1130+
*
1131+
* To delete any data from proprietary key value in PSBT input.
11221132
* Default identifierEncoding is utf-8 for identifier.
11231133
*/
11241134
deleteProprietaryKeyVals(inputIndex: number, keysToDelete?: ProprietaryKeySearch): this {

modules/utxo-lib/src/bitgo/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export * from './zcash';
2121
export * from './tnumber';
2222
export * from './litecoin';
2323
export * from './PsbtUtil';
24+
export * from './ProprietaryKeyValUtils';
2425

2526
import { PsbtInput } from 'bip174/src/lib/interfaces';
2627
/**
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as assert from 'assert';
2+
import { constructPsbt } from '../../../src/testutil';
3+
import { bip32, BIP32Interface, networks, testutil } from '../../../src';
4+
import { RootWalletKeys, PSBT_PROPRIETARY_IDENTIFIER } from '../../../src/bitgo';
5+
import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
6+
import {
7+
addProprietaryKeyValuesFromUnknownKeyValues,
8+
deleteProprietaryKeyValuesFromUnknownKeyValues,
9+
getProprietaryKeyValuesFromUnknownKeyValues,
10+
updateProprietaryKeyValuesFromUnknownKeyValues,
11+
} from '../../../src/bitgo/ProprietaryKeyValUtils';
12+
13+
const network = networks.bitcoin;
14+
const keys = [1, 2, 3].map((v) => bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network)) as BIP32Interface[];
15+
const rootWalletKeys = new RootWalletKeys([keys[0], keys[1], keys[2]]);
16+
const dummyKey1 = rootWalletKeys.deriveForChainAndIndex(50, 200);
17+
const dummyTapOutputKey = dummyKey1.user.publicKey.subarray(1, 33);
18+
const dummyTapInternalKey = dummyKey1.bitgo.publicKey.subarray(1, 33);
19+
20+
const invalidTapOutputKey = Buffer.alloc(1);
21+
const psbtInputs = testutil.inputScriptTypes.map((scriptType) => ({
22+
scriptType,
23+
value: BigInt(1000),
24+
}));
25+
const psbtOutputs = testutil.outputScriptTypes.map((scriptType) => ({
26+
scriptType,
27+
value: BigInt(900),
28+
}));
29+
30+
describe('Proprietary key value helper functions', () => {
31+
it('addProprietaryKeyValues to PSBT input unknown key vals', function () {
32+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
33+
const key = {
34+
identifier: 'DUMMY',
35+
subtype: 100,
36+
keydata: dummyTapOutputKey,
37+
};
38+
const input = checkForInput(psbt.data.inputs, 0);
39+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'input', 0, { key, value: dummyTapInternalKey });
40+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(input);
41+
assert.strictEqual(keyVal[0].key.identifier, 'DUMMY');
42+
});
43+
44+
it('addProprietaryKeyValues to PSBT output unknown key vals', function () {
45+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
46+
const key = {
47+
identifier: 'DUMMY',
48+
subtype: 100,
49+
keydata: dummyTapOutputKey,
50+
};
51+
const output = checkForOutput(psbt.data.outputs, 0);
52+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', 0, { key, value: dummyTapInternalKey });
53+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(output);
54+
assert.strictEqual(keyVal[0].key.identifier, 'DUMMY');
55+
});
56+
57+
it('addOrUpdateProprietaryKeyValues to PSBT input unknown key vals', () => {
58+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
59+
const key = {
60+
identifier: 'DUMMY',
61+
subtype: 100,
62+
keydata: dummyTapOutputKey,
63+
};
64+
const input = checkForInput(psbt.data.inputs, 0);
65+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'input', 0, { key, value: dummyTapInternalKey });
66+
updateProprietaryKeyValuesFromUnknownKeyValues({ key, value: invalidTapOutputKey }, input);
67+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(input);
68+
assert.strictEqual(keyVal[0].value, invalidTapOutputKey);
69+
});
70+
71+
it('addOrUpdateProprietaryKeyValues to PSBT output unknown key vals', () => {
72+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
73+
const key = {
74+
identifier: 'DUMMY',
75+
subtype: 100,
76+
keydata: dummyTapOutputKey,
77+
};
78+
const output = checkForOutput(psbt.data.outputs, 0);
79+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', 0, { key, value: dummyTapInternalKey });
80+
updateProprietaryKeyValuesFromUnknownKeyValues({ key, value: invalidTapOutputKey }, output);
81+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(output);
82+
assert.strictEqual(keyVal[0].value, invalidTapOutputKey);
83+
});
84+
85+
it('deleteProprietaryKeyValues in PSBT input unknown key vals', () => {
86+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
87+
const key = {
88+
identifier: 'DUMMY',
89+
subtype: 100,
90+
keydata: dummyTapOutputKey,
91+
};
92+
const input = checkForInput(psbt.data.inputs, 0);
93+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'input', 0, { key, value: dummyTapInternalKey });
94+
deleteProprietaryKeyValuesFromUnknownKeyValues(input, { identifier: PSBT_PROPRIETARY_IDENTIFIER });
95+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(input);
96+
assert.strictEqual(keyVal[0].key.identifier, 'DUMMY');
97+
});
98+
99+
it('deleteProprietaryKeyValues in PSBT output unknown key vals', () => {
100+
const psbt = constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
101+
const key = {
102+
identifier: 'DUMMY',
103+
subtype: 100,
104+
keydata: dummyTapOutputKey,
105+
};
106+
const key2 = {
107+
identifier: 'OTHER_DUMMY',
108+
subtype: 200,
109+
keydata: dummyTapOutputKey,
110+
};
111+
const output = checkForOutput(psbt.data.outputs, 0);
112+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', 0, { key, value: dummyTapInternalKey });
113+
addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', 0, { key: key2, value: dummyTapInternalKey });
114+
deleteProprietaryKeyValuesFromUnknownKeyValues(output, { identifier: 'DUMMY' });
115+
const keyVal = getProprietaryKeyValuesFromUnknownKeyValues(output);
116+
assert.strictEqual(keyVal[0].key.identifier, 'OTHER_DUMMY');
117+
});
118+
});

0 commit comments

Comments
 (0)