Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge acbd03c into 543f3c2
Browse files Browse the repository at this point in the history
  • Loading branch information
CruzMolina authored Apr 16, 2019
2 parents 543f3c2 + acbd03c commit 3fe6905
Show file tree
Hide file tree
Showing 4 changed files with 980 additions and 146 deletions.
28 changes: 15 additions & 13 deletions packages/truffle-hdwallet-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,28 @@ $ npm install truffle-hdwallet-provider
## Requirements
```
Node >= 7.6
Web3 1.0.0-beta.37
```

## General Usage

You can use this provider wherever a Web3 provider is needed, not just in Truffle. For Truffle-specific usage, see next section.

```javascript
var HDWalletProvider = require("truffle-hdwallet-provider");
var mnemonic = "mountains supernatural bird..."; // 12 word mnemonic
var provider = new HDWalletProvider(mnemonic, "http://localhost:8545");
const HDWalletProvider = require("truffle-hdwallet-provider");
const Web3 = require("web3");
const mnemonic = "mountains supernatural bird..."; // 12 word mnemonic
let provider = new HDWalletProvider(mnemonic, "http://localhost:8545");

// Or, alternatively pass in a zero-based address index.
var provider = new HDWalletProvider(mnemonic, "http://localhost:8545", 5);
provider = new HDWalletProvider(mnemonic, "http://localhost:8545", 5);

// Or, use your own hierarchical derivation path
var provider = new HDWalletProvider(mnemonic, "http://localhost:8545", 5, 1, "m/44'/137'/0'/0/");
provider = new HDWalletProvider(mnemonic, "http://localhost:8545", 5, 1, "m/44'/137'/0'/0/");


// HDWalletProvider is compatible with Web3. Use it at Web3 constructor, just like any other Web3 Provider
const web3 = new Web3(provider);
const web3 = new Web3(provider);

// Or, if web3 is alreay initialized, you can call the 'setProvider' on web3, web3.eth, web3.shh and/or web3.bzz
web3.setProvider(provider)
Expand Down Expand Up @@ -61,16 +63,16 @@ Parameters:
Instead of a mnemonic, you can alternatively provide a private key or array of private keys as the first parameter. When providing an array, `address_index` and `num_addresses` are fully supported.

```javascript
var HDWalletProvider = require("truffle-hdwallet-provider");
const HDWalletProvider = require("truffle-hdwallet-provider");
//load single private key as string
var provider = new HDWalletProvider("3f841bf589fdf83a521e55d51afddc34fa65351161eead24f064855fc29c9580", "http://localhost:8545");
let provider = new HDWalletProvider("3f841bf589fdf83a521e55d51afddc34fa65351161eead24f064855fc29c9580", "http://localhost:8545");

// Or, pass an array of private keys, and optionally use a certain subset of addresses
var privateKeys = [
const privateKeys = [
"3f841bf589fdf83a521e55d51afddc34fa65351161eead24f064855fc29c9580",
"9549f39decea7b7504e15572b2c6a72766df0281cea22bd1a3bc87166b1ca290",
];
var provider = new HDWalletProvider(privateKeys, "http://localhost:8545", 0, 2); //start at address_index 0 and load both addresses
provider = new HDWalletProvider(privateKeys, "http://localhost:8545", 0, 2); //start at address_index 0 and load both addresses
```
**NOTE: This is just an example. NEVER hard code production/mainnet private keys in your code or commit them to git. They should always be loaded from environment variables or a secure secret management system.**

Expand All @@ -80,9 +82,9 @@ You can easily use this within a Truffle configuration. For instance:

truffle-config.js
```javascript
var HDWalletProvider = require("truffle-hdwallet-provider");
const HDWalletProvider = require("truffle-hdwallet-provider");

var mnemonic = "mountains supernatural bird ...";
const mnemonic = "mountains supernatural bird ...";

module.exports = {
networks: {
Expand All @@ -94,7 +96,7 @@ module.exports = {
ropsten: {
// must be a thunk, otherwise truffle commands may hang in CI
provider: () =>
new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"),
new HDWalletProvider(mnemonic, "https://ropsten.infura.io/v3/YOUR-PROJECT-ID"),
network_id: '3',
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/truffle-hdwallet-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@
"js-scrypt": "^0.2.0",
"mocha": "^5.1.1",
"web3": "1.0.0-beta.37",
"web3-provider-engine": "git+https://github.com/cgewecke/provider-engine.git#web3-one",
"web3-provider-engine": "^15.0.0",
"webpack": "^4.24.0",
"webpack-cli": "^3.1.2"
},
"gitHead": "b207efb3c1409746537293b3e0fc27350029188e",
"dependencies": {
"any-promise": "^1.3.0",
"bindings": "^1.3.1",
"web3": "1.0.0-beta.37",
"websocket": "^1.0.28"
}
}
252 changes: 137 additions & 115 deletions packages/truffle-hdwallet-provider/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,136 +18,158 @@ const ethUtil = require("ethereumjs-util");
// See issue #65 for more
const singletonNonceSubProvider = new NonceSubProvider();

function HDWalletProvider(
mnemonic,
provider,
address_index = 0,
num_addresses = 1,
shareNonce = true,
wallet_hdpath = "m/44'/60'/0'/0/"
) {
if ((mnemonic && mnemonic.indexOf(" ") === -1) || Array.isArray(mnemonic)) {
const privateKeys = Array.isArray(mnemonic) ? mnemonic : [mnemonic];
this.wallets = {};
this.addresses = [];

for (let i = address_index; i < address_index + num_addresses; i++) {
const privateKey = Buffer.from(privateKeys[i].replace("0x", ""), "hex");
if (ethUtil.isValidPrivate(privateKey)) {
const wallet = ethJSWallet.fromPrivateKey(privateKey);
const address = wallet.getAddressString();
this.addresses.push(address);
this.wallets[address] = wallet;
}
}
} else {
this.hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic));
class HDWalletProvider {
constructor(
mnemonic,
provider,
address_index = 0,
num_addresses = 1,
shareNonce = true,
wallet_hdpath = "m/44'/60'/0'/0/"
) {
this.hdwallet;
this.wallet_hdpath = wallet_hdpath;
this.wallets = {};
this.addresses = [];
this.engine = new ProviderEngine();

if (!bip39.validateMnemonic(mnemonic)) {
throw new Error("Mnemonic invalid or undefined");
}
// private helper to normalize given mnemonic
const normalizePrivateKeys = mnemonic => {
if (Array.isArray(mnemonic)) return mnemonic;
else if (mnemonic && !mnemonic.includes(" ")) return [mnemonic];
// if truthy, but no spaces in mnemonic
else return false; // neither an array nor valid value passed;
};

for (let i = address_index; i < address_index + num_addresses; i++) {
const wallet = this.hdwallet
.derivePath(this.wallet_hdpath + i)
.getWallet();
const addr = "0x" + wallet.getAddress().toString("hex");
this.addresses.push(addr);
this.wallets[addr] = wallet;
}
}
// private helper to check if given mnemonic uses BIP39 passphrase protection
const checkBIP39Mnemonic = mnemonic => {
this.hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic));

const tmp_accounts = this.addresses;
const tmp_wallets = this.wallets;

this.engine = new ProviderEngine();
this.engine.addProvider(
new HookedSubprovider({
getAccounts: function(cb) {
cb(null, tmp_accounts);
},
getPrivateKey: function(address, cb) {
if (!tmp_wallets[address]) {
return cb("Account not found");
} else {
cb(null, tmp_wallets[address].getPrivateKey().toString("hex"));
}
},
signTransaction: function(txParams, cb) {
let pkey;
const from = txParams.from.toLowerCase();
if (tmp_wallets[from]) {
pkey = tmp_wallets[from].getPrivateKey();
} else {
cb("Account not found");
}
const tx = new Transaction(txParams);
tx.sign(pkey);
const rawTx = "0x" + tx.serialize().toString("hex");
cb(null, rawTx);
},
signMessage(message, cb) {
const dataIfExists = message.data;
if (!dataIfExists) {
cb("No data to sign");
}
if (!tmp_wallets[message.from]) {
cb("Account not found");
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error("Mnemonic invalid or undefined");
}

// crank the addresses out
for (let i = address_index; i < address_index + num_addresses; i++) {
const wallet = this.hdwallet
.derivePath(this.wallet_hdpath + i)
.getWallet();
const addr = `0x${wallet.getAddress().toString("hex")}`;
this.addresses.push(addr);
this.wallets[addr] = wallet;
}
};

// private helper leveraging ethUtils to populate wallets/addresses
const ethUtilValidation = privateKeys => {
// crank the addresses out
for (let i = address_index; i < address_index + num_addresses; i++) {
const privateKey = Buffer.from(privateKeys[i].replace("0x", ""), "hex");
if (ethUtil.isValidPrivate(privateKey)) {
const wallet = ethJSWallet.fromPrivateKey(privateKey);
const address = wallet.getAddressString();
this.addresses.push(address);
this.wallets[address] = wallet;
}
let pkey = tmp_wallets[message.from].getPrivateKey();
const dataBuff = ethUtil.toBuffer(dataIfExists);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
const sig = ethUtil.ecsign(msgHashBuff, pkey);
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
cb(null, rpcSig);
},
signPersonalMessage() {
this.signMessage(...arguments);
}
})
);
};

!shareNonce
? this.engine.addProvider(new NonceSubProvider())
: this.engine.addProvider(singletonNonceSubProvider);
const privateKeys = normalizePrivateKeys(mnemonic);

if (!privateKeys) checkBIP39Mnemonic(mnemonic);
else ethUtilValidation(privateKeys);

const tmp_accounts = this.addresses;
const tmp_wallets = this.wallets;

this.engine.addProvider(new FiltersSubprovider());
if (typeof provider === "string") {
this.engine.addProvider(
new ProviderSubprovider(
new Web3.providers.HttpProvider(provider, { keepAlive: false })
)
new HookedSubprovider({
getAccounts(cb) {
cb(null, tmp_accounts);
},
getPrivateKey(address, cb) {
if (!tmp_wallets[address]) {
return cb("Account not found");
} else {
cb(null, tmp_wallets[address].getPrivateKey().toString("hex"));
}
},
signTransaction(txParams, cb) {
let pkey;
const from = txParams.from.toLowerCase();
if (tmp_wallets[from]) {
pkey = tmp_wallets[from].getPrivateKey();
} else {
cb("Account not found");
}
const tx = new Transaction(txParams);
tx.sign(pkey);
const rawTx = `0x${tx.serialize().toString("hex")}`;
cb(null, rawTx);
},
signMessage({ data, from }, cb) {
const dataIfExists = data;
if (!dataIfExists) {
cb("No data to sign");
}
if (!tmp_wallets[from]) {
cb("Account not found");
}
let pkey = tmp_wallets[from].getPrivateKey();
const dataBuff = ethUtil.toBuffer(dataIfExists);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
const sig = ethUtil.ecsign(msgHashBuff, pkey);
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
cb(null, rpcSig);
},
signPersonalMessage(...args) {
this.signMessage(...args);
}
})
);
} else {
this.engine.addProvider(new ProviderSubprovider(provider));

!shareNonce
? this.engine.addProvider(new NonceSubProvider())
: this.engine.addProvider(singletonNonceSubProvider);

this.engine.addProvider(new FiltersSubprovider());
if (typeof provider === "string") {
// shim Web3 to give it expected sendAsync method
Web3.providers.HttpProvider.prototype.sendAsync =
Web3.providers.HttpProvider.prototype.send;
this.engine.addProvider(
new ProviderSubprovider(
new Web3.providers.HttpProvider(provider, { keepAlive: false })
)
);
} else {
this.engine.addProvider(new ProviderSubprovider(provider));
}
this.engine.start(); // Required by the provider engine.
}
this.engine.start(); // Required by the provider engine.
}

HDWalletProvider.prototype.sendAsync = function() {
this.engine.sendAsync.apply(this.engine, arguments);
};

HDWalletProvider.prototype.send = function() {
return this.engine.send.apply(this.engine, arguments);
};

// returns the address of the given address_index, first checking the cache
HDWalletProvider.prototype.getAddress = function(idx) {
debug("getting addresses", this.addresses[0], idx);
if (!idx) {
return this.addresses[0];
} else {
return this.addresses[idx];
sendAsync(...args) {
this.engine.sendAsync.apply(this.engine, args);
}
};

// returns the addresses cache
HDWalletProvider.prototype.getAddresses = function() {
return this.addresses;
};
send(...args) {
return this.engine.send.apply(this.engine, args);
}

// returns the address of the given address_index, first checking the cache
getAddress(idx) {
debug("getting addresses", this.addresses[0], idx);
if (!idx) {
return this.addresses[0];
} else {
return this.addresses[idx];
}
}

// returns the addresses cache
getAddresses() {
return this.addresses;
}
}

module.exports = HDWalletProvider;
Loading

0 comments on commit 3fe6905

Please sign in to comment.