Skip to content

Commit

Permalink
feat: add psbt signing for v1 wallets
Browse files Browse the repository at this point in the history
This commit adds optional PSBT signing to the v1 wallet API.

Call the `signTransaction` method with a `psbt: string` (hex-encoded) argument
to co-sign a PSBT.

The method will return the signed PSBT as a hex-encoded string.

Issue: BTC-1351
  • Loading branch information
OttoAllmendinger committed Sep 25, 2024
1 parent 155a0c6 commit 2c01a77
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
38 changes: 38 additions & 0 deletions modules/sdk-api/src/v1/signPsbt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as utxolib from '@bitgo/utxo-lib';

import * as buildDebug from 'debug';

const debug = buildDebug('bitgo:v1:txb');

/**
* Co-sign a PSBT.
* Simply a wrapper around `utxolib.bitgo.createPsbtFromBuffer` and `psbt.signAllInputsHD`.
* @param params
*/
export function signPsbtRequest(params: { psbt: string; keychain: { xprv: string } } | unknown): {
psbt: string;
} {
if (typeof params !== 'object' || params === null) {
throw new Error(`invalid argument`);
}

if (!('psbt' in params) || typeof params.psbt !== 'string') {
throw new Error(`invalid params.psbt`);
}

if (!('keychain' in params) || typeof params.keychain !== 'object' || params.keychain === null) {
throw new Error(`invalid params.keychain`);
}

if (!('xprv' in params.keychain) || typeof params.keychain.xprv !== 'string') {
throw new Error(`invalid params.keychain.xprv`);
}

const psbt = utxolib.bitgo.createPsbtFromBuffer(Buffer.from(params.psbt, 'hex'), utxolib.networks.bitcoin);
const keypair = utxolib.bip32.fromBase58(params.keychain.xprv, utxolib.networks.bitcoin);
debug('signing PSBT with keychain %s', keypair.neutered().toBase58());
utxolib.bitgo.withUnsafeNonSegwit(psbt, () => psbt.signAllInputsHD(keypair));
return {
psbt: psbt.toBuffer().toString('hex'),
};
}
10 changes: 10 additions & 0 deletions modules/sdk-api/src/v1/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@bitgo/sdk-core';
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { signPsbtRequest } from './signPsbt';

const TransactionBuilder = require('./transactionBuilder');
const PendingApproval = require('./pendingapproval');
Expand Down Expand Up @@ -896,6 +897,15 @@ Wallet.prototype.createTransaction = function (params, callback) {
// callback(err, transaction)
Wallet.prototype.signTransaction = function (params, callback) {
params = _.extend({}, params);

if (params.psbt) {
try {
return callback(null, signPsbtRequest(params));
} catch (e) {
return callback(e);
}
}

common.validateParams(params, ['transactionHex'], [], callback);

if (!Array.isArray(params.unspents)) {
Expand Down
27 changes: 27 additions & 0 deletions modules/sdk-api/test/unit/v1/signPsbt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as assert from 'assert';
import * as utxolib from '@bitgo/utxo-lib';
import { signPsbtRequest } from '../../../src/v1/signPsbt';

describe('signPsbt', function () {
it('signs psbt', function () {
const keys = utxolib.testutil.getDefaultWalletKeys();
const psbt = utxolib.testutil.constructPsbt(
[{ scriptType: 'p2sh', value: BigInt(1e8) }],
[{ scriptType: 'p2sh', value: BigInt(1e8 - 1000) }],
utxolib.networks.bitcoin,
keys,
'unsigned'
);
const result = signPsbtRequest({
psbt: psbt.toHex(),
keychain: {
xprv: keys.triple[0].toBase58(),
},
});
const halfSignedPsbt = utxolib.bitgo.createPsbtFromBuffer(
Buffer.from(result.psbt, 'hex'),
utxolib.networks.bitcoin
);
assert.ok(halfSignedPsbt.validateSignaturesOfInputHD(0, keys.triple[0]));
});
});

0 comments on commit 2c01a77

Please sign in to comment.