From 6548adfd9523cc9aec40cdfbfee4f6d20a84efd2 Mon Sep 17 00:00:00 2001 From: Alejo Acosta <alejoacos@gmail.com> Date: Mon, 28 Oct 2024 14:39:33 -0300 Subject: [PATCH 1/2] export address info interfaces --- src/quais.ts | 2 ++ src/wallet/index.ts | 4 ++-- src/wallet/qi-hdwallet.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/quais.ts b/src/quais.ts index d5ddcce7..a7d0a14c 100644 --- a/src/quais.ts +++ b/src/quais.ts @@ -218,6 +218,8 @@ export { encryptKeystoreJsonSync, SerializedHDWallet, SerializedQiHDWallet, + QiAddressInfo, + NeuteredAddressInfo, } from './wallet/index.js'; // WORDLIST diff --git a/src/wallet/index.ts b/src/wallet/index.ts index 85043b2d..dec13e6d 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -13,7 +13,7 @@ export { BaseWallet } from './base-wallet.js'; -export type { SerializedHDWallet } from './hdwallet.js'; +export type { SerializedHDWallet, NeuteredAddressInfo } from './hdwallet.js'; export { QuaiHDWallet } from './quai-hdwallet.js'; @@ -31,6 +31,6 @@ export { Wallet } from './wallet.js'; export type { KeystoreAccount, EncryptOptions } from './json-keystore.js'; -export { QiHDWallet, SerializedQiHDWallet } from './qi-hdwallet.js'; +export { QiHDWallet, SerializedQiHDWallet, QiAddressInfo } from './qi-hdwallet.js'; export { HDNodeVoidWallet, HDNodeWallet } from './hdnodewallet.js'; diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index 46f20253..14eb915a 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -62,7 +62,7 @@ type DerivationPath = 'BIP44:external' | 'BIP44:change' | string; // string for * * @extends NeuteredAddressInfo */ -interface QiAddressInfo extends NeuteredAddressInfo { +export interface QiAddressInfo extends NeuteredAddressInfo { status: AddressStatus; derivationPath: DerivationPath; } From 8934d8b52b7655ca64adfbf991118e4d3fef7bf1 Mon Sep 17 00:00:00 2001 From: Alejo Acosta <alejoacos@gmail.com> Date: Mon, 28 Oct 2024 17:39:03 -0300 Subject: [PATCH 2/2] add unit test for Qi address derivation --- ...qihdwallet-address-derivation.unit.test.ts | 113 ++++++++++++++++++ testcases/qi-address-derivation.json.gz | Bin 0 -> 2941 bytes 2 files changed, 113 insertions(+) create mode 100644 src/_tests/unit/qihdwallet-address-derivation.unit.test.ts create mode 100644 testcases/qi-address-derivation.json.gz diff --git a/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts b/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts new file mode 100644 index 00000000..5060bad9 --- /dev/null +++ b/src/_tests/unit/qihdwallet-address-derivation.unit.test.ts @@ -0,0 +1,113 @@ +import assert from 'assert'; +import { loadTests } from '../utils.js'; +import { Mnemonic, QiHDWallet, Zone, QiAddressInfo } from '../../index.js'; + +interface TestCaseQiAddressDerivation { + mnemonic: string; + externalAddresses: Array<{ + zone: string; + addresses: Array<QiAddressInfo>; + }>; + changeAddresses: Array<{ + zone: string; + addresses: Array<QiAddressInfo>; + }>; + paymentCodeAddresses: { + bobMnemonic: string; + sendAddresses: Array<{ + zone: string; + addresses: Array<QiAddressInfo>; + }>; + receiveAddresses: Array<{ + zone: string; + addresses: Array<QiAddressInfo>; + }>; + }; +} + +describe('QiHDWallet Address Derivation', function () { + this.timeout(2 * 60 * 1000); + const tests = loadTests<TestCaseQiAddressDerivation>('qi-address-derivation'); + + for (const test of tests) { + it('derives external addresses correctly', function () { + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); + + for (const externalAddressesInfo of test.externalAddresses) { + const zone = externalAddressesInfo.zone as Zone; + for (const expectedAddressInfo of externalAddressesInfo.addresses) { + const derivedAddressInfo = qiWallet.getNextAddressSync(0, zone); + assert.deepEqual( + derivedAddressInfo, + expectedAddressInfo, + `External address mismatch for zone ${zone}, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`, + ); + } + } + }); + + it('derives change addresses correctly', function () { + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); + + for (const changeAddressesInfo of test.changeAddresses) { + const zone = changeAddressesInfo.zone as Zone; + for (const expectedAddressInfo of changeAddressesInfo.addresses) { + const derivedAddressInfo = qiWallet.getNextChangeAddressSync(0, zone); + assert.deepEqual( + derivedAddressInfo, + expectedAddressInfo, + `Change address mismatch for zone ${zone}, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`, + ); + } + } + }); + + it('derives payment code send addresses correctly', function () { + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); + + const bobMnemonic = Mnemonic.fromPhrase(test.paymentCodeAddresses.bobMnemonic); + const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic); + const bobPaymentCode = bobQiWallet.getPaymentCode(0); + + qiWallet.openChannel(bobPaymentCode); + + for (const sendAddressesInfo of test.paymentCodeAddresses.sendAddresses) { + const zone = sendAddressesInfo.zone as Zone; + for (const expectedAddressInfo of sendAddressesInfo.addresses) { + const derivedAddressInfo = qiWallet.getNextSendAddress(bobPaymentCode, zone); + assert.deepEqual( + derivedAddressInfo, + expectedAddressInfo, + `Payment code send address mismatch, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`, + ); + } + } + }); + + it('derives payment code receive addresses correctly', function () { + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); + + const bobMnemonic = Mnemonic.fromPhrase(test.paymentCodeAddresses.bobMnemonic); + const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic); + const bobPaymentCode = bobQiWallet.getPaymentCode(0); + + qiWallet.openChannel(bobPaymentCode); + + for (const receiveAddressesInfo of test.paymentCodeAddresses.receiveAddresses) { + const zone = receiveAddressesInfo.zone as Zone; + for (const expectedAddressInfo of receiveAddressesInfo.addresses) { + const derivedAddressInfo = qiWallet.getNextReceiveAddress(bobPaymentCode, zone); + assert.deepEqual( + derivedAddressInfo, + expectedAddressInfo, + `Payment code receive address mismatch, expected: ${JSON.stringify(expectedAddressInfo)}, derived: ${JSON.stringify(derivedAddressInfo)}`, + ); + } + } + }); + } +}); diff --git a/testcases/qi-address-derivation.json.gz b/testcases/qi-address-derivation.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..023247746e817e8a6189d2b6501e43bf614c46a9 GIT binary patch literal 2941 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 literal 0 HcmV?d00001