Skip to content

Commit

Permalink
feat(abstract-utxo): move keysSignatures test to separate file
Browse files Browse the repository at this point in the history
Loop over all utxo coins

BG-79423
  • Loading branch information
OttoAllmendinger authored and rushilbg committed Jun 28, 2023
1 parent 1c7a8f6 commit b082b3b
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 21 deletions.
26 changes: 20 additions & 6 deletions modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -744,7 +744,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;
}
Expand Down Expand Up @@ -814,8 +820,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) {
Expand Down
17 changes: 2 additions & 15 deletions modules/bitgo/test/v2/unit/coins/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -247,7 +247,7 @@ describe('Abstract UTXO Coin:', () => {
};

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sign = async (key, keychain, coinToSignFor = coin) => (await coinToSignFor.signMessage(keychain, key.pub!)).toString('hex');
const sign = async (key, keychain) => (await coin.signMessage(keychain, key.pub!)).toString('hex');
const signUser = (key) => sign(key, userKeychain);
const signOther = (key) => sign(key, otherKeychain);
const passphrase = 'test_passphrase';
Expand Down Expand Up @@ -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();
});

});
});
31 changes: 31 additions & 0 deletions modules/bitgo/test/v2/unit/coins/utxo/keySignatures.ts
Original file line number Diff line number Diff line change
@@ -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));

15 changes: 15 additions & 0 deletions modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,19 @@ export abstract class BaseCoin implements IBaseCoin {
getHashFunction(): Hash {
throw new NotImplementedError('getHashFunction is not supported for this coin');
}

// `AbstractUtxoCoin` implements and uses the complementary `verifyKeySignature` method.
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'),
};
}
}
62 changes: 62 additions & 0 deletions modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,61 +416,123 @@ export type MPCAlgorithm = 'ecdsa' | 'eddsa';
export interface IBaseCoin {
type: string;
tokenConfig?: BaseTokenConfig;

url(suffix: string): string;

wallets(): IWallets;

enterprises(): IEnterprises;

keychains(): IKeychains;

webhooks(): IWebhooks;

pendingApprovals(): IPendingApprovals;

markets(): IMarkets;

getChain(): string;

getFamily(): string;

getFullName(): string;

valuelessTransferAllowed(): boolean;

sweepWithSendMany(): boolean;

transactionDataAllowed(): boolean;

allowsAccountConsolidations(): boolean;

getTokenEnablementConfig(): TokenEnablementConfig;

supportsTss(): boolean;

isEVM(): boolean;

supportsBlsDkg(): boolean;

getBaseFactor(): number | string;

baseUnitsToBigUnits(baseUnits: string | number): string;

bigUnitsToBaseUnits(bigUnits: string | number): string;

signMessage(key: { prv: string }, message: string): Promise<Buffer>;

explainTransaction(options: Record<string, any>): Promise<ITransactionExplanation<any, string | number> | undefined>;

verifyTransaction(params: VerifyTransactionOptions): Promise<boolean>;

verifyAddress(params: VerifyAddressOptions): Promise<boolean>;

isWalletAddress(params: VerifyAddressOptions): Promise<boolean>;

canonicalAddress(address: string, format: unknown): string;

supportsBlockTarget(): boolean;

supportsLightning(): boolean;

supportsMessageSigning(): boolean;

supportsSigningTypedData(): boolean;

supplementGenerateWallet(walletParams: SupplementGenerateWalletOptions, keychains: KeychainsTriplet): Promise<any>;

getExtraPrebuildParams(buildParams: ExtraPrebuildParamsOptions): Promise<Record<string, unknown>>;

postProcessPrebuild(prebuildResponse: TransactionPrebuild): Promise<TransactionPrebuild>;

presignTransaction(params: PresignTransactionOptions): Promise<PresignTransactionOptions>;

signWithCustomSigningFunction?(
customSigningFunction: CustomSigningFunction,
signTransactionParams: { txPrebuild: TransactionPrebuild; pubs?: string[] }
): Promise<SignedTransaction>;

newWalletObject(walletParams: any): IWallet;

feeEstimate(params: FeeEstimateOptions): Promise<any>;

deriveKeyWithSeed(params: DeriveKeyWithSeedOptions): { key: string; derivationPath: string };

keyIdsForSigning(): number[];

preCreateBitGo(params: PrecreateBitGoOptions): void;

initiateRecovery(params: InitiateRecoveryOptions): never;

parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction>;

generateKeyPair(seed?: Buffer): KeyPair;

isValidPub(pub: string): boolean;

isValidMofNSetup(params: ValidMofNOptions): boolean;

isValidAddress(address: string): boolean;

signTransaction(params: SignTransactionOptions): Promise<SignedTransaction>;

getSignablePayload(serializedTx: string): Promise<Buffer>;

getMPCAlgorithm(): MPCAlgorithm;

// TODO - this only belongs in eth coins
recoverToken(params: RecoverWalletTokenOptions): Promise<RecoverTokenTransaction>;

getInscriptionBuilder(wallet: Wallet): IInscriptionBuilder;

getHashFunction(): Hash;

createKeySignatures(
prv: string,
backupKeychain: { pub: string },
bitgoKeychain: { pub: string }
): Promise<{
backup: string;
bitgo: string;
}>;
}

0 comments on commit b082b3b

Please sign in to comment.