diff --git a/modules/sdk-coin-canton/.gitignore b/modules/sdk-coin-canton/.gitignore index 67ccce4c64..8ef89a38c9 100644 --- a/modules/sdk-coin-canton/.gitignore +++ b/modules/sdk-coin-canton/.gitignore @@ -1,3 +1,4 @@ node_modules/ .idea/ dist/ +.DS_Store diff --git a/modules/sdk-coin-canton/src/canton.ts b/modules/sdk-coin-canton/src/canton.ts index 9600a3b3e1..a7c88d65ec 100644 --- a/modules/sdk-coin-canton/src/canton.ts +++ b/modules/sdk-coin-canton/src/canton.ts @@ -10,11 +10,13 @@ import { ParseTransactionOptions, SignedTransaction, SignTransactionOptions, - VerifyAddressOptions, + TransactionType, + TssVerifyAddressOptions, VerifyTransactionOptions, } from '@bitgo/sdk-core'; import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc'; -import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; +import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; +import { TransactionBuilderFactory } from './lib'; import { KeyPair as CantonKeyPair } from './lib/keyPair'; import utils from './lib/utils'; @@ -70,12 +72,29 @@ export class Canton extends BaseCoin { } /** @inheritDoc */ - verifyTransaction(params: VerifyTransactionOptions): Promise { - throw new Error('Method not implemented.'); + async verifyTransaction(params: VerifyTransactionOptions): Promise { + const coinConfig = coins.get(this.getChain()); + // extract `txParams` when verifying other transaction types + const { txPrebuild: txPrebuild } = params; + const rawTx = txPrebuild.txHex; + if (!rawTx) { + throw new Error('missing required tx prebuild property txHex'); + } + const txBuilder = new TransactionBuilderFactory(coinConfig).from(rawTx); + const transaction = txBuilder.transaction; + switch (transaction.type) { + case TransactionType.WalletInitialization: { + // there is no input for this type of transaction, so always return true + return true; + } + default: { + throw new Error(`unknown transaction type, ${transaction.type}`); + } + } } /** @inheritDoc */ - isWalletAddress(params: VerifyAddressOptions): Promise { + isWalletAddress(params: TssVerifyAddressOptions): Promise { throw new Error('Method not implemented.'); } @@ -104,7 +123,9 @@ export class Canton extends BaseCoin { /** @inheritDoc */ isValidAddress(address: string): boolean { - throw new Error('Method not implemented.'); + // canton addresses are of the form, partyHint::fingerprint + // where partyHint is of length 5 and fingerprint is 68 characters long + return utils.isValidAddress(address); } /** @inheritDoc */ diff --git a/modules/sdk-coin-canton/src/lib/transactionBuilder.ts b/modules/sdk-coin-canton/src/lib/transactionBuilder.ts index 6f6a965334..8b635f05af 100644 --- a/modules/sdk-coin-canton/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-canton/src/lib/transactionBuilder.ts @@ -27,7 +27,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { protected abstract get transactionType(): TransactionType; /** @inheritdoc */ - protected get transaction(): Transaction { + get transaction(): Transaction { return this._transaction; } diff --git a/modules/sdk-coin-canton/src/lib/utils.ts b/modules/sdk-coin-canton/src/lib/utils.ts index 3ab062d8e0..58050e9df8 100644 --- a/modules/sdk-coin-canton/src/lib/utils.ts +++ b/modules/sdk-coin-canton/src/lib/utils.ts @@ -12,7 +12,10 @@ import { RecordField } from './resourcesInterface'; export class Utils implements BaseUtils { /** @inheritdoc */ isValidAddress(address: string): boolean { - throw new Error('Method not implemented.'); + if (!address || address.trim() === '') return false; + const [partyHint, fingerprint] = address.trim().split('::'); + if (!partyHint || !fingerprint) return false; + return partyHint.length === 5 && this.isValidCantonHex(fingerprint); } /** @inheritdoc */ @@ -40,6 +43,16 @@ export class Utils implements BaseUtils { throw new Error('Method not implemented.'); } + /** + * Method to validate the input is a valid canton hex string + * @param {String} value the hex string value + * @returns {Boolean} true if valid + */ + isValidCantonHex(value: string): boolean { + const regex = /^[a-fA-F0-9]{68}$/; + return regex.test(value); + } + /** * Method to create fingerprint (part of the canton partyId) from public key * @param {String} publicKey the public key diff --git a/modules/sdk-coin-canton/test/resources.ts b/modules/sdk-coin-canton/test/resources.ts index b513d65356..d894b96fcf 100644 --- a/modules/sdk-coin-canton/test/resources.ts +++ b/modules/sdk-coin-canton/test/resources.ts @@ -59,3 +59,13 @@ export const InvalidOneStepPreApprovalPrepareResponse = { hashingSchemeVersion: 'HASHING_SCHEME_VERSION_V2', hashingDetails: null, }; + +export const CANTON_ADDRESSES = { + VALID_ADDRESS: '12205::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d', + // party hint is not 5 characters + INVALID_PARTY_HINT: '123456::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d', + // fingerprint is not a valid hex value + INVALID_FINGERPRINT: '12205::12205b4e3537a95126d9060459234gd8ad3c3ddccda4f79901954280ee19c576714d', + MISSING_PARTY_HINT: '::12205b4e3537a95126d9060459234gd8ad3c3ddccda4f79901954280ee19c576714d', + MISSING_FINGERPRINT: '12205::', +}; diff --git a/modules/sdk-coin-canton/test/unit/utils.ts b/modules/sdk-coin-canton/test/unit/utils.ts index 57ce0c0a48..32ce645f53 100644 --- a/modules/sdk-coin-canton/test/unit/utils.ts +++ b/modules/sdk-coin-canton/test/unit/utils.ts @@ -1,7 +1,12 @@ import assert from 'assert'; import should from 'should'; import utils from '../../src/lib/utils'; -import { GenerateTopologyResponse, PreparedTransactionRawData, PrepareSubmissionResponse } from '../resources'; +import { + CANTON_ADDRESSES, + GenerateTopologyResponse, + PreparedTransactionRawData, + PrepareSubmissionResponse, +} from '../resources'; describe('Canton Util', function () { describe('Raw transaction parser', function () { @@ -31,4 +36,36 @@ describe('Canton Util', function () { assert.strictEqual(computedHash, PrepareSubmissionResponse.preparedTransactionHash); }); }); + + describe('Check if the address is valid', function () { + it('should return true when the address is valid', function () { + const isValid = utils.isValidAddress(CANTON_ADDRESSES.VALID_ADDRESS); + should.exist(isValid); + assert.strictEqual(isValid, true); + }); + + it('should return false when party hint is invalid', function () { + const isValid = utils.isValidAddress(CANTON_ADDRESSES.INVALID_PARTY_HINT); + should.exist(isValid); + assert.strictEqual(isValid, false); + }); + + it('should return false when fingerprint is invalid', function () { + const isValid = utils.isValidAddress(CANTON_ADDRESSES.INVALID_FINGERPRINT); + should.exist(isValid); + assert.strictEqual(isValid, false); + }); + + it('should return false when party hint is missing', function () { + const isValid = utils.isValidAddress(CANTON_ADDRESSES.MISSING_PARTY_HINT); + should.exist(isValid); + assert.strictEqual(isValid, false); + }); + + it('should return false when fingerprint is missing', function () { + const isValid = utils.isValidAddress(CANTON_ADDRESSES.MISSING_FINGERPRINT); + should.exist(isValid); + assert.strictEqual(isValid, false); + }); + }); });