Please help me in developing Cardano address generator in python 2.7
Thanks for your help. I was looking forward to make an easier implementation of address generation than this. All contributions and suggestions are welcome
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()
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.
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
The address derivation is more complicated in Daedalus, thatās why I have not implemented it i cwag.
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 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
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