ebellocchia / bip_utils

Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies
MIT License
308 stars 85 forks source link

Cardano created address and recovered address differently #135

Closed tigermap7 closed 3 months ago

tigermap7 commented 3 months ago

Hello I'm trying to create a Cardano address and recover the address with the Private key I created. I'm asking because there is a problem with the address you created and the address you recovered.

Cardano has created an address and a private key

seed = CardanoIcarusSeedGenerator(mnemonic).Generate() coinType = Cip1852Coins.CARDANO_ICARUS bip44_mst_ctx = Cip1852.FromSeed(seed, coinType) bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0) shelly = CardanoShelley.FromCip1852Object(bip44_acc_ctx) bip44_chg_ctx = shelly.Change(Bip44Changes.CHAIN_EXT) bip44_addr_ctx = bip44_chg_ctx.AddressIndex(0)

print(f"Address: {bip44_addr_ctx.PublicKeys().ToAddress()}") print(f"Public Key: {bip44_addr_ctx.PublicKeys().AddressKey().RawCompressed().ToHex()}") print(f"Private Key: {bip44_addr_ctx.PrivateKeys().AddressKey().Raw().ToHex()}")

So I'm going to get a private key and I'm going to get it back to the address from the private key (using bip44_addr_ctx.PrivateKeys().AddressKey().Raw().ToHex())

` fromPk = Cip1852.FromPrivateKey(bytes.fromhex(pk.lstrip('0x')), Cip1852Coins.CARDANO_ICARUS) bip44_acc_ctx = fromPk.Purpose().Coin().Account(0) shelly = CardanoShelley.FromCip1852Object(bip44_acc_ctx) bip44_chg_ctx = shelly.Change(Bip44Changes.CHAIN_EXT) bip44_addr_ctx = bip44_chg_ctx.AddressIndex(0)

print(f"Address: {bip44_addr_ctx.PublicKeys().ToAddress()}") `

create address : addr1q8uymmyzmj5q88patkq28wjscseawn2m8xxxc2npkzzv2sq2q7a0l82p3fg87ayem8cd00rta4djwysphquzu9h9vx4qe308w9

recovered address : addr1q8uwaz4gwxuslslr7j3z0gyd9usynfm9cnz4ex85qeqjwdm9f2fvh2g3s8ednkelqv6tvjqje8sys2h8r46yqxqu299smfy0tf

How do I implement it to recover the same way?

ebellocchia commented 3 months ago

Hi, First of all, you should use bip44_mst_ctx and not bip44_addr_ctx for getting the private key, because you are recovering from the master private key. Secondly, when you recover from a raw private key, you also have to specify the chaincode that was generated from the seed (it's usually the second half of the computed HMAC). If not specified, it will be set to all zeroes. You don't need to specify it when you recover from an extended private key, because the chaincode is included in it. This is valid in general, i.e. also for BIP44/49/etc... So, your example becomes:

from bip_utils import *

mnemonic = "solid dinner elephant audit toilet violin decade weasel invite orange purse say symbol behave typical"

seed = CardanoIcarusSeedGenerator(mnemonic).Generate()
bip44_mst_ctx = Cip1852.FromSeed(seed, Cip1852Coins.CARDANO_ICARUS)
shelly = CardanoShelley.FromCip1852Object(
    bip44_mst_ctx.Purpose().Coin().Account(0)
)
bip44_addr_ctx = shelly.Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)

print(f"Address: {bip44_addr_ctx.PublicKeys().ToAddress()}")
print(f"Public Key: {bip44_addr_ctx.PublicKeys().AddressKey().RawCompressed().ToHex()}")
print(f"Private Key: {bip44_addr_ctx.PrivateKeys().AddressKey().Raw().ToHex()}")

fromPk = Cip1852.FromPrivateKey(
    bip44_mst_ctx.PrivateKey().Raw().ToBytes(),
    Cip1852Coins.CARDANO_ICARUS,
    Bip32KeyData(chain_code=bip44_mst_ctx.PrivateKey().Bip32Key().ChainCode().ToBytes())
)
shelly = CardanoShelley.FromCip1852Object(
    fromPk.Purpose().Coin().Account(0)
)
bip44_addr_ctx = shelly.Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)

# Same address
print(f"Address: {bip44_addr_ctx.PublicKeys().ToAddress()}")
tigermap7 commented 3 months ago

Thank you for your response. I have a question from what you said.

bip44_addr_ctx.PrivateKeys().AddressKey().Raw().ToHex() Can't I recover the address with this private key?

My intention is to get an address where the private key and the private key of one address match without a mnemonic. For example, getting an address in a private key for each address, such as Ethereum.

In the recovery code you told me, it seems that there must be a mnemonic.

ebellocchia commented 3 months ago

Nope, I added a mnemonic just to make the code running, but if you see I'm using the private key to recover not the mnemonic. Anyway, I used the master private key because in your example you derived keys again, so I thought you wanted to use it.

It's possible to use the address private key to recover a BIP object (i.e. Bip44, Bip49, etc...), in this case you don't even need the chain code because you don't derive keys (chain code is only used to derive keys):

from bip_utils import *

# Just for the example
mnemonic = Bip39MnemonicGenerator().FromWordsNumber(Bip39WordsNum.WORDS_NUM_12)

seed = Bip39SeedGenerator(mnemonic).Generate()
bip44_mst = Bip44.FromSeed(seed, Bip44Coins.ETHEREUM)
bip44_addr = bip44_mst.Purpose().Coin().Account(0).Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)

print(f"Address: {bip44_addr.PublicKey().ToAddress()}")

# Store private key in hex format
priv_key_hex = bip44_addr.PrivateKey().Raw().ToHex()

# Recover from stored private key
bip44_recovered = Bip44.FromPrivateKey(BytesUtils.FromHexString(priv_key_hex), Bip44Coins.ETHEREUM)

print(f"Recovered address from private key: {bip44_recovered.PublicKey().ToAddress()}")

However, for Cardano Shelley, this is not possible because it uses 2 private keys, one for receiving payments and one for staking. I didn't create a method for recovering from the 2 private keys because it's mathematically impossible to know if the 2 keys are valid, i.e. they are derived from the same parent key. What you can do is store the extended private key (in this way you don't need to store the chain code) of the Cip1852 account object and then use it to recover the CardanoShelley object:

from bip_utils import *

# Just for the example
mnemonic = Bip39MnemonicGenerator().FromWordsNumber(Bip39WordsNum.WORDS_NUM_15)

seed = CardanoIcarusSeedGenerator(mnemonic).Generate()
cip1852_acc = Cip1852.FromSeed(seed, Cip1852Coins.CARDANO_ICARUS).Purpose().Coin().Account(0)
shelley_addr = CardanoShelley.FromCip1852Object(cip1852_acc).Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)

print(f"Address: {shelley_addr.PublicKeys().ToAddress()}")

# Store private key in extended format
priv_key_ex = cip1852_acc.PrivateKey().ToExtended()

# Recover from stored private key
cip1852_recovered = Cip1852.FromExtendedKey(priv_key_ex, Cip1852Coins.CARDANO_ICARUS)
shelley_addr = CardanoShelley.FromCip1852Object(cip1852_recovered).Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)

print(f"Recovered address from private ke: {shelley_addr.PublicKeys().ToAddress()}")