diff --git a/docs/wallet/Wallet.md b/docs/wallet/Wallet.md index 8f2921331..c6845323f 100644 --- a/docs/wallet/Wallet.md +++ b/docs/wallet/Wallet.md @@ -20,12 +20,14 @@ Parameters: | **walletOpts.HDPrivateKey** | string/HDPrivateKey| no | If you only have a HDPrivateKey representation, you can pass it instead of mnemonic to init the wallet from it | | **walletOpts.HDPublicKey** | string/HDPublicKey | no | If you only have a HDPublicKey representation, you can pass it instead of mnemonic to init the wallet from it | | **walletOpts.privateKey** | string/PrivateKey | no | If you only have a PrivateKey representation, you can pass it instead of mnemonic to init the wallet from it | +| **walletOpts.publicKey** | string/PublicKey | no | If you only have a PublicKey representation, you can pass it instead of mnemonic to init the wallet from it | N.B 1 : If both mnemonic, seed and privateKey are filled, only mnemonic will be used. If none is entered, the wallet will create a mnemonic. -N.B 2 : When initialized from a `privateKey` or an `HDPublicKey`, comportment of Wallet-lib differs slightly. +N.B 2 : When initialized from a `privateKey`, `publicKey` or an `HDPublicKey`, comportment of Wallet-lib differs slightly. - PrivateKey : There is no path in this mode. It's a unique public address. +- PrivateKey : There is no path in this mode. It's a unique public address. Watch-only. - HDPublicKey : There is no signing in this mode. Watch-only. Returns : Wallet instance. @@ -67,7 +69,7 @@ const wallet = new Wallet({ }) ``` -### Creation from HDPrivateKey +### Creation from HDPublicKey ```js const wallet = new Wallet({ @@ -82,3 +84,19 @@ const wallet = new Wallet({ seed: '436905e6756c24551bffaebe97d0ebd51b2fa027e838c18d45767bd833b02a80a1dd55728635b54f2b1dbed5963f4155e160ee1e96e2d67f7e8ac28557d87d96' }) ``` + +### Creation from privateKey + +```js +const wallet = new Wallet({ + privateKey: 'cR4t6evwVZoCp1JsLk4wURK4UmBCZzZotNzn9T1mhBT19SH9JtNt' +}) +``` + +### Creation from publicKey + +```js +const wallet = new Wallet({ + HDPublicKey: 'tpubDEB6BgW9JvZRWVbFmwwGuJ2vifakABuxQWdY9yXbFC2rc3zagie1RkhwUEnahb1dzaapchEVeKqKcx99TzkjNvjXcmoQkLJwsYnA1J5bGNj' +}) +``` diff --git a/src/CONSTANTS.js b/src/CONSTANTS.js index 8e7ce8163..edcb365a4 100644 --- a/src/CONSTANTS.js +++ b/src/CONSTANTS.js @@ -40,6 +40,10 @@ const CONSTANTS = { }, UNCONFIRMED_TRANSACTION_STATUS_CODE: -1, WALLET_TYPES: { + ADDRESS: 'address', + PUBLICKEY: 'publicKey', + PRIVATEKEY: 'privateKey', + // TODO: DEPRECATE. SINGLE_ADDRESS: 'single_address', HDWALLET: 'hdwallet', HDPUBLIC: 'hdpublic', diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 9a38c96af..ba5c39082 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -172,9 +172,9 @@ class TransactionSyncStreamWorker extends Worker { const { walletId } = this; const accountsStore = this.storage.store.wallets[walletId].accounts; - const accountStore = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) - ? accountsStore[this.index.toString()] - : accountsStore[this.BIP44PATH.toString()]; + const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) + ? accountsStore[this.BIP44PATH.toString()] + : accountsStore[this.index.toString()]; accountStore.blockHash = hash; @@ -185,9 +185,9 @@ class TransactionSyncStreamWorker extends Worker { const { walletId } = this; const accountsStore = this.storage.store.wallets[walletId].accounts; - const { blockHash } = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) - ? accountsStore[this.index.toString()] - : accountsStore[this.BIP44PATH.toString()]; + const { blockHash } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) + ? accountsStore[this.BIP44PATH.toString()] + : accountsStore[this.index.toString()]; return blockHash; } diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js index b925a70fb..16597f841 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js @@ -108,7 +108,7 @@ const mockedStore2 = { const mockSelfPrivateKeyType = { storage: { getStore:()=>mockedStore1 }, walletId: '123456789', - walletType: 'single_address', + walletType: 'privateKey', } const mockSelfIndex0 = { storage: { getStore:()=>mockedStore2 }, diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js index 039bc4440..ef9ca277d 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js @@ -7,9 +7,9 @@ module.exports = function getLastSyncedBlockHeight() { const { walletId } = this; const accountsStore = this.storage.store.wallets[walletId].accounts; - let { blockHeight } = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) - ? accountsStore[this.index.toString()] - : accountsStore[this.BIP44PATH.toString()]; + let { blockHeight } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) + ? accountsStore[this.BIP44PATH.toString()] + : accountsStore[this.index.toString()]; // Fix Genesis issue on DCore if (blockHeight === 0) blockHeight = 1; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js index eec5dbd8b..19bd654ac 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js @@ -10,9 +10,9 @@ module.exports = function setLastSyncedBlockHeight(blockHeight) { const { walletId } = this; const accountsStore = this.storage.store.wallets[walletId].accounts; - const accountStore = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) - ? accountsStore[this.index.toString()] - : accountsStore[this.BIP44PATH.toString()]; + const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) + ? accountsStore[this.BIP44PATH.toString()] + : accountsStore[this.index.toString()]; accountStore.blockHeight = blockHeight; diff --git a/src/transport/DAPIClientTransport/methods/subscribeToTransactionsWithProofs.js b/src/transport/DAPIClientTransport/methods/subscribeToTransactionsWithProofs.js index 17c19aa0f..95bacbace 100644 --- a/src/transport/DAPIClientTransport/methods/subscribeToTransactionsWithProofs.js +++ b/src/transport/DAPIClientTransport/methods/subscribeToTransactionsWithProofs.js @@ -23,6 +23,7 @@ module.exports = async function subscribeToTransactionWithProofs( const { client } = this; logger.silly(`DAPIClient.subscribeToTransactionWithProofs[${addressList}]`); + if (!addressList.length) throw new Error('Unable to subscribe to transaction without addresses'); const bloomfilter = BloomFilter.create(addressList.length, BLOOM_FALSE_POSITIVE_RATE); addressList.forEach((address) => { diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 7720ed020..914f372a9 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -112,20 +112,28 @@ class Account extends EventEmitter { super.emit(...args); }; } - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) { - this.storage.createAccount( - this.walletId, - this.BIP44PATH, - this.network, - this.label, - ); - } - if (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) { - this.storage.createSingleAddress( - this.walletId, - this.network, - this.label, - ); + switch (this.walletType) { + case WALLET_TYPES.HDWALLET: + case WALLET_TYPES.HDPUBLIC: + this.storage.createAccount( + this.walletId, + this.BIP44PATH, + this.network, + this.label, + ); + break; + case WALLET_TYPES.PRIVATEKEY: + case WALLET_TYPES.PUBLICKEY: + case WALLET_TYPES.ADDRESS: + case WALLET_TYPES.SINGLE_ADDRESS: + this.storage.createSingleAddress( + this.walletId, + this.network, + this.label, + ); + break; + default: + throw new Error(`Invalid wallet type ${this.walletType}`); } this.keyChain = wallet.keyChain; diff --git a/src/types/Account/Account.spec.js b/src/types/Account/Account.spec.js index 4f7f1fa0a..a7b01b6ff 100644 --- a/src/types/Account/Account.spec.js +++ b/src/types/Account/Account.spec.js @@ -33,12 +33,14 @@ describe('Account - class', function suite() { getStore: () => {}, saveState: () => {}, stopWorker: () => {}, + createAccount: () => {}, importBlockHeader: (blockheader)=>{ mockStorage.emit(EVENTS.BLOCKHEADER, {type: EVENTS.BLOCKHEADER, payload:blockheader}); } }; mocks.wallet = (new (function Wallet() { this.walletId = '1234567891'; + this.walletType = WALLET_TYPES.HDWALLET; this.accounts = []; this.network = Dashcore.Networks.testnet; this.storage = mockStorage; diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index e1fa07766..82dee4c2a 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -31,11 +31,10 @@ async function _initializeAccount(account, userUnsafePlugins) { account.index, account.getAddress.bind(account), ); - } - - if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) { + } else { await account.getAddress('0'); // We force what is usually done by the BIP44Worker. } + if (!account.offlineMode) { await account.injectPlugin(ChainPlugin, true); @@ -89,10 +88,19 @@ async function _initializeAccount(account, userUnsafePlugins) { // while SyncWorker fetch'em on network clearInterval(self.readinessInterval); - if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) { - account.generateAddress(0); - sendReady(); - return resolve(true); + switch (account.walletType) { + case WALLET_TYPES.PRIVATEKEY: + case WALLET_TYPES.SINGLE_ADDRESS: + account.generateAddress(0); + sendReady(); + return resolve(true); + case WALLET_TYPES.PUBLICKEY: + case WALLET_TYPES.ADDRESS: + account.generateAddress(0); + sendReady(); + return resolve(true); + default: + break; } if (!account.injectDefaultPlugins) { diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index c248cbdeb..450f582fe 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -1,3 +1,4 @@ +const { PublicKey } = require('@dashevo/dashcore-lib'); const EVENTS = require('../../../EVENTS'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const { is } = require('../../../utils'); @@ -10,24 +11,32 @@ function generateAddress(path) { if (is.undefOrNull(path)) throw new Error('Expected path to generate an address'); let index = 0; let privateKey; - + let address; const { network } = this; switch (this.walletType) { + case WALLET_TYPES.ADDRESS: + address = this.keyChain.address; + break; + case WALLET_TYPES.PUBLICKEY: + address = new PublicKey(this.keyChain.publicKey.toString()).toAddress(network).toString(); + break; case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring index = parseInt(path.toString().split('/')[5], 10); privateKey = this.keyChain.getKeyForPath(path); + address = privateKey.publicKey.toAddress(network).toString(); break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); privateKey = this.keyChain.getKeyForChild(index); + address = privateKey.publicKey.toAddress(network).toString(); break; case WALLET_TYPES.SINGLE_ADDRESS: default: privateKey = this.keyChain.getKeyForPath(path.toString()); + address = privateKey.publicKey.toAddress(network).toString(); } - const address = privateKey.publicKey.toAddress(network).toString(); const addressData = { path: path.toString(), diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index 700d20224..d4771dc77 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -14,6 +14,9 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index, type = 'external'; path = `${BIP44PATH}/${addressTypeIndex}/${index}`; break; + case WALLET_TYPES.PUBLICKEY: + case WALLET_TYPES.ADDRESS: + case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: default: type = 'misc'; diff --git a/src/types/Account/methods/getAddresses.js b/src/types/Account/methods/getAddresses.js index d8973fb81..92a64e653 100644 --- a/src/types/Account/methods/getAddresses.js +++ b/src/types/Account/methods/getAddresses.js @@ -6,7 +6,13 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); * @return {[AddressObj]} address - All address matching the type */ function getAddresses(_type = 'external') { - const walletType = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) + const miscTypes = [ + WALLET_TYPES.SINGLE_ADDRESS, + WALLET_TYPES.PUBLICKEY, + WALLET_TYPES.PRIVATEKEY, + WALLET_TYPES.ADDRESS, + ]; + const walletType = (miscTypes.includes(this.walletType)) ? 'misc' : ((_type) || 'external'); const store = this.storage.getStore(); diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 486194e80..d97cf9985 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -24,8 +24,14 @@ class KeyChain { } else if (has(opts, 'privateKey')) { this.type = 'privateKey'; this.privateKey = opts.privateKey; + } else if (has(opts, 'publicKey')) { + this.type = 'publicKey'; + this.publicKey = opts.publicKey; + } else if (has(opts, 'address')) { + this.type = 'address'; + this.address = opts.address.toString(); } else { - throw new Error('Expect privateKey, HDPublicKey or HDPrivateKey'); + throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'); } if (opts.network) this.network = opts.network; if (opts.keys) this.keys = { ...opts.keys }; diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 90eb76f0b..ade1883f6 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -9,7 +9,7 @@ const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e'; describe('Keychain', function suite() { this.timeout(10000); it('should create a keychain', () => { - const expectedException1 = 'Expect privateKey, HDPublicKey or HDPrivateKey'; + const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'; expect(() => new KeyChain()).to.throw(expectedException1); expect(() => new KeyChain(mnemonic)).to.throw(expectedException1); diff --git a/src/types/Wallet/Wallet.d.ts b/src/types/Wallet/Wallet.d.ts index 84f6a68a3..566d157ef 100644 --- a/src/types/Wallet/Wallet.d.ts +++ b/src/types/Wallet/Wallet.d.ts @@ -1,4 +1,4 @@ -import { Mnemonic, PrivateKey, HDPublicKey, Network, Plugins } from "../types"; +import { Mnemonic, PrivateKey, PublicKey, PublicAddress, Address, HDPublicKey, Network, Plugins } from "../types"; import { Account } from "../Account/Account"; import { Storage } from "../Storage/Storage"; import { HDPrivateKey } from "@dashevo/dashcore-lib"; @@ -59,6 +59,8 @@ export declare namespace Wallet { privateKey?: PrivateKey | string; HDPrivateKey?: HDPrivateKey | string; HDPublicKey?: HDPublicKey | string; + publicKey?: PublicKey | string; + address?: Address | PublicAddress | string; unsafeOptions?: IWalletUnsafeOptions; waitForInstantLockTimeout?: number; } diff --git a/src/types/Wallet/Wallet.js b/src/types/Wallet/Wallet.js index 604768f6f..2186d9007 100644 --- a/src/types/Wallet/Wallet.js +++ b/src/types/Wallet/Wallet.js @@ -21,6 +21,8 @@ const defaultOptions = { const fromMnemonic = require('./methods/fromMnemonic'); const fromPrivateKey = require('./methods/fromPrivateKey'); +const fromPublicKey = require('./methods/fromPublicKey'); +const fromAddress = require('./methods/fromAddress'); const fromSeed = require('./methods/fromSeed'); const fromHDPublicKey = require('./methods/fromHDPublicKey'); const fromHDPrivateKey = require('./methods/fromHDPrivateKey'); @@ -53,6 +55,8 @@ class Wallet extends EventEmitter { fromSeed, fromHDPrivateKey, fromPrivateKey, + fromPublicKey, + fromAddress, fromHDPublicKey, generateNewWalletId, }); @@ -85,8 +89,12 @@ class Wallet extends EventEmitter { this.fromPrivateKey((opts.privateKey === null) ? new PrivateKey(network).toString() : opts.privateKey); + } else if ('publicKey' in opts) { + this.fromPublicKey(opts.publicKey); } else if ('HDPublicKey' in opts) { this.fromHDPublicKey(opts.HDPublicKey); + } else if ('address' in opts) { + this.fromAddress(opts.address); } else { this.fromMnemonic(generateNewMnemonic()); } diff --git a/src/types/Wallet/Wallet.spec.js b/src/types/Wallet/Wallet.spec.js index 59fdf6b6e..180f5fc18 100644 --- a/src/types/Wallet/Wallet.spec.js +++ b/src/types/Wallet/Wallet.spec.js @@ -106,7 +106,7 @@ describe('Wallet - class', function suite() { }); it('should create a wallet with PrivateKey', () => { const wallet1 = new Wallet({ privateKey: cR4t6ePrivateKey.privateKey, network: 'testnet', ...mocks }); - expect(wallet1.walletType).to.be.equal(WALLET_TYPES.SINGLE_ADDRESS); + expect(wallet1.walletType).to.be.equal(WALLET_TYPES.PRIVATEKEY); expect(wallet1.mnemonic).to.be.equal(null); expect(wallet1.plugins).to.be.deep.equal({}); @@ -122,6 +122,43 @@ describe('Wallet - class', function suite() { wallet1.disconnect(); }); }); + it('should create a wallet with PublicKey', () => { + const publicKey = new Dashcore.PrivateKey(cR4t6ePrivateKey.privateKey).toPublicKey(); + expect(publicKey.toString()).to.equal('03353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844'); + const wallet1 = new Wallet({ publicKey: publicKey.toString(), network: 'testnet', ...mocks }); + expect(wallet1.walletType).to.be.equal(WALLET_TYPES.PUBLICKEY); + expect(wallet1.mnemonic).to.be.equal(null); + + expect(wallet1.plugins).to.be.deep.equal({}); + expect(wallet1.accounts).to.be.deep.equal([]); + expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); + expect(wallet1.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet1.passphrase).to.be.deep.equal(null); + expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); + expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); + expect(wallet1.walletId).to.be.equal('9f1f6f37f7'); + + wallet1.storage.on('CONFIGURED', () => { + wallet1.disconnect(); + }); + + const wallet2 = new Wallet({ publicKey, network: 'testnet', ...mocks }); + expect(wallet2.walletType).to.be.equal(WALLET_TYPES.PUBLICKEY); + expect(wallet2.mnemonic).to.be.equal(null); + + expect(wallet2.plugins).to.be.deep.equal({}); + expect(wallet2.accounts).to.be.deep.equal([]); + expect(wallet2.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); + expect(wallet2.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet2.passphrase).to.be.deep.equal(null); + expect(wallet2.allowSensitiveOperations).to.be.deep.equal(false); + expect(wallet2.injectDefaultPlugins).to.be.deep.equal(true); + expect(wallet2.walletId).to.be.equal('9f1f6f37f7'); + + wallet2.storage.on('CONFIGURED', () => { + wallet2.disconnect(); + }); + }); it('should have an offline Mode', () => { const wallet = new Wallet({ offlineMode: true, privateKey: cR4t6ePrivateKey.privateKey, network: 'testnet', ...mocks, diff --git a/src/types/Wallet/methods/exportWallet.js b/src/types/Wallet/methods/exportWallet.js index 61fa49682..f64c4afec 100644 --- a/src/types/Wallet/methods/exportWallet.js +++ b/src/types/Wallet/methods/exportWallet.js @@ -5,18 +5,38 @@ function exportMnemonic(mnemonic) { return mnemonic.toString(); } -function exportSingleAddressWallet(_outputType = 'privateKey') { - switch (_outputType) { +function exportPublicKeyWallet(outputType = 'publicKey') { + switch (outputType) { + case 'publicKey': + if (!this.publicKey) throw new Error('No PublicKey to export'); + return this.publicKey.toString(); + default: + throw new Error(`Tried to export to invalid output : ${outputType}`); + } +} + +function exportAddressWallet(outputType = 'address') { + switch (outputType) { + case 'address': + if (!this.address) throw new Error('No Address to export'); + return this.address.toString(); + default: + throw new Error(`Tried to export to invalid output : ${outputType}`); + } +} + +function exportSingleAddressWallet(outputType = 'privateKey') { + switch (outputType) { case 'privateKey': if (!this.privateKey) throw new Error('No PrivateKey to export'); - return this.privateKey; + return this.privateKey.toString(); default: - throw new Error(`Tried to export to invalid output : ${_outputType}`); + throw new Error(`Tried to export to invalid output : ${outputType}`); } } -function exportHDWallet(_outputType) { - switch (_outputType) { +function exportHDWallet(outputType) { + switch (outputType) { case undefined: // We did not define any output, so we try first mnemonic, or HDPrivateKey try { @@ -31,17 +51,17 @@ function exportHDWallet(_outputType) { if (!this.HDPrivateKey) throw new Error('No PrivateKey to export'); return this.HDPrivateKey.toString(); default: - throw new Error(`Tried to export to invalid output : ${_outputType}`); + throw new Error(`Tried to export to invalid output : ${outputType}`); } } -function exportHDPublicWallet(_outputType = 'HDPublicKey') { - switch (_outputType) { +function exportHDPublicWallet(outputType = 'HDPublicKey') { + switch (outputType) { case 'HDPublicKey': if (!this.HDPublicKey) throw new Error('No publicKey to export'); return this.HDPublicKey.toString(); default: - throw new Error(`Tried to export to invalid output : ${_outputType}`); + throw new Error(`Tried to export to invalid output : ${outputType}`); } } @@ -50,15 +70,22 @@ function exportHDPublicWallet(_outputType = 'HDPublicKey') { * The default output differs from the wallet type. * For an HDWallet, it will be it's mnemonic. * For an HDPublic wallet (watch), it's will be that HDPubKey. - * If initiated from a single private (single address), we output that privKey. + * If initiated from a private key, we output that key, similarly + * if initiated from a public key. + * On the case it's initiated from an address, we output it. * * @param outputType - Allow to overwrite the default output type * @return {Mnemonic|HDPrivateKey} */ module.exports = function exportWallet(outputType) { switch (this.walletType) { + case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: return exportSingleAddressWallet.call(this, outputType); + case WALLET_TYPES.ADDRESS: + return exportAddressWallet.call(this, outputType); + case WALLET_TYPES.PUBLICKEY: + return exportPublicKeyWallet.call(this, outputType); case WALLET_TYPES.HDPUBLIC: return exportHDPublicWallet.call(this, outputType); case WALLET_TYPES.HDWALLET: diff --git a/src/types/Wallet/methods/exportWallet.spec.js b/src/types/Wallet/methods/exportWallet.spec.js index 5e8a2c84b..d7396d6f2 100644 --- a/src/types/Wallet/methods/exportWallet.spec.js +++ b/src/types/Wallet/methods/exportWallet.spec.js @@ -1,9 +1,12 @@ const { expect } = require('chai'); const Wallet = require('../Wallet'); +const { PrivateKey, Networks } = require('@dashevo/dashcore-lib'); const exportWallet = require('./exportWallet'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const cR4t6ePrivateKey = require('../../../../fixtures/cR4t6e_pk'); const knifeMnemonic = require('../../../../fixtures/knifeeasily'); +const cR4t6eFixture = require("../../../../fixtures/cR4t6e_pk"); +const cR4t6ePublicKey = new PrivateKey(cR4t6eFixture.privateKey).toPublicKey(); describe('Wallet - export Wallet', function suite() { this.timeout(10000); @@ -112,4 +115,28 @@ describe('Wallet - exportWallet - integration', function suite() { wallet.disconnect(); }); }); + describe('fromPublicKey', () => { + const wallet = new Wallet({ + offlineMode: true, + publicKey: cR4t6ePublicKey, + }); + it('should work as expected', () => { + expect(wallet.exportWallet()).to.equal(cR4t6ePublicKey.toString()); + }); + after(() => { + wallet.disconnect(); + }); + }); + describe('fromAddress', () => { + const wallet = new Wallet({ + offlineMode: true, + address: cR4t6ePublicKey.toAddress(Networks.testnet), + }); + it('should work as expected', () => { + expect(wallet.exportWallet()).to.equal(cR4t6ePublicKey.toAddress(Networks.testnet).toString()); + }); + after(() => { + wallet.disconnect(); + }); + }); }); diff --git a/src/types/Wallet/methods/fromAddress.js b/src/types/Wallet/methods/fromAddress.js new file mode 100644 index 000000000..c69c7749b --- /dev/null +++ b/src/types/Wallet/methods/fromAddress.js @@ -0,0 +1,14 @@ +const { is } = require('../../../utils'); +const KeyChain = require('../../KeyChain/KeyChain'); +const { WALLET_TYPES } = require('../../../CONSTANTS'); + +/** + * @param address + */ +module.exports = function fromAddress(address) { + if (!is.address(address)) throw new Error('Expected a valid address (typeof Address or String)'); + this.walletType = WALLET_TYPES.ADDRESS; + this.mnemonic = null; + this.address = address.toString(); + this.keyChain = new KeyChain({ address }); +}; diff --git a/src/types/Wallet/methods/fromAddress.spec.js b/src/types/Wallet/methods/fromAddress.spec.js new file mode 100644 index 000000000..44bd996a4 --- /dev/null +++ b/src/types/Wallet/methods/fromAddress.spec.js @@ -0,0 +1,46 @@ +const { expect } = require('chai'); +const { PrivateKey } = require('@dashevo/dashcore-lib'); +const fromAddress = require('./fromAddress'); +const cR4t6eFixture = require('../../../../fixtures/cR4t6e_pk'); +const { WALLET_TYPES } = require('../../../CONSTANTS'); +const cR4t6ePublicKey = new PrivateKey(cR4t6eFixture.privateKey).toPublicKey(); + +describe('Wallet - fromAddress', function suite() { + this.timeout(10000); + it('should indicate missing data', () => { + const mockOpts1 = { }; + const exceptedException1 = 'Expected a valid address (typeof Address or String)'; + expect(() => fromAddress.call(mockOpts1)).to.throw(exceptedException1); + }); + it('should set wallet from address', () => { + const self1 = {}; + fromAddress.call(self1, cR4t6ePublicKey.toAddress()); + expect(self1.walletType).to.equal(WALLET_TYPES.ADDRESS); + expect(self1.mnemonic).to.equal(null); + expect(self1.address).to.equal(cR4t6ePublicKey.toAddress().toString()); + expect(self1.keyChain.type).to.equal('address'); + expect(self1.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); + expect(self1.keyChain.keys).to.deep.equal({}); + + const self2 = {}; + fromAddress.call(self2, cR4t6ePublicKey.toAddress().toString()); + expect(self2.walletType).to.equal(WALLET_TYPES.ADDRESS); + expect(self2.mnemonic).to.equal(null); + expect(self2.address).to.equal(cR4t6ePublicKey.toAddress().toString()); + expect(self2.keyChain.type).to.equal('address'); + expect(self2.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); + expect(self2.keyChain.keys).to.deep.equal({}); + }); + it('should reject invalid mnemonic', () => { + const invalidInputs = [ + { privateKey: 0 }, + { privateKey: true }, + { privateKey: false }, + ]; + + return invalidInputs.forEach((invalidInput) => { + const self = {}; + expect(() => fromAddress.call(self, invalidInput)).to.throw('Expected a valid address (typeof Address or String)'); + }); + }); +}); diff --git a/src/types/Wallet/methods/fromPrivateKey.js b/src/types/Wallet/methods/fromPrivateKey.js index dcb91340d..2f8f919bc 100644 --- a/src/types/Wallet/methods/fromPrivateKey.js +++ b/src/types/Wallet/methods/fromPrivateKey.js @@ -8,7 +8,7 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); */ module.exports = function fromPrivateKey(privateKey) { if (!is.privateKey(privateKey)) throw new Error('Expected a valid private key (typeof PrivateKey or String)'); - this.walletType = WALLET_TYPES.SINGLE_ADDRESS; + this.walletType = WALLET_TYPES.PRIVATEKEY; this.mnemonic = null; this.privateKey = privateKey; this.keyChain = new KeyChain({ privateKey }); diff --git a/src/types/Wallet/methods/fromPrivateKey.spec.js b/src/types/Wallet/methods/fromPrivateKey.spec.js index 56dc4bb3e..c707c2fad 100644 --- a/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromPrivateKey.spec.js @@ -13,7 +13,7 @@ describe('Wallet - fromPrivateKey', function suite() { it('should set wallet from private Key', () => { const self1 = {}; fromPrivateKey.call(self1, cR4t6eFixture.privateKey); - expect(self1.walletType).to.equal(WALLET_TYPES.SINGLE_ADDRESS); + expect(self1.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self1.mnemonic).to.equal(null); expect(self1.privateKey).to.equal(cR4t6eFixture.privateKey); expect(self1.keyChain.type).to.equal('privateKey'); @@ -22,7 +22,7 @@ describe('Wallet - fromPrivateKey', function suite() { const self2 = {}; fromPrivateKey.call(self2, cR4t6eFixture.privateKey); - expect(self2.walletType).to.equal(WALLET_TYPES.SINGLE_ADDRESS); + expect(self2.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self2.mnemonic).to.equal(null); expect(self2.privateKey).to.equal(cR4t6eFixture.privateKey); expect(self2.keyChain.type).to.equal('privateKey'); diff --git a/src/types/Wallet/methods/fromPublicKey.js b/src/types/Wallet/methods/fromPublicKey.js new file mode 100644 index 000000000..9e6867ac7 --- /dev/null +++ b/src/types/Wallet/methods/fromPublicKey.js @@ -0,0 +1,15 @@ +const { is } = require('../../../utils'); +const KeyChain = require('../../KeyChain/KeyChain'); +const { WALLET_TYPES } = require('../../../CONSTANTS'); + +/** + * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) + * @param privateKey + */ +module.exports = function fromPublicKey(publicKey) { + if (!is.publicKey(publicKey)) throw new Error('Expected a valid public key (typeof PublicKey or String)'); + this.walletType = WALLET_TYPES.PUBLICKEY; + this.mnemonic = null; + this.publicKey = publicKey; + this.keyChain = new KeyChain({ publicKey }); +}; diff --git a/src/types/Wallet/methods/fromPublicKey.spec.js b/src/types/Wallet/methods/fromPublicKey.spec.js new file mode 100644 index 000000000..8852ee6c6 --- /dev/null +++ b/src/types/Wallet/methods/fromPublicKey.spec.js @@ -0,0 +1,46 @@ +const { expect } = require('chai'); +const { PrivateKey } = require('@dashevo/dashcore-lib'); +const fromPublicKey = require('./fromPublicKey'); +const cR4t6eFixture = require('../../../../fixtures/cR4t6e_pk'); +const { WALLET_TYPES } = require('../../../CONSTANTS'); +const cR4t6ePublicKey = new PrivateKey(cR4t6eFixture.privateKey).toPublicKey(); + +describe('Wallet - fromPublicKey', function suite() { + this.timeout(10000); + it('should indicate missing data', () => { + const mockOpts1 = { }; + const exceptedException1 = 'Expected a valid public key (typeof PublicKey or String)'; + expect(() => fromPublicKey.call(mockOpts1)).to.throw(exceptedException1); + }); + it('should set wallet from public Key', () => { + const self1 = {}; + fromPublicKey.call(self1, cR4t6ePublicKey); + expect(self1.walletType).to.equal(WALLET_TYPES.PUBLICKEY); + expect(self1.mnemonic).to.equal(null); + expect(self1.publicKey).to.equal(cR4t6ePublicKey); + expect(self1.keyChain.type).to.equal('publicKey'); + expect(self1.keyChain.publicKey).to.equal(cR4t6ePublicKey); + expect(self1.keyChain.keys).to.deep.equal({}); + + const self2 = {}; + fromPublicKey.call(self2, cR4t6ePublicKey.toString()); + expect(self2.walletType).to.equal(WALLET_TYPES.PUBLICKEY); + expect(self2.mnemonic).to.equal(null); + expect(self2.publicKey).to.equal(cR4t6ePublicKey.toString()); + expect(self2.keyChain.type).to.equal('publicKey'); + expect(self2.keyChain.publicKey).to.equal(cR4t6ePublicKey.toString()); + expect(self2.keyChain.keys).to.deep.equal({}); + }); + it('should reject invalid mnemonic', () => { + const invalidInputs = [ + { privateKey: 0 }, + { privateKey: true }, + { privateKey: false }, + ]; + + return invalidInputs.forEach((invalidInput) => { + const self = {}; + expect(() => fromPublicKey.call(self, invalidInput)).to.throw('Expected a valid public key (typeof PublicKey or String)'); + }); + }); +}); diff --git a/src/types/Wallet/methods/generateNewWalletId.js b/src/types/Wallet/methods/generateNewWalletId.js index 82fb6732d..66d4f87c2 100644 --- a/src/types/Wallet/methods/generateNewWalletId.js +++ b/src/types/Wallet/methods/generateNewWalletId.js @@ -9,6 +9,16 @@ module.exports = function generateNewWalletId() { const { walletType } = this; const errorMessageBase = 'Cannot generate a walletId'; switch (walletType) { + case WALLET_TYPES.ADDRESS: + if (!this.address) throw new Error(`${errorMessageBase} : No address found`); + this.walletId = mnemonicToWalletId(this.address); + break; + case WALLET_TYPES.PUBLICKEY: + if (!this.publicKey) throw new Error(`${errorMessageBase} : No publicKey found`); + this.walletId = mnemonicToWalletId(this.publicKey); + break; + // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY + case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: if (!this.privateKey) throw new Error(`${errorMessageBase} : No privateKey found`); this.walletId = mnemonicToWalletId(this.privateKey); diff --git a/src/types/Wallet/methods/generateNewWalletId.spec.js b/src/types/Wallet/methods/generateNewWalletId.spec.js index d83d45d0d..409a630f4 100644 --- a/src/types/Wallet/methods/generateNewWalletId.spec.js +++ b/src/types/Wallet/methods/generateNewWalletId.spec.js @@ -10,7 +10,7 @@ describe('Wallet - generateNewWalletId', function suite() { it('should indicate on missing data', () => { const mockOpts1 = { }; const mockOpts2 = { walletType: WALLET_TYPES.HDWALLET }; - const mockOpts3 = { walletType: WALLET_TYPES.SINGLE_ADDRESS }; + const mockOpts3 = { walletType: WALLET_TYPES.PRIVATEKEY }; const exceptedException1 = 'Cannot generate a walletId : No HDPrivateKey found'; const exceptedException3 = 'Cannot generate a walletId : No privateKey found'; @@ -42,7 +42,7 @@ describe('Wallet - generateNewWalletId', function suite() { }); it('should generate a wallet id from single pk', () => { const mockOpts = { - walletType: WALLET_TYPES.SINGLE_ADDRESS, + walletType: WALLET_TYPES.PRIVATEKEY, privateKey: cR4t6ePrivateKey.privateKey, }; diff --git a/src/types/Wallet/methods/sweepWallet.js b/src/types/Wallet/methods/sweepWallet.js index fd349e180..b00ae2f64 100644 --- a/src/types/Wallet/methods/sweepWallet.js +++ b/src/types/Wallet/methods/sweepWallet.js @@ -15,7 +15,7 @@ async function sweepWallet(opts = {}) { const self = this; // eslint-disable-next-line no-async-promise-executor,consistent-return return new Promise(async (resolve, reject) => { - if (self.walletType !== WALLET_TYPES.SINGLE_ADDRESS) { + if (self.walletType !== WALLET_TYPES.PRIVATEKEY) { return reject(new Error('Can only sweep wallet initialized from privateKey')); } diff --git a/src/utils/is.js b/src/utils/is.js index 345e23cf0..d8b25fa11 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -1,7 +1,13 @@ /* eslint-disable max-len */ // Todo : Some validators here are really proto type of methods, urgent impr is needed here. const { - PrivateKey, HDPrivateKey, HDPublicKey, Transaction, Mnemonic, Address, + PrivateKey, + PublicKey, + HDPrivateKey, + HDPublicKey, + Transaction, + Mnemonic, + Address, } = require('@dashevo/dashcore-lib'); const is = { @@ -26,6 +32,7 @@ const is = { stringified(val) { try { JSON.parse(val); return true; } catch (e) { return false; } }, mnemonic: (mnemonic) => !is.undefOrNull(mnemonic) && (is.string(mnemonic) || mnemonic.constructor.name === Mnemonic.name), network: (network) => !is.undefOrNull(network) && (is.string(network)), + publicKey: (pKey) => !is.undefOrNull(pKey) && (pKey.constructor.name === PublicKey.name || (is.string(pKey) && PublicKey.isValid(pKey))), privateKey: (pKey) => !is.undefOrNull(pKey) && (pKey.constructor.name === PrivateKey.name || (is.string(pKey) && PrivateKey.isValid(pKey))), HDPrivateKey: (hdKey) => !is.undefOrNull(hdKey) && (hdKey.constructor.name === HDPrivateKey.name || (is.string(hdKey) && HDPrivateKey.isValidSerialized(hdKey))), HDPublicKey: (hdKey) => !is.undefOrNull(hdKey) && (hdKey.constructor.name === HDPublicKey.name || (is.string(hdKey) && HDPublicKey.isValidSerialized(hdKey))),