Skip to content

Commit 01475ec

Browse files
Merge pull request #6445 from BitGo/BTC-2118.verify-proof
feat(abstract-utxo): verify paygo
2 parents 8bdf7ec + a3203bc commit 01475ec

File tree

6 files changed

+96
-1
lines changed

6 files changed

+96
-1
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
/modules/utxo-ord/ @BitGo/btc-team
2929
/modules/utxo-staking/ @BitGo/btc-team
3030
/modules/babylonlabs-io-btc-staking-ts @BitGo/btc-team
31+
/modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts @BitGo/btc-team
32+
/modules/bitgo/test/v2/unit/coins/payGoPSBTHexFixture/psbtHexProof.ts @BitGo/btc-team
3133

3234
# Lightning coin modules
3335
/modules/abstract-lightning/ @BitGo/btc-team

modules/abstract-utxo/src/transaction/fixedScript/explainTransaction.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as utxolib from '@bitgo/utxo-lib';
22
import { bip32, BIP32Interface, bitgo } from '@bitgo/utxo-lib';
33
import { Triple } from '@bitgo/sdk-core';
4+
import * as utxocore from '@bitgo/utxo-core';
45

56
import { Output, TransactionExplanation, FixedScriptWalletOutput } from '../../abstractUtxoCoin';
67
import { toExtendedAddressFormat } from '../recipient';
8+
import { getPayGoVerificationPubkey } from '../getPayGoVerificationPubkey';
79

810
export type ChangeAddressInfo = { address: string; chain: number; index: number };
911

@@ -144,7 +146,8 @@ export function explainPsbt<TNumber extends number | bigint, Tx extends bitgo.Ut
144146
pubs?: string[];
145147
txInfo?: { unspents?: bitgo.Unspent<TNumber>[] };
146148
},
147-
network: utxolib.Network
149+
network: utxolib.Network,
150+
{ strict = false }: { strict?: boolean } = {}
148151
): TransactionExplanation {
149152
const txOutputs = psbt.txOutputs;
150153

@@ -191,6 +194,51 @@ export function explainPsbt<TNumber extends number | bigint, Tx extends bitgo.Ut
191194
throw e;
192195
}
193196
}
197+
198+
/**
199+
* Extract PayGo address proof information from the PSBT if present
200+
* @returns Information about the PayGo proof, including the output index and address
201+
*/
202+
function getPayGoVerificationInfo(): { outputIndex: number; verificationPubkey: string } | undefined {
203+
let outputIndex: number | undefined = undefined;
204+
let address: string | undefined = undefined;
205+
// Check if this PSBT has any PayGo address proofs
206+
if (!utxocore.paygo.psbtOutputIncludesPaygoAddressProof(psbt)) {
207+
return undefined;
208+
}
209+
210+
// This pulls the pubkey depending on given network
211+
const verificationPubkey = getPayGoVerificationPubkey(network);
212+
// find which output index that contains the PayGo proof
213+
outputIndex = utxocore.paygo.getPayGoAddressProofOutputIndex(psbt);
214+
if (outputIndex === undefined || !verificationPubkey) {
215+
return undefined;
216+
}
217+
const output = txOutputs[outputIndex];
218+
address = utxolib.address.fromOutputScript(output.script, network);
219+
if (!address) {
220+
throw new Error(`Can not derive address ${address} Pay Go Attestation.`);
221+
}
222+
223+
return { outputIndex, verificationPubkey };
224+
}
225+
226+
const payGoVerificationInfo = getPayGoVerificationInfo();
227+
if (payGoVerificationInfo) {
228+
try {
229+
utxocore.paygo.verifyPayGoAddressProof(
230+
psbt,
231+
payGoVerificationInfo.outputIndex,
232+
utxolib.bip32.fromBase58(payGoVerificationInfo.verificationPubkey, utxolib.networks.bitcoin).publicKey
233+
);
234+
} catch (e) {
235+
if (strict) {
236+
throw e;
237+
}
238+
console.error(e);
239+
}
240+
}
241+
194242
const changeInfo = getChangeInfo();
195243
const tx = psbt.getUnsignedTx() as bitgo.UtxoTransaction<TNumber>;
196244
const common = explainCommon(tx, { ...params, changeInfo }, network);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as utxolib from '@bitgo/utxo-lib';
2+
3+
const BITGOPAYGOATTESTATIONPUBKEY =
4+
'xpub6BKRgmCPX5oQiJgJ6Vq6BF8tDvZhwQki5dVVQohckK2ZJXtxj8K6M9pavLwt9piW33hZz17SWmG8QWsjJ1tHdde2Fs5UA3DFbApCtbdaGKn';
5+
6+
/**
7+
* We want to return the verification pubkey from our statics that has our
8+
* verification pubkey.
9+
* @param network
10+
* @returns
11+
*/
12+
export function getPayGoVerificationPubkey(network: utxolib.Network): string | undefined {
13+
if (utxolib.isTestnet(network)) {
14+
return BITGOPAYGOATTESTATIONPUBKEY;
15+
} else if (utxolib.isMainnet(network)) {
16+
return undefined;
17+
}
18+
}

modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as sinon from 'sinon';
44
import { Wallet, UnexpectedAddressError, VerificationOptions } from '@bitgo/sdk-core';
55
import { TestBitGo } from '@bitgo/sdk-test';
66
import { BitGo } from '../../../../src/bitgo';
7+
import { psbtTxHex } from './payGoPSBTHexFixture/psbtHexProof';
78
import { AbstractUtxoCoin, UtxoWallet, Output, TransactionExplanation, TransactionParams } from '@bitgo/abstract-utxo';
89

910
describe('Abstract UTXO Coin:', () => {
@@ -583,4 +584,18 @@ describe('Abstract UTXO Coin:', () => {
583584
bitcoinMock.restore();
584585
});
585586
});
587+
588+
describe('Verify paygo output when explaining psbt transaction', function () {
589+
const bitgo: BitGo = TestBitGo.decorate(BitGo, { env: 'mock' });
590+
let coin: AbstractUtxoCoin;
591+
592+
beforeEach(() => {
593+
coin = bitgo.coin('tbtc4') as AbstractUtxoCoin;
594+
});
595+
596+
it('should detect and verify paygo address proof in PSBT', async function () {
597+
// Call explainTransaction
598+
await coin.explainTransaction(psbtTxHex);
599+
});
600+
});
586601
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// We got this psbt tx hex by getting a test wallet id in test env on wp,
2+
// called the build-transaction.js in examples/ with the wallet id
3+
export const psbtTxHex = {
4+
txHex:
5+
'70736274ff0100b4010000000176e65d40a648da1a3b9412d5b49cc309a37a6a7f3c9cd8b8f1f41a2fbbfd479f0100000000fdffffff03204e0000000000002251206f6b69b20d569b8b14500e3e2e6ba10573d1ce4ddcbc2d0b056219fe27b3ed3beb0d1e00000000002251205f4f20e046cca9808a211ee6e217ca3f9b4023b56e5b5875bccebe414746a2a600127a000000000022002037f34405b83128e4cdecb64f44bff64d0d1b6188d2d656782b136e3488eab1f2000000004f010488b21e00000000000000000067b8325a2303718652d085e2bb65e4c84e8a51eaa4f4ea56e11542290bd27875037cb4cea965ae9d7846addc2a534318a377436864f90a38e2619ca51513cf7a7b0488f255714f010488b21e000000000000000000a5d5ff1d68596719baad5a209925b5edfdc7899ca460a071b3c84bd5bc76b99303f9dc3f6f974f2c90c6d0b7b7c50bc4de49bb8105bfa27223babc909720c6191a043d4b2b964f010488b21e000000000000000000dd32781f8dff65567d76ba1c9485014f7eb07d9527a502cbe0dafb64c20961cd028a55ce3f0c98964dfc6814b1b55b2b77cc31d33a5ae1a994476ce8b20b1618530435eabe8d0001012bd36e980000000000225120866e64ab95028afde6e42cd0dcbd6b38f8dec74d3f3a7648e11259f64075321b0103040000000021163024116333a45124af6141080383eea1b756c1983a07d71d50a2c62d774b7b64150088f25571000000000000000029000000000000002116e5a56ea96f692990ef584ebecaa55acf8d7d4e186db4c489e5498210cc2162df15003d4b2b96000000000000000029000000000000000117201263360542e6b356eaf5c4f03b4af4e0693e169ca7c44207209d3bcc2a3b8f620118208dca1a18f7db9002face5d00d8ebe62ecd0b557a6590b6a2f5686890926bdde548fc05424954474f01866e64ab95028afde6e42cd0dcbd6b38f8dec74d3f3a7648e11259f64075321b1263360542e6b356eaf5c4f03b4af4e0693e169ca7c44207209d3bcc2a3b8f6242033024116333a45124af6141080383eea1b756c1983a07d71d50a2c62d774b7b6402e5a56ea96f692990ef584ebecaa55acf8d7d4e186db4c489e5498210cc2162df0048fc05424954474f04576367473770677a4c556d7036587263684a7169483374326337654a5663546b573959503336586d703258573474344262424336657538504242676a66563934411fd7799767369cfdab6d24bea3a10f3fe18639aba182a1ce6d04242c2795802ff70b60a55f761144622565dcb30317018095af2fa9bf20ff71fd49f8a272ef433b00010520751e929f9b2701a7ae083395f3ccdc26a295bf4b06348d54a330a461ad713f2301068e01c04420165e9a0bcba3fe78e403086052286e24d1a96e04c942f23fd3b407086541d55aad20a7106400ab4a303e360251ac4c0d1ebf73d9f42df2411b383c9675a4899926c0ac01c04420a7106400ab4a303e360251ac4c0d1ebf73d9f42df2411b383c9675a4899926c0ad203abe647510bb3b08087f3025763d8d34b8bdc85d36a91ceaf6289a3466169ba3ac2107165e9a0bcba3fe78e403086052286e24d1a96e04c942f23fd3b407086541d55a3501905217dd2aa5707a9c5ee0476ae4cd0fa56c3a4c01cd832564e4f649a9dea8e888f255710000000000000000290000000f00000021073abe647510bb3b08087f3025763d8d34b8bdc85d36a91ceaf6289a3466169ba335019b06176d0562c9e8007d34dad4b159042cce1a1d7327c42185c050dcf4e6aa963d4b2b960000000000000000290000000f0000002107a7106400ab4a303e360251ac4c0d1ebf73d9f42df2411b383c9675a4899926c05502905217dd2aa5707a9c5ee0476ae4cd0fa56c3a4c01cd832564e4f649a9dea8e89b06176d0562c9e8007d34dad4b159042cce1a1d7327c42185c050dcf4e6aa9635eabe8d0000000000000000290000000f0000000000',
6+
};

modules/utxo-lib/src/testutil/psbt.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
UtxoPsbt,
2525
UtxoTransaction,
2626
verifySignatureWithUnspent,
27+
addXpubsToPsbt,
2728
} from '../bitgo';
2829
import { Network } from '../networks';
2930
import { mockReplayProtectionUnspent, mockWalletUnspent } from './mock';
@@ -167,6 +168,7 @@ export function constructPsbt(
167168
signers?: { signerName: KeyName; cosignerName?: KeyName };
168169
deterministic?: boolean;
169170
skipNonWitnessUtxo?: boolean;
171+
addGlobalXPubs?: boolean;
170172
}
171173
): UtxoPsbt {
172174
const { signers, deterministic, skipNonWitnessUtxo } = params ?? {};
@@ -219,6 +221,10 @@ export function constructPsbt(
219221
signAllPsbtInputs(psbt, inputs, rootWalletKeys, sign, { signers, deterministic, skipNonWitnessUtxo });
220222
}
221223

224+
if (params?.addGlobalXPubs) {
225+
addXpubsToPsbt(psbt, rootWalletKeys);
226+
}
227+
222228
return psbt;
223229
}
224230

0 commit comments

Comments
 (0)