Skip to content

Commit

Permalink
new wallet API (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
fpelliccioni authored Aug 12, 2023
1 parent 9dde87d commit 68b1ae0
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 16 deletions.
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
7 changes: 5 additions & 2 deletions src/wallet/paymentAddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
27 changes: 27 additions & 0 deletions src/wallet/wallet.d.ts
Original file line number Diff line number Diff line change
@@ -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';

107 changes: 107 additions & 0 deletions src/wallet/wallet.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion test/node.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions test/wallet/wallet.test.js
Original file line number Diff line number Diff line change
@@ -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);
// });
});

0 comments on commit 68b1ae0

Please sign in to comment.