diff --git a/con_values_testing.py b/con_values_testing.py deleted file mode 100644 index 4cc705d..0000000 --- a/con_values_testing.py +++ /dev/null @@ -1,21 +0,0 @@ -yourState = Hash(default_value='') - -@export -def test_values(UID: str, Str: str, Int: int, Float: float, Bool: bool, Dict: dict, List: list, ANY: Any, DateTime: datetime.datetime, TimeDelta: datetime.timedelta): - yourState[UID, 'Str'] = Str - assert isinstance(yourState[UID, 'Str'], str), 'Str should be of type str' - yourState[UID, 'Int'] = Int - assert isinstance(yourState[UID, 'Int'], int), 'Int should be of type int' - yourState[UID, 'Float'] = Float - #assert isinstance(yourState[UID, 'Float'], ContractingDecimal), type(yourState[UID, 'Float']) - yourState[UID, 'Bool'] = Bool - assert isinstance(yourState[UID, 'Bool'], bool), 'Bool should be of type bool' - yourState[UID, 'Dict'] = Dict - assert isinstance(yourState[UID, 'Dict'], dict), 'Dict should be of type dict' - yourState[UID, 'List'] = List - assert isinstance(yourState[UID, 'List'], list), 'List should be of type list' - yourState[UID, 'ANY'] = ANY - yourState[UID, 'DateTime'] = DateTime - assert isinstance(yourState[UID, 'DateTime'], datetime.datetime), 'DateTime should be of type DateTime' - yourState[UID, 'TimeDelta'] = TimeDelta - assert isinstance(yourState[UID, 'TimeDelta'], datetime.timedelta), 'TimeDelta should be of type TimeDelta' \ No newline at end of file diff --git a/dist/lamden.js b/dist/lamden.js index 1cd2c10..3b81962 100644 --- a/dist/lamden.js +++ b/dist/lamden.js @@ -2206,6 +2206,76 @@ var dist = createCommonjsModule(function (module, exports) { var validators = unwrapExports(dist); +const nodeCryptoJs = require("node-cryptojs-aes"); +const { CryptoJS, JsonFormatter } = nodeCryptoJs; +const validators$1 = require('types-validate-assert'); +const { validateTypes, assertTypes } = validators$1; + +/** + * Encrypt a Javascript object with a string password + * The object passed must pass JSON.stringify or the method will fail. + * + * @param {string} password A password to encrypt the object with + * @param {Object} obj A javascript object (must be JSON compatible) + * @return {string} Encrypted string + */ +function encryptObject ( password, obj ){ + assertTypes.isStringWithValue(password); + assertTypes.isObject(obj); + + const encrypted = CryptoJS.AES.encrypt(JSON.stringify(obj), password, { format: JsonFormatter }).toString(); + return encrypted; +} +/** + * Decrypt an Object using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} objString A javascript object as JSON string + * @return {string} Encrypted string +*/ +function decryptObject ( password, objString ) { + assertTypes.isStringWithValue(password); + assertTypes.isStringWithValue(objString); + + try{ + const decrypt = CryptoJS.AES.decrypt(objString, password, { format: JsonFormatter }); + return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypt)); + } catch (e){ + return false; + } +} +/** + * Encrypt a string using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} string A string to be password encrypted + * @return {string} Encrypted string +*/ +function encryptStrHash( password, string ){ + assertTypes.isStringWithValue(password); + assertTypes.isString(string); + + const encrypt = CryptoJS.AES.encrypt(string, password).toString(); + return encrypt; +} +/** + * Decrypt a string using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} encryptedString A string to decrypt + * @return {string} Decrypted string +*/ +function decryptStrHash ( password, encryptedString ){ + assertTypes.isStringWithValue(password); + assertTypes.isStringWithValue(encryptedString); + + try{ + const decrypted = CryptoJS.AES.decrypt(encryptedString, password); + return CryptoJS.enc.Utf8.stringify(decrypted) === '' ? false : CryptoJS.enc.Utf8.stringify(decrypted); + } catch (e) { + return false; + } +} function buf2hex(buffer) { return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } @@ -2224,6 +2294,40 @@ function concatUint8Arrays(array1, array2) { const nacl = require('tweetnacl'); +/** + * Create a wallet object for signing and verifying messages + * + * @param {Object} [args={}] Args Object + * @param {string} [args.sk=undefined] A 32 character long hex representation of a signing key (private key) to create wallet from + * @param {Uint8Array(length: 32)} [args.seed=null] A Uint8Array with a length of 32 to seed the keyPair with. This is advanced behavior and should be avoided by everyday users + * @param {boolean} [args.keepPrivate=false] No direct access to the sk. Will still allow the wallet to sign messages + * @return {Object} Wallet Object with sign and verify methods + */ +let create_wallet = (args = {}) => { + let { sk = undefined, keepPrivate = false, seed = null } = args; + + let vk; + + if (sk) { + vk = get_vk(sk); + }else{ + let keyPair = new_wallet(seed); + vk = keyPair.vk; + sk = keyPair.sk; + } + + const wallet = () => { + return { + sign: (msg) => sign(sk, msg), + verify: (msg, sig) => verify(vk, msg, sig), + vk, + sk: !keepPrivate ? sk : undefined + } + }; + + return wallet() +}; + /** * @param Uint8Array(length: 32) seed * seed: A Uint8Array with a length of 32 to seed the keyPair with. This is advanced behavior and should be @@ -2349,6 +2453,7 @@ function verify(vk, msg, sig) { var wallet = /*#__PURE__*/Object.freeze({ __proto__: null, + create_wallet: create_wallet, generate_keys: generate_keys, get_vk: get_vk, format_to_keys: format_to_keys, @@ -2392,10 +2497,10 @@ class EventEmitter { } /* - * bignumber.js v9.0.1 + * bignumber.js v9.0.0 * A JavaScript library for arbitrary-precision arithmetic. * https://github.com/MikeMcl/bignumber.js - * Copyright (c) 2020 Michael Mclaughlin + * Copyright (c) 2019 Michael Mclaughlin * MIT Licensed. * * BigNumber.prototype methods | BigNumber methods @@ -4787,7 +4892,7 @@ function clone(configObject) { e = bitFloor((e + 1) / 2) - (e < 0 || e % 2); if (s == 1 / 0) { - n = '5e' + e; + n = '1e' + e; } else { n = s.toExponential(); n = n.slice(0, n.indexOf('e') + 1) + e; @@ -5471,13 +5576,13 @@ var encoder = { }; var encoder_1 = encoder.Encoder; -const { validateTypes } = validators; +const { validateTypes: validateTypes$1 } = validators; const fetch = require('node-fetch').default; class LamdenMasterNode_API{ constructor(networkInfoObj){ - if (!validateTypes.isObjectWithKeys(networkInfoObj)) throw new Error(`Expected Object and got Type: ${typeof networkInfoObj}`) - if (!validateTypes.isArrayWithValues(networkInfoObj.hosts)) throw new Error(`HOSTS Required (Type: Array)`) + if (!validateTypes$1.isObjectWithKeys(networkInfoObj)) throw new Error(`Expected Object and got Type: ${typeof networkInfoObj}`) + if (!validateTypes$1.isArrayWithValues(networkInfoObj.hosts)) throw new Error(`HOSTS Required (Type: Array)`) this.hosts = this.validateHosts(networkInfoObj.hosts); } @@ -5539,7 +5644,7 @@ class LamdenMasterNode_API{ async getVariable(contract, variable, key = ''){ let parms = {}; - if (validateTypes.isStringWithValue(key)) parms.key = key; + if (validateTypes$1.isStringWithValue(key)) parms.key = key; let path = `/contracts/${contract}/${variable}/`; return this.send('GET', path, {parms}, undefined, (res, err) => { @@ -5618,7 +5723,7 @@ class LamdenMasterNode_API{ } async getNonce(sender, callback){ - if (!validateTypes.isStringHex(sender)) return `${sender} is not a hex string.` + if (!validateTypes$1.isStringHex(sender)) return `${sender} is not a hex string.` let path = `/nonce/${sender}`; let url = this.host; return this.send('GET', path, {}, url, (res, err) => { @@ -5657,7 +5762,7 @@ class LamdenMasterNode_API{ } } -const { validateTypes: validateTypes$1 } = validators; +const { validateTypes: validateTypes$2 } = validators; class Network { // Constructor needs an Object with the following information to build Class. @@ -5668,16 +5773,16 @@ class Network { // }, constructor(networkInfoObj){ //Reject undefined or missing info - if (!validateTypes$1.isObjectWithKeys(networkInfoObj)) throw new Error(`Expected Network Info Object and got Type: ${typeof networkInfoObj}`) - if (!validateTypes$1.isArrayWithValues(networkInfoObj.hosts)) throw new Error(`HOSTS Required (Type: Array)`) + if (!validateTypes$2.isObjectWithKeys(networkInfoObj)) throw new Error(`Expected Network Info Object and got Type: ${typeof networkInfoObj}`) + if (!validateTypes$2.isArrayWithValues(networkInfoObj.hosts)) throw new Error(`HOSTS Required (Type: Array)`) - this.type = validateTypes$1.isStringWithValue(networkInfoObj.type) ? networkInfoObj.type.toLowerCase() : "custom"; + this.type = validateTypes$2.isStringWithValue(networkInfoObj.type) ? networkInfoObj.type.toLowerCase() : "custom"; this.events = new EventEmitter(); this.hosts = this.validateHosts(networkInfoObj.hosts); - this.currencySymbol = validateTypes$1.isStringWithValue(networkInfoObj.currencySymbol) ? networkInfoObj.currencySymbol : 'TAU'; - this.name = validateTypes$1.isStringWithValue(networkInfoObj.name) ? networkInfoObj.name : 'lamden network'; - this.lamden = validateTypes$1.isBoolean(networkInfoObj.lamden) ? networkInfoObj.lamden : false; - this.blockExplorer = validateTypes$1.isStringWithValue(networkInfoObj.blockExplorer) ? networkInfoObj.blockExplorer : undefined; + this.currencySymbol = validateTypes$2.isStringWithValue(networkInfoObj.currencySymbol) ? networkInfoObj.currencySymbol : 'TAU'; + this.name = validateTypes$2.isStringWithValue(networkInfoObj.name) ? networkInfoObj.name : 'lamden network'; + this.lamden = validateTypes$2.isBoolean(networkInfoObj.lamden) ? networkInfoObj.lamden : false; + this.blockExplorer = validateTypes$2.isStringWithValue(networkInfoObj.blockExplorer) ? networkInfoObj.blockExplorer : undefined; this.online = false; try{ @@ -5701,7 +5806,7 @@ class Network { async ping(callback = undefined){ this.online = await this.API.pingServer(); this.events.emit('online', this.online); - if (validateTypes$1.isFunction(callback)) callback(this.online); + if (validateTypes$2.isFunction(callback)) callback(this.online); return this.online } get host() {return this.hosts[Math.floor(Math.random() * this.hosts.length)]} @@ -5718,7 +5823,7 @@ class Network { } } -const { validateTypes: validateTypes$2 } = validators; +const { validateTypes: validateTypes$3 } = validators; class TransactionBuilder extends Network { // Constructor needs an Object with the following information to build Class. @@ -5741,33 +5846,33 @@ class TransactionBuilder extends Network { // } // arg[2] (txData): [Optional] state hydrating data constructor(networkInfo, txInfo, txData) { - if (validateTypes$2.isSpecificClass(networkInfo, 'Network')) + if (validateTypes$3.isSpecificClass(networkInfo, 'Network')) super(networkInfo.getNetworkInfo()); else super(networkInfo); //Validate arguments - if(!validateTypes$2.isObjectWithKeys(txInfo)) throw new Error(`txInfo object not found`) - if(!validateTypes$2.isStringHex(txInfo.senderVk)) throw new Error(`Sender Public Key Required (Type: Hex String)`) - if(!validateTypes$2.isStringWithValue(txInfo.contractName)) throw new Error(`Contract Name Required (Type: String)`) - if(!validateTypes$2.isStringWithValue(txInfo.methodName)) throw new Error(`Method Required (Type: String)`) - if(!validateTypes$2.isInteger(txInfo.stampLimit)) throw new Error(`Stamps Limit Required (Type: Integer)`) + if(!validateTypes$3.isObjectWithKeys(txInfo)) throw new Error(`txInfo object not found`) + if(!validateTypes$3.isStringHex(txInfo.senderVk)) throw new Error(`Sender Public Key Required (Type: Hex String)`) + if(!validateTypes$3.isStringWithValue(txInfo.contractName)) throw new Error(`Contract Name Required (Type: String)`) + if(!validateTypes$3.isStringWithValue(txInfo.methodName)) throw new Error(`Method Required (Type: String)`) + if(!validateTypes$3.isInteger(txInfo.stampLimit)) throw new Error(`Stamps Limit Required (Type: Integer)`) //Store variables in self for reference - this.uid = validateTypes$2.isStringWithValue(txInfo.uid) ? txInfo.uid : undefined; + this.uid = validateTypes$3.isStringWithValue(txInfo.uid) ? txInfo.uid : undefined; this.sender = txInfo.senderVk; this.contract = txInfo.contractName; this.method = txInfo.methodName; this.kwargs = {}; - if(validateTypes$2.isObject(txInfo.kwargs)) this.kwargs = txInfo.kwargs; + if(validateTypes$3.isObject(txInfo.kwargs)) this.kwargs = txInfo.kwargs; this.stampLimit = txInfo.stampLimit; //validate and set nonce and processor if user provided them if (typeof txInfo.nonce !== 'undefined'){ - if(!validateTypes$2.isInteger(txInfo.nonce)) throw new Error(`arg[6] Nonce is required to be an Integer, type ${typeof txInfo.none} was given`) + if(!validateTypes$3.isInteger(txInfo.nonce)) throw new Error(`arg[6] Nonce is required to be an Integer, type ${typeof txInfo.none} was given`) this.nonce = txInfo.nonce; } if (typeof txInfo.processor !== 'undefined'){ - if(!validateTypes$2.isStringWithValue(txInfo.processor)) throw new Error(`arg[7] Processor is required to be a String, type ${typeof txInfo.processor} was given`) + if(!validateTypes$3.isStringWithValue(txInfo.processor)) throw new Error(`arg[7] Processor is required to be a String, type ${typeof txInfo.processor} was given`) this.processor = txInfo.processor; } @@ -5786,18 +5891,18 @@ class TransactionBuilder extends Network { //Hydrate other items if passed if (txData){ if (txData.uid) this.uid = txData.uid; - if (validateTypes$2.isObjectWithKeys(txData.txSendResult)) this.txSendResult = txData.txSendResult; - if (validateTypes$2.isObjectWithKeys(txData.nonceResult)){ + if (validateTypes$3.isObjectWithKeys(txData.txSendResult)) this.txSendResult = txData.txSendResult; + if (validateTypes$3.isObjectWithKeys(txData.nonceResult)){ this.nonceResult = txData.nonceResult; - if (validateTypes$2.isInteger(this.nonceResult.nonce)) this.nonce = this.nonceResult.nonce; - if (validateTypes$2.isStringWithValue(this.nonceResult.processor)) this.processor = this.nonceResult.processor; + if (validateTypes$3.isInteger(this.nonceResult.nonce)) this.nonce = this.nonceResult.nonce; + if (validateTypes$3.isStringWithValue(this.nonceResult.processor)) this.processor = this.nonceResult.processor; } - if (validateTypes$2.isObjectWithKeys(txData.txSendResult)){ + if (validateTypes$3.isObjectWithKeys(txData.txSendResult)){ this.txSendResult = txData.txSendResult; if (this.txSendResult.hash) this.txHash = this.txSendResult.hash; } - if (validateTypes$2.isObjectWithKeys(txData.txBlockResult)) this.txBlockResult = txData.txBlockResult; - if (validateTypes$2.isObjectWithKeys(txData.resultInfo)) this.resultInfo = txData.resultInfo; + if (validateTypes$3.isObjectWithKeys(txData.txBlockResult)) this.txBlockResult = txData.txBlockResult; + if (validateTypes$3.isObjectWithKeys(txData.resultInfo)) this.resultInfo = txData.resultInfo; } //Create Capnp messages and transactionMessages this.makePayload(); @@ -5899,7 +6004,7 @@ class TransactionBuilder extends Network { } async send(sk = undefined, masternode = undefined, callback = undefined) { //Error if transaction is not signed and no sk provided to the send method to sign it before sending - if (!validateTypes$2.isStringWithValue(sk) && !this.transactionSigned){ + if (!validateTypes$3.isStringWithValue(sk) && !this.transactionSigned){ throw new Error(`Transation Not Signed: Private key needed or call sign() first`); } @@ -5907,9 +6012,9 @@ class TransactionBuilder extends Network { try{ //If the nonce isn't set attempt to get it - if (isNaN(this.nonce) || !validateTypes$2.isStringWithValue(this.processor)) await this.getNonce(); + if (isNaN(this.nonce) || !validateTypes$3.isStringWithValue(this.processor)) await this.getNonce(); //if the sk is provided then sign the transaction - if (validateTypes$2.isStringWithValue(sk)) this.sign(sk); + if (validateTypes$3.isStringWithValue(sk)) this.sign(sk); //Serialize transaction this.makeTransaction(); //Send transaction to the masternode @@ -5917,9 +6022,9 @@ class TransactionBuilder extends Network { if (!masternodeURL && this.nonceMasternode) masternodeURL = this.nonceMasternode; let response = await this.API.sendTransaction(this.tx, masternodeURL); //Set error if txSendResult doesn't exist - if (response === 'undefined' || validateTypes$2.isStringWithValue(response)){ + if (response === 'undefined' || validateTypes$3.isStringWithValue(response)){ this.txSendResult.errors = ['TypeError: Failed to fetch']; - }else { + }else{ if (response.error) this.txSendResult.errors = [response.error]; else this.txSendResult = response; } @@ -5939,34 +6044,34 @@ class TransactionBuilder extends Network { if (typeof res === 'undefined'){ res = {}; res.error = 'TypeError: Failed to fetch'; - }else { + }else{ if (typeof res === 'string') { if (this.txCheckAttempts < this.txCheckLimit){ checkAgain = true; - }else { + }else{ this.txCheckResult.errors = [res]; } - }else { + }else{ if (res.error){ if (res.error === 'Transaction not found.'){ if (this.txCheckAttempts < this.txCheckLimit){ checkAgain = true; - }else { + }else{ this.txCheckResult.errors = [res.error, `Retry Attmpts ${this.txCheckAttempts} hit while checking for Tx Result.`]; } - }else { + }else{ this.txCheckResult.errors = [res.error]; } - }else { + }else{ this.txCheckResult = res; } } } if (checkAgain) timerId = setTimeout(checkTx.bind(this), 1000); - else { - if (validateTypes$2.isNumber(this.txCheckResult.status)){ + else{ + if (validateTypes$3.isNumber(this.txCheckResult.status)){ if (this.txCheckResult.status > 0){ - if (!validateTypes$2.isArray(this.txCheckResult.errors)) this.txCheckResult.errors = []; + if (!validateTypes$3.isArray(this.txCheckResult.errors)) this.txCheckResult.errors = []; this.txCheckResult.errors.push('This transaction returned a non-zero status code'); } } @@ -5979,15 +6084,15 @@ class TransactionBuilder extends Network { } handleMasterNodeResponse(result, callback = undefined){ //Check to see if this is a successful transacation submission - if (validateTypes$2.isStringWithValue(result.hash) && validateTypes$2.isStringWithValue(result.success)){ + if (validateTypes$3.isStringWithValue(result.hash) && validateTypes$3.isStringWithValue(result.success)){ this.txHash = result.hash; this.setPendingBlockInfo(); - }else { + }else{ this.setBlockResultInfo(result); this.txBlockResult = result; } this.events.emit('response', result, this.resultInfo.subtitle); - if (validateTypes$2.isFunction(callback)) callback(result); + if (validateTypes$3.isFunction(callback)) callback(result); return result } setPendingBlockInfo(){ @@ -6002,10 +6107,10 @@ class TransactionBuilder extends Network { setBlockResultInfo(result){ let erroredTx = false; let errorText = `returned an error and `; - let statusCode = validateTypes$2.isNumber(result.status) ? result.status : undefined; + let statusCode = validateTypes$3.isNumber(result.status) ? result.status : undefined; let stamps = (result.stampsUsed || result.stamps_used) || 0; let message = ''; - if(validateTypes$2.isArrayWithValues(result.errors)){ + if(validateTypes$3.isArrayWithValues(result.errors)){ erroredTx = true; message = `This transaction returned ${result.errors.length} errors.`; if (result.result){ @@ -6055,11 +6160,11 @@ class TransactionBuilder extends Network { } } -const { validateTypes: validateTypes$3 } = validators; +const { validateTypes: validateTypes$4 } = validators; class TransactionBatcher extends Network { constructor(networkInfo) { - if (validateTypes$3.isSpecificClass(networkInfo, 'Network')) + if (validateTypes$4.isSpecificClass(networkInfo, 'Network')) super(networkInfo.getNetworkInfo()); else super(networkInfo); @@ -6169,12 +6274,196 @@ class TransactionBatcher extends Network { } } +const { validateTypes: validateTypes$5, assertTypes: assertTypes$1 } = validators; + +class Keystore { + /** + * Lamden Keystores + * + * This Class will create a lamden keystore instance + * + * @param {Object|undefined} arg constructor argument + * @param {String|undefined} arg.key Create an instance and load it with one private key + * @param {String|undefined} arg.keyList Create an instance and load it with an array of private keys + * @param {String|undefined} arg.keystoreData Create an instance from an existing keystore file data + * @return {Keystore} + */ + constructor(arg = undefined) { + this.KEYSTORE_VERSION = "1.0"; + this.password = null; + this.encryptedData = null; + + this.keyList = (() => { + let keyList = []; + let outerClass = this; + let wallets = []; + + const addKey = (key) => { + keyList.push(key); + createWallets(); + }; + const clearKeys = () => { + keyList = []; + createWallets(); + }; + const numOfKeys = () => keyList.length; + const createWallets = () => { + wallets = []; + keyList.forEach(key => wallets.push(create_wallet({sk: key, keepPrivate: true})) ); + }; + const createKeystore = (password, hint = undefined) => { + return JSON.stringify({ + data: encryptObject(password, {version: outerClass.KEYSTORE_VERSION, keyList}), + w: !hint ? "" : encryptStrHash('n1ahcKc0lb', hint), + }); + }; + const decryptKeystore = (password, data) => { + let decrypted = decryptObject(password, data); + if (decrypted) { + assertTypes$1.isArray(decrypted.keyList); + decrypted.keyList.forEach(key => assertTypes$1.isStringWithValue(key)); + decrypted.keyList.forEach(key => addKey(key)); + outerClass.version = decrypted.version; + } else { + throw new Error("Incorrect Keystore Password.") + } + }; + + return { + getWallets: () => wallets, + getWallet: (vk) => wallets.find(wallet => wallet.vk === vk), + addKey, + clearKeys, + numOfKeys, + createKeystore, + decryptKeystore + } + })(); + + if (arg){ + if (arg.key) this.addKey(arg.key); + if (arg.keyList) this.addKeys(arg.keyList); + if (arg.keystoreData) this.addKeystoreData(arg.keystoreData); + } + } + /** + * Add a list of keys to add to the keystore + * @param {Array.} keyList An array of 32 character long Lamden private keys + */ + addKeys(keyList){ + assertTypes$1.isArray(keyList); + keyList.forEach(key => this.addKey(key)); + } + /** + * Add a key to the keystore + * @param {string} key A 32 character long Lamden private key + */ + addKey(key){ + assertTypes$1.isStringWithValue(key); + this.keyList.addKey(key); + } + /** + * Load the keystore with the data from an existing keystore + * @param {string} keystoreData The contents of an existing encrypted keystore file + */ + addKeystoreData(keystoreData){ + if (validateTypes$5.isString(keystoreData)) keystoreData = JSON.parse(keystoreData); + if(this.validateKeyStore(keystoreData)){ + this.encryptedData = keystoreData; + } + } + /** + * Returns the password hint in a keystore file + * @param {String|undefined} keystoreData The contents of an existing encrypted keystore file if one wasn't supplied to the constructor + */ + getPasswordHint(keystoreData = undefined){ + if (!this.encryptedData && !keystoreData) throw new Error("No keystore data found.") + + if (keystoreData) { + if (validateTypes$5.isString(keystoreData)) keystoreData = JSON.parse(keystoreData); + } + else keystoreData = this.encryptedData; + + if (keystoreData.w) return decryptStrHash('n1ahcKc0lb', keystoreData.w); + else return "" + } + /** + * Clears all keys from the keystore + */ + clearKeys(){ + this.keyList.clearKeys(); + } + /** + * Clears all keys from the keystore + * @return {Array.} An array of wallet objects + */ + get wallets() { + return this.keyList.getWallets() + } + /** + * Load the keystore with the data from an existing keystore + * @param {String} vk A 32 character long Lamden public key + * @return {Object} A wallet object + */ + getWallet(vk) { + return this.keyList.getWallet(vk) + } + /** + * Used to validate that a keystore is the proper Lamden Format (does not decrypt data) + * @param {String} keystoreData The contents of an existing encrypted keystore file + * @return {Boolean} valid + * @throws {Error} This is not a valid keystore file. + */ + validateKeyStore(keystoreData){ + assertTypes$1.isObjectWithKeys(keystoreData); + try{ + let encryptedData = JSON.parse(keystoreData.data); + if (!encryptedData.ct || !encryptedData.iv || !encryptedData.s){ + throw new Error("This is not a valid keystore file.") + } + } catch (e) { + throw new Error("This is not a valid keystore file.") + } + return true; + } + /** + * Create a Keystore text string from the keys contained in the Keystore instance + * @param {String} password A password to encrypt the data + * @param {String|undefined} hint An optional password hint. Not stored in clear text (obsured) but not encrypted with the password. + * @return {String} A JSON stringified object containing the encrypted data + * @throws {Error} Any errors from the encyption process + */ + createKeystore(password, hint = undefined) { + assertTypes$1.isStringWithValue(password); + if (hint){ + assertTypes$1.isStringWithValue(hint); + } + return this.keyList.createKeystore(password, hint) + } + /** + * Decrypt a keystore into a useable array of wallets. Any decrypted keys will be added to existing keys in the keystore. + * @param {String} password A password to encrypt the data + * @param {String|undefined} keystoreData The encrypted contents from a keystore file if not passed into the constructor. + * @throws {Error} Any errors from the encyption process + */ + decryptKeystore(password, keystoreData = undefined){ + if (keystoreData) this.addKeystoreData(keystoreData); + if (!this.encryptedData) throw new Error ("No keystoreData to decrypt.") + try{ + this.keyList.decryptKeystore(password, this.encryptedData.data); + }catch (e){ + throw new Error("Incorrect Keystore Password.") + } + } +} + var index = { TransactionBuilder, TransactionBatcher, Masternode_API: LamdenMasterNode_API, Network, wallet, + Keystore, Encoder: encoder_1 }; diff --git a/docs/keystore.md b/docs/keystore.md new file mode 100644 index 0000000..360654d --- /dev/null +++ b/docs/keystore.md @@ -0,0 +1,248 @@ +#### constructor([arg]) + +Lamden Keystores + +This Class will create a lamden keystore instance + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| arg | `Object` | constructor argument | *Optional* | +| arg.key | `String` | Create an instance and load it with one private key | *Optional* | +| arg.keyList | `String` | Create an instance and load it with an array of private keys | *Optional* | +| arg.keystoreData | `String` | Create an instance from an existing keystore file data | *Optional* | + + + + +##### Returns + + +- `Keystore` + + + +#### addKeys(keyList) + +Add a list of keys to add to the keystore + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| keyList | `Array.` | An array of 32 character long Lamden private keys |   | + + + + +##### Returns + + +- `Void` + + + +#### addKey(key) + +Add a key to the keystore + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| key | `string` | A 32 character long Lamden private key |   | + + + + +##### Returns + + +- `Void` + + + +#### addKeystoreData(keystoreData) + +Load the keystore with the data from an existing keystore + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| keystoreData | `string` | The contents of an existing encrypted keystore file |   | + + + + +##### Returns + + +- `Void` + + + +#### getPasswordHint([keystoreData]) + +Returns the password hint in a keystore file + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| keystoreData | `String` | The contents of an existing encrypted keystore file if one wasn't supplied to the constructor | *Optional* | + + + + +##### Returns + + +- `Void` + + + +#### clearKeys() + +Clears all keys from the keystore + + + + + + +##### Returns + + +- `Void` + + + +#### wallets() + +Clears all keys from the keystore + + + + + + +##### Returns + + +- `Array.<Object>` An array of wallet objects + + + +#### getWallet(vk) + +Load the keystore with the data from an existing keystore + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| vk | `String` | A 32 character long Lamden public key |   | + + + + +##### Returns + + +- `Object` A wallet object + + + +#### validateKeyStore(keystoreData) + +Used to validate that a keystore is the proper Lamden Format (does not decrypt data) + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| keystoreData | `String` | The contents of an existing encrypted keystore file |   | + + + + +##### Returns + + +- `Boolean` valid + + + +#### createKeystore(password[, hint]) + +Create a Keystore text string from the keys contained in the Keystore instance + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| password | `String` | A password to encrypt the data |   | +| hint | `String` | An optional password hint. Not stored in clear text (obsured) but not encrypted with the password. | *Optional* | + + + + +##### Returns + + +- `String` A JSON stringified object containing the encrypted data + + + +#### decryptKeystore(password[, keystoreData]) + +Decrypt a keystore into a useable array of wallets. Any decrypted keys will be added to existing keys in the keystore. + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| password | `String` | A password to encrypt the data |   | +| keystoreData | `String` | The encrypted contents from a keystore file if not passed into the constructor. | *Optional* | + + + + +##### Returns + + +- `Void` + + + + +*Documentation generated with [doxdox](https://github.com/neogeek/doxdox).* diff --git a/package-lock.json b/package-lock.json index 027d079..ed9a1da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamden-js", - "version": "1.21.0", + "version": "1.3.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -324,14 +324,23 @@ } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "assert": { @@ -359,45 +368,10 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "bl": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.8.2.tgz", - "integrity": "sha1-yba8oI0bwuoA/Ir7Txpf0eHGbk4=", - "dev": true, - "requires": { - "readable-stream": "~1.0.26" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "brace-expansion": { @@ -480,28 +454,55 @@ } }, "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "dev": true, "requires": { - "bn.js": "^4.1.0", + "bn.js": "^5.0.0", "randombytes": "^2.0.1" } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "buffer-es6": { @@ -621,6 +622,12 @@ } } }, + "clone": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.1.19.tgz", + "integrity": "sha1-YT+2hjmyaklKxTJT4Vsaa9iK2oU=", + "dev": true + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -678,13 +685,21 @@ "dev": true }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "create-hash": { @@ -791,21 +806,43 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } } }, "emoji-regex": { @@ -815,9 +852,9 @@ "dev": true }, "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "requires": { "prr": "~1.0.1" @@ -1049,13 +1086,39 @@ "dev": true }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "hash.js": { @@ -1328,14 +1391,6 @@ "octal": "^1.0.0", "once": "^1.3.0", "xtend": "^2.2.0" - }, - "dependencies": { - "xtend": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", - "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=", - "dev": true - } } }, "level-fix-range": { @@ -1405,12 +1460,6 @@ "xtend": "~2.0.4" }, "dependencies": { - "clone": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.1.19.tgz", - "integrity": "sha1-YT+2hjmyaklKxTJT4Vsaa9iK2oU=", - "dev": true - }, "level-fix-range": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/level-fix-range/-/level-fix-range-2.0.0.tgz", @@ -1458,6 +1507,15 @@ "xtend": "~3.0.0" }, "dependencies": { + "bl": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.8.2.tgz", + "integrity": "sha1-yba8oI0bwuoA/Ir7Txpf0eHGbk4=", + "dev": true, + "requires": { + "readable-stream": "~1.0.26" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -1561,6 +1619,14 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "minimalistic-assert": { @@ -1637,6 +1703,11 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "node-cryptojs-aes": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-cryptojs-aes/-/node-cryptojs-aes-0.4.0.tgz", + "integrity": "sha1-ZM+6gMH7yfrDR8jrLCwSrb06igc=" + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -1732,14 +1803,13 @@ "dev": true }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -1764,9 +1834,9 @@ "dev": true }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -1812,6 +1882,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "randombytes": { @@ -2007,6 +2085,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -2264,6 +2348,12 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=", + "dev": true + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 20e8bc4..ba2fe22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lamden-js", - "version": "1.3.6", + "version": "1.4.0", "description": "A javascript implementaion for creating wallets, submitting transactions and interacting with masternodes on the Lamden Blockchain.", "main": "dist/lamden.js", "scripts": { @@ -12,7 +12,9 @@ "test-transaction-batcher": "npm run build && mocha test/transactionBatcher-test.js --timeout 60000", "test-wallet": "npm run build && mocha test/wallet-test.js", "test-encoder": "npm run build && mocha test/encoder-test.js", - "build": "rollup --config" + "test-keystore": "npm run build && mocha test/keystore-test.js", + "build": "rollup --config", + "doc": "doxdox 'src/js/keystore.js' --layout markdown --output docs/keystore.md" }, "repository": { "type": "git", @@ -33,6 +35,7 @@ "dependencies": { "assert": "1.4.1", "bignumber.js": "^9.0.0", + "node-cryptojs-aes": "^0.4.0", "node-fetch": "^2.6.1", "tweetnacl": "1.0.1", "types-validate-assert": "^1.0.1" diff --git a/src/index.js b/src/index.js index 7201851..be8b6c9 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import { TransactionBuilder } from './js/transactionBuilder'; import { TransactionBatcher } from './js/transactionBatcher'; import { Network } from './js/network'; import { Encoder } from './js/encoder'; +import { Keystore } from './js/keystore'; import { LamdenMasterNode_API as Masternode_API } from './js/masternode-api'; import * as wallet from './js/wallet'; @@ -11,5 +12,6 @@ export default { Masternode_API, Network, wallet, + Keystore, Encoder }; \ No newline at end of file diff --git a/src/js/helpers.js b/src/js/helpers.js index 746c47a..4c1ed9c 100644 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -1,3 +1,77 @@ +const nodeCryptoJs = require("node-cryptojs-aes") +const { CryptoJS, JsonFormatter } = nodeCryptoJs; +const validators = require('types-validate-assert') +const { validateTypes, assertTypes } = validators; + +/** + * Encrypt a Javascript object with a string password + * The object passed must pass JSON.stringify or the method will fail. + * + * @param {string} password A password to encrypt the object with + * @param {Object} obj A javascript object (must be JSON compatible) + * @return {string} Encrypted string + */ +export function encryptObject ( password, obj ){ + assertTypes.isStringWithValue(password) + assertTypes.isObject(obj) + + const encrypted = CryptoJS.AES.encrypt(JSON.stringify(obj), password, { format: JsonFormatter }).toString(); + return encrypted; +}; + +/** + * Decrypt an Object using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} objString A javascript object as JSON string + * @return {string} Encrypted string +*/ +export function decryptObject ( password, objString ) { + assertTypes.isStringWithValue(password) + assertTypes.isStringWithValue(objString) + + try{ + const decrypt = CryptoJS.AES.decrypt(objString, password, { format: JsonFormatter }) + return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypt)); + } catch (e){ + return false; + } +}; + +/** + * Encrypt a string using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} string A string to be password encrypted + * @return {string} Encrypted string +*/ +export function encryptStrHash( password, string ){ + assertTypes.isStringWithValue(password) + assertTypes.isString(string) + + const encrypt = CryptoJS.AES.encrypt(string, password).toString(); + return encrypt; +}; + +/** + * Decrypt a string using a password string + * + * @param {string} password A password to encrypt the object with + * @param {string} encryptedString A string to decrypt + * @return {string} Decrypted string +*/ +export function decryptStrHash ( password, encryptedString ){ + assertTypes.isStringWithValue(password) + assertTypes.isStringWithValue(encryptedString) + + try{ + const decrypted = CryptoJS.AES.decrypt(encryptedString, password); + return CryptoJS.enc.Utf8.stringify(decrypted) === '' ? false : CryptoJS.enc.Utf8.stringify(decrypted); + } catch (e) { + return false; + } +}; + export function buf2hex(buffer) { return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } @@ -54,4 +128,9 @@ export function isStringHex(string = '') { let hexRegEx = /([0-9]|[a-f])/gim; return typeof string === 'string' && (string.match(hexRegEx) || []).length === string.length; -} \ No newline at end of file +} + +export function isLamdenKey( string ){ + if (validateTypes.isStringHex(string) && string.length === 64) return true; + return false; +}; \ No newline at end of file diff --git a/src/js/keystore.js b/src/js/keystore.js new file mode 100644 index 0000000..c96a1c3 --- /dev/null +++ b/src/js/keystore.js @@ -0,0 +1,185 @@ +import validators from 'types-validate-assert' +const { validateTypes, assertTypes } = validators; +import * as helpers from './helpers'; +import * as wallet from './wallet' + +export class Keystore { + /** + * Lamden Keystores + * + * This Class will create a lamden keystore instance + * + * @param {Object|undefined} arg constructor argument + * @param {String|undefined} arg.key Create an instance and load it with one private key + * @param {String|undefined} arg.keyList Create an instance and load it with an array of private keys + * @param {String|undefined} arg.keystoreData Create an instance from an existing keystore file data + * @return {Keystore} + */ + constructor(arg = undefined) { + this.KEYSTORE_VERSION = "1.0" + this.password = null + this.encryptedData = null; + + this.keyList = (() => { + let keyList = [] + let outerClass = this + let wallets = [] + + const addKey = (key) => { + keyList.push(key) + createWallets() + } + const clearKeys = () => { + keyList = [] + createWallets() + } + const numOfKeys = () => keyList.length + const createWallets = () => { + wallets = [] + keyList.forEach(key => wallets.push(wallet.create_wallet({sk: key, keepPrivate: true})) ) + } + const createKeystore = (password, hint = undefined) => { + return JSON.stringify({ + data: helpers.encryptObject(password, {version: outerClass.KEYSTORE_VERSION, keyList}), + w: !hint ? "" : helpers.encryptStrHash('n1ahcKc0lb', hint), + }); + } + const decryptKeystore = (password, data) => { + let decrypted = helpers.decryptObject(password, data) + if (decrypted) { + assertTypes.isArray(decrypted.keyList) + decrypted.keyList.forEach(key => assertTypes.isStringWithValue(key)) + decrypted.keyList.forEach(key => addKey(key)) + outerClass.version = decrypted.version + } else { + throw new Error("Incorrect Keystore Password.") + } + } + + return { + getWallets: () => wallets, + getWallet: (vk) => wallets.find(wallet => wallet.vk === vk), + addKey, + clearKeys, + numOfKeys, + createKeystore, + decryptKeystore + } + })() + + if (arg){ + if (arg.key) this.addKey(arg.key) + if (arg.keyList) this.addKeys(arg.keyList) + if (arg.keystoreData) this.addKeystoreData(arg.keystoreData) + } + } + /** + * Add a list of keys to add to the keystore + * @param {Array.} keyList An array of 32 character long Lamden private keys + */ + addKeys(keyList){ + assertTypes.isArray(keyList) + keyList.forEach(key => this.addKey(key)) + } + /** + * Add a key to the keystore + * @param {string} key A 32 character long Lamden private key + */ + addKey(key){ + assertTypes.isStringWithValue(key) + this.keyList.addKey(key) + } + /** + * Load the keystore with the data from an existing keystore + * @param {string} keystoreData The contents of an existing encrypted keystore file + */ + addKeystoreData(keystoreData){ + if (validateTypes.isString(keystoreData)) keystoreData = JSON.parse(keystoreData) + if(this.validateKeyStore(keystoreData)){ + this.encryptedData = keystoreData + } + } + /** + * Returns the password hint in a keystore file + * @param {String|undefined} keystoreData The contents of an existing encrypted keystore file if one wasn't supplied to the constructor + */ + getPasswordHint(keystoreData = undefined){ + if (!this.encryptedData && !keystoreData) throw new Error("No keystore data found.") + + if (keystoreData) { + if (validateTypes.isString(keystoreData)) keystoreData = JSON.parse(keystoreData) + } + else keystoreData = this.encryptedData + + if (keystoreData.w) return helpers.decryptStrHash('n1ahcKc0lb', keystoreData.w); + else return "" + } + /** + * Clears all keys from the keystore + */ + clearKeys(){ + this.keyList.clearKeys() + } + /** + * Clears all keys from the keystore + * @return {Array.} An array of wallet objects + */ + get wallets() { + return this.keyList.getWallets() + } + /** + * Load the keystore with the data from an existing keystore + * @param {String} vk A 32 character long Lamden public key + * @return {Object} A wallet object + */ + getWallet(vk) { + return this.keyList.getWallet(vk) + } + /** + * Used to validate that a keystore is the proper Lamden Format (does not decrypt data) + * @param {String} keystoreData The contents of an existing encrypted keystore file + * @return {Boolean} valid + * @throws {Error} This is not a valid keystore file. + */ + validateKeyStore(keystoreData){ + assertTypes.isObjectWithKeys(keystoreData) + try{ + let encryptedData = JSON.parse(keystoreData.data); + if (!encryptedData.ct || !encryptedData.iv || !encryptedData.s){ + throw new Error("This is not a valid keystore file.") + } + } catch (e) { + throw new Error("This is not a valid keystore file.") + } + return true; + } + /** + * Create a Keystore text string from the keys contained in the Keystore instance + * @param {String} password A password to encrypt the data + * @param {String|undefined} hint An optional password hint. Not stored in clear text (obsured) but not encrypted with the password. + * @return {String} A JSON stringified object containing the encrypted data + * @throws {Error} Any errors from the encyption process + */ + createKeystore(password, hint = undefined) { + assertTypes.isStringWithValue(password) + if (hint){ + assertTypes.isStringWithValue(hint) + } + return this.keyList.createKeystore(password, hint) + } + /** + * Decrypt a keystore into a useable array of wallets. Any decrypted keys will be added to existing keys in the keystore. + * @param {String} password A password to encrypt the data + * @param {String|undefined} keystoreData The encrypted contents from a keystore file if not passed into the constructor. + * @throws {Error} Any errors from the encyption process + */ + decryptKeystore(password, keystoreData = undefined){ + if (keystoreData) this.addKeystoreData(keystoreData) + if (!this.encryptedData) throw new Error ("No keystoreData to decrypt.") + try{ + this.keyList.decryptKeystore(password, this.encryptedData.data) + }catch (e){ + throw new Error("Incorrect Keystore Password.") + } + } +} diff --git a/src/js/wallet.js b/src/js/wallet.js index 96b7a19..0b45559 100644 --- a/src/js/wallet.js +++ b/src/js/wallet.js @@ -2,6 +2,40 @@ import * as helpers from './helpers'; const nacl = require('tweetnacl') +/** + * Create a wallet object for signing and verifying messages + * + * @param {Object} [args={}] Args Object + * @param {string} [args.sk=undefined] A 32 character long hex representation of a signing key (private key) to create wallet from + * @param {Uint8Array(length: 32)} [args.seed=null] A Uint8Array with a length of 32 to seed the keyPair with. This is advanced behavior and should be avoided by everyday users + * @param {boolean} [args.keepPrivate=false] No direct access to the sk. Will still allow the wallet to sign messages + * @return {Object} Wallet Object with sign and verify methods + */ +export let create_wallet = (args = {}) => { + let { sk = undefined, keepPrivate = false, seed = null } = args + + let vk; + + if (sk) { + vk = get_vk(sk) + }else{ + let keyPair = new_wallet(seed) + vk = keyPair.vk + sk = keyPair.sk + } + + const wallet = () => { + return { + sign: (msg) => sign(sk, msg), + verify: (msg, sig) => verify(vk, msg, sig), + vk, + sk: !keepPrivate ? sk : undefined + } + } + + return wallet() +} + /** * @param Uint8Array(length: 32) seed * seed: A Uint8Array with a length of 32 to seed the keyPair with. This is advanced behavior and should be diff --git a/test/keystore-test.js b/test/keystore-test.js new file mode 100644 index 0000000..5f693aa --- /dev/null +++ b/test/keystore-test.js @@ -0,0 +1,206 @@ +const expect = require('expect.js'); +const Lamden = require('../dist/lamden'); +const validators = require('types-validate-assert') +const { validateTypes, assertTypes } = validators; +const { wallet } = Lamden; + +const KEYSTORE_PASSWORD = "Testing010203" +const KEYSTORE_HINT = "Testing010203" + +// Overwritted in "createKeystore() - Can create a keystore" +let KEYSTORE_DATA = { + data: '{"ct":"nqBdCILO/cRj2pwGU9PLxvIwxWuWQFd9tQuZkoANnyiig55YTIzCPhRSKgFv7xmmuU823aP2jznNkrVWy2k15l+W3gfENe9dXF300HRcTGEFFvdOE52wxsQG4KRNC+UYOB/3VgjCJczb+HCJ39EWzSm+4qQXQI/5/K0ZzG5R+VGRZbI4b6LkfUgpDsOhXdb0BPVrFy45o/c2RjEZ1ocBgTVw63E+9SUYoxNQDHuviMU=","iv":"add60fe81ae267a01f22f18d78fade60","s":"52571fdd0c5d481c"}', + w: 'U2FsdGVkX19dmxAHJ6wQ7DuwkPNawIAD1rmNRrJdMF8=' +} + +const keyPairs = [wallet.new_wallet(), wallet.new_wallet()] +const keyList = [keyPairs[0].sk, keyPairs[1].sk] + +describe('Test Lamden Keystore Class', () => { + context('keystore construcutor: ', () => { + it('creates an instance with no constructor arguments', () => { + let keystore = new Lamden.Keystore() + assertTypes.isSpecificClass(keystore, "Keystore") + expect( keystore.keyList.numOfKeys() ).to.be( 0 ) + expect( keystore.keyList.getWallets().length ).to.be( 0 ) + }) + it('creates an instance by passing a string to the key property', () => { + let keystore = new Lamden.Keystore({key:keyList[0]}) + assertTypes.isSpecificClass(keystore, "Keystore") + expect( keystore.keyList.numOfKeys() ).to.be( 1 ) + expect( keystore.keyList.getWallets().length ).to.be( 1 ) + }) + it('creates an instance by passing an array to the keys property', () => { + let keystore = new Lamden.Keystore({keyList}) + assertTypes.isSpecificClass(keystore, "Keystore") + expect( keystore.keyList.numOfKeys() ).to.be( 2 ) + expect( keystore.keyList.getWallets().length ).to.be( 2 ) + }) + it('creates an instance by passing a keystoreData object', () => { + let keystore = new Lamden.Keystore({keystoreData: KEYSTORE_DATA}) + assertTypes.isSpecificClass(keystore, "Keystore") + assertTypes.isObjectWithKeys(keystore.encryptedData) + }) + it('creates an instance by passing a keystoreData string', () => { + let keystore = new Lamden.Keystore({keystoreData: JSON.stringify(KEYSTORE_DATA)}) + assertTypes.isSpecificClass(keystore, "Keystore") + assertTypes.isObjectWithKeys(keystore.encryptedData) + }) + it('NEGATIVE - Errors on "keyArg" not Array', () => { + expect(() => new Lamden.Keystore({keyList: {key1: "key1"}})).throwException(); + }) + it('NEGATIVE - Errors on if array value is not type string', () => { + expect(() => new Lamden.Keystore({keyList: [keyList[0] ,2]})) + .throwException((e) => { + expect(e.message).to.be('Expected "2" to be [object String] and not empty'); + }); + }) + }) + context('Adding Keys to the Keystore', () => { + it('addKey() - Can add a single key to the internal "keyList"', () => { + let keystore = new Lamden.Keystore() + keystore.addKey(keyList[0]) + expect( keystore.keyList.numOfKeys() ).to.be( 1 ) + }) + it('NEGATIVE - addKey() - Errors if value passed is not type string', () => { + let keystore = new Lamden.Keystore() + expect(() => keystore.addKey(1)) + .throwException((e) => { + expect(e.message).to.be('Expected "1" to be [object String] and not empty'); + }); + }) + it('addKeys() - Can add to the internal "keyList" via an array of keys', () => { + let keystore = new Lamden.Keystore() + keystore.addKeys(keyList) + expect( keystore.keyList.numOfKeys() ).to.be( 2 ) + }) + it('NEGATIVE - addKeys() - Errors if value passed is not type array', () => { + let keystore = new Lamden.Keystore() + expect(() => keystore.addKeys({key1: "key1", key2: "key2"})) + .throwException((e) => { + expect(e.message).to.be("Expected type [object Array] but got [object Object]"); + }); + }) + }) + context('Using keystore wallets', () => { + it('keystore.wallets - Deletes keys from the keystore', () => { + let keystore = new Lamden.Keystore({keyList}) + expect( keystore.wallets.length ).to.be( 2 ) + }) + it('getWallet() - Can get a specific wallet', () => { + let keystore = new Lamden.Keystore({keyList}) + let keystoreWallet = keystore.getWallet(keystore.wallets[0].vk) + expect(keystoreWallet).to.have.property('sign') + expect(keystoreWallet).to.have.property('verify') + expect(keystoreWallet).to.have.property('vk') + expect(() => assertTypes.isStringHex(keystoreWallet.sk)).throwException(); + }) + }) + context('Clearing a keystore', () => { + it('clearKeys() - Deletes keys from the keystore', () => { + let keystore = new Lamden.Keystore() + keystore.addKey(keyList[0]) + expect( keystore.keyList.numOfKeys() ).to.be( 1 ) + keystore.clearKeys() + expect( keystore.keyList.numOfKeys() ).to.be( 0 ) + }) + }) + context('Creating a Keystore', () => { + it('createKeystore() - Can create a keystore', () => { + let keystore = new Lamden.Keystore({keyList}) + let encryptedKeystore = keystore.createKeystore(KEYSTORE_PASSWORD, KEYSTORE_HINT) + let keystoreObj = JSON.parse(encryptedKeystore) + KEYSTORE_DATA = JSON.parse(encryptedKeystore) + expect(keystoreObj).to.have.property('data') + assertTypes.isStringWithValue(keystoreObj.data) + expect(keystoreObj).to.have.property('w') + assertTypes.isStringWithValue(keystoreObj.w) + }) + it('createKeystore() - Can create a keystore without "hint"', () => { + let keystore = new Lamden.Keystore({keyList}) + let encryptedKeystore = keystore.createKeystore(KEYSTORE_PASSWORD) + let keystoreObj = JSON.parse(encryptedKeystore) + + expect(keystoreObj).to.have.property('data') + assertTypes.isStringWithValue(keystoreObj.data) + + expect(keystoreObj).to.have.property('w') + assertTypes.isString(keystoreObj.w) + + expect(() => assertTypes.isStringWithValue(keystoreObj.w)) + .throwException((e) => { + expect(e.message).to.be('Expected "" to be [object String] and not empty'); + }); + + }) + it('NEGATIVE - createKeystore() - Errors if "password" value passed is not type string', () => { + let keystore = new Lamden.Keystore({keyList}) + expect(() => keystore.createKeystore(12345)) + .throwException((e) => { + expect(e.message).to.be('Expected "12345" to be [object String] and not empty'); + }); + }) + it('NEGATIVE - createKeystore() - Errors if a non-string value for "hint" is provided', () => { + let keystore = new Lamden.Keystore({keyList}) + expect(() => keystore.createKeystore(KEYSTORE_PASSWORD, 12345)) + .throwException((e) => { + expect(e.message).to.be('Expected "12345" to be [object String] and not empty'); + }); + }) + }) + + context('Keystore password hints', () => { + it('getPasswordHint() - Can get the hint from the keystore instance', () => { + let keystore = new Lamden.Keystore({keystoreData: KEYSTORE_DATA}) + let hint = keystore.getPasswordHint() + expect( hint ).to.be( KEYSTORE_HINT ) + }) + it('getPasswordHint() - Can get the hint from a supplied keystore', () => { + let keystore = new Lamden.Keystore() + let hint = keystore.getPasswordHint(KEYSTORE_DATA) + console.log(hint) + expect( hint ).to.be( KEYSTORE_HINT ) + }) + it('getPasswordHint() - Can get the hint from a supplied string keystore', () => { + let keystore = new Lamden.Keystore() + let hint = keystore.getPasswordHint(JSON.stringify(KEYSTORE_DATA)) + console.log(hint) + expect( hint ).to.be( KEYSTORE_HINT ) + }) + }) + context('Decrypting a Keystore', () => { + it('decryptKeystore() - Can decrypte a keystore', () => { + let keystore = new Lamden.Keystore({keystoreData: KEYSTORE_DATA}) + keystore.decryptKeystore(KEYSTORE_PASSWORD) + expect( keystore.keyList.numOfKeys() ).to.be( 2 ) + expect( keystore.version ).to.be( "1.0" ) + }) + it('decryptKeystore() - Can decrypte a keystore passed as a string', () => { + let keystore = new Lamden.Keystore({keystoreData: JSON.stringify(KEYSTORE_DATA)}) + keystore.decryptKeystore(KEYSTORE_PASSWORD) + expect( keystore.keyList.numOfKeys() ).to.be( 2 ) + expect( keystore.version ).to.be( "1.0" ) + }) + it('decryptKeystore() - Can decrypt a provided keystore', () => { + let keystore = new Lamden.Keystore() + keystore.decryptKeystore(KEYSTORE_PASSWORD, KEYSTORE_DATA) + expect( keystore.keyList.numOfKeys() ).to.be( 2 ) + expect( keystore.version ).to.be( "1.0" ) + }) + it('NEGATIVE - decryptKeystore() - Reports Incorrect Password', () => { + let keystore = new Lamden.Keystore() + expect(() => keystore.decryptKeystore("Nope", KEYSTORE_DATA)) + .throwException((e) => { + expect(e.message).to.be('Incorrect Keystore Password.'); + }); + }) + it('NEGATIVE - decryptKeystore() - Errors if no keystoreData found', () => { + let keystore = new Lamden.Keystore() + expect(() => keystore.decryptKeystore(KEYSTORE_PASSWORD)) + .throwException((e) => { + expect(e.message).to.be('No keystoreData to decrypt.'); + }); + }) + }) + +}) diff --git a/test/wallet-test.js b/test/wallet-test.js index 0be5437..e7cbc46 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1,7 +1,7 @@ const expect = require('expect.js'); const Lamden = require('../dist/lamden'); const validators = require('types-validate-assert') -const { validateTypes } = validators; +const { validateTypes, assertTypes } = validators; const { wallet } = Lamden; describe('Test Lamden Wallet methods', () => { @@ -52,4 +52,42 @@ describe('Test Lamden Wallet methods', () => { expect( wallet.verify(newWallet2.vk, message, signedMessage) ).to.be( false ) }) }) + + context('wallet.create_wallet(): ', () => { + it('can create a new wallet object', () => { + let newWallet = wallet.create_wallet() + expect(newWallet).to.have.property('sign') + expect(newWallet).to.have.property('verify') + expect(newWallet).to.have.property('vk') + expect(newWallet).to.have.property('sk') + assertTypes.isStringHex(newWallet.vk) + assertTypes.isStringHex(newWallet.sk) + }) + it('can create a new wallet from a private key', () => { + let keypair = wallet.new_wallet() + let newWallet = wallet.create_wallet({sk: keypair.sk}) + expect(newWallet).to.have.property('sign') + expect(newWallet).to.have.property('verify') + expect(newWallet).to.have.property('vk') + expect(newWallet).to.have.property('sk') + assertTypes.isStringHex(newWallet.vk) + assertTypes.isStringHex(newWallet.sk) + }) + it('secret key is not accessible is set to private', () => { + let newWallet = wallet.create_wallet({keepPrivate: true}) + expect(() => assertTypes.isStringHex(newWallet.sk)).throwException(); + }) + it('wallet object can sign messages', () => { + let newWallet = wallet.create_wallet({keepPrivate: true}) + let message = new Uint8Array('this is a message') + let signedMessage = newWallet.sign(message) + expect( wallet.verify(newWallet.vk, message, signedMessage) ).to.be( true ) + }) + it('wallet object can verify a messages', () => { + let newWallet = wallet.create_wallet({keepPrivate: true}) + let message = new Uint8Array('this is a message') + let signedMessage = newWallet.sign(message) + expect( newWallet.verify(message, signedMessage) ).to.be( true ) + }) + }) })