fetchai / cosmpy

A Python client library for interacting with blockchains based on the Cosmos-SDK
Apache License 2.0
129 stars 75 forks source link

Bug report: Unable to generate the expected addresses for Injective #373

Open Jackson-DKMG opened 1 year ago

Jackson-DKMG commented 1 year ago

Prerequisites

Expected Behavior

Injective support was added by the developer of bip_utils, which now allows to generate the expected addresses from a mnemonic.

from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey, PublicKey from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins

mnemonic = "24 WORDS HERE" seed_bytes = Bip39SeedGenerator(mnemonic).Generate()

bip44_mst_ctx = Bip44.FromSeed(seed_bytes, Bip44Coins.INJECTIVE) bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0) bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT)

bip44_addr_ctx = bip44_chg_ctx.AddressIndex(0) #index 0 appears to be the right one here

print(bip44_addr_ctx.PublicKey().ToAddress()) # --> CORRECT ADDRESS

print(LocalWallet(PrivateKey(bip44_addr_ctx.PrivateKey().Raw().ToBytes()), prefix='inj')) #--> WRONG ADDRESS The above is expected to print the same address twice.

Current Behavior

Using LocalWallet the generated address is incorrect. This seems to be related to the fact that Cosmpy uses the Atom address encoder.

Running this code generates the same address than LocalWallet:

from bip_utils import AtomAddrEncoder

print(AtomAddrEncoder.EncodeKey( bip44_addr_ctx.PublicKey().Bip32Key().KeyObject(), hrp="inj" ))

To Reproduce

No response

Context

Tested on Ubuntu 23.04 with python3.11 and latest Fetchd and bip_utils.

Do you know of a workaround to this issue?

Failure Logs

No response

TheUnity commented 9 months ago

Hi. Did you solve it?

Jackson-DKMG commented 9 months ago

No, I ended up using py-injective for this particular wallet, cosmpy for the other ones.

kovalevvlad commented 7 months ago

same issue here

kovalevvlad commented 7 months ago

OK after reading the source of a few libraries I have figured this out. For unknown to me reason comspy and Injective use different hashing strategies for address derivation. Here is a reproduction of this issue:


import sha3
from bip_utils import Hash160
from pyinjective import PrivateKey as InjectivePrivateKey

from cosmpy.crypto.address import Address as CosmpyAddress
from cosmpy.crypto.keypairs import PrivateKey as CosmpyPrivateKey

def via_injective(private_key_hex: str) -> str:
    priv_key = InjectivePrivateKey.from_hex(private_key_hex)
    pub_key = priv_key.to_public_key()
    return pub_key.to_address().to_acc_bech32()

def via_cosmpy(private_key_hex: str) -> str:
    private_key = CosmpyPrivateKey(bytes.fromhex(private_key_hex))
    public_key = private_key.public_key
    return str(CosmpyAddress(public_key, prefix="inj"))

def keccak256(pubkey: bytes) -> bytes:
    keccak_hash = sha3.keccak_256()
    keccak_hash.update(pubkey[1:])
    return keccak_hash.digest()[12:]

def via_bip_utils(private_key_hex: str, hrp="inj", use_keccak=False) -> str:
    from bip_utils import Secp256k1PrivateKey, Bech32Encoder
    import binascii

    # Decode the hex string to bytes
    private_key_bytes = binascii.unhexlify(private_key_hex)

    # Create a private key object
    private_key = Secp256k1PrivateKey.FromBytes(private_key_bytes)

    # Get the public key from the private key
    public_key = private_key.PublicKey()

    # Hash the public key using SHA256, then RIPEMD-160 or keccak256
    pub_key_hash = keccak256(public_key.RawUncompressed().ToBytes()) if use_keccak else Hash160.QuickDigest(public_key.RawCompressed().ToBytes())

    # Convert the hash of the public key to an address using bech32 encoding
    return Bech32Encoder.Encode(hrp, pub_key_hash)

private_key = "1112f3b9df973c25c5423f9c1ed5eb46b64059ba28a1b5870c525c816f9dbb52"
print("Injective lib", via_injective(private_key))
print("compy", via_cosmpy(private_key))
print("manual via keccak256", via_bip_utils(private_key, use_keccak=True))
print("manual via ripemd160", via_bip_utils(private_key, use_keccak=False))

Output is:

Injective lib inj1ftqt0zhaa7dvnx77q8y5ls2ups5khdx7wk8n4l
compy inj1pkczxghy5ms2fy3cqtty0l6kxqg4wa9eetwpx2
manual via keccak256 inj1ftqt0zhaa7dvnx77q8y5ls2ups5khdx7wk8n4l
manual via ripemd160 inj1pkczxghy5ms2fy3cqtty0l6kxqg4wa9eetwpx2

Would love to hear somebody's opinion on why this might be the case!

matomoniwano commented 7 months ago

yes I noticed the same thing here