@@ -19,6 +19,7 @@ import {
1919 CustomEddsaMPCv2SigningRound2GeneratingFunction ,
2020 CustomEddsaMPCv2SigningRound3GeneratingFunction ,
2121 EDDSAUtils ,
22+ EddsaMPCv2KeyGenCallbacks ,
2223 EddsaMPCv2Utils ,
2324 IBaseCoin ,
2425 IWallet ,
@@ -1812,3 +1813,156 @@ describe('signRecoveryEddsaMPCv2', () => {
18121813 ) ;
18131814 } ) ;
18141815} ) ;
1816+
1817+ describe ( 'EddsaMPCv2Utils.createKeychainsWithExternalSigner' , function ( ) {
1818+ let utils : EddsaMPCv2Utils ;
1819+ let callbacks : EddsaMPCv2KeyGenCallbacks ;
1820+ const enterprise = 'enterprise-id' ;
1821+ const sessionId = 'session-001' ;
1822+ const commonKeychain = 'common-keychain-hex' ;
1823+
1824+ const userState = { encryptedData : 'u-data' , encryptedDataKey : 'u-data-key' } ;
1825+ const backupState = { encryptedData : 'b-data' , encryptedDataKey : 'b-data-key' } ;
1826+ const userSignedMsg1 : MPSTypes . MPSSignedMessage = { message : 'umsg1' , signature : 'usig1' } ;
1827+ const backupSignedMsg1 : MPSTypes . MPSSignedMessage = { message : 'bmsg1' , signature : 'bsig1' } ;
1828+ const userSignedMsg2 : MPSTypes . MPSSignedMessage = { message : 'umsg2' , signature : 'usig2' } ;
1829+ const backupSignedMsg2 : MPSTypes . MPSSignedMessage = { message : 'bmsg2' , signature : 'bsig2' } ;
1830+ const bitgoMsg1 : MPSTypes . MPSSignedMessage = { message : 'bitgo-msg1' , signature : 'bitgo-sig1' } ;
1831+ const bitgoMsg2 : MPSTypes . MPSSignedMessage = { message : 'bitgo-msg2' , signature : 'bitgo-sig2' } ;
1832+
1833+ beforeEach ( function ( ) {
1834+ callbacks = {
1835+ initializeCallback : sinon . stub ( ) . resolves ( {
1836+ userGpgPublicKey : 'user-gpg-pub' ,
1837+ backupGpgPublicKey : 'backup-gpg-pub' ,
1838+ userState,
1839+ backupState,
1840+ } ) ,
1841+ round1Callback : sinon . stub ( ) . resolves ( {
1842+ userSignedMsg1,
1843+ backupSignedMsg1,
1844+ userState,
1845+ backupState,
1846+ } ) ,
1847+ round2Callback : sinon . stub ( ) . resolves ( {
1848+ userSignedMsg2,
1849+ backupSignedMsg2,
1850+ userState,
1851+ backupState,
1852+ } ) ,
1853+ finalizeCallback : sinon . stub ( ) . resolves ( { commonKeychain } ) ,
1854+ } ;
1855+
1856+ const mockBitGo = {
1857+ getEnv : sinon . stub ( ) . returns ( 'dev' ) ,
1858+ } as any ;
1859+
1860+ const mockKeychains = {
1861+ add : sinon
1862+ . stub ( )
1863+ . callsFake ( ( params : any ) => Promise . resolve ( { id : `${ params . source } -key-id` , commonKeychain, isMPCv2 : true } ) ) ,
1864+ } ;
1865+
1866+ const mockCoin = {
1867+ keychains : sinon . stub ( ) . returns ( mockKeychains ) ,
1868+ } as any ;
1869+
1870+ utils = new EddsaMPCv2Utils ( mockBitGo , mockCoin ) ;
1871+
1872+ sinon . stub ( utils , 'getBitgoGpgPubkeyBasedOnFeatureFlags' as any ) . resolves ( { eddsaMpcv2PublicKey : null } ) ;
1873+ ( utils as any ) . bitgoEddsaMpcv2PublicGpgKey = { armor : ( ) => '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' } ;
1874+
1875+ sinon . stub ( utils , 'sendKeyGenerationRound1' ) . resolves ( {
1876+ sessionId : sessionId as any ,
1877+ bitgoMsg1,
1878+ } ) ;
1879+ sinon . stub ( utils , 'sendKeyGenerationRound2' ) . resolves ( {
1880+ sessionId : sessionId as any ,
1881+ commonPublicKeychain : commonKeychain as any ,
1882+ bitgoMsg2,
1883+ } ) ;
1884+ sinon . stub ( utils as any , 'addBitgoKeychain' ) . resolves ( { id : 'bitgo-key-id' , commonKeychain, isMPCv2 : true } ) ;
1885+ } ) ;
1886+
1887+ afterEach ( function ( ) {
1888+ sinon . restore ( ) ;
1889+ } ) ;
1890+
1891+ it ( 'should invoke callbacks in order and return keychains' , async function ( ) {
1892+ const keychains = await utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ;
1893+
1894+ assert . ok ( keychains . userKeychain ) ;
1895+ assert . ok ( keychains . backupKeychain ) ;
1896+ assert . ok ( keychains . bitgoKeychain ) ;
1897+
1898+ sinon . assert . calledOnce ( callbacks . initializeCallback as sinon . SinonStub ) ;
1899+ sinon . assert . calledOnce ( callbacks . round1Callback as sinon . SinonStub ) ;
1900+ sinon . assert . calledOnce ( callbacks . round2Callback as sinon . SinonStub ) ;
1901+ sinon . assert . calledOnce ( callbacks . finalizeCallback as sinon . SinonStub ) ;
1902+ } ) ;
1903+
1904+ it ( 'should pass round1 response to round2Callback' , async function ( ) {
1905+ await utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ;
1906+
1907+ const round2Args = ( callbacks . round2Callback as sinon . SinonStub ) . firstCall . args [ 0 ] ;
1908+ assert . deepStrictEqual ( round2Args . bitgoMsg1 , bitgoMsg1 ) ;
1909+ assert . deepStrictEqual ( round2Args . userSignedMsg1 , userSignedMsg1 ) ;
1910+ assert . deepStrictEqual ( round2Args . backupSignedMsg1 , backupSignedMsg1 ) ;
1911+ assert . deepStrictEqual ( round2Args . userState , userState ) ;
1912+ assert . deepStrictEqual ( round2Args . backupState , backupState ) ;
1913+ } ) ;
1914+
1915+ it ( 'should pass round2 response to finalizeCallback' , async function ( ) {
1916+ await utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ;
1917+
1918+ const finalizeArgs = ( callbacks . finalizeCallback as sinon . SinonStub ) . firstCall . args [ 0 ] ;
1919+ assert . deepStrictEqual ( finalizeArgs . bitgoMsg2 , bitgoMsg2 ) ;
1920+ assert . strictEqual ( finalizeArgs . bitgoCommonKeychain , commonKeychain ) ;
1921+ assert . deepStrictEqual ( finalizeArgs . userState , userState ) ;
1922+ assert . deepStrictEqual ( finalizeArgs . backupState , backupState ) ;
1923+ } ) ;
1924+
1925+ it ( 'should register keychains with isMPCv2: true' , async function ( ) {
1926+ await utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ;
1927+
1928+ const mockKeychains = ( utils as any ) . baseCoin . keychains ( ) ;
1929+ const addCalls = ( mockKeychains . add as sinon . SinonStub ) . getCalls ( ) ;
1930+ assert . ok ( addCalls . length >= 2 , 'keychains.add should be called for user and backup' ) ;
1931+ for ( const call of addCalls ) {
1932+ assert . strictEqual ( call . args [ 0 ] . isMPCv2 , true ) ;
1933+ assert . strictEqual ( call . args [ 0 ] . keyType , 'tss' ) ;
1934+ assert . strictEqual ( call . args [ 0 ] . commonKeychain , commonKeychain ) ;
1935+ }
1936+ } ) ;
1937+
1938+ it ( 'should reject when finalizeCallback returns mismatched commonKeychain' , async function ( ) {
1939+ ( callbacks . finalizeCallback as sinon . SinonStub ) . resolves ( { commonKeychain : 'different-keychain' } ) ;
1940+
1941+ await assert . rejects (
1942+ ( ) => utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ,
1943+ / C o m m o n k e y c h a i n s d o n o t m a t c h /
1944+ ) ;
1945+ } ) ;
1946+
1947+ it ( 'should reject when round2 returns mismatched sessionId' , async function ( ) {
1948+ ( utils . sendKeyGenerationRound2 as sinon . SinonStub ) . resolves ( {
1949+ sessionId : 'different-session' as any ,
1950+ commonPublicKeychain : commonKeychain as any ,
1951+ bitgoMsg2,
1952+ } ) ;
1953+
1954+ await assert . rejects (
1955+ ( ) => utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ,
1956+ / R o u n d 1 a n d r o u n d 2 s e s s i o n I D s d o n o t m a t c h /
1957+ ) ;
1958+ } ) ;
1959+
1960+ it ( 'should reject when BitGo EdDSA MPCv2 GPG public key is missing' , async function ( ) {
1961+ ( utils as any ) . bitgoEddsaMpcv2PublicGpgKey = undefined ;
1962+
1963+ await assert . rejects (
1964+ ( ) => utils . createKeychainsWithExternalSigner ( { enterprise, callbacks } ) ,
1965+ / F a i l e d t o g e t B i t G o E d D S A M P C v 2 G P G p u b l i c k e y /
1966+ ) ;
1967+ } ) ;
1968+ } ) ;
0 commit comments