Skip to content

Commit ab25b8f

Browse files
committed
fix(abstract-eth): fix TSS consolidation verification for ETH wallets
Ticket: COIN-6431
1 parent eadb629 commit ab25b8f

File tree

2 files changed

+232
-1
lines changed

2 files changed

+232
-1
lines changed

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3053,7 +3053,9 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
30533053
!txParams?.recipients &&
30543054
!(
30553055
txParams.prebuildTx?.consolidateId ||
3056-
(txParams.type && ['acceleration', 'fillNonce', 'transferToken', 'tokenApproval'].includes(txParams.type))
3056+
txPrebuild?.consolidateId ||
3057+
(txParams.type &&
3058+
['acceleration', 'fillNonce', 'transferToken', 'tokenApproval', 'consolidate'].includes(txParams.type))
30573059
)
30583060
) {
30593061
throw new Error('missing txParams');
@@ -3126,6 +3128,31 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
31263128
}
31273129
}
31283130

3131+
// Verify consolidation transactions send to base address
3132+
if (params.verification?.consolidationToBaseAddress) {
3133+
const coinSpecific = wallet.coinSpecific();
3134+
if (!coinSpecific || !coinSpecific.baseAddress) {
3135+
throw new Error('Unable to determine base address for consolidation');
3136+
}
3137+
const baseAddress = coinSpecific.baseAddress;
3138+
3139+
const txBuilder = this.getTransactionBuilder();
3140+
txBuilder.from(txPrebuild.txHex);
3141+
const tx = await txBuilder.build();
3142+
const txJson = tx.toJson();
3143+
3144+
// Verify the transaction recipient matches the base address
3145+
if (!txJson.to) {
3146+
throw new Error('Consolidation transaction is missing recipient address');
3147+
}
3148+
3149+
if (txJson.to.toLowerCase() !== baseAddress.toLowerCase()) {
3150+
throwRecipientMismatch('Consolidation transaction recipient does not match wallet base address', [
3151+
{ address: txJson.to, amount: txJson.value },
3152+
]);
3153+
}
3154+
}
3155+
31293156
return true;
31303157
}
31313158

modules/sdk-coin-eth/test/unit/eth.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,210 @@ describe('ETH:', function () {
663663
});
664664
});
665665

666+
describe('TSS Transaction Verification', function () {
667+
it('should verify TSS consolidation transaction when txPrebuild has consolidateId', async function () {
668+
const coin = bitgo.coin('hteth') as Hteth;
669+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
670+
const wallet = new Wallet(bitgo, coin, {
671+
coinSpecific: {
672+
baseAddress: baseAddress,
673+
},
674+
});
675+
676+
// txParams without recipients (as in consolidation flow)
677+
const txParams = {
678+
wallet: wallet,
679+
walletPassphrase: 'fakeWalletPassphrase',
680+
};
681+
682+
// txPrebuild with consolidateId (set by server during consolidation build)
683+
const txPrebuild = {
684+
consolidateId: '68a7d5d0c66e74e216b97173bd558c6d',
685+
txHex: '0x',
686+
coin: 'hteth',
687+
walletId: 'fakeWalletId',
688+
};
689+
690+
const verification = {};
691+
692+
const isTransactionVerified = await coin.verifyTransaction({
693+
txParams: txParams as any,
694+
txPrebuild: txPrebuild as any,
695+
wallet,
696+
verification,
697+
walletType: 'tss',
698+
});
699+
isTransactionVerified.should.equal(true);
700+
});
701+
702+
it('should verify TSS transaction when txParams.type is consolidate', async function () {
703+
const coin = bitgo.coin('hteth') as Hteth;
704+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
705+
const wallet = new Wallet(bitgo, coin, {
706+
coinSpecific: {
707+
baseAddress: baseAddress,
708+
},
709+
});
710+
711+
const txParams = {
712+
type: 'consolidate',
713+
wallet: wallet,
714+
walletPassphrase: 'fakeWalletPassphrase',
715+
};
716+
717+
const txPrebuild = {
718+
txHex: '0x',
719+
coin: 'hteth',
720+
walletId: 'fakeWalletId',
721+
};
722+
723+
const verification = {};
724+
725+
const isTransactionVerified = await coin.verifyTransaction({
726+
txParams: txParams as any,
727+
txPrebuild: txPrebuild as any,
728+
wallet,
729+
verification,
730+
walletType: 'tss',
731+
});
732+
isTransactionVerified.should.equal(true);
733+
});
734+
735+
it('should verify TSS transaction when txParams.prebuildTx has consolidateId', async function () {
736+
const coin = bitgo.coin('hteth') as Hteth;
737+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
738+
const wallet = new Wallet(bitgo, coin, {
739+
coinSpecific: {
740+
baseAddress: baseAddress,
741+
},
742+
});
743+
744+
const txParams = {
745+
wallet: wallet,
746+
walletPassphrase: 'fakeWalletPassphrase',
747+
prebuildTx: {
748+
consolidateId: '68a7d5d0c66e74e216b97173bd558c6d',
749+
},
750+
};
751+
752+
const txPrebuild = {
753+
txHex: '0x',
754+
coin: 'hteth',
755+
walletId: 'fakeWalletId',
756+
};
757+
758+
const verification = {};
759+
760+
const isTransactionVerified = await coin.verifyTransaction({
761+
txParams: txParams as any,
762+
txPrebuild: txPrebuild as any,
763+
wallet,
764+
verification,
765+
walletType: 'tss',
766+
});
767+
isTransactionVerified.should.equal(true);
768+
});
769+
770+
it('should reject TSS transaction without recipients, consolidateId, or valid type', async function () {
771+
const coin = bitgo.coin('hteth') as Hteth;
772+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
773+
const wallet = new Wallet(bitgo, coin, {
774+
coinSpecific: {
775+
baseAddress: baseAddress,
776+
},
777+
});
778+
779+
const txParams = {
780+
wallet: wallet,
781+
walletPassphrase: 'fakeWalletPassphrase',
782+
};
783+
784+
const txPrebuild = {
785+
txHex: '0x',
786+
coin: 'hteth',
787+
walletId: 'fakeWalletId',
788+
};
789+
790+
const verification = {};
791+
792+
await coin
793+
.verifyTransaction({
794+
txParams: txParams as any,
795+
txPrebuild: txPrebuild as any,
796+
wallet,
797+
verification,
798+
walletType: 'tss',
799+
})
800+
.should.be.rejectedWith('missing txParams');
801+
});
802+
803+
it('should verify TSS transaction with acceleration type', async function () {
804+
const coin = bitgo.coin('hteth') as Hteth;
805+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
806+
const wallet = new Wallet(bitgo, coin, {
807+
coinSpecific: {
808+
baseAddress: baseAddress,
809+
},
810+
});
811+
812+
const txParams = {
813+
type: 'acceleration',
814+
wallet: wallet,
815+
walletPassphrase: 'fakeWalletPassphrase',
816+
};
817+
818+
const txPrebuild = {
819+
txHex: '0x',
820+
coin: 'hteth',
821+
walletId: 'fakeWalletId',
822+
};
823+
824+
const verification = {};
825+
826+
const isTransactionVerified = await coin.verifyTransaction({
827+
txParams: txParams as any,
828+
txPrebuild: txPrebuild as any,
829+
wallet,
830+
verification,
831+
walletType: 'tss',
832+
});
833+
isTransactionVerified.should.equal(true);
834+
});
835+
836+
it('should verify TSS transaction with fillNonce type', async function () {
837+
const coin = bitgo.coin('hteth') as Hteth;
838+
const baseAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
839+
const wallet = new Wallet(bitgo, coin, {
840+
coinSpecific: {
841+
baseAddress: baseAddress,
842+
},
843+
});
844+
845+
const txParams = {
846+
type: 'fillNonce',
847+
wallet: wallet,
848+
walletPassphrase: 'fakeWalletPassphrase',
849+
};
850+
851+
const txPrebuild = {
852+
txHex: '0x',
853+
coin: 'hteth',
854+
walletId: 'fakeWalletId',
855+
};
856+
857+
const verification = {};
858+
859+
const isTransactionVerified = await coin.verifyTransaction({
860+
txParams: txParams as any,
861+
txPrebuild: txPrebuild as any,
862+
wallet,
863+
verification,
864+
walletType: 'tss',
865+
});
866+
isTransactionVerified.should.equal(true);
867+
});
868+
});
869+
666870
describe('Address Verification', function () {
667871
describe('isWalletAddress', function () {
668872
it('should verify an address generated using forwarder version 1', async function () {

0 commit comments

Comments
 (0)