diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 522c212b31..a9c74f931d 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -281,9 +281,9 @@ export interface RecoverFromWrongChainOptions { } export interface VerifyKeySignaturesOptions { - userKeychain?: Keychain; - keychainToVerify?: Keychain; - keySignature?: string; + userKeychain: { pub?: string }; + keychainToVerify: { pub?: string }; + keySignature: string; } export interface VerifyUserPublicKeyOptions { @@ -671,6 +671,20 @@ export abstract class AbstractUtxoCoin extends BaseCoin { return true; } + public async createKeySignatures( + prv: string, + backupKeychain: { pub: string }, + bitgoKeychain: { pub: string } + ): Promise<{ + backup: string; + bitgo: string; + }> { + return { + backup: (await this.signMessage({ prv }, backupKeychain.pub)).toString('hex'), + bitgo: (await this.signMessage({ prv }, bitgoKeychain.pub)).toString('hex'), + }; + } + /** * Verify signatures produced by the user key over the backup and bitgo keys. * @@ -744,7 +758,13 @@ export abstract class AbstractUtxoCoin extends BaseCoin { if (!keySignature) { throw new Error(`missing required custom change ${KeyIndices[keyIndex].toLowerCase()} keychain signature`); } - if (!this.verifyKeySignature({ userKeychain, keychainToVerify, keySignature })) { + if ( + !this.verifyKeySignature({ + userKeychain: userKeychain as { pub: string }, + keychainToVerify: keychainToVerify as { pub: string }, + keySignature, + }) + ) { debug('failed to verify custom change %s key signature!', KeyIndices[keyIndex].toLowerCase()); return false; } @@ -814,8 +834,16 @@ export abstract class AbstractUtxoCoin extends BaseCoin { // let's verify these keychains const keySignatures = parsedTransaction.keySignatures; if (!_.isEmpty(keySignatures)) { - const verify = (key, pub) => - this.verifyKeySignature({ userKeychain: keychains.user, keychainToVerify: key, keySignature: pub }); + const verify = (key, pub) => { + if (!keychains.user || !keychains.user.pub) { + throw new Error('missing user keychain'); + } + return this.verifyKeySignature({ + userKeychain: keychains.user as { pub: string }, + keychainToVerify: key, + keySignature: pub, + }); + }; const isBackupKeySignatureValid = verify(keychains.backup, keySignatures.backupPub); const isBitgoKeySignatureValid = verify(keychains.bitgo, keySignatures.bitgoPub); if (!isBackupKeySignatureValid || !isBitgoKeySignatureValid) { diff --git a/modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts b/modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts index 5fecaa8db1..c70dcabf51 100644 --- a/modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts +++ b/modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts @@ -1,7 +1,7 @@ import * as utxolib from '@bitgo/utxo-lib'; import * as should from 'should'; import * as sinon from 'sinon'; -import { Keychain, UnexpectedAddressError, VerificationOptions } from '@bitgo/sdk-core'; +import { UnexpectedAddressError, VerificationOptions } from '@bitgo/sdk-core'; import { TestBitGo } from '@bitgo/sdk-test'; import { BitGo } from '../../../../src/bitgo'; import { @@ -526,18 +526,5 @@ describe('Abstract UTXO Coin:', () => { coinMock.restore(); bitcoinMock.restore(); }); - - it('should verify key signature of ZEC', async () => { - const zecCoin = bitgo.coin('tzec') as AbstractUtxoCoin; - const userKeychain = await zecCoin.keychains().create(); - const otherKeychain = await zecCoin.keychains().create(); - - await zecCoin.verifyKeySignature({ - userKeychain: (userKeychain as unknown) as Keychain, - keychainToVerify: (otherKeychain as unknown) as Keychain, - keySignature: await sign(userKeychain, otherKeychain, zecCoin), - }).should.be.true(); - }); - }); }); diff --git a/modules/bitgo/test/v2/unit/coins/utxo/keySignatures.ts b/modules/bitgo/test/v2/unit/coins/utxo/keySignatures.ts new file mode 100644 index 0000000000..46b26613f4 --- /dev/null +++ b/modules/bitgo/test/v2/unit/coins/utxo/keySignatures.ts @@ -0,0 +1,31 @@ +import * as assert from 'assert'; +import { AbstractUtxoCoin } from '@bitgo/abstract-utxo'; +import { Keychain } from '@bitgo/sdk-core'; + +import { utxoCoins } from './util'; + +function describeWithCoin(coin: AbstractUtxoCoin) { + describe(`verifyKeySignatures for ${coin.getChain()}`, function () { + it('should verify key signature of ZEC', async () => { + const userKeychain = await coin.keychains().create(); + const backupKeychain = await coin.keychains().create(); + const bitgoKeychain = await coin.keychains().create(); + + const signatures = await coin.createKeySignatures( + userKeychain.prv, + { pub: backupKeychain.pub as string }, + { pub: bitgoKeychain.pub as string }, + ); + + assert.ok( + await coin.verifyKeySignature({ + userKeychain: (userKeychain as unknown) as Keychain, + keychainToVerify: (backupKeychain as unknown) as Keychain, + keySignature: signatures.backup, + })); + }); + }); +} + +utxoCoins.forEach(coin => describeWithCoin(coin)); +