mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add encryption, decryption commands
This commit is contained in:
parent
6ca32cee02
commit
ab2af2670c
11 changed files with 799 additions and 328 deletions
|
|
@ -1 +1 @@
|
|||
exclude = '{$exclude,node_modules}'
|
||||
exclude = '{$exclude,node_modules,*~}'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# Versions
|
||||
|
||||
## Version 5.11.0
|
||||
|
||||
- `decrypt`: Add method to decrypt a message using the node key
|
||||
- `encrypt`: Add method to decrypt a message using the node key
|
||||
|
||||
## Version 5.10.1
|
||||
|
||||
- `liquidity-cost`: Add parameter to get fast swap out price
|
||||
|
|
|
|||
42
bos
42
bos
|
|
@ -16,7 +16,9 @@ const request = require('request');
|
|||
const {accountingCategories} = require('./balances');
|
||||
const {adjustSavedNodes} = require('./nodes');
|
||||
const {authenticatedLnd} = require('./lnd');
|
||||
const {encryptToNode} = require('./encryption');
|
||||
const {exchanges} = require('./fiat');
|
||||
const {decryptWithNode} = require('./encryption');
|
||||
const {findRecord} = require('./lnd');
|
||||
const {gateway} = require('./lnd');
|
||||
const {getAccountingReport} = require('./balances');
|
||||
|
|
@ -301,6 +303,46 @@ prog
|
|||
});
|
||||
})
|
||||
|
||||
// Decrypt a message
|
||||
.command('decrypt', 'Decrypt data using the node key')
|
||||
.argument('<encrypted>', 'Encrypted message')
|
||||
.help('Decrypt a message encrypted to the node key or to another node key')
|
||||
.option('--node <node_name>', 'Node to decrypt with')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return decryptWithNode({
|
||||
encrypted: args.encrypted,
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Encrypt a message
|
||||
.command('encrypt', 'Encrypt data using the node key')
|
||||
.help('Encrypt a message to the node key or to another node key')
|
||||
.option('--node <node_name>', 'Node to encrypt with')
|
||||
.option('--message <message>', 'Text message to encrypt')
|
||||
.option('--to <to>', 'Encrypt message to another node')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
return encryptToNode({
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
message: options.message,
|
||||
to: options.to,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Fan out utxos
|
||||
.command('fanout', 'Fan out utxos')
|
||||
.argument('<size>', 'UTXO minimum size', INT)
|
||||
|
|
|
|||
134
encryption/decrypt_with_node.js
Normal file
134
encryption/decrypt_with_node.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {decodeFirst} = require('cbor');
|
||||
const {diffieHellmanComputeSecret} = require('ln-service');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const decryptWithSecret = require('./decrypt_with_secret');
|
||||
|
||||
const hexAsBuf = hex => Buffer.from(hex, 'hex');
|
||||
const {isArray} = Array;
|
||||
|
||||
/** Decrypt data from node
|
||||
|
||||
{
|
||||
encrypted: <Encrypted Data Hex String>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
message: <Message Object>
|
||||
with_alias: <With Node Public Key Hex String>
|
||||
with_public_key: <With Public Key Hex String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({encrypted, lnd}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!encrypted) {
|
||||
return cbk([400, 'ExpectedEncryptedPayloadToDecryptData']);
|
||||
}
|
||||
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndToDecryptData']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Decode encrypted CBOR
|
||||
decode: ['validate', ({}, cbk) => {
|
||||
return decodeFirst(encrypted, (err, details) => {
|
||||
if (!!err) {
|
||||
return cbk([400, 'ExpectedCborEncodedEncryptedData', {err}]);
|
||||
}
|
||||
|
||||
if (!details) {
|
||||
return cbk([400, 'ExpectedDecodedDetailsToDecryptWithNode']);
|
||||
}
|
||||
|
||||
if (!details.settings) {
|
||||
return cbk([400, 'ExpectedEncryptionSettingsToDecryptWithNode']);
|
||||
}
|
||||
|
||||
if (!isArray(details.settings.with)) {
|
||||
return cbk([400, 'ExpectedWithNodesPublicKeyToDecrypt']);
|
||||
}
|
||||
|
||||
return cbk(null, details);
|
||||
});
|
||||
}],
|
||||
|
||||
// Get node public key
|
||||
getPublicKey: ['decode', ({decode}, cbk) => {
|
||||
return getWalletInfo({lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Determine the foreign key if any
|
||||
foreignKey: ['decode', 'getPublicKey', ({decode, getPublicKey}, cbk) => {
|
||||
const keys = decode.settings.with;
|
||||
|
||||
const foreignKey = keys.find(n => n !== getPublicKey.public_key);
|
||||
|
||||
return cbk(null, foreignKey || getPublicKey.public_key);
|
||||
}],
|
||||
|
||||
// Get details about the node
|
||||
getNode: ['foreignKey', ({foreignKey}, cbk) => {
|
||||
return getNode({lnd, public_key: foreignKey}, (err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(null, {public_key: foreignKey});
|
||||
}
|
||||
|
||||
return cbk(null, {alias: res.alias, public_key: foreignKey});
|
||||
});
|
||||
}],
|
||||
|
||||
// Derive the shared secret
|
||||
getSecret: ['foreignKey', ({foreignKey}, cbk) => {
|
||||
return diffieHellmanComputeSecret({
|
||||
lnd,
|
||||
partner_public_key: foreignKey,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Decrypt the data
|
||||
decrypt: ['decode', 'getSecret', ({decode, getSecret}, cbk) => {
|
||||
return decryptWithSecret({
|
||||
encrypted: Buffer.from(decode.encrypted, 'hex'),
|
||||
iv: decode.iv,
|
||||
salt: decode.salt,
|
||||
secret: getSecret.secret,
|
||||
settings: {
|
||||
algorithm: decode.settings.algorithm,
|
||||
derivation: decode.settings.derivation,
|
||||
digest: decode.settings.digest,
|
||||
key_length: decode.settings.key_length,
|
||||
},
|
||||
tag: decode.tag,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Cleartext
|
||||
clear: [
|
||||
'decode',
|
||||
'decrypt',
|
||||
'getNode',
|
||||
({decode, decrypt, getNode}, cbk) =>
|
||||
{
|
||||
return cbk(null, {
|
||||
with_alias: getNode.alias || undefined,
|
||||
with_public_key: getNode.public_key,
|
||||
message: hexAsBuf(decrypt.plain).toString(decode.settings.encoding),
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'clear'}, cbk));
|
||||
});
|
||||
};
|
||||
113
encryption/decrypt_with_secret.js
Normal file
113
encryption/decrypt_with_secret.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
const {createDecipheriv} = require('crypto');
|
||||
const {scrypt} = require('crypto');
|
||||
|
||||
const asyncAuto = require('async/auto');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const bufAsHex = n => n.toString('hex');
|
||||
const derivation = 'scrypt';
|
||||
const digest = 'sha512';
|
||||
const fromHex = n => Buffer.from(n, 'hex');
|
||||
const keyLength = 32;
|
||||
|
||||
/** Decrypt encrypted data with a secret
|
||||
|
||||
{
|
||||
encrypted: <Encrypted Data Buffer Object>
|
||||
iv: <Initialization Vector Buffer Object>
|
||||
salt: <Salt Buffer Object>
|
||||
secret: <Secret Hex String>
|
||||
settings: {
|
||||
algorithm: <Algorithm String>
|
||||
derivation: <Key Derivation Type String>
|
||||
digest: <Digest String>
|
||||
key_length: <Key Length Number>
|
||||
}
|
||||
tag: <Auth Tag Buffer Object>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
plain: <Decrypted Data Hex String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({encrypted, iv, salt, secret, settings, tag}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!Buffer.isBuffer(encrypted)) {
|
||||
return cbk([400, 'ExpectedEncryptedDataToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(iv)) {
|
||||
return cbk([400, 'ExpectedIvToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(salt)) {
|
||||
return cbk([400, 'ExpectedSaltToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (!secret) {
|
||||
return cbk([400, 'ExpectedSecretToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
return cbk([400, 'ExpectedSettingsToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (settings.algorithm !== algorithm) {
|
||||
return cbk([400, 'ExpectedKnownAlgorithmToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (settings.derivation !== derivation) {
|
||||
return cbk([400, 'ExpectedKnownKeyDerivationToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (settings.digest !== digest) {
|
||||
return cbk([400, 'ExpectedKnownDigestToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (settings.key_length !== keyLength) {
|
||||
return cbk([400, 'ExpectedKnownKeyLengthToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
return cbk([400, 'ExpectedAuthTagToDecryptWithSecret']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Derive the key from the secret
|
||||
key: ['validate', ({}, cbk) => {
|
||||
const keyLength = settings.key_length;
|
||||
|
||||
return scrypt(fromHex(secret), salt, keyLength, (err, derivedKey) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'UnexpectedErrorGettingDerivativeKey', {err}]);
|
||||
}
|
||||
|
||||
return cbk(null, derivedKey);
|
||||
});
|
||||
}],
|
||||
|
||||
// Decrypt data with the derived key
|
||||
decrypt: ['key', ({key}, cbk) => {
|
||||
const decipher = createDecipheriv(settings.algorithm, key, iv);
|
||||
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
try {
|
||||
const elements = [decipher.update(encrypted), decipher.final()];
|
||||
|
||||
return cbk(null, {plain: bufAsHex(Buffer.concat(elements))});
|
||||
} catch (err) {
|
||||
return cbk([503, 'FailedToDecryptMessage', {err}]);
|
||||
}
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'decrypt'}, cbk));
|
||||
});
|
||||
};
|
||||
92
encryption/encrypt_to_node.js
Normal file
92
encryption/encrypt_to_node.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {diffieHellmanComputeSecret} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const encryptToSecret = require('./encrypt_to_secret');
|
||||
|
||||
/** Encrypt data to a node
|
||||
|
||||
{
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
message: <Message to Encrypt String>
|
||||
[to]: <Encrypt to Public Key Hex String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
encrypted: <Encrypted Data String>
|
||||
to: <Encrypted To Node Hex String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({lnd, message, to}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndToEncryptToNode']);
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return cbk([400, 'ExpectedMessageToEncryptToNode']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get own public key
|
||||
getPublicKey: ['validate', ({}, cbk) => {
|
||||
return getWalletInfo({lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Encode to public key
|
||||
toPublicKey: ['getPublicKey', ({getPublicKey}, cbk) => {
|
||||
if (!!to) {
|
||||
return cbk(null, {public_key: to});
|
||||
}
|
||||
|
||||
return cbk(null, {public_key: getPublicKey.public_key});
|
||||
}],
|
||||
|
||||
// Get the secret
|
||||
getSecret: ['toPublicKey', ({toPublicKey}, cbk) => {
|
||||
return diffieHellmanComputeSecret({
|
||||
lnd,
|
||||
partner_public_key: toPublicKey.public_key,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Encrypt to the secret
|
||||
encryptToSecret: [
|
||||
'getPublicKey',
|
||||
'getSecret',
|
||||
'toPublicKey',
|
||||
({getPublicKey, getSecret, toPublicKey}, cbk) =>
|
||||
{
|
||||
return encryptToSecret({
|
||||
encoding: 'utf8',
|
||||
from: getPublicKey.public_key,
|
||||
plain: Buffer.from(message, 'utf8').toString('hex'),
|
||||
secret: getSecret.secret,
|
||||
to: toPublicKey.public_key,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Encrypted
|
||||
encrypted: [
|
||||
'encryptToSecret',
|
||||
'toPublicKey',
|
||||
({encryptToSecret, toPublicKey}, cbk) =>
|
||||
{
|
||||
return cbk(null, {
|
||||
encrypted: encryptToSecret.cipher,
|
||||
to: toPublicKey.public_key,
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'encrypted'}, cbk));
|
||||
});
|
||||
};
|
||||
98
encryption/encrypt_to_secret.js
Normal file
98
encryption/encrypt_to_secret.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
const {createCipheriv} = require('crypto');
|
||||
const {randomBytes} = require('crypto');
|
||||
const {scrypt} = require('crypto');
|
||||
|
||||
const asyncAuto = require('async/auto');
|
||||
const {encode} = require('cbor');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const cbor = n => encode(n).toString('hex');
|
||||
const derivation = 'scrypt';
|
||||
const digest = 'sha512';
|
||||
const fromHex = n => Buffer.from(n, 'hex');
|
||||
const ivByteLength = 16;
|
||||
const keyLength = 32;
|
||||
const saltByteLength = 32;
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
||||
/** Encrypt data to a secret
|
||||
|
||||
{
|
||||
[encoding]: <Data Encoding Format String>
|
||||
[from]: <Data is From Public Key Hex String>
|
||||
plain: <Data To Encrypt Hex String>
|
||||
secret: <Encryption Secret Hex String>
|
||||
[to]: <Data is To Public Key Hex String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
cipher: <CBOR Encoded Encrypted Hex String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({encoding, from, plain, secret, to}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!plain) {
|
||||
return cbk([400, 'ExectedDataToEncryptToSecret']);
|
||||
}
|
||||
|
||||
if (!secret) {
|
||||
return cbk([400, 'ExpectedSecretToEncryptToSecret']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Generate salt
|
||||
salt: ['validate', ({}, cbk) => cbk(null, randomBytes(saltByteLength))],
|
||||
|
||||
// Encryption settings
|
||||
settings: ['validate', ({}, cbk) => {
|
||||
return cbk(null, {
|
||||
algorithm,
|
||||
derivation,
|
||||
digest,
|
||||
encoding,
|
||||
key_length: keyLength,
|
||||
with: uniq([from, to]),
|
||||
});
|
||||
}],
|
||||
|
||||
// Generate a derivative key
|
||||
key: ['salt', ({salt}, cbk) => {
|
||||
return scrypt(fromHex(secret), salt, keyLength, (err, derivedKey) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'UnexpectedErrorGeneratingDerivativeKey', {err}]);
|
||||
}
|
||||
|
||||
return cbk(null, derivedKey);
|
||||
});
|
||||
}],
|
||||
|
||||
// Encrypt to derivative key
|
||||
encrypt: ['key', 'salt', 'settings', ({key, salt, settings}, cbk) => {
|
||||
const iv = randomBytes(ivByteLength);
|
||||
|
||||
try {
|
||||
createCipheriv(algorithm, key, iv);
|
||||
} catch (err) {
|
||||
return cbk([500, 'FailedToCreateCipherWhenEncryptingToSecret']);
|
||||
}
|
||||
|
||||
const cipher = createCipheriv(algorithm, key, iv);
|
||||
|
||||
const updated = [cipher.update(fromHex(plain)), cipher.final()];
|
||||
|
||||
const encrypted = Buffer.concat(updated);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return cbk(null, {cipher: cbor({encrypted, iv, salt, settings, tag})});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'encrypt'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
const decryptCiphertext = require('./decrypt_ciphertext');
|
||||
const decryptPayload = require('./decrypt_payload');
|
||||
const decryptWithNode = require('./decrypt_with_node');
|
||||
const derAsPem = require('./der_as_pem');
|
||||
const encryptToNode = require('./encrypt_to_node');
|
||||
const encryptToPublicKeys = require('./encrypt_to_public_keys');
|
||||
const pemAsDer = require('./pem_as_der');
|
||||
|
||||
module.exports = {
|
||||
decryptCiphertext,
|
||||
decryptPayload,
|
||||
decryptWithNode,
|
||||
derAsPem,
|
||||
encryptToNode,
|
||||
encryptToPublicKeys,
|
||||
pemAsDer,
|
||||
};
|
||||
|
|
|
|||
627
package-lock.json
generated
627
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,8 +26,8 @@
|
|||
"html2unicode": "1.1.3",
|
||||
"ini": "1.3.5",
|
||||
"inquirer": "7.0.3",
|
||||
"ln-accounting": "3.1.7",
|
||||
"ln-service": "47.10.4",
|
||||
"ln-accounting": "3.1.8",
|
||||
"ln-service": "47.12.0",
|
||||
"moment": "2.24.0",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"request": "2.88.0",
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"description": "Lightning balance CLI",
|
||||
"devDependencies": {
|
||||
"tap": "14.10.5"
|
||||
"tap": "14.10.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
|
|
@ -62,5 +62,5 @@
|
|||
"scripts": {
|
||||
"test": "tap test/arrays/*.js test/balances/*.js test/chain/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/responses/*.js test/routing/*.js test/swaps/*.js test/telegram/*.js"
|
||||
},
|
||||
"version": "5.10.1"
|
||||
"version": "5.11.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -907,7 +907,7 @@ module.exports = (args, cbk) => {
|
|||
min_fee_rate: minFeeRate,
|
||||
private_key: claim.private_key,
|
||||
secret: claim.secret,
|
||||
start_height: initiateSwap.start_height || depositHeight,
|
||||
start_height: depositHeight || initiateSwap.start_height,
|
||||
sweep_address: createAddress.address,
|
||||
transaction_id: claim.transaction_id,
|
||||
transaction_vout: claim.transaction_vout,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue