python-libdogecoin: address <-> pubkey-hash wrappers don't compose
The Python wrappers for converting between a p2pkh address and its
pubkey-hash form do not round-trip, because two similarly-named wrappers
return/expect different data formats for what they both call a
"pubkey hash."
Observed
import libdogecoin as l
l.w_context_start()
priv, addr = l.w_generate_priv_pub_key_pair(0, False)
hash_form = l.w_dogecoin_address_to_pubkey_hash(addr)
# -> 20-byte hash160, e.g. "8350f452...d6886112" (40 hex chars)
script_form = l.w_dogecoin_private_key_wif_to_pubkey_hash(priv)
# -> 25-byte P2PKH scriptPubKey, e.g. "76a9148350f452...88ac" (50 hex chars)
l.w_get_addr_from_pubkey_hash(hash_form, False) # WRONG address
l.w_get_addr_from_pubkey_hash(script_form, False) # correct -> addr
l.w_context_stop()
The natural-looking round trip
addr -> w_dogecoin_address_to_pubkey_hash -> w_get_addr_from_pubkey_hash
returns a wrong address, because:
w_dogecoin_address_to_pubkey_hash returns a bare 20-byte hash160.
w_get_addr_from_pubkey_hash wraps the C function getAddrFromPubkeyHash,
which expects a full 25-byte scriptPubKey (76a914<hash>88ac). It strips
the P2PKH opcodes internally and reads the hash from offset 3.
So the two wrappers are not inverses, despite their names suggesting they are.
Root cause (not a C bug)
The underlying C is internally consistent and correct. Verified directly:
generatePrivPubKeypair(wif, addr, false);
char res[51];
dogecoin_p2pkh_address_to_pubkey_hash(addr, res); // scriptPubKey
char back[100];
getAddrFromPubkeyHash(res, false, back); // scriptPubKey -> addr
// back == addr (MATCH)
getAddrFromPubkeyHash's contract is scriptPubKey -> address, as the C
test suite confirms (test/transaction_tests.c feeds it the output of
dogecoin_p2pkh_address_to_pubkey_hash, which is a scriptPubKey, and asserts
it round-trips to the original address). The pubkey_hash[PUBKEYHASHLEN]
parameter name is misleading but the behavior is correct and tested.
The mismatch is purely at the binding layer: it exposes a function that
returns a hash160 (w_dogecoin_address_to_pubkey_hash) alongside one that
consumes a scriptPubKey (w_get_addr_from_pubkey_hash) without making the
format difference visible, so users who pair them get wrong results silently.
Suggested fixes (any of)
- Make
w_get_addr_from_pubkey_hash accept a bare hash160 (the format its
name implies) by wrapping a hash160->address path in the binding, e.g.
build the scriptPubKey from the hash before calling, or call a hash160
helper directly. This makes the round trip with
w_dogecoin_address_to_pubkey_hash work as users expect.
- Rename / document the wrappers to make the format explicit, e.g.
w_scriptpubkey_to_address vs a separate w_hash160_to_address, and note
in docstrings exactly which 20- vs 25-byte hex each produces/consumes.
- Add a round-trip test at the binding level
(addr -> hash -> addr and addr -> scriptpubkey -> addr) so the format
mismatch is caught in CI.
Note
The three *_to_pubkey_hash wrappers return inconsistent formats for the same
key (w_dogecoin_address_to_pubkey_hash -> 20-byte hash160;
w_dogecoin_private_key_wif_to_pubkey_hash -> 25-byte scriptPubKey). Aligning
or clearly documenting these would prevent the same confusion elsewhere.
Affects
Reproduced against libdogecoin==0.1.5rc1 (PyPI, built from v0.1.5-pre).
python-libdogecoin: address <-> pubkey-hash wrappers don't compose
The Python wrappers for converting between a p2pkh address and its
pubkey-hash form do not round-trip, because two similarly-named wrappers
return/expect different data formats for what they both call a
"pubkey hash."
Observed
The natural-looking round trip
addr -> w_dogecoin_address_to_pubkey_hash -> w_get_addr_from_pubkey_hashreturns a wrong address, because:
w_dogecoin_address_to_pubkey_hashreturns a bare 20-byte hash160.w_get_addr_from_pubkey_hashwraps the C functiongetAddrFromPubkeyHash,which expects a full 25-byte scriptPubKey (
76a914<hash>88ac). It stripsthe P2PKH opcodes internally and reads the hash from offset 3.
So the two wrappers are not inverses, despite their names suggesting they are.
Root cause (not a C bug)
The underlying C is internally consistent and correct. Verified directly:
getAddrFromPubkeyHash's contract is scriptPubKey -> address, as the Ctest suite confirms (
test/transaction_tests.cfeeds it the output ofdogecoin_p2pkh_address_to_pubkey_hash, which is a scriptPubKey, and assertsit round-trips to the original address). The
pubkey_hash[PUBKEYHASHLEN]parameter name is misleading but the behavior is correct and tested.
The mismatch is purely at the binding layer: it exposes a function that
returns a hash160 (
w_dogecoin_address_to_pubkey_hash) alongside one thatconsumes a scriptPubKey (
w_get_addr_from_pubkey_hash) without making theformat difference visible, so users who pair them get wrong results silently.
Suggested fixes (any of)
w_get_addr_from_pubkey_hashaccept a bare hash160 (the format itsname implies) by wrapping a hash160->address path in the binding, e.g.
build the scriptPubKey from the hash before calling, or call a hash160
helper directly. This makes the round trip with
w_dogecoin_address_to_pubkey_hashwork as users expect.w_scriptpubkey_to_addressvs a separatew_hash160_to_address, and notein docstrings exactly which 20- vs 25-byte hex each produces/consumes.
(
addr -> hash -> addrandaddr -> scriptpubkey -> addr) so the formatmismatch is caught in CI.
Note
The three
*_to_pubkey_hashwrappers return inconsistent formats for the samekey (
w_dogecoin_address_to_pubkey_hash-> 20-byte hash160;w_dogecoin_private_key_wif_to_pubkey_hash-> 25-byte scriptPubKey). Aligningor clearly documenting these would prevent the same confusion elsewhere.
Affects
Reproduced against
libdogecoin==0.1.5rc1(PyPI, built from v0.1.5-pre).