diff --git a/package-lock.json b/package-lock.json index 8bbe896..92d90f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@knuth/bch", - "version": "1.24.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@knuth/bch", - "version": "1.24.0", + "version": "2.0.0", "license": "MIT", "dependencies": { - "@knuth/bch-native": "^0.57.0", + "@knuth/bch-native": "^1.0.0", "bluebird": "^3.7.2", "memoizee": "^0.4.14" }, @@ -992,13 +992,13 @@ "dev": true }, "node_modules/@knuth/bch-native": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@knuth/bch-native/-/bch-native-0.57.0.tgz", - "integrity": "sha512-D0Gi7McxURm1gRPZe2JmqiTg4CNz7D4E6pR4eN/3lIWtir6oiJCKuMaOaz1u12hp34SbKBfqGjHW8dcjvLoCEA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@knuth/bch-native/-/bch-native-1.0.0.tgz", + "integrity": "sha512-Z9jjL9c//nY4w9vRArMr9yirDbkp8Zzs5JQnoLVS26fd+jZSp942DVBtK0sycNAxu7/pmcPhM6xTqB1aFIuIUg==", "hasInstallScript": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", - "nan": "^2.16.0" + "nan": "^2.17.0" }, "engines": { "node": ">=12.0.0", @@ -9657,12 +9657,12 @@ } }, "@knuth/bch-native": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@knuth/bch-native/-/bch-native-0.57.0.tgz", - "integrity": "sha512-D0Gi7McxURm1gRPZe2JmqiTg4CNz7D4E6pR4eN/3lIWtir6oiJCKuMaOaz1u12hp34SbKBfqGjHW8dcjvLoCEA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@knuth/bch-native/-/bch-native-1.0.0.tgz", + "integrity": "sha512-Z9jjL9c//nY4w9vRArMr9yirDbkp8Zzs5JQnoLVS26fd+jZSp942DVBtK0sycNAxu7/pmcPhM6xTqB1aFIuIUg==", "requires": { "@mapbox/node-pre-gyp": "^1.0.11", - "nan": "^2.16.0" + "nan": "^2.17.0" } }, "@mapbox/node-pre-gyp": { diff --git a/package.json b/package.json index 3452456..c1119b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@knuth/bch", - "version": "1.24.0", + "version": "2.0.0", "description": "Bitcoin Cash development platform for Javascript and Typescript applications", "main": "src/kth.js", "scripts": { @@ -39,7 +39,7 @@ "homepage": "https://github.com/k-nuth/js-api#readme", "dependencies": { "bluebird": "^3.7.2", - "@knuth/bch-native": "^0.57.0", + "@knuth/bch-native": "^1.0.0", "memoizee": "^0.4.14" }, "devDependencies": { diff --git a/src/wallet/paymentAddress.js b/src/wallet/paymentAddress.js index 3e48167..ee53b50 100644 --- a/src/wallet/paymentAddress.js +++ b/src/wallet/paymentAddress.js @@ -29,8 +29,11 @@ class PaymentAddress { } encoded() { - const res = memoizedEncoded(this, false); - return res; + if (this.tokenAware) { + const res = memoizedEncoded(this, false); + return res; + } + return this.addressStr; } // This is an alias for encoded() diff --git a/src/wallet/wallet.d.ts b/src/wallet/wallet.d.ts new file mode 100644 index 0000000..a1c18fd --- /dev/null +++ b/src/wallet/wallet.d.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2016-2023 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import { Result } from '../result'; + +export declare class Wallet { + constructor(mnemonic: string[], derivationPath: string, network?: string); + + readonly rootKey: string; + readonly extendedPrivateKey: string; + readonly extendedPublicKey: string; + + getAddress(index: number): PaymentAddress; + getAddresses(count?: number): PaymentAddress[]; + + deriveAccount(derivationPath: string): Wallet; + + //TODO: static generate(): Wallet; + //TODO: static isValidMnemonic(mnemonic: string[]): boolean; + //TODO: mnemonic(): string[]; + //TODO: toPublic(): Wallet; +} + +// Si la clase PaymentAddress está en un archivo separado, deberías importarla así: +import { PaymentAddress } from './paymentAddress'; + diff --git a/src/wallet/wallet.js b/src/wallet/wallet.js new file mode 100644 index 0000000..ef5d390 --- /dev/null +++ b/src/wallet/wallet.js @@ -0,0 +1,107 @@ +// Copyright (c) 2016-2023 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +const kth = require('@knuth/bch-native'); + +const paymentAddress = require('./paymentAddress'); + +class Wallet { + constructor(mnemonic, derivationPath, network = 'MAINNET') { + if ( ! derivationPath) { + throw new Error('Derivation path is required.'); + } + + this.mnemonic = mnemonic; + this.derivationPath = derivationPath; + this.network = network; + + this._initialize(); + } + + _initialize() { + const wl = kth.core_string_list_construct(); + this.mnemonic.forEach(word => { + kth.core_string_list_push_back(wl, word); + }); + + this.seed = kth.wallet_mnemonics_to_seed(wl); + + const version = this.network === 'MAINNET' ? 326702167824577054 : 303293221666392015; + this.master = kth.wallet_hd_private_construct_with_seed(this.seed, version); + + const paths = this.derivationPath.split('/'); + this.lastDerived = paths.reduce((prevKey, pathChunk) => { + if (pathChunk === 'm') { + return prevKey; + } + const hardened = pathChunk.endsWith("'"); + const index = parseInt(pathChunk, 10); + if (isNaN(index)) { + throw new Error('Invalid derivation path.'); + } + + if (hardened) { + return kth.wallet_hd_private_derive_private(prevKey, index + 0x80000000); + } else { + return kth.wallet_hd_private_derive_private(prevKey, index); + } + }, this.master); + } + + get rootKey() { + return kth.wallet_hd_private_encoded(this.master); + } + + get extendedPrivateKey() { + return kth.wallet_hd_private_encoded(this.lastDerived); + } + + get extendedPublicKey() { + return kth.wallet_hd_public_encoded(kth.wallet_hd_private_to_public(this.lastDerived)); + } + + getAddress(index) { + const key = kth.wallet_hd_private_derive_private(this.lastDerived, index); + const secret = kth.wallet_hd_private_secret(key); + const point = kth.wallet_secret_to_public(secret); + const ecp = kth.wallet_ec_public_construct_from_point(point, true); + const pa = kth.wallet_ec_public_to_payment_address(ecp, this.network === 'MAINNET' ? 0x00 : 0x05); + return kth.wallet_payment_address_encoded_cashaddr(pa, false); + } + + getAddress(index) { + const key = kth.wallet_hd_private_derive_private(this.lastDerived, index); + const secret = kth.wallet_hd_private_secret(key); + const point = kth.wallet_secret_to_public(secret); + const ecp = kth.wallet_ec_public_construct_from_point(point, true); + const nativePA = kth.wallet_ec_public_to_payment_address(ecp, this.network === 'MAINNET' ? 0x00 : 0x05); + return paymentAddress.fromNative(nativePA); + } + + getAddresses(count = 20) { + const addresses = []; + for (let i = 0; i < count; i++) { + addresses.push(this.getAddress(i)); + } + return addresses; + } + + deriveAccount(derivationPath) { + return new Wallet(this.mnemonic, derivationPath, this.network); + } + + //TODO: Implement isValidMnemonic(mnemonic) function. + //TODO: Implement generate() function. + //TODO: Implement mnemonic() function. + + // This will return a new Wallet instance with only public info. + // toPublic() { + // const pubWallet = new Wallet(null, this.derivationPath, this.network); + // pubWallet.master = kth.wallet_hd_private_to_public(this.master); + // pubWallet.lastDerived = kth.wallet_hd_private_to_public(this.lastDerived); + // return pubWallet; + // } +} + +exports.Wallet = Wallet; diff --git a/test/node.test.js b/test/node.test.js index d427469..b089ff9 100644 --- a/test/node.test.js +++ b/test/node.test.js @@ -124,7 +124,7 @@ describe('full indexed node tests', () => { it('node information', async () => { expect(node_.capi_version).toEqual("0.39.0"); expect(node_.cppapi_version).toEqual("0.38.0"); - expect(node_.version).toEqual("1.23.0"); + expect(node_.version).toEqual("2.0.0"); expect(node_.microarchitecture).toEqual("ZLm9Pjh"); expect(node_.march_names).toEqual("64 bits, CMOV, CX8, FPU, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF-SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT ABM, MOVBE, XSAVE"); expect(node_.currency_symbol).toEqual("BCH"); diff --git a/test/wallet/wallet.test.js b/test/wallet/wallet.test.js new file mode 100644 index 0000000..591e644 --- /dev/null +++ b/test/wallet/wallet.test.js @@ -0,0 +1,67 @@ +const { Wallet } = require('../../src/wallet/wallet'); +const { PaymentAddress } = require('../../src/wallet/paymentAddress'); + +describe('Wallet', () => { + const TEST_MNEMONIC = [ + 'car', 'slab', 'tail', 'dirt', 'wife', 'custom', 'front', + 'shield', 'diet', 'pear', 'skull', 'vapor', 'gorilla', 'token', 'yard' + ]; + + const TEST_DERIVATION_PATH = "m/44'/145'/0'/0"; + const TEST_NETWORK = 'MAINNET'; //'TESTNET'; + + let wallet; + + beforeEach(() => { + wallet = new Wallet(TEST_MNEMONIC, TEST_DERIVATION_PATH, TEST_NETWORK); + }); + + test('should correctly instantiate with given mnemonic, derivation path, and network', () => { + expect(wallet).toBeInstanceOf(Wallet); + }); + + test('rootKey should return expected BIP32 root key', () => { + const rootKey = wallet.rootKey; + expect(typeof rootKey).toBe('string'); + expect(rootKey).toBe('xprv9s21ZrQH143K2DQjFCenT6uLwd7dNoMKXEsiQ2v5EkNFGd54wN9td5GnDKLR1amKpXFPwHBHdUNL3uowUZd4jZtFEbSG73wEyPrYn9sfbNN'); + }); + + test('extendedPrivateKey should return expected extended private key', () => { + const extPrivateKey = wallet.extendedPrivateKey; + expect(typeof extPrivateKey).toBe('string'); + expect(extPrivateKey).toBe('xprvA2NTL1ZqHfcTbSbCXeRcuUWo9jib7TroFrQSMFyNS4YpSCs8Aqi23nPQHiQC6SVNXp68AFQLU5Nt2CEKjBmtaFhYdBTGhd7tydWxKhWzSc7'); + }); + + test('extendedPublicKey should return expected extended public key', () => { + const extPublicKey = wallet.extendedPublicKey; + expect(typeof extPublicKey).toBe('string'); + expect(extPublicKey).toBe('xpuWo6KFYMvDoyMYeU3x2z326V49Bw9kppazNf4cbhQNAL7QWMTcCmr7cjiLP9yLbS1E1JHt2MoTHM3PofwJhPhmmYSNgCjiAQUibkHd4LxfG1P'); + }); + + test('getAddress should return PaymentAddress instance for given index', () => { + const address = wallet.getAddress(0); + expect(address).toBeInstanceOf(PaymentAddress); + expect(address.encoded()).toBe('bitcoincash:qr9sawzzmstkluq9nqefsu7eqya4zk2w7udune2pmf'); + }); + + test('getAddresses should return array of PaymentAddress instances', () => { + const addresses = wallet.getAddresses(5); + expect(Array.isArray(addresses)).toBeTruthy(); + expect(addresses).toHaveLength(5); + + expect(addresses.map(a => a.encoded())).toStrictEqual([ + 'bitcoincash:qr9sawzzmstkluq9nqefsu7eqya4zk2w7udune2pmf', + 'bitcoincash:qpvmwrhxcdyyq64ar6kz46rejp0r2tjcwg8d462hum', + 'bitcoincash:qqftgwpz0wm45z3sumncfrzm0s3n7x5rcqq9350gd6', + 'bitcoincash:qrwelh5dw56rjnr3nnttfc45j0p0yv2a3vtuwu9nlt', + 'bitcoincash:qpawyf7fp6lhvhld5gtz74smm969fx2j2546uj60l0']) + }); + + // test('deriveAccount should return a new Wallet instance with new derivation path', () => { + // const newDerivationPath = "m/44'/0'/0'/0"; + // const derivedWallet = wallet.deriveAccount(newDerivationPath); + // expect(derivedWallet).toBeInstanceOf(Wallet); + // expect(derivedWallet.derivationPath).toBe(newDerivationPath); + // }); +}); +