Skip to content

Commit

Permalink
Merge branch 'dukei-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-da committed Jun 22, 2018
2 parents a72c3f6 + bd3d455 commit b2999ea
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
/vendor
42 changes: 30 additions & 12 deletions README.md
@@ -1,8 +1,8 @@
# hd-wallet-derive

A command-line tool that derives bip32 addresses and private keys.
A command-line tool that derives bip32 addresses and private keys for Bitcoin and Ethereum.

Derivation reports show privkey (wif encoded), xprv, xpub, and address.
Derivation reports show privkey (wif encoded), xprv, xpub, and addresses for Bitcoin and Ethereum .

Input can be a xprv key, xpub key, or bip39 mnemonic string (eg 12 words) with
optional password.
Expand Down Expand Up @@ -131,6 +131,20 @@ $ ./hd-wallet-derive.php -g --key=xpub6BfKpqjTwvH21wJGWEfxLppb8sU7C6FJge2kWb9315
+-----------------------------------------------------------------------------------------------------------------+------------------------------------+
```

## We can view ethereum addresses.

```
$ ./hd-wallet-derive.php --key=xpub6BfKpqjTwvH21wJGWEfxLppb8sU7C6FJge2kWb9315oP4ZVqCXG29cdUtkyu7YQhHyfA5nt63nzcNZHYmqXYHDxYo8mm1Xq1dAC7YtodwUR --cols=path,address,eth_address --numderive=3 -g
+------+------------------------------------+--------------------------------------------+
| path | address | eth_address |
+------+------------------------------------+--------------------------------------------+
| m/0 | 1FZKdR3E7S1UPvqsuqStXAhZiovntFirge | 0xc7eE60fFD437cf206A4E6deFaEd020c54b63d3f5 |
| m/1 | 12UMERLGAHKe5PQPaSYX8sczr52rSAg2Mi | 0x96790F426AC663989605B806Ac8360891bD76359 |
| m/2 | 1Pyk8NLx3gaXSng7XhKoNMLfBffUsJGAjr | 0x76580a4cD31C5EC607a713C922Fd3dE278Ab49c1 |
+------+------------------------------------+--------------------------------------------+
```

## We can get results in a variety of additional formats

### simple list
Expand Down Expand Up @@ -246,34 +260,38 @@ The report may be printed in the following formats:
Options:
-g go! ( required )
--key=<key> xpriv or xpub key
--mnemonic=<words> bip39 seed words
note: either key or nmemonic is required.
--mnemonic-pw=<pw> optionally specify password for mnemonic.
--numderive=<n> Number of keys to derive. default=10
--startderive=<n> Starting key index to derive. default=0
--cols=<cols> a csv list of columns, or "all"
all:
(path,xprv,xpub,privkey,address,index)
(path,address,xprv,xpub,privkey,pubkey,pubkeyhash,index,eth_address)
default:
(path,privkey,address)
(path,address,privkey)
--outfile=<path> specify output file path.
--format=<format> txt|csv|json|jsonpretty|html|list|all default=txt
if 'all' is specified then a file will be created
for each format with appropriate extension.
only works when outfile is specified.
'list' prints only the first column. see --cols
--path=<path> bip32 path to derive, relative to provided key (m).
eg "", "m/0" or "m/1"
default = "m"
--includeroot include root key as first element of report.
--logfile=<file> path to logfile. if not present logs to stdout.
--loglevel=<level> debug,info,specialinfo,warning,exception,fatalerror
default = info
Expand Down
6 changes: 2 additions & 4 deletions composer.json
Expand Up @@ -6,9 +6,7 @@
"ext-json": "*",
"php": ">=5.5",
"dan-da/texttable-php": "^1.0",
"dan-da/strictmode-php": "^1.0"
},

"require-dev": {
"dan-da/strictmode-php": "^1.0",
"kornrunner/keccak": "^1.0"
}
}
4 changes: 4 additions & 0 deletions hd-wallet-derive.php
Expand Up @@ -60,6 +60,7 @@ function get_cli_params() {
'mnemonic-pw:',
'outfile:',
'numderive:',
'startderive:',
'includeroot',
'path:',
'format:', 'cols:',
Expand Down Expand Up @@ -112,6 +113,7 @@ function process_cli_params( $params ) {
$params['format'] = @$params['format'] ?: 'txt';
$params['cols'] = @$params['cols'] ?: 'all';
$params['numderive'] = @$params['numderive'] ?: 10;
$params['startderive'] = @$params['startderive'] ?: 0;
$params['includeroot'] = isset($params['includeroot'] );

return [$params, $success];
Expand Down Expand Up @@ -153,6 +155,8 @@ function print_help() {
--mnemonic-pw=<pw> optionally specify password for mnemonic.
--numderive=<n> Number of keys to derive. default=10
--startderive=<n> Starting key index to derive. default=0
--cols=<cols> a csv list of columns, or "all"
all:
Expand Down
72 changes: 63 additions & 9 deletions lib/wallet_derive.class.php
Expand Up @@ -7,11 +7,20 @@
use \BitWasp\Bitcoin\Address;
use \BitWasp\Bitcoin\Key\Deterministic\HierarchicalKeyFactory;
use \BitWasp\Buffertools\Buffer;
use \BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;

// For Bip39 Mnemonics
use \BitWasp\Bitcoin\Mnemonic\Bip39\Bip39SeedGenerator;
use BitWasp\Bitcoin\Mnemonic\MnemonicFactory;

// For ethereum addresses
use kornrunner\Keccak;
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Serializer\Key\PublicKeySerializer;
use BitWasp\Bitcoin\Crypto\EcAdapter\EcAdapterFactory;
use Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
use Mdanter\Ecc\EccFactory;

// For generating html tables.
require_once __DIR__ . '/html_table.class.php';

Expand All @@ -36,6 +45,40 @@ private function get_params() {
return $this->params;
}

private function getEthereumAddress(PublicKeyInterface $publicKey){
static $pubkey_serializer = null;
static $point_serializer = null;
if(!$pubkey_serializer){
$adapter = EcAdapterFactory::getPhpEcc(Bitcoin::getMath(), Bitcoin::getGenerator());
$pubkey_serializer = new PublicKeySerializer($adapter);
$point_serializer = new UncompressedPointSerializer(EccFactory::getAdapter());
}

$pubKey = $pubkey_serializer->parse($publicKey->getHex());
$point = $pubKey->getPoint();
$upk = $point_serializer->serialize($point);
$upk = hex2bin(substr($upk, 2));

$keccak = Keccak::hash($upk, 256);
$eth_address_lower = strtolower(substr($keccak, -40));

$hash = Keccak::hash($eth_address_lower, 256);
$eth_address = '';
for($i = 0; $i < 40; $i++) {
// the nth letter should be uppercase if the nth digit of casemap is 1
$char = substr($eth_address_lower, $i, 1);

if(ctype_digit($char))
$eth_address .= $char;
else if('0' <= $hash[$i] && $hash[$i] <= '7')
$eth_address .= strtolower($char);
else
$eth_address .= strtoupper($char);
}

return '0x'. $eth_address;
}

/* Derives child keys/addresses for a given key.
*/
public function derive_keys($key) {
Expand All @@ -49,23 +92,28 @@ public function derive_keys($key) {
$master = HierarchicalKeyFactory::fromExtended($key, $network);


$start = 0;
$end = $params['numderive'];
$start = $params['startderive'];
$end = $params['startderive'] + $params['numderive'];

if( $params['includeroot'] ) {
$address = $master->getPublicKey()->getAddress()->getAddress();
$publicKey = $master->getPublicKey();
$address = new PayToPubKeyHashAddress($publicKey->getPubKeyHash());
$address = $address->getAddress();

$xprv = $master->isPrivate() ? $master->toExtendedKey($network) : null;
$wif = $master->isPrivate() ? $master->getPrivateKey()->toWif($network) : null;
$pubkey = $master->getPublicKey()->getHex();
$pubkeyhash = $master->getPublicKey()->getPubKeyHash()->getHex();
$pubkey = $publicKey->getHex();
$pubkeyhash = $publicKey->getPubKeyHash()->getHex();
$xpub = $master->toExtendedPublicKey($network);
$eth_address = $this->getEthereumAddress($publicKey);

$addrs[] = array( 'xprv' => $xprv,
'privkey' => $wif,
'pubkey' => $pubkey,
'pubkeyhash' => $pubkey,
'xpub' => $xpub,
'address' => $address,
'eth_address' => $eth_address,
'index' => null,
'path' => 'm');
}
Expand All @@ -82,12 +130,17 @@ public function derive_keys($key) {
// fixme: hack for copay/multisig. maybe should use a callback?
if(method_exists($key, 'getPublicKey')) {
// bip32 path
$address = $key->getPublicKey()->getAddress()->getAddress();
$publicKey = $key->getPublicKey();
$address = new PayToPubKeyHashAddress($publicKey->getPubKeyHash());
$address = $address->getAddress();

$xprv = $key->isPrivate() ? $key->toExtendedKey($network) : null;
$priv_wif = $key->isPrivate() ? $key->getPrivateKey()->toWif($network) : null;
$pubkey = $key->getPublicKey()->getHex();
$pubkeyhash = $key->getPublicKey()->getPubKeyHash()->getHex();
$pubkey = $publicKey->getHex();
$pubkeyhash = $publicKey->getPubKeyHash()->getHex();
$xpub = $key->toExtendedPublicKey($network);

$eth_address = $this->getEthereumAddress($publicKey);
}
else {
throw new Exception("multisig keys not supported");
Expand All @@ -98,6 +151,7 @@ public function derive_keys($key) {
'pubkeyhash' => $pubkeyhash,
'xpub' => $xpub,
'address' => $address,
'eth_address' => $eth_address,
'index' => $i,
'path' => $path);
}
Expand All @@ -124,7 +178,7 @@ static public function mnemonicToKey($mnemonic, $password=null) {
/* Returns all columns available for reports
*/
static public function all_cols() {
return ['path', 'address', 'xprv', 'xpub', 'privkey', 'pubkey', 'pubkeyhash', 'index'];
return ['path', 'address', 'xprv', 'xpub', 'privkey', 'pubkey', 'pubkeyhash', 'index', 'eth_address'];
}

/* Returns default reporting columns
Expand Down

0 comments on commit b2999ea

Please sign in to comment.