11import assert from 'assert' ;
22
3- import { type TestBitGoAPI , TestBitGo } from '@bitgo/sdk-test' ;
4- import { BitGoAPI , encrypt } from '@bitgo/sdk-api' ;
53import * as testutils from '@bitgo/wasm-utxo/testutils' ;
64import { Wallet } from '@bitgo/sdk-core' ;
75
8- import { Tbtc } from '../../src/impl/btc' ;
9-
10- import { constructPsbt } from './util' ;
6+ import { constructPsbt , getWalletAddress , getUtxoCoin , defaultBitGo , nockBitGo , nockWalletKeys } from './util' ;
117
128describe ( 'Transaction Spoofability Tests' , function ( ) {
13- describe ( 'Unspent management spoofability - Consolidation (BUILD_SIGN_SEND)' , function ( ) {
14- let coin : Tbtc ;
15- let bitgoTest : TestBitGoAPI ;
16-
17- before ( function ( ) {
18- bitgoTest = TestBitGo . decorate ( BitGoAPI , { env : 'test' } ) ;
19- bitgoTest . safeRegister ( 'tbtc' , Tbtc . createInstance ) ;
20- bitgoTest . initializeTestVars ( ) ;
21- coin = bitgoTest . coin ( 'tbtc' ) as Tbtc ;
22- } ) ;
9+ const coin = getUtxoCoin ( 'tbtc' ) ;
2310
24- it ( 'should detect hex spoofing in BUILD_SIGN_SEND' , async function ( ) : Promise < void > {
11+ describe ( 'Unspent management spoofability - Consolidation (BUILD_SIGN_SEND)' , function ( ) {
12+ it ( 'should detect spoofed consolidation to attacker address' , async function ( ) : Promise < void > {
2513 const keyTriple = testutils . getKeyTriple ( 'default' ) ;
26- const rootWalletKey = testutils . getDefaultWalletKeys ( ) ;
27- const [ user ] = keyTriple ;
14+ const walletKeys = testutils . getDefaultWalletKeys ( ) ;
15+ const attackerKeys = testutils . getWalletKeysForSeed ( 'attacker' ) ;
2816
29- const wallet = new Wallet ( bitgoTest , coin , {
17+ const wallet = new Wallet ( defaultBitGo , coin , {
3018 id : '5b34252f1bf349930e34020a' ,
3119 coin : 'tbtc' ,
3220 keys : [ 'user' , 'backup' , 'bitgo' ] ,
3321 } ) ;
3422
35- // originalPsbt is created to show what the legitimate transaction would look like
36- // eslint-disable-next-line @typescript-eslint/no-unused-vars
37- const originalPsbt = constructPsbt (
38- [ { scriptType : 'p2wsh' as const , value : BigInt ( 10000 ) } ] ,
39- [ { address : 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7' , value : BigInt ( 9000 ) } ] ,
40- 'tbtc' ,
41- rootWalletKey
42- ) ;
23+ // The attacker replaces the legitimate wallet address with their own
24+ const attackerAddress = getWalletAddress ( 'tbtc' , attackerKeys ) ;
4325 const spoofedPsbt = constructPsbt (
4426 [ { scriptType : 'p2wsh' as const , value : BigInt ( 10000 ) } ] ,
45- [ { address : 'tb1pjgg9ty3s2ztp60v6lhgrw76f7hxydzuk9t9mjsndh3p2gf2ah7gs4850kn' , value : BigInt ( 9000 ) } ] ,
27+ [ { address : attackerAddress , value : BigInt ( 9000 ) } ] ,
4628 'tbtc' ,
47- rootWalletKey
29+ walletKeys // Input uses wallet keys (the funds being stolen)
4830 ) ;
4931 const spoofedHex : string = Buffer . from ( spoofedPsbt . serialize ( ) ) . toString ( 'hex' ) ;
5032
51- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52- const bgUrl : string = ( bitgoTest as any ) . _baseUrl ;
53- const nock = require ( 'nock' ) ;
54-
55- nock ( bgUrl )
33+ nockBitGo ( )
5634 . post ( `/api/v2/${ wallet . coin ( ) } /wallet/${ wallet . id ( ) } /consolidateUnspents` )
5735 . reply ( 200 , { txHex : spoofedHex , consolidateId : 'test' } ) ;
5836
59- nock ( bgUrl )
37+ nockBitGo ( )
6038 . post ( `/api/v2/${ wallet . coin ( ) } /wallet/${ wallet . id ( ) } /tx/send` )
6139 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6240 . reply ( ( requestBody : any ) => {
@@ -66,15 +44,7 @@ describe('Transaction Spoofability Tests', function () {
6644 return [ 200 , { txid : 'test-txid-123' , status : 'signed' } ] ;
6745 } ) ;
6846
69- const pubs = keyTriple . map ( ( k ) => k . neutered ( ) . toBase58 ( ) ) ;
70- const responses = [
71- { pub : pubs [ 0 ] , encryptedPrv : encrypt ( 'pass' , user . toBase58 ( ) ) } ,
72- { pub : pubs [ 1 ] } ,
73- { pub : pubs [ 2 ] } ,
74- ] ;
75- wallet
76- . keyIds ( )
77- . forEach ( ( id , i ) => nock ( bgUrl ) . get ( `/api/v2/${ wallet . coin ( ) } /key/${ id } ` ) . reply ( 200 , responses [ i ] ) ) ;
47+ nockWalletKeys ( wallet , keyTriple , 'pass' ) ;
7848
7949 await assert . rejects (
8050 wallet . consolidateUnspents ( { walletPassphrase : 'pass' } ) ,
@@ -87,53 +57,32 @@ describe('Transaction Spoofability Tests', function () {
8757 } ) ;
8858
8959 describe ( 'Unspent management spoofability - Fanout (BUILD_SIGN_SEND)' , function ( ) {
90- let coin : Tbtc ;
91- let bitgoTest : TestBitGoAPI ;
92-
93- before ( function ( ) {
94- bitgoTest = TestBitGo . decorate ( BitGoAPI , { env : 'test' } ) ;
95- bitgoTest . safeRegister ( 'tbtc' , Tbtc . createInstance ) ;
96- bitgoTest . initializeTestVars ( ) ;
97- coin = bitgoTest . coin ( 'tbtc' ) as Tbtc ;
98- } ) ;
99-
100- it ( 'should detect hex spoofing in fanout BUILD_SIGN_SEND' , async function ( ) : Promise < void > {
60+ it ( 'should detect spoofed fanout to attacker address' , async function ( ) : Promise < void > {
10161 const keyTriple = testutils . getKeyTriple ( 'default' ) ;
102- const rootWalletKey = testutils . getDefaultWalletKeys ( ) ;
103- const [ user ] = keyTriple ;
62+ const walletKeys = testutils . getDefaultWalletKeys ( ) ;
63+ const attackerKeys = testutils . getWalletKeysForSeed ( 'attacker' ) ;
10464
105- const wallet = new Wallet ( bitgoTest , coin , {
65+ const wallet = new Wallet ( defaultBitGo , coin , {
10666 id : '5b34252f1bf349930e34020a' ,
10767 coin : 'tbtc' ,
10868 keys : [ 'user' , 'backup' , 'bitgo' ] ,
10969 } ) ;
11070
111- // originalPsbt is created to show what the legitimate transaction would look like
112- // eslint-disable-next-line @typescript-eslint/no-unused-vars
113- const originalPsbt = constructPsbt (
114- [ { scriptType : 'p2wsh' as const , value : BigInt ( 10000 ) } ] ,
115- [ { address : 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7' , value : BigInt ( 9000 ) } ] ,
116- 'tbtc' ,
117- rootWalletKey
118- ) ;
119-
71+ // The attacker replaces the legitimate wallet address with their own
72+ const attackerAddress = getWalletAddress ( 'tbtc' , attackerKeys ) ;
12073 const spoofedPsbt = constructPsbt (
12174 [ { scriptType : 'p2wsh' as const , value : BigInt ( 10000 ) } ] ,
122- [ { address : 'tb1pjgg9ty3s2ztp60v6lhgrw76f7hxydzuk9t9mjsndh3p2gf2ah7gs4850kn' , value : BigInt ( 9000 ) } ] ,
75+ [ { address : attackerAddress , value : BigInt ( 9000 ) } ] ,
12376 'tbtc' ,
124- rootWalletKey
77+ walletKeys // Input uses wallet keys (the funds being stolen)
12578 ) ;
12679 const spoofedHex : string = Buffer . from ( spoofedPsbt . serialize ( ) ) . toString ( 'hex' ) ;
12780
128- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129- const bgUrl : string = ( bitgoTest as any ) . _baseUrl ;
130- const nock = require ( 'nock' ) ;
131-
132- nock ( bgUrl )
81+ nockBitGo ( )
13382 . post ( `/api/v2/${ wallet . coin ( ) } /wallet/${ wallet . id ( ) } /fanoutUnspents` )
13483 . reply ( 200 , { txHex : spoofedHex , fanoutId : 'test' } ) ;
13584
136- nock ( bgUrl )
85+ nockBitGo ( )
13786 . post ( `/api/v2/${ wallet . coin ( ) } /wallet/${ wallet . id ( ) } /tx/send` )
13887 // eslint-disable-next-line @typescript-eslint/no-explicit-any
13988 . reply ( ( requestBody : any ) => {
@@ -143,15 +92,7 @@ describe('Transaction Spoofability Tests', function () {
14392 return [ 200 , { txid : 'test-txid-123' , status : 'signed' } ] ;
14493 } ) ;
14594
146- const pubs = keyTriple . map ( ( k ) => k . neutered ( ) . toBase58 ( ) ) ;
147- const responses = [
148- { pub : pubs [ 0 ] , encryptedPrv : encrypt ( 'pass' , user . toBase58 ( ) ) } ,
149- { pub : pubs [ 1 ] } ,
150- { pub : pubs [ 2 ] } ,
151- ] ;
152- wallet
153- . keyIds ( )
154- . forEach ( ( id , i ) => nock ( bgUrl ) . get ( `/api/v2/${ wallet . coin ( ) } /key/${ id } ` ) . reply ( 200 , responses [ i ] ) ) ;
95+ nockWalletKeys ( wallet , keyTriple , 'pass' ) ;
15596
15697 await assert . rejects (
15798 wallet . fanoutUnspents ( { walletPassphrase : 'pass' } ) ,
0 commit comments