let Dashcore = require("@dashevo/dashcore-lib");
+const NO_SHADOW = "NONE";
const DUFFS = 100000000;
let qrWidth = 2 + 67 + 2;
// Sign Up Fees:
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 <addr>");
// flags
let forceConfirm = removeItem(args, "--unconfirmed");
+ let plainText = removeItem(args, "--plain-text");
let noReserve = removeItem(args, "--no-reserve");
let subcommand = args.shift();
}
if ("generate" === subcommand) {
- await generateKey({ defaultKey: defaultAddr }, args);
+ await generateKey({ defaultKey: defaultAddr, plainText }, args);
return;
}
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);
* @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);
/**
* @param {Object} psuedoState
* @param {String} psuedoState.defaultKey - addr name of default key
+ * @param {Boolean} psuedoState.plainText - don't encrypt
* @param {Array<String>} 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();
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`);
* @param {Array<String>} 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,
);
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(``);
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;
}
}
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) {
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,
}
/**
- * @param {Null} psuedoState
+ * @param {Object} opts
+ * @param {Boolean} [opts._rotatePassphrase]
+ * @param {Boolean} [opts._force]
* @param {Array<String>} 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
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;
}
}
// (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");
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);
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;
}