dan-da / hd-wallet-derive

A command-line tool that derives bip32 addresses and private keys.
GNU General Public License v3.0
225 stars 79 forks source link

electrum compatibility and P2SH addresses #37

Open melaxon opened 4 years ago

melaxon commented 4 years ago

Hello I noticed 2 things when I run such commands: ./hd-wallet-derive.php -g --key="xprv......" --addr-type=p2sh-segwit --preset=electrum

./hd-wallet-derive.php -g --key="yprv......" --addr-type=p2sh-segwit --preset=electrum

The first one is meaningless for those who wish to generate the same addresses in Electrum because Electrum will never generate 3bitcoinaddres... addresses from XPRV (or XPUB). There is no such an option (currently)

The second command will generate the same addresses and public keys as Electrum does from given YPRV (or YPUB) but pubkeyhash seems to be WRONG (or maybe I'm wrong and missed something).

To get the address balance I inquire electrumx using this code:

...
$pubkeyhash = !empty($array['pubkeyhash']) ? $array['pubkeyhash'] : fn_get_hash160($address, $addrCreator, $network);  //if pubkeyhash exists we'll use it
$data = Base58::decodeCheck($address);
$prefixByte = $data->slice(0, $network->getP2shPrefixLength())->getHex();
if ($prefixByte === $network->getP2shByte()) { // P2SH: BTC: 3.., LTC: M...
            $script = "a914" . $pubkeyhash . "87";
} 
elseif ($prefixByte === $network->getAddressByte()) { // P2PKH: BTC: 1..., LTC: L...
            $script = "76a914" . $pubkeyhash . "88ac";
}

$hash = hash('sha256', hex2bin($script));
$hsah = ReverseEndianness($hash);
$result = $electrum->blockchainAddressGetBalance($hsah);
...

In case of XPRV everything is okay. For YPRV, if I use pubkeyhashes generated by hd-wallet-derive ($array['pubkeyhash']) the balance is always zero. If I delete pubkeyhashes and calculate them with some function fn_get_hash160 it works smoothly. I guess I have to look into Bitwasp code (?)

dan-da commented 4 years ago

hi @melaxon . Thx for the report.

The first one is meaningless for those who wish to generate the same addresses in Electrum because Electrum will never generate 3bitcoinaddres... addresses from XPRV (or XPUB). There is no such an option (currently)

Good observation. The --preset, aka Path Preset is only an aid for choosing the bip32 path. It's not attempting to emulate a particular wallet in every regard.

The second command will generate the same addresses and public keys as Electrum does from given YPRV (or YPUB) but pubkeyhash seems to be WRONG (or maybe I'm wrong and missed something).

I will need to look into this in more detail. It would be helpful if you can post an example YPRV or YPUB (without funds!) along with electrum and hd-wallet-derive derived value(s) that demonstrate the discrepancy.

I guess I have to look into Bitwasp code (?)

maybe. It's been pretty solid though.

melaxon commented 4 years ago

ypub6Z5GQFUWdyxxA6cKP8ZKacJTvtpk6ao3P54tvvzPSPevYH752rYWW6REEd5u5R77VdmkcrzWJmDHGKwx3qRmA9m39T26FJg5LngyQKSnokw

The address with index 15 contains 0.00022 BTC

This key was used to generate addresses in my app. The addresses and public keys are identical with electrum

./hd-wallet-derive.php -g --key="ypub6Z5GQFUWdyxxA6cKP8ZKacJTvtpk6ao3P54tvvzPSPevYH752rYWW6REEd5u5R77VdmkcrzWJmDHGKwx3qRmA9m39T26FJg5LngyQKSnokw" --addr-type=p2sh-segwit --preset=electrum --numderive=20 > electrum-p2sh-segwit-xpub.txt

I cannot see pubkeyhash in electrum unfortunately.

The function that calculates the "correct" pubkeyhashes:

function fn_get_hash160($addressInput, $addrCreator, $network)
{
    $address = null;
    try {
       $address = $addrCreator->fromString($addressInput, $network)->getHash();
    } catch ( \BitWasp\Bitcoin\Exceptions\UnrecognizedAddressException $e) {
       $errormessage = 'The address you entered is not valid: ' . $addressInput;
       echo "Error message: ".$errormessage;
    }
    $hash160 = bin2hex(getProtectedValue($address, 'buffer'));
    return $hash160;
}

function getProtectedValue($obj,$name)
{
    $array = (array)$obj;
    $prefix = chr(0).'*'.chr(0);
    return !empty($array[$prefix.$name]) ? $array[$prefix.$name] : false;
}
dan-da commented 4 years ago

ok, so what are the "correct" and "incorrect" pubkeyhash values you are seeing for index 0? (assuming that your fn_get_hash160() is producing correct results).

melaxon commented 4 years ago

address 0 3Eezg77r6TK4rd9gHSDSyCJTLrpStzbn3i 616d3566dc438763b59480512e1a08db35e6e4e2 - incorrect (derived with hd-wallet-derive) 8e37c2be6eb697c05ab109f7c7e38bea12477dd4 - correct (calculated)

address 15 3HCvCZMV4Y4jYV59yVT3Lb4GehJLH3EzPu e2b3802c91e9926f7ce91226d15d72d7584e952e aa319669fca81e0fc8ada49f29490d8f0c06c806

melaxon commented 4 years ago

examples of P2PKH:

1NR4KPhzQagzSUBGunPSuKbWN7vnS6r77Z eae764bd4d55cd53eab21991e56cb5f9e859ae5d eae764bd4d55cd53eab21991e56cb5f9e859ae5d

Did not try bc1 yet

melaxon commented 4 years ago

bach32 is also okay bc1quauz4ymap0wstrt8vn4lcnxj0wug6lnc6zsas5 e7782a937d0bdd058d6764ebfc4cd27bb88d7e78 - the same pabkeyhash calculated and derived

dan-da commented 4 years ago

hmm, I took a brief look at this. I was trying to compare with output from other tools, but I couldn't seem to find one supports ypub and also outputs the pubkeyhash. So, it's not immediately obvious which implementation is correct.

The hd-wallet-derive code that gets the pubkeyhas is pretty straight-forward, ie:

            $pubkey = $key->getPublicKey()->getHex();
            $pubkeyhash = $key->getPublicKey()->getPubKeyHash()->getHex();

So this is just calling into the Bitwasp bitcoin-php lib to do the heavy lifting. One would think that if pubkey is correct, then pubkeyhash should be also, but I haven't investigated further yet.

melaxon commented 4 years ago

Now I'm pretty sure that the problem resides is Bitwasp code. First, I will search through their PRs as the version of bitcoin-php you use is far not the latest one and maybe the problem is already solved in later releases.

dan-da commented 4 years ago

fwiw, I updated composer require to latest bitwasp release (1.0.4), found one issue, and determined that hd-wallet-derive still generates the "incorrect" pubkeyhash.

$ ./hd-wallet-derive.php -g --key="ypub6Z5GQFUWdyxxA6cKP8ZKacJTvtpk6ao3P54tvvzPSPevYH752rYWW6REEd5u5R77VdmkcrzWJmDHGKwx3qRmA9m39T26FJg5LngyQKSnokw" --addr-type=p2sh-segwit --path='0/x' --numderive=1 --cols=address,pubkeyhash

+------------------------------------+------------------------------------------+
| address                            | pubkeyhash                               |
+------------------------------------+------------------------------------------+
| 3Eezg77r6TK4rd9gHSDSyCJTLrpStzbn3i | 616d3566dc438763b59480512e1a08db35e6e4e2 |
+------------------------------------+------------------------------------------+