I was convinced that the only difference between the generation of addresses starting with '1' and '3' was simply changing the prefix added to the digest from 0x00 to 0x05 after the ripemd160 part.

Although the addresses that i get do not match the addresses from Bitcoin Core wallet, when import the same private key, and also from the https://segwitaddress.org/. Does the prefix 04 remains the same or also changes? There's something i am missing. Could you please help? Thank you.


This is the method i have at the moment and still does not work. I am surely misinterpreting something..

def getPublicAddress(self, digest):

    oSk = ecdsa.SigningKey.from_string(digest, curve=ecdsa.SECP256k1)                    
    oVk = oSk.get_verifying_key()

    hexlify = codecs.getencoder('hex')             
    self.pubkey = str(hexlify(b'\04' + oVk.to_string())[0].decode('utf-8'))

    ripemd160 = hashlib.new('ripemd160')
    keyhash = hashlib.sha256(codecs.decode(self.pubkey, "hex")).digest()

    redeem_script = hashlib.new('ripemd160')
    redeem_script.update(b'\x00\x14' + ripemd160.digest())

    prefix = b'\x05' 
    m = prefix + redeem_script.digest()
    checksum = hashlib.sha256(hashlib.sha256(m).digest()).digest()[:4]        

    return base58.b58encode(m + checksum)

P2PKH, P2SH, and Segwit are all different address types. Segwit and P2SH addresses are not the same.

P2PKH and P2SH addresses are generated in a similar way. P2PKH takes the hash160 of a public key (RIPMED160 of the SHA256 of the public key), appends the version byte of 0x00 to the hash160, and Base58 Check encodes it.

P2SH addresses are the Base58 Check encoding of the hash160 of a script (known as the redeemScript). It uses a version byte of 0x05 instead. The rest of the encoding is the same, just Base58 Check encoding.

For Segwit addresses, there are multiple types. There are native segwit addresses which follow the Bech32 standard. There are also P2SH wrapped segwit addresses.

For a P2WPKH (pay to witness pubkey hash) wrapped in a P2SH address, the redeemScript is 0x0014 <hash 160 of the pubkey>. That redeemScript is hashed and encoded in the typical P2SH way.

For a P2WSH (pay to witness script hash) wrapped in a P2SH address, the witnessScript (redeemScript but for segwit addresses) is first hashed with SHA256. Then the P2SH redeemScript is 0x0020 <SHA256 of witnessScript>. The hash160 of this redeemScript is then encoded in the typical P2SH way.

As for your code, you are appending 0x04 to your public key which is simply incorrect. The 0x04 is not part of the address encoding, it is part of the public key encoding itself. Your public key generator should already be doing this for you. Note that if the public key is compressed that the prefix byte will be either 0x02 or 0x03 (depends on the Y value of the public key) instead of 0x04 which is for uncompressed public keys.

[Technical difference - Example solution]


    def hash160(self, v):
        r = hashlib.new('ripemd160')
        return r

    def doublehash256(self, v):
        return hashlib.sha256(hashlib.sha256(v).digest())

    def ecdsaSECP256k1(self, digest):
        # SECP256k1 - Bitcoin elliptic curve 
        sk = ecdsa.SigningKey.from_string(digest, curve=ecdsa.SECP256k1)                    
        return sk.get_verifying_key()

   def publicaddress1(self):

        prefix_a = b'\x04'
        prefix_b = b'\x00'

        digest = self.privkeyhex.digest()

        p = prefix_a + self.ecdsaSECP256k1(digest).to_string() # 1 + 32 bytes + 32 bytes
        self.pubkey = str(binascii.hexlify(p).decode('utf-8'))

        hash160 = self.hash160(p)

        m = prefix_b + hash160.digest()
        checksum = self.doublehash256(m).digest()[:4]        

        self.pubaddr1 = base58.b58encode(m + checksum)                  

    def publicaddress3(self):

        prefix_even = b'\x02'
        prefix_odd = b'\x03'
        prefix_a = prefix_odd
        prefix_b = b'\x05'
        prefix_redeem = b'\x00\x14'

        digest = self.privkeyhex.digest()

        ecdsa_digest = self.ecdsaSECP256k1(digest).to_string()

        x_coord = ecdsa_digest[:int(len(ecdsa_digest)/2)]
        y_coord = ecdsa_digest[int(len(ecdsa_digest)/2):]            

        if (int(binascii.hexlify(y_coord),16) % 2 == 0): prefix_a = prefix_even

        p = prefix_a + x_coord

        self.pubkeycompressed = str(binascii.hexlify(p).decode('utf-8'))

        redeem_script = self.hash160(prefix_redeem + self.hash160(p).digest()).digest() # 20 bytes

        m = prefix_b + redeem_script
        checksum = self.doublehash256(m).digest()[:4]        

        self.pubaddr3 = base58.b58encode(m + checksum)`
  • Thank you for your help. So basically i am missing the part where i need to feed a compressed wif private key and to check for the Y value after. The problem now is that the ECDSA is expecting a 32bytes input digest and if i decode base58 compressed WIF 51bytes + x01 and feed into ECDSA is gives me this error: assert len(string) == curve.baselen, (len(string), curve.baselen) AssertionError: (38, 32) it is expecting a 32 byte size. Should i just input the digest of the private key like the classic P2PKH? I will jump into the Y value part meanwhile.. Thanks a lot. – fortesp May 16 '18 at 16:23
  • Your WIF decoding sounds like it is wrong. You should get a 34 byte object. The first byte is the version number, and the last byte indicates compressedness, but is only relevant to WIF. You should drop those two bytes and you will have a 32 byte object that is the private key. Handling public key compression happens later, so you need to have something else remember whether the public key is compressed or not. – Andrew Chow May 16 '18 at 16:32
  • Thank you. What i was missing was the separation of the digest (coordinates) in 32 bytes each. The rest was easy to implement. Going to put the solution up.. – fortesp May 16 '18 at 21:20

Segwit inside P2SH:

redeem_script = hash160(b'\x00\x14' + hash160(public_key))

0x00 -> Witness version, 0x14 -> 20(or 32)bytes push opcode

prefix = b'\x05' -> P2SH main net

checksum = double_sha256(prefix + redeem_script)[:4]
address = base58(prefix + redeem_script + checksum)
  • Thanks for your help! I notice that those steps are basically what they have here bitcoincore.org/en/segwit_wallet_dev ? Not able to understand a few things. Where do you get that hash160 function? Are you refering to the ripemd160 ? Are those snipets placed after generating a x00 or in replace of? I don't really understand also what they mention by "the public key used in P2SH-P2WPKH MUST be compressed i.e. 33 bytes in size, and starting with a 0x02 or 0x03".. – fortesp May 13 '18 at 18:58
  • @fortesp hash160(x) is ripemd160(sha256(x)). You can find more information on what a compressed public key is here bitcoin.stackexchange.com/questions/3059/… – Mike D May 14 '18 at 9:07

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.