Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions modules/sdk-coin-canton/src/canton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import {
SignedTransaction,
SignTransactionOptions,
TransactionType,
TssVerifyAddressOptions,
VerifyTransactionOptions,
TransactionExplanation as BaseTransactionExplanation,
BaseTransaction,
PopulatedIntent,
PrebuildTransactionWithIntentOptions,
TssVerifyAddressOptions,
InvalidAddressError,
extractCommonKeychain,
EDDSAMethods,
} from '@bitgo/sdk-core';
import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
Expand Down Expand Up @@ -115,8 +118,25 @@ export class Canton extends BaseCoin {
}

/** @inheritDoc */
isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
throw new Error('Method not implemented.');
async isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
// TODO: refactor this and use the `verifyEddsaMemoBasedWalletAddress` once published from sdk-core
// https://bitgoinc.atlassian.net/browse/COIN-6347
const { keychains, address: newAddress, index } = params;
const [addressPart, memoId] = newAddress.split('?memoId=');
if (!this.isValidAddress(addressPart)) {
throw new InvalidAddressError(`invalid address: ${newAddress}`);
}
if (memoId && memoId !== index) {
throw new InvalidAddressError(`invalid memoId index: ${memoId}`);
}
const commonKeychain = extractCommonKeychain(keychains);
const MPC = await EDDSAMethods.getInitializedMpcInstance();
const derivationPath = 'm/0';
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);
const publicKeyBase64 = Buffer.from(derivedPublicKey, 'hex').toString('base64');
const rootAddressFingerprint = utils.getAddressFromPublicKey(publicKeyBase64);
const rootAddress = `${rootAddressFingerprint.slice(0, 5)}::${rootAddressFingerprint}`;
return addressPart === rootAddress;
}

/** @inheritDoc */
Expand Down Expand Up @@ -163,6 +183,11 @@ export class Canton extends BaseCoin {
return utils.isValidAddress(address);
}

getAddressFromPublicKey(publicKeyHex: string): string {
const publicKeyBase64 = Buffer.from(publicKeyHex, 'hex').toString('base64');
return utils.getAddressFromPublicKey(publicKeyBase64);
}

/** @inheritDoc */
signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
throw new Error('Method not implemented.');
Expand Down
79 changes: 79 additions & 0 deletions modules/sdk-coin-canton/test/integration/canton.integration.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import assert from 'assert';

import { BitGoAPI } from '@bitgo/sdk-api';
import { TransactionType } from '@bitgo/sdk-core';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';

import { getCantonBuilderFactory } from '../helper';
import {
CANTON_RECEIVE_ADDRESS,
GenerateTopologyResponse,
TransferAcceptRawTransaction,
TransferRejectRawTransaction,
WalletInitRawTransaction,
} from '../resources';
import { Tcanton } from '../../src';

describe('Canton integration tests', function () {
let bitgo: TestBitGoAPI;
let basecoin: Tcanton;
before(() => {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
bitgo.safeRegister('tcanton', Tcanton.createInstance);
basecoin = bitgo.coin('tcanton') as Tcanton;
});

describe('Explain raw transaction', function () {
const factory = getCantonBuilderFactory('tcanton');
it('should explain raw wallet init transaction', function () {
Expand Down Expand Up @@ -41,4 +53,71 @@ describe('Canton integration tests', function () {
assert.equal(explainTxData.inputAmount, '5.0000000000');
});
});

describe('isWalletAddress', function () {
let keychains;
let commonKeychain: string;
before(function () {
commonKeychain =
'19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781';
keychains = [
{
id: '6424c353eaf78d000766e95949868468',
source: 'user',
type: 'tss',
commonKeychain:
'19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781',
encryptedPrv:
'{"iv":"cZd5i7L4RxtwrALW2rK7UA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"5zgoH1Bd3Fw=","ct":"9vVlnXFRtrM9FVEo+d2chbGHlM9lFZemueBuAs3BIkPo33Fo7jzwwNK/kIWkEyg+NmEBd5IaqAS157nvvvwzzsmMWlQdUz9qbmXNv3pg987cXFR08exS+4uhwP1YNOjJTRvRNcO9ZqHb46d4fmyJ/yC9/susCge7r/EsbaN5C3afv1dzybuq912FwaQElZLYYp5BICudFOMZ9k0UDMfKM/PMDkH7WexoGHr9GKq/bgCH2B39TZZyHKU6Uy47lXep2s6h0DrMwHOrnmiL3DZjOj88Ynvphlzxuo4eOlD2UHia2+nvIaISYs29Pr0DAvREutchvcBpExj1kWWPv7hQYrv8F0NAdatsbWl3w+xKyfiMKo1USlrwyJviypGtQtXOJyw0XPN0rv2+L5lW8BbjpzHfYYN13fJTedlGTFhhkzVtbbPAKE02kx7zCJcjYaiexdSTsrDLScYNT9/Jhdt27KpsooehwVohLfSKz4vbFfRu2MPZw3/+c/hfiJNgtz6esWbnxGrcE8U2IwPYCaK+Ghk4DcqWNIni59RI5B5kAsQOToII40qPN510uTgxBSPO7q7MHgkxdd4CqBq+ojr9j0P7oao8E5Y+CBDJrojDoCh1oCCDW9vo2dXlVcD8SIbw7U/9AfvEbA4xyE/5md1M7CIwLnWs2Ynv0YtaKoqhdS9x6FmHlMDhN/DKHinrwmowtrTT82fOkpO5g9saSmgU7Qy3gLt8t+VwdEyeFeQUKRSyci8qgqXQaZIg4+aXgaSOnlCFMtmB8ekYxEhTY5uzRfrNgS4s1QeqFBpNtUF+Ydi297pbVXnJoXAN+SVWd80GCx+yI2dpVC89k3rOWK9WeyqlnzuLJWp2RIOB9cdW8GFv/fN+QAJpYeVxOE4+nZDsKnsj8nKcg9t4Dlx1G6gLM1/Vq9YxNLbuzuRC0asUYvdMnoMvszmpm++TxndYisgNYscpZSoz7wvcazJNEPfhPVjEkd6tUUuN4GM35H0DmKCUQNT+a6B6hmHlTZvjxiyGAg5bY59hdjvJ+22QduazlEEC6LI3HrA7uK0TpplWzS1tCIFvTMUhj65DEZmNJ2+ZY9bQ4vsMf+DRR3OOG4t+DMlNfjOd3zNv3QoY95BjfWpryFwPzDq7bCP67JDsoj7j2TY5FRSrRkD77H0Ewlux2cWfjRTwcMHcdQxxuV0OP0aNjGDjybFN"}',
},
{
id: '6424c353eaf78d000766e96137d4404b',
source: 'backup',
type: 'tss',
commonKeychain:
'19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781',
encryptedPrv:
'{"iv":"vi0dPef/Rx7kG/pRySQi6Q==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"9efhQsiEvVs=","ct":"Gw6atvf6gxKzsjtl3xseipO3rAxp1mAz7Yu1ihFsi5/lf2vMZegApgZx+pyILFS9KKLHbNF3U6WgSYdrr2t4vzdLsXkH1WIxfHS+cd2C5N59yADZDnPJBT6pv/IRvaYelP0Ck3nIYQ2hSMm8op+VOWC/SzHeh7slYDqwEHTGan0Wigfvk1yRd7CCJTaEAomnc/4eFi2NY3X3gt/3opy9IAgknnwUFohn96EWpEQ0F6pbzH/Z8VF6gF+DUcrrByAxExUPnHQZiFk3YHU/vVV4FxBU/mVAE8xBsBn5ul5e5SUMPfc7TBuJWv4BByTNg9xDShF/91Yx2nbfUm5d9QmM8lpKgzzQvcK8POAPk87gRCuKnsGh5vNS0UppkHc+ocfzRQlGA6jze7QyyQO0rMj5Ly8kWjwk2vISvKYHYS1NR7VU549UIXo7NXjatunKSc3+IreoRUHIshiaLg6hl+pxCCuc0qQ43V0mdIfCjTN8gkGWLNk8R7tAGPz9jyapQPcPEGHgEz0ATIi6yMNWCsibS2eLiE1uVEJONoM4lk6FPl3Q2CHbW2MeEbqjY8hbaw18mNb2xSBH/Fwpiial+Tvi2imqgnCO4ZpO9bllKftZPcQy0stN+eGBlb5ufyflKkDSiChHYroGjEpmiFicdde48cJszF52uKNnf1q67fA9/S2FAHQab3EXojxH2Gbk+kkV2h/TYKFFZSWC3vi4e8mO+vjMUcR0AdsgPFyEIz0SCGuba3CnTLNdEuZwsauAeHkx2vUTnRgJPVgNeeuXmsVG76Sy2ggJHuals0Hj8U2Xda0qO1RuFfoCWfss9wn6HGRwPPkhSB/8oNguAqmRVGKkd8Zwt3IvrTd9fk0/rFFDJKGz7WyNHkYgUmNiGcItD12v0jx7FZ52EJzl3Av1RyJUQK18+8EYPh3SGiU9dt7VX0aF0uo6JouKhOeldUvMP+AugQz8fUclwTQsbboVg27Yxo0DyATVwThW5a56R6Qf5ZiQJluFuzs5y98rq0S5q046lE6o3vVmJpEdwjeSCJoET5CL4nTgkXyWvhm4eB8u/e66l3o0qbaSx8q9YYmT9EpRcl5TP4ThLBKETYdzVvg4exjQfektMatk5EyUpEIhZPXh5vXpJZesdfO9LJ8zTaHBsBjDPU7cdNgQMbebpataRi8A0el2/IJXl+E+olgAz5zC4i2O1Q=="}',
},
{
id: '6424c353eaf78d000766e9510b125fba',
source: 'bitgo',
type: 'tss',
commonKeychain:
'19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781',
verifiedVssProof: true,
isBitGo: true,
},
];
});
it('should return true when receive address is valid', async function () {
const address = CANTON_RECEIVE_ADDRESS.VALID_ADDRESS;
const index = '1';
const params = { commonKeychain, address: address, index, keychains };
const isValid = await basecoin.isWalletAddress(params);
isValid.should.equal(true);
});

it('should throw error when receive address is invalid', async function () {
const address = CANTON_RECEIVE_ADDRESS.INVALID_ADDRESS;
const index = '1';
const params = { commonKeychain, address: address, index, keychains };
try {
await basecoin.isWalletAddress(params);
} catch (e) {
assert.equal(e.message, 'invalid address: ' + address);
}
});

it('should throw error when receive address memoId is incorrect index', async function () {
const address = CANTON_RECEIVE_ADDRESS.VALID_ADDRESS;
const index = '2';
const params = { commonKeychain, address: address, index, keychains };
try {
await basecoin.isWalletAddress(params);
} catch (e) {
assert.equal(e.message, 'invalid memoId index: 1');
}
});
});
});
11 changes: 8 additions & 3 deletions modules/sdk-coin-canton/test/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const WalletInitRequestData = {
};

export const OneStepEnablement = {
partyId: 'ravi-test-party-1::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d',
partyId: 'ravi-test-party-1::1220a43d89dc7d8f85316116aac093667f769fce55411aef6846ccb933b2e1a3b598',
commandId: '3935a06d-3b03-41be-99a5-95b2ecaabf7d',
};

Expand All @@ -63,9 +63,9 @@ export const InvalidOneStepPreApprovalPrepareResponse = {
};

export const CANTON_ADDRESSES = {
VALID_ADDRESS: '12205::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d',
VALID_ADDRESS: '1220a::1220a43d89dc7d8f85316116aac093667f769fce55411aef6846ccb933b2e1a3b598',
// party hint is not 5 characters
INVALID_PARTY_HINT: '123456::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d',
INVALID_PARTY_HINT: '123456::1220a43d89dc7d8f85316116aac093667f769fce55411aef6846ccb933b2e1a3b598',
// fingerprint is not a valid hex value
INVALID_FINGERPRINT: '12205::12205b4e3537a95126d9060459234gd8ad3c3ddccda4f79901954280ee19c576714d',
MISSING_PARTY_HINT: '::12205b4e3537a95126d9060459234gd8ad3c3ddccda4f79901954280ee19c576714d',
Expand All @@ -78,6 +78,11 @@ export const CANTON_BLOCK_HEIGHT = {
NEGATIVE_BLOCK_HASH: '-100',
};

export const CANTON_RECEIVE_ADDRESS = {
VALID_ADDRESS: `${CANTON_ADDRESSES.VALID_ADDRESS}?memoId=1`,
INVALID_ADDRESS: `${CANTON_ADDRESSES.INVALID_FINGERPRINT}?memoId=1`,
};

export const TransferAcceptance = {
partyId: 'ravi-test-party-1::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d',
commandId: '3935a06d-3b03-41be-99a5-95b2ecaabf7d',
Expand Down