4

I want to make a BIP32 wallet for Namecoin and other alt coins. I just need to figure out how to generate a a public and private version number so that the BIP32 extended key prefix is something that makes sense like "nprv" for a Namecoin private key and "npub" for a public key.

Been struggling with this one

2 Answers 2

5

I can not tell you really how you generate them, but to partially answer your question, here is a list of known Bitcoin and Altcoin prefixes, including Litecoin and Darkcoin:

EXT_SECRET_KEY, EXT_PUBLIC_KEY # Network                 : Prefixes
----------------------------------------------------------------------
0x0488ADE4,     0x0488B21E     # BTC  Bitcoin    mainnet : xprv / xpub
0x04358394,     0x043587CF     # BTC  Bitcoin    testnet : tprv / tpub
0x019D9CFE,     0x019DA462     # LTC  Litecoin   mainnet : Ltpv / Ltub
0x0436EF7D,     0x0436F6E1     # LTC  Litecoin   testnet : ttpv / ttub
0x02FE52F8,     0x02FE52CC     # DRK  Darkcoin   mainnet : drkv / drkp
0x3A8061A0,     0x3A805837     # DRK  Darkcoin   testnet : DRKV / DRKP
0x0488ADE4,     0x0488B21E     # VIA  Viacoin    mainnet : xprv / xpub
0x04358394,     0x043587CF     # VIA  Viacoin    testnet : tprv / tpub
0x02FAC398,     0x02FACAFD     # DOGE Dogecoin   mainnet : dgpv / dgub
0x0488ADE4,     0x0488B21E     # VTC  Vertcoin   mainnet : vtcv / vtcp
0x02CFBF60,     0x02CFBEDE     # BC   Blackcoin  mainnet : bcpv / bcpb
0x03A04DB7,     0x03A04D8B     # MEC  Megacoin   mainnet : mecv / mecp
0x0488ADE4,     0x0488B21E     # MYR  Myriadcoin mainnet : myrv / myrp
0x0488ADE4,     0x0488B21E     # UNO  Unobtanium mainnet : unov / unop
0x037A6460,     0x037A689A     # JBS  Jumbucks   mainnet : jprv / jpub
0x0488ADE4,     0x0488B21E     # MZC  Mazacoin   mainnet : xprv / xpub

I've taken them from the pycoin api implementation which supports HD wallets. https://github.com/richardkiss/pycoin/blob/master/pycoin/networks.py

2

Rather old question I realize, but I thought I'd answer it anyways.

The xpub/xprv/etc. strings are simply big numbers represented in base-58, as opposed to the base-10 we're all familiar with. If we wanted to take a "normal" 5-digit number and prepend, say, 889 to the front, we'd simply add 88900000 to the 5-digit number:

     12345
+ 88900000
----------
  88912345

Working in base-58 is essentially the same, but as always the devil's in the details because we have to convert from a big base-58 number to a big base-256 number (the "base" of an array of bytes, which is what we're looking for).

What follows is a Python 3 script which can be run from the command line, e.g. to prepend the string xpub to 74 bytes of data (the length of a BIP32 extended key, not including the 4 trailing check bytes), the bytes to prepend before base-58-encoding are:

$ python3 prepend_bytes.py xpub 74
0x04, 0x88, 0xb2, 0x1e

#!/usr/bin/env python3

from math import log, ceil
import sys


# lookup tables to convert integers in the range [0, 58) to base-58 digits and back
int_to_b58_digit = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
b58_digit_to_int = { b58:i for i,b58 in enumerate(int_to_b58_digit) }

# convert a (long) integer to its base-58 representation string
def int_to_base58_str(int_rep):
    base58_str = ''
    while int_rep:
        int_rep, remainder = divmod(int_rep, 58)
        base58_str = int_to_b58_digit[remainder] + base58_str
    return base58_str


def prepended_bytes(prepended_b58_digits, b256_digit_count):

    # ones are a special case in base58check format;
    # count and remove them, they are added back in later
    ones = 0
    for b58 in prepended_b58_digits:
        if b58 != '1':
            break
        ones += 1
        prepended_b58_digits = prepended_b58_digits[1:]
    if not prepended_b58_digits:  # if they're all 1's
        return ones * b'\0'

    # calc the # of base58 digits required for b256_digit_count bytes of "real" data
    # (not including the prepended base58 digits)
    b58_digit_count = ceil(b256_digit_count * log(256) / log(58))

    do_overflow_check = True
    while True:
        # calc the minimum integral value which starts with the desired digits in base-58
        min_int = 0
        for b58 in prepended_b58_digits:
            min_int *= 58
            min_int += b58_digit_to_int[b58]
        #
        # left-shift (mult. by a power of 58) to be just left of the "real" base-58 data
        min_int *= 58 ** b58_digit_count

        # uncomment to sanity-check that min_int is correct
        #print(" min_int:", ones * '1' + int_to_base58_str(min_int))

        # right-shift by multiples of 8 bits (base-256) to retrieve only the
        # most-significant bytes which are to the left of the "real" base-256 data
        min_int >>= b256_digit_count * 8
        # right-shifing effectively rounds min_int down, but we
        # need it rounded up, so add one to round it up instead
        min_int += 1

        # because min_int has been rounded up above, it's possible that adding it to "real"
        # data could cause an overflow in base-58 making the prepended_b58_digits increment
        # by one; if this could happen, left-shift prepended_b58_digits and repeat
        if do_overflow_check:
            max_real_data_int = (1 << b256_digit_count*8) - 1
            max_base58_str = int_to_base58_str((min_int << b256_digit_count*8) + max_real_data_int)
            if not max_base58_str.startswith(prepended_b58_digits):
                prepended_b58_digits += '1'
                do_overflow_check = False  # it doesn't matter if the '1' appended above overflows to '2'

                # uncomment to confirm that the max possible value
                # wouldn't have the desired prepended base-58 digits
                #print("overflow:", ones * '1' + max_base58_str)

                continue

        # prepend any ones according to base58check rules, and convert min_int to a byte string
        return ones * b'\0' + min_int.to_bytes(length= (min_int.bit_length() + 7) // 8, byteorder='big')


if __name__ == '__main__':
    if len(sys.argv) != 3:
        sys.exit('usage: {} <STRING-TO-PREPEND> <DATA-BYTE-LEN (excluding 4-byte checksum)>'.format(sys.argv[0]))

    result = prepended_bytes(sys.argv[1], int(sys.argv[2]) + 4)  # add 4 for the checksum
    print(', '.join('{:#04x}'.format(i) for i in result))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.