Skip to content

Commit 959bf98

Browse files
feat(utxo-lib): psbt util functions for output proprietary key vals
TICKET: BTC-2183
1 parent eb46a79 commit 959bf98

File tree

2 files changed

+99
-23
lines changed

2 files changed

+99
-23
lines changed

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

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { decodeProprietaryKey, ProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal';
2-
import { PsbtInput } from 'bip174/src/lib/interfaces';
2+
import { PsbtInput, PsbtOutput, KeyValue } from 'bip174/src/lib/interfaces';
33
import { Psbt } from 'bitcoinjs-lib/src/psbt';
44

55
/**
@@ -38,21 +38,14 @@ export interface ProprietaryKeySearch {
3838
identifierEncoding?: BufferEncoding;
3939
}
4040

41-
/**
42-
* Search any data from psbt proprietary key value against keydata.
43-
* Default identifierEncoding is utf-8 for identifier.
44-
*/
45-
export function getPsbtInputProprietaryKeyVals(
46-
input: PsbtInput,
41+
function getProprietaryKeyValuesFromUnknownKeyValues(
42+
unknownKeyVals: KeyValue[],
4743
keySearch?: ProprietaryKeySearch
4844
): ProprietaryKeyValue[] {
49-
if (!input.unknownKeyVals?.length) {
50-
return [];
51-
}
5245
if (keySearch && keySearch.subtype === undefined && Buffer.isBuffer(keySearch.keydata)) {
5346
throw new Error('invalid proprietary key search filter combination. subtype is required');
5447
}
55-
const keyVals = input.unknownKeyVals.map(({ key, value }, i) => {
48+
const keyVals = unknownKeyVals.map(({ key, value }, i) => {
5649
return { key: decodeProprietaryKey(key), value };
5750
});
5851
return keyVals.filter((keyVal) => {
@@ -65,7 +58,28 @@ export function getPsbtInputProprietaryKeyVals(
6558
);
6659
});
6760
}
68-
61+
/**
62+
* Search any data from psbt proprietary key value against keydata.
63+
* Default identifierEncoding is utf-8 for identifier.
64+
*/
65+
export function getPsbtInputProprietaryKeyVals(
66+
input: PsbtInput,
67+
keySearch?: ProprietaryKeySearch
68+
): ProprietaryKeyValue[] {
69+
if (!input.unknownKeyVals?.length) {
70+
return [];
71+
}
72+
return getProprietaryKeyValuesFromUnknownKeyValues(input.unknownKeyVals, keySearch);
73+
}
74+
export function getPsbtOutputProprietaryKeyVals(
75+
output: PsbtOutput,
76+
keySearch?: ProprietaryKeySearch
77+
): ProprietaryKeyValue[] {
78+
if (!output.unknownKeyVals?.length) {
79+
return [];
80+
}
81+
return getProprietaryKeyValuesFromUnknownKeyValues(output.unknownKeyVals, keySearch);
82+
}
6983
/**
7084
* @return partialSig/tapScriptSig/MUSIG2_PARTIAL_SIG count iff input is not finalized
7185
*/
@@ -82,14 +96,12 @@ export function getPsbtInputSignatureCount(input: PsbtInput): number {
8296
}).length
8397
);
8498
}
85-
8699
/**
87100
* @return true iff PSBT input is finalized
88101
*/
89102
export function isPsbtInputFinalized(input: PsbtInput): boolean {
90103
return Buffer.isBuffer(input.finalScriptSig) || Buffer.isBuffer(input.finalScriptWitness);
91104
}
92-
93105
/**
94106
* @return true iff data starts with magic PSBT byte sequence
95107
* @param data byte array or hex string
@@ -105,7 +117,6 @@ export function isPsbt(data: Buffer | string): boolean {
105117
}
106118
return 5 <= data.length && data.readUInt32BE(0) === 0x70736274 && data.readUInt8(4) === 0xff;
107119
}
108-
109120
/**
110121
* First checks if the input is already a buffer that starts with the magic PSBT byte sequence.
111122
* If not, it checks if the input is a base64- or hex-encoded string that starts with PSBT header.
@@ -123,11 +134,9 @@ export function toPsbtBuffer(data: Buffer | string): Buffer {
123134
if (isPsbt(data)) {
124135
return data;
125136
}
126-
127137
// we could be dealing with a buffer that could be a hex or base64 encoded psbt
128138
data = data.toString('ascii');
129139
}
130-
131140
if (typeof data === 'string') {
132141
const encodings = ['hex', 'base64'] as const;
133142
for (const encoding of encodings) {
@@ -141,13 +150,10 @@ export function toPsbtBuffer(data: Buffer | string): Buffer {
141150
return buffer;
142151
}
143152
}
144-
145153
throw new Error(`data is not in any of the following formats: ${encodings.join(', ')}`);
146154
}
147-
148155
throw new Error('data must be a buffer or a string');
149156
}
150-
151157
/**
152158
* This function allows signing or validating a psbt with non-segwit inputs those do not contain nonWitnessUtxo.
153159
*/

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

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Transaction as ITransaction,
88
TransactionFromBuffer,
99
} from 'bip174/src/lib/interfaces';
10-
import { checkForInput } from 'bip174/src/lib/utils';
10+
import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
1111
import { BufferWriter, varuint } from 'bitcoinjs-lib/src/bufferutils';
1212
import { SessionKey } from '@brandonblack/musig';
1313
import { BIP32Factory, BIP32Interface } from 'bip32';
@@ -57,6 +57,7 @@ import { getTaprootOutputKey } from '../taproot';
5757
import {
5858
getPsbtInputProprietaryKeyVals,
5959
getPsbtInputSignatureCount,
60+
getPsbtOutputProprietaryKeyVals,
6061
ProprietaryKeySearch,
6162
ProprietaryKeySubtype,
6263
ProprietaryKeyValue,
@@ -1109,7 +1110,7 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11091110
}
11101111

11111112
/**
1112-
* To search any data from proprietary key value against keydata.
1113+
* To search any data from proprietary key value against keydata in the inputs.
11131114
* Default identifierEncoding is utf-8 for identifier.
11141115
*/
11151116
getProprietaryKeyVals(inputIndex: number, keySearch?: ProprietaryKeySearch): ProprietaryKeyValue[] {
@@ -1118,7 +1119,7 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11181119
}
11191120

11201121
/**
1121-
* To delete any data from proprietary key value.
1122+
* To delete any data from proprietary key value in PSBT input.
11221123
* Default identifierEncoding is utf-8 for identifier.
11231124
*/
11241125
deleteProprietaryKeyVals(inputIndex: number, keysToDelete?: ProprietaryKeySearch): this {
@@ -1142,6 +1143,75 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11421143
return this;
11431144
}
11441145

1146+
/**
1147+
* Adds a proprietary key value pair to PSBT output
1148+
* Default identifier is utf-8 for identifier
1149+
*/
1150+
addProprietaryKeyValToOutput(outputIndex: number, keyValueData: ProprietaryKeyValue): this {
1151+
const output = checkForOutput(this.data.outputs, outputIndex);
1152+
assert(output.unknownKeyVals);
1153+
return this.addUnknownKeyValToOutput(outputIndex, {
1154+
key: encodeProprietaryKey(keyValueData.key),
1155+
value: keyValueData.value,
1156+
});
1157+
}
1158+
1159+
/**
1160+
* To search any data from proprietary key value against keydata in the PSBT outputs.
1161+
* Default identifierEncoding is utf-8 for identifier.
1162+
*/
1163+
getOutputProprietaryKeyVals(outputIndex: number, keySearch?: ProprietaryKeySearch): ProprietaryKeyValue[] {
1164+
const output = checkForOutput(this.data.outputs, outputIndex);
1165+
return getPsbtOutputProprietaryKeyVals(output, keySearch);
1166+
}
1167+
1168+
/**
1169+
* Adds or updates (if exists) proprietary key value pair to PSBT output.
1170+
* Default identifierEncoding is utf-8 for identifier.
1171+
*/
1172+
addOrUpdateProprietaryKeyValsToOutput(outputIndex: number, keyValueData: ProprietaryKeyValue): this {
1173+
const output = checkForOutput(this.data.outputs, outputIndex);
1174+
const key = encodeProprietaryKey(keyValueData.key);
1175+
const { value } = keyValueData;
1176+
if (output.unknownKeyVals?.length) {
1177+
const ukvIndex = output.unknownKeyVals.findIndex((ukv) => ukv.key.equals(key));
1178+
if (ukvIndex > -1) {
1179+
output.unknownKeyVals[ukvIndex] = { key, value };
1180+
return this;
1181+
}
1182+
}
1183+
this.addUnknownKeyValToOutput(outputIndex, {
1184+
key,
1185+
value,
1186+
});
1187+
return this;
1188+
}
1189+
1190+
/**
1191+
* To delete any data from proprietary key value in PSBT output.
1192+
* Default identifierEncoding is utf-8 for identifier.
1193+
*/
1194+
deleteProprietaryKeyValsInOutput(outputIndex: number, keysToDelete?: ProprietaryKeySearch): this {
1195+
const output = checkForOutput(this.data.outputs, outputIndex);
1196+
if (!output.unknownKeyVals?.length) {
1197+
return this;
1198+
}
1199+
if (keysToDelete && keysToDelete.subtype === undefined && Buffer.isBuffer(keysToDelete.keydata)) {
1200+
throw new Error('invalid proprietary key search filter combination. subtype is required');
1201+
}
1202+
output.unknownKeyVals = output.unknownKeyVals.filter((keyValue, i) => {
1203+
const key = decodeProprietaryKey(keyValue.key);
1204+
return !(
1205+
keysToDelete === undefined ||
1206+
(keysToDelete.identifier === key.identifier &&
1207+
(keysToDelete.subtype === undefined ||
1208+
(keysToDelete.subtype === key.subtype &&
1209+
(!Buffer.isBuffer(keysToDelete.keydata) || keysToDelete.keydata.equals(key.keydata)))))
1210+
);
1211+
});
1212+
return this;
1213+
}
1214+
11451215
private createMusig2NonceForInput(
11461216
inputIndex: number,
11471217
keyPair: BIP32Interface,

0 commit comments

Comments
 (0)