Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ As of version 0.4.1, Bitcoin-core style key derivation is supported.

As of version 0.4.3, [Preset paths](#path-presets) are available for common wallet software.

As of version 0.5.0, PHP 8+ is supported. Older versions of PHP are not.

Derivation reports show privkey (wif encoded), xprv, xpub, and address.

Input can be a xprv key, xpub key, or bip39 mnemonic string (eg 12 words) with
Expand Down Expand Up @@ -230,7 +232,7 @@ note: The --path argument defaults to the bip44 extended key path when using
--mnemonic to make address generation easier. If a Bip44 ID is not defined for
the coin then --path must be specified explicitly.

you can verify these results [with this tool](https://iancoleman.github.io/bip39/).
you can verify these results [with Ian Coleman's tool](https://iancoleman.io/bip39/).


### With a password
Expand Down Expand Up @@ -273,7 +275,7 @@ $ ./hd-wallet-derive.php --key-type=z --mnemonic="refuse brush romance together
+-----------------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------+
```

note: you can verify these results [with this tool](https://iancoleman.github.io/bip39/).
note: you can verify these results [with Ian Coleman's tool](https://iancoleman.io/bip39/).


## Derive addresses from xpub key
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.4.4
v0.5.0
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
"name": "dan-da/hd-wallet-derive",
"minimum-stability": "dev",
"require": {
"bitwasp/bitcoin": "dev-master#8782bc6c470e342d4e0cb67eb036974f133b950b",
"ext-json": "*",
"php": ">=5.5",
"ext-gmp": "*",
"php": ">=8",
"dan-da/texttable-php": "1.0.3",
"dan-da/strictmode-php": "1.0.1",
"dan-da/strictmode-php": "^1.0.2",
"dan-da/coinparams": "0.2.9",
"olegabr/keccak": "1.0.4"
"olegabr/keccak": "1.0.4",
"protonlabs/bitcoin": "dev-main"
},

"require-dev": {
Expand Down
7 changes: 7 additions & 0 deletions hd-wallet-derive.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ function main()
}
}
$key = @$params['key'] ?: $walletDerive->mnemonicToKey($params['coin'], $params['mnemonic'], $params['key-type'], $params['mnemonic-pw']);

if (@$params['gen-extended']) {
$result = $walletDerive->getExtendedPublicKeys($key);
WalletDeriveReport::printResults($params, $result, true);
return 0;
}

$addrs = $walletDerive->derive_keys($key);

// Prints result
Expand Down
6 changes: 3 additions & 3 deletions src/Utils/FlexNetwork.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function __construct($coin) {
// could be made configurable.
$scripthash = @$prefixes['scripthash2'] ?
$prefixes['scripthash2'] : $prefixes['scripthash'];

$this->base58PrefixMap = [
self::BASE58_ADDRESS_P2PKH => self::dh(@$params['prefixes']['public']),
self::BASE58_ADDRESS_P2SH => self::dh($scripthash),
Expand Down Expand Up @@ -66,13 +66,13 @@ function __construct($coin) {
* and prepends 0 if necessary to make length an even number.
*/
static private function th($hex, $prepend_zero = false) {
$hex = substr($hex, 2);
$hex = @substr($hex, 2);
$pre = strlen($hex) % 2 == 0 ? '' : '0';
return $pre . $hex;
}

static private function dh($dec, $prepend_zero = false) {
$hex = dechex($dec);
$hex = dechex((int)$dec);
$pre = strlen($hex) % 2 == 0 ? '' : '0';
return $pre . $hex;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Utils/MyLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ protected function _gpg_encrypt_to_address( $address, $plaintext ) {

$rc = @exec ( $command, $output, $status );

$output = implode($output, "\n");
$output = implode("\n", $output);

$success = ( $status === 0 ) ? true : false;
if (!$success) {
Expand Down
4 changes: 3 additions & 1 deletion src/Utils/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static function getCliParams()
'list-cols',
'bch-format:',
'alt-extended:',
'gen-key', 'gen-key-all',
'gen-key', 'gen-key-all', 'gen-extended',
'gen-words:',
'version', 'help', 'help-coins',
'preset:', 'path-change', 'path-account:', 'help-presets',
Expand Down Expand Up @@ -95,6 +95,7 @@ public static function processCliParams()

$params['gen-key'] = isset($params['gen-key']) || isset($params['gen-words']);
$params['gen-key-all'] = isset($params['gen-key-all']); // hidden param, for the truly worthy who read the code.
$params['gen-extended'] = isset($params['gen-extended']);
$key = @$params['key'];
$mnemonic = @$params['mnemonic'];

Expand Down Expand Up @@ -284,6 +285,7 @@ public static function printHelp()

--includeroot include root key as first element of report.
--gen-key generates a new key.
--gen-extended generate the coresponding x, y or z public keys from a provided key
--gen-words=<n> num words to generate. implies --gen-key.
one of: [$allowed_numwords]
default = 24.
Expand Down
76 changes: 71 additions & 5 deletions src/WalletDerive.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
// For HD-Wallet Key Derivation
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Key\Factory\HierarchicalKeyFactory;
use BitWasp\Bitcoin\Key\Deterministic\HierarchicalKey;
use BitWasp\Bitcoin\Key\Deterministic\HierarchicalKeySequence;
use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
use BitWasp\Bitcoin\Crypto\Random\Random;
use BitWasp\Bitcoin\Mnemonic\Bip39\Bip39SeedGenerator;
use BitWasp\Bitcoin\Mnemonic\MnemonicFactory;
use Exception;
use BitWasp\Bitcoin\Exceptions\InvalidDerivationException;
use App\Utils\NetworkCoinFactory;
use App\Utils\MyLogger;
use App\Utils\CashAddress;
Expand Down Expand Up @@ -101,7 +104,7 @@ private function derive_keys_worker($params, $key)
}

MyLogger::getInstance()->log( "Deriving keys", MyLogger::info );
$path_base = is_numeric( $params['path']{0} ) ? 'm/' . $params['path'] : $params['path'];
$path_base = is_numeric( $params['path'][0] ) ? 'm/' . $params['path'] : $params['path'];

// Allow paths to end with i or i'.
// i' specifies that addresses should be hardened.
Expand Down Expand Up @@ -135,8 +138,9 @@ private function derive_keys_worker($params, $key)
for($i = $start; $i < $end; $i++)
{
$path = sprintf($path_mask, $i);
$key = $master->derivePath($path);


// $key = $master->derivePath($path);
$key = $this->derive_path($master, $path);
$this->derive_key_worker($coin, $symbol, $network, $addrs, $key, $key_type, $i, $path);

$count = $i + 1;
Expand All @@ -151,6 +155,32 @@ private function derive_keys_worker($params, $key)

return $addrs;
}

// This function is a replacement for HierarchicalKey::derivePath()
// since that function now accepts only relative paths.
//
// This function is exactly the same except it will detect if first
// char is 'm' or 'M' and then will call decodeAbsolute() instead.
private function derive_path($key, string $path): HierarchicalKey {
$sequences = new HierarchicalKeySequence();
$is_abs = in_array(@$path[0], ['m', 'M']);
$parts = $is_abs ? @$sequences->decodeAbsolute($path)[1] : $sequences->decodeRelative($path);
$numParts = count($parts);

for ($i = 0; $i < $numParts; $i++) {
try {
$key = $key->deriveChild($parts[$i]);
} catch (InvalidDerivationException $e) {
if ($i === $numParts - 1) {
throw new InvalidDerivationException($e->getMessage());
} else {
throw new InvalidDerivationException("Invalid derivation for non-terminal index: cannot use this path!");
}
}
}

return $key;
}

private function derive_key_worker($coin, $symbol, $network, &$addrs, $key, $key_type, $index, $path) {

Expand Down Expand Up @@ -194,6 +224,36 @@ function serializePrivKey($symbol, $network, $key) {
return $hex ? '0x' . $key->getHex() : $key->toWif($network);
}

/**
* Get extended public keys from a given key in all available formats
*/
public function getExtendedPublicKeys($key) {
$params = $this->get_params();
$coin = $params['coin'];
list($symbol) = explode('-', $coin);
$networkCoinFactory = new NetworkCoinFactory();
$network = $networkCoinFactory->getNetworkCoinInstance($coin);
Bitcoin::setNetwork($network);
// get initial key type for the coin
$initial_key_type = $this->getKeyTypeFromCoinAndKey($coin, $key);
$addrTypes = [
'x' => 'legacy',
'y' => 'p2sh-segwit',
'z' => 'bech32'
];
$types = ['x', 'y', 'z'];
$extkeys = array();
foreach ($types as $type) {
$this->params['addr-type'] = $addrTypes[$type];
$key_type = $type;
$xyzPub = $type . 'pub';
$master = $this->fromExtended($coin, $key, $network, $initial_key_type);
if ( $this->networkSupportsKeyType($network, $key_type, $coin ) && method_exists($master, 'getPublicKey') ) {
$extkeys[] = [ $xyzPub => $this->toExtendedKey($coin, $master->withoutPrivateKey(), $network, $key_type)];
}
}
return $extkeys;
}

private function address($key, $network) {
$addrCreator = new AddressCreator();
Expand All @@ -214,6 +274,9 @@ private function getKeyTypeFromCoinAndKey($coin, $key) {

$ext = $this->getExtendedPrefixes($coin);
foreach($ext as $kt => $info) {
if(!is_array($info)) {
continue;
}
if( $key_prefix == strtolower(@$info['public']) ) {
return $kt[0];
}
Expand All @@ -234,7 +297,6 @@ private function getSerializer($coin, $network, $key_type) {

$prefix = $this->getScriptPrefixForKeyType($coin, $key_type);
$config = new GlobalPrefixConfig([new NetworkConfig($network, [$prefix]),]);
//print_r($config); exit;

$serializer = new Base58ExtendedKeySerializer(new ExtendedKeySerializer($adapter, $config));
return $serializer;
Expand Down Expand Up @@ -278,6 +340,9 @@ private function getExtendedPrefixes($coin) {
$val = $val ?: [];
// ensure no entries with empty values.
foreach($val as $k => $v) {
if(!is_array($v)) {
continue;
}
if(!@$v['public'] || !@$v['private']) {
unset($val[$k]);
}
Expand Down Expand Up @@ -464,7 +529,8 @@ protected function genKeysFromSeed($coin, $seedinfo) {
$bip32path = $this->getCoinBip44ExtKeyPathPurpose($coin, $purpose);
if($bip32path) {
// derive extended priv/pub keys.
$prv = $xkey->derivePath($bip32path);
// $prv = $xkey->derivePath($bip32path);
$prv = $this->derive_path($xkey, $bip32path);
$pub = $prv->withoutPrivateKey();
$row[$pf . 'path'] = $bip32path;
$row['xprv'] = $this->toExtendedKey($coin, $prv, $network, $key_type);
Expand Down
4 changes: 2 additions & 2 deletions tests/eth.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ protected function test_eth_addr() {
'format' => 'json',
];
$r = $this->derive_params( $params );
$this->eq( $r['address'], $addr_correct, 'address' );
$this->eq( $r['privkey'], $privkey_correct, 'privkey' );
$this->eq( @$r['address'], $addr_correct, 'address' );
$this->eq( @$r['privkey'], $privkey_correct, 'privkey' );
}

}