@@ -3,12 +3,15 @@ import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
33import { Flrp , TflrP } from '../../src/' ;
44import { randomBytes } from 'crypto' ;
55import { BitGoAPI } from '@bitgo/sdk-api' ;
6- import { SEED_ACCOUNT , ACCOUNT_1 , ACCOUNT_2 } from '../resources/account' ;
6+ import { coins } from '@bitgo/statics' ;
7+ import { SEED_ACCOUNT , ACCOUNT_1 , ACCOUNT_2 , ON_CHAIN_TEST_WALLET , CONTEXT } from '../resources/account' ;
78import { EXPORT_IN_C } from '../resources/transactionData/exportInC' ;
89import { EXPORT_IN_P } from '../resources/transactionData/exportInP' ;
910import { IMPORT_IN_P } from '../resources/transactionData/importInP' ;
1011import { IMPORT_IN_C } from '../resources/transactionData/importInC' ;
1112import { HalfSignedAccountTransaction , TransactionType , MPCAlgorithm } from '@bitgo/sdk-core' ;
13+ import { secp256k1 } from '@flarenetwork/flarejs' ;
14+ import { FlrpContext } from '@bitgo/public-types' ;
1215import assert from 'assert' ;
1316
1417describe ( 'Flrp test cases' , function ( ) {
@@ -687,4 +690,274 @@ describe('Flrp test cases', function () {
687690 } ) ;
688691 } ) ;
689692 } ) ;
693+
694+ describe ( 'MPC/TSS Coin-Level Methods' , ( ) => {
695+ const coinConfig = coins . get ( 'tflrp' ) ;
696+ const factory = new FlrpLib . TransactionBuilderFactory ( coinConfig ) ;
697+
698+ // Helper: build unsigned MPC ExportInC tx and return its hex
699+ async function buildUnsignedExportInC ( ) : Promise < string > {
700+ const txBuilder = factory
701+ . getExportInCBuilder ( )
702+ . fromPubKey ( EXPORT_IN_C . cHexAddress )
703+ . nonce ( EXPORT_IN_C . nonce )
704+ . amount ( EXPORT_IN_C . amount )
705+ . threshold ( 1 )
706+ . locktime ( 0 )
707+ . to ( ON_CHAIN_TEST_WALLET . user . pChainAddress )
708+ . fee ( EXPORT_IN_C . fee )
709+ . context ( CONTEXT as FlrpContext ) ;
710+
711+ const tx = await txBuilder . build ( ) ;
712+ return tx . toBroadcastFormat ( ) ;
713+ }
714+
715+ // Helper: build unsigned MPC ImportInP tx and return its hex
716+ async function buildUnsignedImportInP ( ) : Promise < string > {
717+ const mpcUtxo = {
718+ outputID : 7 ,
719+ amount : '50000000' ,
720+ txid : 'aLwVQequmbhhjfhL6SvfM6MGWAB8wHwQfJ67eowEbAEUpkueN' ,
721+ threshold : 1 ,
722+ addresses : [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] ,
723+ outputidx : '0' ,
724+ locktime : '0' ,
725+ } ;
726+ const txBuilder = factory
727+ . getImportInPBuilder ( )
728+ . threshold ( 1 )
729+ . locktime ( 0 )
730+ . fromPubKey ( [ ON_CHAIN_TEST_WALLET . user . corethAddress ] )
731+ . to ( [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] )
732+ . externalChainId ( IMPORT_IN_P . sourceChainId )
733+ . decodedUtxos ( [ mpcUtxo ] )
734+ . context ( IMPORT_IN_P . context as FlrpContext )
735+ . feeState ( IMPORT_IN_P . feeState as any ) ;
736+
737+ const tx = await txBuilder . build ( ) ;
738+ return tx . toBroadcastFormat ( ) ;
739+ }
740+
741+ // Helper: build unsigned MPC ExportInP tx and return its hex
742+ async function buildUnsignedExportInP ( ) : Promise < string > {
743+ const mpcUtxo = {
744+ outputID : 7 ,
745+ amount : '50000000' ,
746+ txid : 'bgHnEJ64td8u31aZrGDaWcDqxZ8vDV5qGd7bmSifgvUnUW8v2' ,
747+ threshold : 1 ,
748+ addresses : [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] ,
749+ outputidx : '0' ,
750+ locktime : '0' ,
751+ } ;
752+ const txBuilder = factory
753+ . getExportInPBuilder ( )
754+ . threshold ( 1 )
755+ . locktime ( 0 )
756+ . fromPubKey ( [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] )
757+ . amount ( '30000000' )
758+ . externalChainId ( EXPORT_IN_P . sourceChainId )
759+ . decodedUtxos ( [ mpcUtxo ] )
760+ . context ( EXPORT_IN_P . context as FlrpContext )
761+ . feeState ( EXPORT_IN_P . feeState as any ) ;
762+
763+ const tx = await txBuilder . build ( ) ;
764+ return tx . toBroadcastFormat ( ) ;
765+ }
766+
767+ // Helper: build unsigned MPC ImportInC tx and return its hex
768+ async function buildUnsignedImportInC ( ) : Promise < string > {
769+ const mpcUtxo = {
770+ outputID : 7 ,
771+ amount : '30000000' ,
772+ txid : 'nSBwNcgfLbk5S425b1qaYaqTTCiMCV75KU4Fbnq8SPUUqLq2' ,
773+ threshold : 1 ,
774+ addresses : [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] ,
775+ outputidx : '1' ,
776+ locktime : '0' ,
777+ } ;
778+ const txBuilder = factory
779+ . getImportInCBuilder ( )
780+ . threshold ( 1 )
781+ . locktime ( 0 )
782+ . fromPubKey ( [ ON_CHAIN_TEST_WALLET . user . pChainAddress ] )
783+ . to ( '0x96993BAEb6AaE2e06BF95F144e2775D4f8efbD35' )
784+ . fee ( '1000000' )
785+ . decodedUtxos ( [ mpcUtxo ] )
786+ . context ( IMPORT_IN_C . context as FlrpContext ) ;
787+
788+ const tx = await txBuilder . build ( ) ;
789+ return tx . toBroadcastFormat ( ) ;
790+ }
791+
792+ describe ( 'getSignablePayload' , ( ) => {
793+ it ( 'should return signable payload for ExportInC' , async ( ) => {
794+ const txHex = await buildUnsignedExportInC ( ) ;
795+ const payload = await basecoin . getSignablePayload ( txHex ) ;
796+
797+ payload . should . be . instanceOf ( Buffer ) ;
798+ payload . length . should . be . greaterThan ( 0 ) ;
799+ } ) ;
800+
801+ it ( 'should return signable payload for ImportInP' , async ( ) => {
802+ const txHex = await buildUnsignedImportInP ( ) ;
803+ const payload = await basecoin . getSignablePayload ( txHex ) ;
804+
805+ payload . should . be . instanceOf ( Buffer ) ;
806+ payload . length . should . be . greaterThan ( 0 ) ;
807+ } ) ;
808+
809+ it ( 'should return signable payload for ExportInP' , async ( ) => {
810+ const txHex = await buildUnsignedExportInP ( ) ;
811+ const payload = await basecoin . getSignablePayload ( txHex ) ;
812+
813+ payload . should . be . instanceOf ( Buffer ) ;
814+ payload . length . should . be . greaterThan ( 0 ) ;
815+ } ) ;
816+
817+ it ( 'should return signable payload for ImportInC' , async ( ) => {
818+ const txHex = await buildUnsignedImportInC ( ) ;
819+ const payload = await basecoin . getSignablePayload ( txHex ) ;
820+
821+ payload . should . be . instanceOf ( Buffer ) ;
822+ payload . length . should . be . greaterThan ( 0 ) ;
823+ } ) ;
824+ } ) ;
825+
826+ describe ( 'addSignatureToTransaction' , ( ) => {
827+ it ( 'should complete getSignablePayload → sign → addSignatureToTransaction round-trip for ExportInC' , async ( ) => {
828+ const txHex = await buildUnsignedExportInC ( ) ;
829+
830+ // Get signable payload (what MPC ceremony would receive)
831+ const payload = await basecoin . getSignablePayload ( txHex ) ;
832+
833+ // Simulate MPC: sign externally
834+ const signature = await secp256k1 . sign ( payload , Buffer . from ( ON_CHAIN_TEST_WALLET . user . privateKey , 'hex' ) ) ;
835+
836+ // Inject signature
837+ const signedHex = await basecoin . addSignatureToTransaction ( txHex , Buffer . from ( signature ) ) ;
838+ signedHex . should . not . equal ( txHex ) ;
839+
840+ // Verify signed tx can be parsed
841+ const txBuilder = factory . from ( signedHex ) ;
842+ const tx = await txBuilder . build ( ) ;
843+ tx . signature . length . should . equal ( 1 ) ;
844+ tx . toJson ( ) . type . should . equal ( TransactionType . Export ) ;
845+ } ) ;
846+
847+ it ( 'should complete round-trip for ImportInP' , async ( ) => {
848+ const txHex = await buildUnsignedImportInP ( ) ;
849+ const payload = await basecoin . getSignablePayload ( txHex ) ;
850+ const signature = await secp256k1 . sign ( payload , Buffer . from ( ON_CHAIN_TEST_WALLET . user . privateKey , 'hex' ) ) ;
851+ const signedHex = await basecoin . addSignatureToTransaction ( txHex , Buffer . from ( signature ) ) ;
852+
853+ signedHex . should . not . equal ( txHex ) ;
854+ const tx = await factory . from ( signedHex ) . build ( ) ;
855+ tx . signature . length . should . equal ( 1 ) ;
856+ tx . toJson ( ) . type . should . equal ( TransactionType . Import ) ;
857+ } ) ;
858+
859+ it ( 'should complete round-trip for ExportInP' , async ( ) => {
860+ const txHex = await buildUnsignedExportInP ( ) ;
861+ const payload = await basecoin . getSignablePayload ( txHex ) ;
862+ const signature = await secp256k1 . sign ( payload , Buffer . from ( ON_CHAIN_TEST_WALLET . user . privateKey , 'hex' ) ) ;
863+ const signedHex = await basecoin . addSignatureToTransaction ( txHex , Buffer . from ( signature ) ) ;
864+
865+ signedHex . should . not . equal ( txHex ) ;
866+ const tx = await factory . from ( signedHex ) . build ( ) ;
867+ tx . signature . length . should . equal ( 1 ) ;
868+ tx . toJson ( ) . type . should . equal ( TransactionType . Export ) ;
869+ } ) ;
870+
871+ it ( 'should complete round-trip for ImportInC' , async ( ) => {
872+ const txHex = await buildUnsignedImportInC ( ) ;
873+ const payload = await basecoin . getSignablePayload ( txHex ) ;
874+ const signature = await secp256k1 . sign ( payload , Buffer . from ( ON_CHAIN_TEST_WALLET . user . privateKey , 'hex' ) ) ;
875+ const signedHex = await basecoin . addSignatureToTransaction ( txHex , Buffer . from ( signature ) ) ;
876+
877+ signedHex . should . not . equal ( txHex ) ;
878+ const tx = await factory . from ( signedHex ) . build ( ) ;
879+ tx . signature . length . should . equal ( 1 ) ;
880+ tx . toJson ( ) . type . should . equal ( TransactionType . Import ) ;
881+ } ) ;
882+
883+ it ( 'should produce valid signed tx via both sign() and addSignatureToTransaction() for ExportInC' , async ( ) => {
884+ const privateKey = ON_CHAIN_TEST_WALLET . user . privateKey ;
885+
886+ // Path 1: sign() via signTransaction
887+ const signResult = await basecoin . signTransaction ( {
888+ txPrebuild : { txHex : await buildUnsignedExportInC ( ) } ,
889+ prv : privateKey ,
890+ } ) ;
891+ const signedHex1 = ( signResult as HalfSignedAccountTransaction ) . halfSigned ! . txHex ! ;
892+
893+ // Path 2: getSignablePayload → external sign → addSignatureToTransaction
894+ const unsignedHex = await buildUnsignedExportInC ( ) ;
895+ const payload = await basecoin . getSignablePayload ( unsignedHex ) ;
896+ const signature = await secp256k1 . sign ( payload , Buffer . from ( privateKey , 'hex' ) ) ;
897+ const signedHex2 = await basecoin . addSignatureToTransaction ( unsignedHex , Buffer . from ( signature ) ) ;
898+
899+ // Both paths should produce valid signed transactions with 1 signature
900+ const tx1 = await factory . from ( signedHex1 ) . build ( ) ;
901+ const tx2 = await factory . from ( signedHex2 ) . build ( ) ;
902+ tx1 . signature . length . should . equal ( 1 ) ;
903+ tx2 . signature . length . should . equal ( 1 ) ;
904+ tx1 . toJson ( ) . type . should . equal ( TransactionType . Export ) ;
905+ tx2 . toJson ( ) . type . should . equal ( TransactionType . Export ) ;
906+ } ) ;
907+ } ) ;
908+
909+ describe ( 'verifyTransaction with MPC params' , ( ) => {
910+ it ( 'should verify MPC ExportInC transaction' , async ( ) => {
911+ const txHex = await buildUnsignedExportInC ( ) ;
912+ const txPrebuild = { txHex, txInfo : { } } ;
913+ const txParams = {
914+ recipients : [ { address : ON_CHAIN_TEST_WALLET . user . pChainAddress , amount : EXPORT_IN_C . amount } ] ,
915+ type : 'Export' ,
916+ locktime : 0 ,
917+ } ;
918+
919+ const isVerified = await basecoin . verifyTransaction ( { txParams, txPrebuild } ) ;
920+ isVerified . should . equal ( true ) ;
921+ } ) ;
922+
923+ it ( 'should verify MPC ImportInP transaction' , async ( ) => {
924+ const txHex = await buildUnsignedImportInP ( ) ;
925+ const txPrebuild = { txHex, txInfo : { } } ;
926+ const txParams = {
927+ recipients : [ ] ,
928+ type : 'Import' ,
929+ locktime : 0 ,
930+ } ;
931+
932+ const isVerified = await basecoin . verifyTransaction ( { txParams, txPrebuild } ) ;
933+ isVerified . should . equal ( true ) ;
934+ } ) ;
935+
936+ it ( 'should verify MPC ExportInP transaction' , async ( ) => {
937+ const txHex = await buildUnsignedExportInP ( ) ;
938+ const txPrebuild = { txHex, txInfo : { } } ;
939+ const txParams = {
940+ recipients : [ { address : ON_CHAIN_TEST_WALLET . user . pChainAddress , amount : '30000000' } ] ,
941+ type : 'Export' ,
942+ locktime : 0 ,
943+ } ;
944+
945+ const isVerified = await basecoin . verifyTransaction ( { txParams, txPrebuild } ) ;
946+ isVerified . should . equal ( true ) ;
947+ } ) ;
948+
949+ it ( 'should verify MPC ImportInC transaction' , async ( ) => {
950+ const txHex = await buildUnsignedImportInC ( ) ;
951+ const txPrebuild = { txHex, txInfo : { } } ;
952+ const txParams = {
953+ recipients : [ { address : '0x96993BAEb6AaE2e06BF95F144e2775D4f8efbD35' , amount : '1' } ] ,
954+ type : 'ImportToC' ,
955+ locktime : 0 ,
956+ } ;
957+
958+ const isVerified = await basecoin . verifyTransaction ( { txParams, txPrebuild } ) ;
959+ isVerified . should . equal ( true ) ;
960+ } ) ;
961+ } ) ;
962+ } ) ;
690963} ) ;
0 commit comments