X-Git-Url: https://git.josue.xyz/?a=blobdiff_plain;f=bin%2Fcrowdnode.js;h=6a13b9f57283e821254f052be936f7c6ed2c8d5a;hb=d4be0b4c4af80aaaac70c015ecfd5c407461e389;hp=2cf8cdfaf269a9dc74b4f50be7449d4ee633fa9d;hpb=f36ed50398c3eb09c6ffb5ceac7d14eb68a37bcd;p=crowdnode.js%2F.git diff --git a/bin/crowdnode.js b/bin/crowdnode.js index 2cf8cdf..6a13b9f 100755 --- a/bin/crowdnode.js +++ b/bin/crowdnode.js @@ -23,6 +23,7 @@ let Ws = require("../lib/ws.js"); let Dashcore = require("@dashevo/dashcore-lib"); +const NO_SHADOW = "NONE"; const DUFFS = 100000000; let qrWidth = 2 + 67 + 2; // Sign Up Fees: @@ -74,7 +75,7 @@ function showHelp() { console.info(""); console.info("Key Management & Encryption:"); - console.info(" crowdnode generate [./privkey.wif]"); + console.info(" crowdnode generate [--plain-text] [./privkey.wif]"); console.info(" crowdnode encrypt"); // TODO allow encrypting one-by-one? console.info(" crowdnode list"); console.info(" crowdnode use "); @@ -119,6 +120,7 @@ async function main() { // flags let forceConfirm = removeItem(args, "--unconfirmed"); + let plainText = removeItem(args, "--plain-text"); let noReserve = removeItem(args, "--no-reserve"); let subcommand = args.shift(); @@ -158,7 +160,7 @@ async function main() { } if ("generate" === subcommand) { - await generateKey({ defaultKey: defaultAddr }, args); + await generateKey({ defaultKey: defaultAddr, plainText }, args); return; } @@ -196,7 +198,10 @@ async function main() { if ("decrypt" === subcommand) { let addr = args.shift() || ""; if (!addr) { - decryptAll(null); + await decryptAll(null); + await Fs.writeFile(shadowPath, NO_SHADOW, "utf8").catch( + emptyStringOnErrEnoent, + ); return; } let keypath = await findWif(addr); @@ -330,12 +335,13 @@ async function initCrowdNode(insightBaseUrl) { * @param {Number} duffs - 1/100000000 of a DASH */ function showQr(addr, duffs = 0) { + let dashAmount = toDash(duffs); let dashUri = `dash://${addr}`; if (duffs) { - dashUri += `?amount=${duffs}`; + dashUri += `?amount=${dashAmount}`; } - let dashQr = Qr.ascii(dashUri, { indent: 4 }); + let dashQr = Qr.ascii(dashUri, { indent: 4, size: "mini" }); let addrPad = Math.ceil((qrWidth - dashUri.length) / 2); console.info(dashQr); @@ -559,9 +565,10 @@ async function mustGetDefaultWif(defaultAddr, opts) { /** * @param {Object} psuedoState * @param {String} psuedoState.defaultKey - addr name of default key + * @param {Boolean} psuedoState.plainText - don't encrypt * @param {Array} args */ -async function generateKey({ defaultKey }, args) { +async function generateKey({ defaultKey, plainText }, args) { let name = args.shift(); //@ts-ignore - TODO submit JSDoc PR for Dashcore let pk = new Dashcore.PrivateKey(); @@ -569,7 +576,10 @@ async function generateKey({ defaultKey }, args) { let addr = pk.toAddress().toString(); let plainWif = pk.toWIF(); - let wif = await maybeEncrypt(plainWif); + let wif = plainWif; + if (!plainText) { + wif = await maybeEncrypt(plainWif); + } let filename = `~/${configdir}/keys/${addr}.wif`; let filepath = Path.join(`${keysDir}/${addr}.wif`); @@ -604,33 +614,20 @@ async function generateKey({ defaultKey }, args) { * @param {Array} args */ async function setPassphrase({ _askPreviousPassphrase }, args) { + let result = { + passphrase: "", + changed: false, + }; let date = getFsDateString(); // get the old passphrase if (false !== _askPreviousPassphrase) { - await cmds.getPassphrase(null, []); + // TODO should contain the shadow? + await cmds.getPassphrase({ _rotatePassphrase: true }, []); } // get the new passphrase - let newPassphrase; - for (;;) { - newPassphrase = await Prompt.prompt("Enter (new) passphrase: ", { - mask: true, - }); - newPassphrase = newPassphrase.trim(); - - let _newPassphrase = await Prompt.prompt("Enter passphrase again: ", { - mask: true, - }); - _newPassphrase = _newPassphrase.trim(); - - let match = Cipher.secureCompare(newPassphrase, _newPassphrase); - if (match) { - break; - } - - console.error("passphrases do not match"); - } + let newPassphrase = await promptPassphrase(); let curShadow = await Fs.readFile(shadowPath, "utf8").catch( emptyStringOnErrEnoent, ); @@ -653,7 +650,7 @@ async function setPassphrase({ _askPreviousPassphrase }, args) { let filepath = Path.join(HOME, `${configdir}/keys.${date}.bak`); console.info(``); console.info(`Backing up previous (encrypted) keys:`); - encAddrs.unshift(curShadow); + encAddrs.unshift(`SHADOW:${curShadow}`); await Fs.writeFile(filepath, encAddrs.join("\n") + "\n", "utf8"); console.info(` ~/${configdir}/keys.${date}.bak`); console.info(``); @@ -662,6 +659,31 @@ async function setPassphrase({ _askPreviousPassphrase }, args) { await encryptAll(rawKeys, { rotateKey: true }); + result.passphrase = newPassphrase; + result.changed = true; + return result; +} + +async function promptPassphrase() { + let newPassphrase; + for (;;) { + newPassphrase = await Prompt.prompt("Enter (new) passphrase: ", { + mask: true, + }); + newPassphrase = newPassphrase.trim(); + + let _newPassphrase = await Prompt.prompt("Enter passphrase again: ", { + mask: true, + }); + _newPassphrase = _newPassphrase.trim(); + + let match = Cipher.secureCompare(newPassphrase, _newPassphrase); + if (match) { + break; + } + + console.error("passphrases do not match"); + } return newPassphrase; } @@ -708,6 +730,16 @@ async function encryptAll(rawKeys, opts) { } let date = getFsDateString(); + let passphrase = cmds._getPassphrase(); + if (!passphrase) { + let result = await cmds.getPassphrase({ _force: true }, []); + if (result.changed) { + // encryptAll was already called on rotation + return; + } + passphrase = result.passphrase; + } + console.info(`Encrypting...`); console.info(``); await rawKeys.reduce(async function (promise, key) { @@ -717,7 +749,7 @@ async function encryptAll(rawKeys, opts) { console.info(`🙈 ${key.addr} [already encrypted]`); return; } - let encWif = await maybeEncrypt(key.wif); + let encWif = await maybeEncrypt(key.wif, { force: true }); await safeSave( Path.join(keysDir, `${key.addr}.wif`), encWif, @@ -791,38 +823,54 @@ async function safeSave(filepath, wif, bakpath) { } /** - * @param {Null} psuedoState + * @param {Object} opts + * @param {Boolean} [opts._rotatePassphrase] + * @param {Boolean} [opts._force] * @param {Array} args */ -cmds.getPassphrase = async function (psuedoState, args) { +cmds.getPassphrase = async function ({ _rotatePassphrase, _force }, args) { + let result = { + passphrase: "", + changed: false, + }; + /* + if (!_rotatePassphrase) { + let cachedphrase = cmds._getPassphrase(); + if (cachedphrase) { + return cachedphrase; + } + } + */ + // Three possible states: // 1. no shadow file yet (ask to set one) // 2. empty shadow file (initialized, but not set - don't ask to set one) // 3. encrypted shadow file (initialized, requires passphrase) let needsInit = false; - let shadow = await Fs.readFile(shadowPath, "utf8").catch(function (err) { - if ("ENOENT" === err.code) { - needsInit = true; - return; - } - throw err; - }); + let shadow = await Fs.readFile(shadowPath, "utf8").catch( + emptyStringOnErrEnoent, + ); + if (!shadow) { + needsInit = true; + } else if (NO_SHADOW === shadow && _force) { + needsInit = true; + } // State 1: not initialized, what does the user want? if (needsInit) { for (;;) { - let no = await Prompt.prompt( - "Would you like to set an encryption passphrase? [Y/n]: ", - ); + let no; + if (!_force) { + no = await Prompt.prompt( + "Would you like to set an encryption passphrase? [Y/n]: ", + ); + } // Set a passphrase and create shadow file if (!no || ["yes", "y"].includes(no.toLowerCase())) { - let passphrase = await setPassphrase( - { _askPreviousPassphrase: false }, - args, - ); - cmds._setPassphrase(passphrase); - return passphrase; + result = await setPassphrase({ _askPreviousPassphrase: false }, args); + cmds._setPassphrase(result.passphrase); + return result; } // ask user again @@ -830,9 +878,9 @@ cmds.getPassphrase = async function (psuedoState, args) { continue; } - // No passphrase, create empty shadow file - await Fs.writeFile(shadowPath, "", "utf8"); - return ""; + // No passphrase, create a NONE shadow file + await Fs.writeFile(shadowPath, NO_SHADOW, "utf8"); + return result; } } @@ -840,26 +888,30 @@ cmds.getPassphrase = async function (psuedoState, args) { // (user doesn't want a passphrase) if (!shadow) { cmds._setPassphrase(""); - return ""; + return result; } // State 3: passphrase & shadow already in use for (;;) { - let passphrase = await Prompt.prompt("Enter (current) passphrase: ", { + let prompt = `Enter passphrase: `; + if (_rotatePassphrase) { + prompt = `Enter (current) passphrase: `; + } + result.passphrase = await Prompt.prompt(prompt, { mask: true, }); - passphrase = passphrase.trim(); - if (!passphrase || "q" === passphrase) { + result.passphrase = result.passphrase.trim(); + if (!result.passphrase || "q" === result.passphrase) { console.error("cancel: no passphrase"); process.exit(1); - return; + return result; } - let match = await Cipher.checkPassphrase(passphrase, shadow); + let match = await Cipher.checkPassphrase(result.passphrase, shadow); if (match) { - cmds._setPassphrase(passphrase); + cmds._setPassphrase(result.passphrase); console.info(``); - return passphrase; + return result; } console.error("incorrect passphrase"); @@ -985,7 +1037,10 @@ async function maybeReadKeyFileRaw(filepath, opts) { async function decrypt(encWif) { let passphrase = cmds._getPassphrase(); if (!passphrase) { - passphrase = await cmds.getPassphrase(null, []); + let result = await cmds.getPassphrase({}, []); + passphrase = result.passphrase; + // we don't return just in case they're setting a passphrase to + // decrypt a previously encrypted file (i.e. for recovery from elsewhere) } let key128 = await Cipher.deriveKey(passphrase); let cipher = Cipher.create(key128); @@ -993,15 +1048,22 @@ async function decrypt(encWif) { return cipher.decrypt(encWif); } +// tuple example {Promise<[String, Boolean]>} /** + * @param {Object} [opts] + * @param {Boolean} [opts.force] * @param {String} plainWif */ -async function maybeEncrypt(plainWif) { +async function maybeEncrypt(plainWif, opts) { let passphrase = cmds._getPassphrase(); if (!passphrase) { - passphrase = await cmds.getPassphrase(null, []); + let result = await cmds.getPassphrase({}, []); + passphrase = result.passphrase; } if (!passphrase) { + if (opts?.force) { + throw new Error(`no passphrase with which to encrypt file`); + } return plainWif; }