Cardano address generate in python

Please help me in developing Cardano address generator in python 2.7

2 Likes

There’s some documentation on Cardano addresses here and a Python implementation here

2 Likes

Thanks for your help. I was looking forward to make an easier implementation of address generation than this. All contributions and suggestions are welcome

1 Like

I found a JS implementation that was easier to follow.
I’m not 100% sure what I’m doing, but I made a Python translation and it seems to work.

For some reason the address in the sample outputs is generated from ‘Root Seed Chain 5’, so if you set that manually it comes out the same when using the same mnemonic:

buf = hmac.new(cborSeed, b'Root Seed Chain 5', hashlib.sha512).digest()

2 Likes

Interesting. And that matches how a Cardano address is generated?

I’m not sure, the documentation doesn’t really specify each step required.
If you know Haskell you could try comparing with this.
I don’t speak Haskell and haven’t tried to go through it yet, I was just working on the assumption that the JS implementation was correct.

1 Like

I mean it looks about right. I guess you would have to derive addresses for the “payment level” you know M/44/ADA/0/0/n and do the same in cardano-cli and see if they were the same.

According to this:

HDAddressPayload datatype represents a derivation path ([Word32]), symmetrically encrypted via ChaChaPoly1305 algorithm. An encryption key may be any ByteString of 32 bytes, known only to owner of the address.
In current Cardano implementation, encryption key is a 32-byte key derived from root public key of wallet using PBKDF2 derivation algorithm with HMAC-SHA512 as pseudorandom function, 500 iterations and string address-hashing as a salt.

So I thought I should be able to generate a new wallet in Daedalus, then try to decrypt the payload of the address with something like this:

from mnemonic import Mnemonic
from hashlib import blake2b, sha512
from base58 import b58decode
from pbkdf2 import PBKDF2
from chacha20poly1305 import ChaCha20Poly1305
from sys import argv
import cbor
import hmac
import ed25519

words = argv[1]
addr = argv[2]

entropy = Mnemonic('english').to_entropy(words)
cborEnt = cbor.dumps(bytes(entropy))
seed = blake2b(cborEnt, digest_size=32)
cborSeed = cbor.dumps(seed.digest())

for i in range(1, 1000):
    buf = hmac.new(cborSeed, b'Root Seed Chain %d' % i, sha512).digest()
    buf_l, buf_r = buf[:32], buf[32:]
    bip32 = ed25519.SigningKey(buf_l)
    if bip32:
        break

xpub = bip32.vk_s + buf_r

decodedAddr = b58decode(addr)
loadedAddr = cbor.loads(decodedAddr)
address = cbor.loads(loadedAddr[0].value)
addrAttributes = address[1][1]

nonce = b'serokellfore'
hdpass = PBKDF2(xpub, 'address-hashing', iterations=500, digestmodule=sha512).read(32)
cip = ChaCha20Poly1305(hdpass)
cip.decrypt(nonce, addrAttributes)

It didn’t work though, I’m not sure where I’ve gone wrong.

I wrote CWAG, and it only generates a Daedalus’ Cardano Wallet ID (root address), without any address derivation.
That iterations simply mean that the valid keys of the modified ed25519 must have some valid bit set (50% probability so that 1000 iteration is overhelmed), check this

1 Like

The address derivation is more complicated in Daedalus, that’s why I have not implemented it i cwag.

1 Like

Right, when I was doing it in Python the first iteration worked, so I was getting a different output to your sample. Then I figured out the sample was derived from the 5th iteration. I was just trying to get it to match yours exactly to make sure it was working.

The address derivation is more complicated in Daedalus

I noticed :stuck_out_tongue: That’s why I thought it might be help to work backwards by taking an address from Daedalus and de-constructing it. I think I’m doing something wrong in deriving the key for the Poly1305 encrypted payload though.

Okay, just trying anything at this point, I managed to decrypt the address by setting ‘Root Seed Chain 5’
Not sure why, but this works:

from mnemonic import Mnemonic
from hashlib import blake2b, sha512
from base58 import b58decode
from pbkdf2 import PBKDF2
from chacha20poly1305 import ChaCha20Poly1305
from sys import argv
import cbor
import hmac
import ed25519
words = 'provide lumber used piece fossil crop defy tornado focus say kitchen gentle'
addr = 'DdzFFzCqrhsfE419Z4WHVTQVP4wnXGqx7pDVTLhNLaLxVxazViMiYhd1zr4bcRpqEHLbZ6DvaAdLro9JZq7q9oD6hopD1wzQu7yiQKxP'
entropy = Mnemonic('english').to_entropy(words)
cborEnt = cbor.dumps(bytes(entropy))
seed = blake2b(cborEnt, digest_size=32)
cborSeed = cbor.dumps(seed.digest())
buf = hmac.new(cborSeed, b'Root Seed Chain 5', sha512).digest()
buf_l, buf_r = buf[:32], buf[32:]
bip32 = ed25519.SigningKey(buf_l)
xpub = bip32.vk_s + buf_r
decodedAddr = b58decode(addr)
loadedAddr = cbor.loads(decodedAddr)
address = cbor.loads(loadedAddr[0].value)
addrAttributes = cbor.loads(address[1][1])
nonce = b'serokellfore'
hdpass = PBKDF2(xpub, 'address-hashing', iterations=500, digestmodule=sha512).read(32)
cip = ChaCha20Poly1305(hdpass)
decryptedAttr = cip.decrypt(nonce, addrAttributes)
cbor.loads(decryptedAttr)
[2147483648, 2147483648]

Edit: I read through the CWAG program again and found that Bip32-Ed25519 requires a certain bit to be unset. Adding this check to the for loop fixes everything:

for i in range(1, 1000):
    buf = hmac.new(cborSeed, b'Root Seed Chain %d' % i, sha512).digest()
    buf_l, buf_r = buf[:32], buf[32:]
    if sha512(buf_l).digest()[31] & 32 == 0:
        bip32 = ed25519.SigningKey(buf_l)
        break

Thanks @_ilap for the helpful comments :slight_smile:

Yes it does for the first child derivation (root) address, they called Cardano Wallet ID. But, the deeper child derivations are divverent as they use different formats and schemes such as BIP44, R_INDEX_LEVELS and V1 and V2.

It must work as, anyway, you would not be able to easily determine whether that address belongs to your wallet’s (private the hardened address or to the public) keys or not. Check this