Search code examples
perlcryptographybitcoin

Dogecoin Address generation - Address not valid


I'm trying to generate Dogecoin addresses. The generated addresses have the same length as valid Dogecoin addresses generated by RPC-API getnewaddress and the same length, but they do not work. They are not valid.

Here are the steps:

  1. Public key from secp256k1
  2. Apply SHA256, then RIPEMD-160 to the result of SHA256
  3. Add 0x1E (Version for Dogecoin) at the begin of the RIPEMD-160 result
  4. Apply SHA256 twice to the encrypted pubkey for the checksum hash
  5. Add first 4 bytes of the checksum hash (8 characters) to the end of the encrypted pub key
  6. Apply BASE56

That generates a 34 characters address starting with D which looks very authentic, but none of them is valid. Why?

    use strict;
    use warnings;
    use Digest::SHA qw(sha256);
    use Crypt::PK::ECC;
    use Crypt::RIPEMD160;
    use v5.10;
    
    use Bitcoin::Crypto::Base58 qw(
            encode_base58
            decode_base58
            encode_base58check
            decode_base58check
    );
    
    # Bitcoin Version 0x00
    # Dogecoin Version 0x1E
    
    sub append_checksum_and_version {
        my ($binstr) = @_;
    
        return (chr(0x1E) . $binstr . substr(sha256(sha256($binstr)), 0, 4));
    }
    
    
    my $pk = Crypt::PK::ECC->new();
    $pk->generate_key('secp256k1');
    my $public_raw = $pk->export_key_raw('public');
    my $encrypted_pubkey = Crypt::RIPEMD160->hash(sha256($public_raw));
    say "Private Key: " . unpack "H*", $pk->export_key_raw('private');
    say "Dogecoin Address: " . encode_base58(append_checksum_and_version($encrypted_pubkey));  

Output:

Dogecoin Address: DGRHFUwqvzh8VBFR1gAhRFVbM476bATZVK

Dogecoin Address: DEvMuu6PbQLDQE72aofFzxk5c8AmzRZos9

Dogecoin Address: DM1zqiW5ZPb1MD8hP1sxtBsPkQAofFTRTw

Maybe the checksum calculation is not correct? Even if I use 0x00 instead of 0x1E for Bitcoin. The Bitcoin addresses are not valid either.


Solution

  • It turned out there was a byte missing.

    return (chr(0x1E) . $binstr . substr(sha256(sha256($binstr)), 0, 4));
    

    must be replaced to

    return (chr(0x1E) . $binstr . substr(sha256(sha256(chr(0x1E) . $binstr)), 0, 4));
    

    The version byte of 0x1E (Dogecoin) or 0x00 (Bitcoin) must be included in the double sha256 calculation for the checksum.

    use strict;
    use warnings;
    use Digest::SHA qw(sha256);
    use Crypt::PK::ECC;
    use Crypt::RIPEMD160;
    use Bitcoin::Crypto::Base58 qw(encode_base58 decode_base58 encode_base58check decode_base58check);
    use v5.10;
    
    # Version byte: Bitcoin 0x00, Dogecoin 0x1E
    sub cv {
        my ($binstr) = @_;
        return (chr(0x1E) . $binstr . substr(sha256(sha256(chr(0x1E) . $binstr)), 0, 4));
    }
    
    my $pk = Crypt::PK::ECC->new()->generate_key('secp256k1');
    my $dogecoin_address = encode_base58(cv(Crypt::RIPEMD160->hash(sha256($pk->export_key_raw('public')))));
    say "Dogecoin Address: " . $dogecoin_address; 
    

    Gives valid addresses:

    Dogecoin Address: DR61RhFNCkswmDx6JjLyVPSoq51gBtxxjK
    Dogecoin Address: DFFV4xa9Ph2LUXaKbyxMtViHFhu9e9h88J
    Dogecoin Address: DAk4ZbSoWnhpt2exkEevG2j93CVEL7g5Fu
    

    UPDATE:

    If you want to generate the wifkey for Dogecoin from pubkey. The version byte for mainnet is 0x9E (Dogecoin) and 0x80 (Bitcoin)

    sub toWifKey {
        my ($binstr) = @_;
        return (chr(0x9E). $binstr . substr(sha256(sha256(chr(0x9E) . $binstr)), 0, 4));
    }
    
    my $wifkey = encode_base58(toWifKey($pk->export_key_raw('private')));