Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utxo-lib): add toPsbtBuffer #4957

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions modules/utxo-lib/src/bitgo/PsbtUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,48 @@ export function isPsbt(data: Buffer | string): boolean {
return 5 <= data.length && data.readUInt32BE(0) === 0x70736274 && data.readUInt8(4) === 0xff;
}

/**
* First checks if the input is already a buffer that starts with the magic PSBT byte sequence.
* If not, it checks if the input is a base64- or hex-encoded string that starts with PSBT header.
*
* This function is useful when reading a file that could be in any of the above formats or when
* dealing with a request that could contain a hex or base64 encoded PSBT.
*
* @param data
* @return buffer that starts with the magic PSBT byte sequence
* @throws Error when conversion is not possible
*/
export function toPsbtBuffer(data: Buffer | string): Buffer {
if (Buffer.isBuffer(data)) {
// we are dealing with a buffer that looks like a psbt already
if (isPsbt(data)) {
return data;
}

// we could be dealing with a buffer that could be a hex or base64 encoded psbt
data = data.toString('ascii');
}

if (typeof data === 'string') {
const encodings = ['hex', 'base64'] as const;
for (const encoding of encodings) {
let buffer: Buffer;
try {
buffer = Buffer.from(data, encoding);
} catch (e) {
continue;
}
if (isPsbt(buffer)) {
return buffer;
}
}

throw new Error(`data is not in any of the following formats: ${encodings.join(', ')}`);
}

throw new Error('data must be a buffer or a string');
}

/**
* This function allows signing or validating a psbt with non-segwit inputs those do not contain nonWitnessUtxo.
*/
Expand Down
20 changes: 20 additions & 0 deletions modules/utxo-lib/src/bitgo/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ZcashPsbt } from './zcash/ZcashPsbt';
import { ZcashTransactionBuilder } from './zcash/ZcashTransactionBuilder';
import { ZcashNetwork, ZcashTransaction } from './zcash/ZcashTransaction';
import { LitecoinPsbt, LitecoinTransaction, LitecoinTransactionBuilder } from './litecoin';
import { isPsbt, toPsbtBuffer } from './PsbtUtil';

export function createTransactionFromBuffer(
buf: Buffer,
Expand Down Expand Up @@ -87,7 +88,15 @@ export function createTransactionFromHex<TNumber extends number | bigint = numbe
return createTransactionFromBuffer<TNumber>(Buffer.from(hex, 'hex'), network, p);
}

/**
* @param buf - must start with the PSBT magic bytes
* @param network
* @param bip32PathsAbsolute
*/
export function createPsbtFromBuffer(buf: Buffer, network: Network, bip32PathsAbsolute = false): UtxoPsbt {
if (!isPsbt(buf)) {
throw new Error(`invalid psbt (does not start with 'psbt' magic bytes)`);
}
switch (getMainnet(network)) {
case networks.bitcoin:
case networks.bitcoincash:
Expand All @@ -108,6 +117,17 @@ export function createPsbtFromBuffer(buf: Buffer, network: Network, bip32PathsAb
throw new Error(`invalid network`);
}

/**
* Like createPsbtFromBuffer, but attempts hex and base64 decoding as well.
*
* @param buf
* @param network
* @param bip32PathsAbsolute
*/
export function createPsbtDecode(buf: Buffer | string, network: Network, bip32PathsAbsolute = false): UtxoPsbt {
return createPsbtFromBuffer(toPsbtBuffer(buf), network, bip32PathsAbsolute);
}

export function createPsbtFromHex(hex: string, network: Network, bip32PathsAbsolute = false): UtxoPsbt {
return createPsbtFromBuffer(Buffer.from(hex, 'hex'), network, bip32PathsAbsolute);
}
Expand Down
27 changes: 27 additions & 0 deletions modules/utxo-lib/test/bitgo/psbt/toPsbtBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as assert from 'assert';

import { toPsbtBuffer } from '../../../src/bitgo';

describe('bufferUtil', function () {
function variants(data: Buffer | string): (Buffer | string)[] {
return [
data,
data.toString('hex'),
Buffer.from(data.toString('hex')),
data.toString('base64'),
Buffer.from(data.toString('base64')),
];
}

it('should convert a buffer to a string', function () {
const psbt = Buffer.from('psbt\xff', 'ascii');
for (const v of variants(psbt)) {
assert.ok(toPsbtBuffer(v).equals(psbt));
}

const nonPsbt = Buffer.from('hello world', 'ascii');
for (const v of variants(nonPsbt)) {
assert.throws(() => toPsbtBuffer(v));
}
});
});
Loading