Compare commits

..

11 Commits

Author SHA1 Message Date
Kgothatso
2d9705577c Introduce a few user arguments 2019-12-07 14:55:09 +02:00
Kgothatso
04d7675c20 Support posting signature to service 2019-12-04 21:47:33 +02:00
Kgothatso
a05e1ba48e Add sign command which can perform actions 2019-12-01 00:08:28 +02:00
Kgothatso
19dd5a2a9d Prefer url over domain 2019-12-01 00:08:06 +02:00
Kgothatso
385a4cedbb Getting rid of all the uneeded logging 2019-11-26 22:43:24 +02:00
Kgothatso
4d16737b23 Verifying service produced challenge 2019-11-26 22:42:57 +02:00
Kgothatso
0a17e78fdc Improved encryption implementation 2019-11-26 22:42:26 +02:00
Kgothatso
de7bd68872 Adding the cryptography stuff
This is probably the wrong way to use the cryptography.
2019-11-24 23:03:27 +02:00
Kgothatso
2bd61cf4c6 Bug fix... yargs argv.js thingy... 2019-11-24 22:58:27 +02:00
Kgothatso
db36cb33cf Genesis commit 2019-11-23 22:34:59 +02:00
Kgothatso
9258d6f234 Keep the database.sqlite file out of the commits 2019-11-23 22:34:28 +02:00
24 changed files with 2425 additions and 0 deletions

1
.gitignore vendored
View File

@@ -76,3 +76,4 @@ typings/
# FuseBox cache
.fusebox/
database.sqlite

18
config/default.json Normal file
View File

@@ -0,0 +1,18 @@
{
"bcrypt" : {
"salt" : 11
},
"database" : {
"dialect" : "sqlite",
"host" : "localhost",
"database" : "databases",
"username" : "databases",
"password" : "thisisfortheotherdbtypes",
"storage" : "database.sqlite",
"logging": false
// Encryption on database...
// "dialectOptions": {
// "encrypt": true
// }
}
}

86
lib/commands/add-lock.js Normal file
View File

@@ -0,0 +1,86 @@
const persistence = require("../core/persistence/persistence");
const cryptoUtil = require("../core/cryptoUtil");
module.exports.description = "Add lock which we will use our keys on."
module.exports.builder = (yargs) => {
return yargs
.usage(`Usage: $0 add-lock [options]`)
.help(false)
.version(false)
.option('lock-definition', {
describe: 'New lock to add to our system',
type: 'string'
})
.demandOption(['lock-definition'])
.argv;
}
module.exports.handler = (argv) => {
console.log("Add lock which we will use our keys on.");
const lockDefintion = argv["lock-definition"].js || argv["lock-definition"];
const challenge = JSON.parse(lockDefintion);
const registerationMessage = JSON.parse(challenge.message);
// TODO: Validate lock-definition is of the correct format...
persistence().LoadDB()
.then(db => {
// TODO validate lock definition has all the parameters we need
// TODO: validate serviceExtendedPublicKey
const verification = cryptoUtil.verifyChallenge(
registerationMessage.serviceExtendedPublicKey,
challenge
);
if(verification) {
return db.ExtendedPublicKey.findOne({
where: {
xpub: challenge.xpub
},
include: [
{
association: db.ExtendedPublicKey.Key // TODO: update this to wallet
}
]
}).then(extendedPublicKey => {
if (extendedPublicKey) {
const encryptedKey = extendedPublicKey.key.encryptedPrivateKey;
const iv = extendedPublicKey.key.iv;
const password = "vanished";
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
var challengeDerivationPath = `${extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
// TODO: save lock
return db.Lock.create({
userIdentifier: registerationMessage.userIdentifier,
url: registerationMessage.url,
serviceExtendedPublicKey: registerationMessage.serviceExtendedPublicKey,
signature: signature.toString('hex'),
message: challenge.message,
extendedPublicKeyId: extendedPublicKey.id
})
} else {
console.error("Sorry we can't create a lock with xpub: ", challenge.xpub);
return null;
}
}).then(lock => {
if(lock) {
console.log("Lock: ", lock.id);
console.log("Signature: ", lock.signature);
} else {
console.error("Failed to create the lock.");
}
})
} else {
console.error("Challenge not signed by service");
}
})
}

View File

@@ -0,0 +1,74 @@
const persistence = require("../core/persistence/persistence");
const cryptoUtil = require("../core/cryptoUtil");
module.exports.description = "Sign a challenge to authenticate papao"
module.exports.builder = (yargs) => {
return yargs
// .usage(`Usage: $0 add-lock [options]`)
.help(false)
.version(false)
.option('challenge', {
describe: 'challenge that needs to be signed',
type: 'string'
})
.option('url', {
describe: 'override url belong to the service making the challenge',
type: 'string'
})
.option('user-identifier', {
describe: 'override user identifier which will be used to sign this challenge',
type: 'string'
})
.demandOption(['challenge'])
.argv;
}
module.exports.handler = (argv) => {
const challenge = JSON.parse(argv.challenge);
// TODO get userIdentifier and domainUrl from challenge
const loginRequest = JSON.parse(challenge.message);
const url = argv.url || loginRequest.url;
const userIdentifier = argv.userIdentifier || loginRequest.userIdentifier;
persistence().LoadDB()
.then(db => {
console.log("Looking for");
console.log("url: ", url);
console.log("userIdentifier: ", userIdentifier);
return db.Lock.findOne({
where: {
url: url,
userIdentifier: userIdentifier
},
include: [
{
association: db.Lock.ExtendedPublicKey,
require: true,
include: [
{
association: db.ExtendedPublicKey.Key, // TODO rename key to wallet
required: true
}
]
}
]
})
.then(lock => {
if(lock) {
// Sign the message with the key that corresponds with this lock...
const encryptedKey = lock.extendedPublicKey.key.encryptedPrivateKey;
const iv = lock.extendedPublicKey.key.iv;
const password = "vanished";
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
var challengeDerivationPath = `${lock.extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
console.log("Signature: ", signature.toString('hex'));
} else {
console.error("Failed to find a lock with these parameters");
}
})
})
}

View File

@@ -0,0 +1,38 @@
const persistence = require("../core/persistence/persistence");
const cryptoUtil = require("../core/cryptoUtil");
const bip39 = require('bip39');
const bip32 = require('bip32');
module.exports.description = "Create keys using a randomly generated seed";
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Create keys using a randomly generated seed");
persistence().LoadDB()
.then(db => {
const mnemonic = bip39.generateMnemonic();
console.log("Backup generated Mnemonic: ", mnemonic);
// TODO: validate user backed up mnemonic
const seed = bip39.mnemonicToSeedSync(mnemonic);
const node = bip32.fromSeed(seed);
// TODO: Get user key password
const password = "vanished";
// TODO: Encrypt text securely...
const encryptedPrivateKey = cryptoUtil.encrypt(node.toBase58(), password);
// Check if a key exist...
db.Key.create({
encryptedPrivateKey: encryptedPrivateKey.cipherText,
iv: encryptedPrivateKey.iv,
name: "FirstKey"
}).then(key => {
console.log("Successfully created: ", key.name)
})
})
}

View File

@@ -0,0 +1,9 @@
module.exports.description = "Drop the keys we have instantiated"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Drop the keys we have instantiated");
}

View File

@@ -0,0 +1,54 @@
const persistence = require("../core/persistence/persistence");
const cryptoUtil = require("../core/cryptoUtil");
const bip32 = require('bip32');
module.exports.description = "Get random xpub to use on a service"
module.exports.builder = (yargs) => {
return yargs.argv;
}
module.exports.handler = (argv) => {
console.log("Get random xpub to use on a service");
persistence().LoadDB()
.then(db => {
db.Key.findOne({
where: {
name: "FirstKey"
}
})
.then(key => {
// TODO: Load password from config...
if(key) {
const password = "vanished";
const iv = key.iv;
const decryptedPrivateKey = cryptoUtil.decrypt(key.encryptedPrivateKey, password, iv);
const masterNode = bip32.fromBase58(decryptedPrivateKey);
const derivationPath = `m/${cryptoUtil.randomDerivationPath(true)}`
const xpubNode = masterNode.derivePath(derivationPath);
// TODO save xpub and derivation path in db...
return db.ExtendedPublicKey.create({
xpub: xpubNode.neutered().toBase58(),
derivationPath: derivationPath,
keyId: key.id // TODO rename this to walletID
// TODO add name if availabe...
}).catch(error => {
console.error("Failed to create extended public key: ", error);
})
} else {
console.error("Couldn't create xpub without key")
}
})
.then(extendedPublicKey => {
console.log("Extened public key: ", extendedPublicKey.xpub);
})
.catch(error => {
console.error("Failed to find key: ", error);
})
})
}

View File

@@ -0,0 +1,9 @@
module.exports.description = "Restore keys using a seed if keys haven't been in"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Restore keys using a seed if keys haven't been in");
}

18
lib/commands/show-keys.js Normal file
View File

@@ -0,0 +1,18 @@
const persistence = require("../core/persistence/persistence");
module.exports.description = "Show the keys we have instantiate"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Show the keys we have instantiate");
persistence().LoadDB()
.then(db => {
db.Key.findAll()
.then(keys => {
console.log("Keys: ", keys.map(k => k.name));
})
})
}

View File

@@ -0,0 +1,19 @@
const persistence = require("../core/persistence/persistence");
module.exports.description = "Show all locks our keys have access to"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Show all locks our keys have access to.");
persistence().LoadDB()
.then(db => {
db.Lock.findAll()
.then(locks => {
console.log("locks: ", locks.map(l => {
return l
}));
})
})
}

View File

@@ -0,0 +1,25 @@
const persistence = require("../core/persistence/persistence");
module.exports.description = "Show the ExtendedPublicKeys we have created"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Show the ExtendedPublicKeys we have created");
persistence().LoadDB()
.then(db => {
db.ExtendedPublicKey.findAll()
.then(extendedPublicKey => {
console.log("Keys: ", extendedPublicKey.map(k => {
return {
id: k.id,
name: k.name,
xpub: k.xpub,
derivationPath: k.derivationPath
};
}));
})
})
}

61
lib/commands/sign.js Normal file
View File

@@ -0,0 +1,61 @@
const registrationLogic = require('../core/logic/register');
const authenticationLogic = require('../core/logic/authentication');
module.exports.description = "Sign message/challenge."
module.exports.builder = (yargs) => {
return yargs
.usage(`Usage: $0 sign [options]`)
.help(false)
.version(false)
.option('challenge', {
describe: 'Challenge to sign',
type: 'string'
})
.option('p', {
describe: 'will post signature to response endpoint',
type: 'boolean'
})
.demandOption(['challenge'])
.argv;
}
module.exports.handler = (argv) => {
console.log("Sign challenge.");
const challengeArgument = argv["challenge"].js || argv["challenge"];
const challenge = JSON.parse(challengeArgument);
// Parse Action
try {
const request = JSON.parse(challenge.message);
if(request.action != undefined) {
switch(request.action) {
case "register":
// Ask user if they want to register with service
registrationLogic.registration(challenge, request);
break;
case "authenticate":
// Ask user if they want to authenticate with service
authenticationLogic.authenticate(challenge, request, argv)
break;
default:
// Tell user that action is unsupported
// ask if they want to sign regardless if they want to continue any way.
break;
}
} else {
// No action provided... should just sign
console.log("No action required.");
}
// TODO: Validate lock-definition is of the correct format...
} catch(e) {
// Should just be signed...
console.error("challenge.messsage isn't json Object")
}
}

View File

@@ -0,0 +1,9 @@
module.exports.description = "Verify a message was signed by a specific key"
module.exports.builder = (yargs) => {
}
module.exports.handler = (argv) => {
console.log("Verify message using one of the keys");
}

84
lib/core/cryptoUtil.js Normal file
View File

@@ -0,0 +1,84 @@
const crypto = require('crypto');
const algorithm = 'aes256';
const bip32 = require('bip32');
// NOTE I'm not a cryptographer... so don't be reusing any code from below as I don't have no idea what I'm doing
module.exports.encrypt = (plainText, password) => {
const iv = crypto.randomBytes(16).toString('hex').slice(0, 16); // TODO figure out this iv stuff...
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
const cipher = crypto.createCipheriv(algorithm, key, iv);
var cipherText = cipher.update(plainText, 'utf8', 'hex');
cipherText += cipher.final('hex');
return {
cipherText: cipherText,
iv: iv
};
}
module.exports.decrypt = (cipherText, password, iv) => {
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
const decipher = crypto.createDecipheriv(algorithm, key, iv);
var plainText = decipher.update(cipherText, 'hex', 'utf8');
plainText += decipher.final('utf8');
return plainText;
}
module.exports.randomDerivationPath = function(hardenedDerivation) {
// TODO harden derivation path
var randomNumbers = [];
for(var i = 0; i < 3; i++) {
randomNumbers.push(parseInt(crypto.randomBytes(3).toString('hex'), 16))
}
return randomNumbers.join('/')
}
module.exports.verifyChallenge = function(serviceXpub, challenge) {
const requestSignature = challenge.request.signature;
const requestDerivationPath = challenge.request.signatureDerivationPath;
return this.verifyMessage(
serviceXpub,
requestDerivationPath,
JSON.stringify(this.unsignedChallenge(challenge)),
requestSignature
);
}
module.exports.verifyMessage = function(base58Key, derivationPath, message, signature) {
if(derivationPath.startsWith("a/")) {
derivationPath = derivationPath.split("a/")[1];
}
if(derivationPath.startsWith("c/")) {
derivationPath = derivationPath.split("c/")[1];
}
const verificationNode = bip32.fromBase58(base58Key).derivePath(derivationPath);
const hash = crypto.createHash('sha256').update(message, 'utf8').digest();
return verificationNode.verify(hash, Buffer.from(signature, 'hex'));
}
module.exports.unsignedChallenge = function(challenge) {
return {
// keys must be alphabetically ordered
derivationPath: challenge.derivationPath,
message: challenge.message,
xpub: challenge.xpub
}
}
module.exports.signMessage = function(base58Key, derivationPath, message) {
let signingNode = bip32.fromBase58(base58Key).derivePath(derivationPath);
var hash = crypto.createHash('sha256').update(message, 'utf8').digest();
return signingNode.sign(hash);
}

View File

@@ -0,0 +1,68 @@
const persistence = require("../persistence/persistence");
const cryptoUtil = require("../cryptoUtil");
const postSignature = require("./post-signature");
module.exports.authenticate = function(challenge, authenticationRequest, argv) {
persistence().LoadDB()
.then(db => {
const url = argv.url || authenticationRequest.url;
const userIdentifier = argv.userIdentifier || authenticationRequest.userIdentifier;
// TODO validate lock definition has all the parameters we need
// TODO: validate serviceExtendedPublicKey
console.log("Looking for");
console.log("url: ", url);
console.log("userIdentifier: ", userIdentifier);
return db.Lock.findOne({
where: {
url: url,
userIdentifier: userIdentifier
},
include: [
{
association: db.Lock.ExtendedPublicKey,
require: true,
include: [
{
association: db.ExtendedPublicKey.Key, // TODO rename key to wallet
required: true
}
]
}
]
})
.then(lock => {
if(lock) {
// Sign the message with the key that corresponds with this lock...
const verification = cryptoUtil.verifyChallenge(
lock.serviceExtendedPublicKey,
challenge
);
if (verification) {
const encryptedKey = lock.extendedPublicKey.key.encryptedPrivateKey;
const iv = lock.extendedPublicKey.key.iv;
const password = "vanished";
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
var challengeDerivationPath = `${lock.extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message).toString('hex');
console.log("Signature: ", signature);
if(argv.p) {
postSignature.postSignature(authenticationRequest.responseEndpoint, {
id: challenge.id,
signature: signature
})
}
} else {
console.error("Challenge not signed by registered service.");
}
} else {
console.error("Failed to find a lock with these parameters");
}
})
})
}

View File

@@ -0,0 +1,10 @@
const needle = require('needle');
module.exports.postSignature = function(endpoint, signature) {
needle('post', endpoint, signature, { json: true })
.then(response => {
console.log("Response: ", response.statusMessage);
console.log("Body: ", response.body);
}).catch(error => {
console.error("Failed to sign request: ", error);
})
}

View File

@@ -0,0 +1,62 @@
const persistence = require("../persistence/persistence");
const cryptoUtil = require("../cryptoUtil");
module.exports.registration = function(challenge, registerationRequest) {
persistence().LoadDB()
.then(db => {
// TODO validate lock definition has all the parameters we need
// TODO: validate serviceExtendedPublicKey
const verification = cryptoUtil.verifyChallenge(
registerationRequest.serviceExtendedPublicKey,
challenge
);
if(verification) {
return db.ExtendedPublicKey.findOne({
where: {
xpub: challenge.xpub
},
include: [
{
association: db.ExtendedPublicKey.Key // TODO: update this to wallet
}
]
}).then(extendedPublicKey => {
if (extendedPublicKey) {
const encryptedKey = extendedPublicKey.key.encryptedPrivateKey;
const iv = extendedPublicKey.key.iv;
const password = "vanished";
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
var challengeDerivationPath = `${extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
// TODO: save lock
return db.Lock.create({
userIdentifier: registerationRequest.userIdentifier,
url: registerationRequest.url,
serviceExtendedPublicKey: registerationRequest.serviceExtendedPublicKey,
signature: signature.toString('hex'),
message: challenge.message,
extendedPublicKeyId: extendedPublicKey.id
})
} else {
console.error("Sorry we can't create a lock with xpub: ", challenge.xpub);
return null;
}
}).then(lock => {
if(lock) {
console.log("Lock: ", lock.id);
console.log("Signature: ", lock.signature);
} else {
console.error("Failed to create the lock.");
}
})
} else {
console.error("Challenge not signed by service");
}
})
}

View File

@@ -0,0 +1,37 @@
module.exports = function (sequelize, DataTypes, options) {
const model = sequelize.define("extendedPublicKey", {
id: {
type: DataTypes.STRING,
defaultValue: function() {
return options.generateUniqueId()
},
primaryKey: true,
unique: true
},
xpub: {
type: DataTypes.STRING,
// TODO add validation
allowNull: false,
unique: true
},
derivationPath: {
type: DataTypes.STRING,
// TODO add validation...
allowNull: false,
unique: true
},
name: {
type: DataTypes.STRING,
unique: true
}
}, {
comment: "Exteneded public keys."
});
model.associate = (db) => {
// TODO Update key to wallet...
model.Key = model.belongsTo(db.Key);
}
return model;
};

View File

@@ -0,0 +1,34 @@
module.exports = function (sequelize, DataTypes, options) {
const model = sequelize.define("key", { // TODO: Rename to wallet
id: {
type: DataTypes.STRING,
defaultValue: function() {
return options.generateUniqueId()
},
primaryKey: true,
unique: true
},
encryptedPrivateKey: {
type: DataTypes.STRING,
allowNull: false
},
iv: {
type: DataTypes.STRING,
allowNull: false
},
name: {
type: DataTypes.STRING,
unique: true
}
// TODO: Add order to allow for user to set default keys
}, {
comment: "The keys we are using in our system (seedphrase)."
});
model.associate = (db) => {
model.Locks = model.hasMany(db.Lock);
model.ExtendedPublicKeys = model.hasMany(db.ExtendedPublicKey);
}
return model;
};

View File

@@ -0,0 +1,43 @@
module.exports = function (sequelize, DataTypes, options) {
const model = sequelize.define("lock", {
id: {
type: DataTypes.STRING,
defaultValue: function() {
return options.generateUniqueId()
},
primaryKey: true,
unique: true
},
userIdentifier: {
type: DataTypes.STRING,
allowNull: false
},
url: {
type: DataTypes.STRING,
allowNull: false
},
serviceExtendedPublicKey: {
type: DataTypes.STRING,
allowNull: false
},
signature: {
// Derivation from the master key...
// TODO: Add validation...
type: DataTypes.STRING,
allowNull: false,
unique: true
},
message: {
type: DataTypes.STRING,
allowNull: false
}
}, {
comment: "lock which our keys has access to"
});
model.associate = (db) => {
model.ExtendedPublicKey = model.belongsTo(db.ExtendedPublicKey);
}
return model;
};

View File

@@ -0,0 +1,82 @@
'use strict';
module.exports = function () {
var db = {};
const config = require('config');
const fs = require('fs');
const path = require('path');
const nanoid = require('nanoid');
var dbOptions = {
db: db,
generateUniqueId: (customIdSize) => {
var idSize = customIdSize || 11;
return nanoid(idSize); //=> "hsCc0ocrXkc"
}
};
dbOptions.Sequelize = require('sequelize');
dbOptions.sequelize = new dbOptions.Sequelize(config.get('database'));
// Load all the models...
var modelDir = path.join(__dirname, './models/');
fs.readdirSync(modelDir)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== "index.js") && (file.slice(-3) === '.js');
})
.forEach(file => {
// const model = sequelize['import'](path.join(modelDir, file));
const model = require(path.join(modelDir, file))(dbOptions.sequelize, dbOptions.Sequelize, dbOptions);
// Format the Model Name like we have for this project...
var modelName = model.name;
modelName = modelName.replace(modelName[0], modelName[0].toUpperCase());
// Add model to the db...
db[modelName] = model;
});
// Time for the associations...
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.LoadDB = () => {
db.Sequelize = dbOptions.Sequelize;
db.sequelize = dbOptions.sequelize;
return new Promise((resolve, reject) => {
dbOptions.sequelize.sync()
.then(() => {
resolve(db);
}).catch(error => {
// Do the things...
reject(error);
console.error('Unable to connect to the database:', error);
})
})
}
db.updateDatabaseFromModels = () => {
return new Promise((resolve, reject) => {
dbOptions.sequelize.sync({
alter: true
})
.then(() => {
resolve(true);
console.log("DB update successful.");
})
.catch(error => {
// Do the things...
reject(error);
console.error('Unable to connect to the database:', error);
})
})
}
return db;
};

31
lib/hd-auth-wallet.js Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env node
const bip32 = require("bip32");
const yargs = require('yargs');
const path = require('path');
const fs = require('fs');
const cli = yargs
.help()
.version()
.strict();
// Lets load up all the commands written in the commands directory
var commandsDirectory = path.join(__dirname, './commands/');
fs.readdirSync(commandsDirectory)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== "index.js") && (file.slice(-3) === '.js');
})
.forEach(file => {
const command = require(path.join(commandsDirectory, file));
cli.command(file, command.description, command);
});
const args = cli.argv;
// show help if no command provided
if (!args._[0]) {
cli.showHelp();
}

1529
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "hd-auth-wallet",
"version": "0.0.1",
"description": "example wallet features for hd-auth use.",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"bip32": "^2.0.4",
"bip38": "^3.0.0",
"bip39": "^3.0.2",
"config": "^3.2.4",
"nanoid": "^2.1.7",
"needle": "^2.4.0",
"sequelize": "^5.21.2",
"sqlite3": "^4.1.0",
"yargs": "^15.0.2"
},
"bin": {
"hd-auth-wallet": "./lib/index.js"
}
}