Bitcoin: ¿diferencia técnica entre la generación de direcciones P2PKH y P2SH?

Estaba convencido de que la única diferencia entre la generación de direcciones que comienzan con '1' y '3' era simplemente cambiar el prefijo agregado al resumen de 0x00 a 0x05 después de la parte ripemd160.

Aunque las direcciones que obtengo no coinciden con las direcciones de la billetera Bitcoin Core, cuando importo la misma clave privada y también desde https://segwitaddress.org/ . ¿El prefijo 04 sigue igual o también cambia? Hay algo que me estoy perdiendo. ¿Podrias ayudarme por favor? Gracias.

[EDITAR]

Este es el método que tengo en este momento y todavía no funciona. Seguramente estoy malinterpretando algo..

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()
    ripemd160.update(keyhash)


    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)

Respuestas (2)

P2PKH, P2SH y Segwit son tipos de direcciones diferentes. Las direcciones de Segwit y P2SH no son las mismas.

Las direcciones P2PKH y P2SH se generan de manera similar. P2PKH toma el hash160 de una clave pública (RIPMED160 del SHA256 de la clave pública), agrega el byte de versión 0x00al hash160 y Base58 Check lo codifica.

Las direcciones P2SH son la codificación Base58 Check del hash160 de un script (conocido como el script redimido). Utiliza un byte de versión de 0x05en su lugar. El resto de la codificación es la misma, solo la codificación Base58 Check.

Para las direcciones de Segwit, hay varios tipos. Hay direcciones segwit nativas que siguen el estándar Bech32. También hay direcciones segwit envueltas en P2SH.

Para un P2WPKH (hash de clave pública de pago para presenciar) envuelto en una dirección P2SH, el script de redimir es 0x0014 <hash 160 of the pubkey>. Ese redimirScript está cifrado y codificado de la forma típica de P2SH.

Para un P2WSH (hash de script de pago para testigo) envuelto en una dirección P2SH, el script de testigo (redeemScript pero para direcciones de segwit) primero se codifica con SHA256. Entonces el P2SH redimeScript es 0x0020 <SHA256 of witnessScript>. El hash160 de este script redimido luego se codifica en la forma típica de P2SH.


En cuanto a su código, está agregando 0x04a su clave pública, lo cual es simplemente incorrecto. El 0x04no es parte de la codificación de la dirección, es parte de la codificación de la clave pública en sí. Su generador de clave pública ya debería estar haciendo esto por usted. Tenga en cuenta que si la clave pública está comprimida, el byte de prefijo será 0x02o 0x03(depende del valor Y de la clave pública) en lugar de 0x04lo que es para las claves públicas sin comprimir.


[Diferencia técnica - Solución de ejemplo]

`

    def hash160(self, v):
        r = hashlib.new('ripemd160')
        r.update(hashlib.sha256(v).digest())
        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)`
Gracias por tu ayuda. Básicamente, me falta la parte en la que necesito alimentar una clave privada wif comprimida y verificar el valor Y después. El problema ahora es que ECDSA espera un resumen de entrada de 32 bytes y si decodifico base58 WIF comprimido de 51 bytes + x01 y lo introduzco en ECDSA, me da este error: assert len(string) == curve.baselen, (len(string), curve.baselen) AssertionError: (38, 32)espera un tamaño de 32 bytes. ¿Debería simplemente ingresar el resumen de la clave privada como el clásico P2PKH? Mientras tanto, pasaré a la parte del valor Y. Muchas gracias.
Su decodificación WIF suena como si estuviera mal. Debería obtener un objeto de 34 bytes. El primer byte es el número de versión y el último byte indica compresión, pero solo es relevante para WIF. Debe eliminar esos dos bytes y tendrá un objeto de 32 bytes que es la clave privada. El manejo de la compresión de la clave pública ocurre más tarde, por lo que debe tener algo más para recordar si la clave pública está comprimida o no.
Gracias. Lo que me faltaba era la separación del resumen (coordenadas) en 32 bytes cada uno. El resto fue fácil de implementar. Voy a poner la solución..

Segwit dentro de P2SH:

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

0x00 -> Versión testigo, 0x14 -> 20 (o 32) bytes empujar código de operación

prefix = b'\x05'-> red principal P2SH

checksum = double_sha256(prefix + redeem_script)[:4]
address = base58(prefix + redeem_script + checksum)
¡Gracias por tu ayuda! Me doy cuenta de que esos pasos son básicamente lo que tienen aquí bitcoincore.org/en/segwit_wallet_dev ? No es capaz de entender algunas cosas. ¿De dónde sacas esa función hash160? ¿Te refieres al ripemd160? ¿Se colocan esos fragmentos después de generar un x00 o en reemplazo de? Realmente tampoco entiendo lo que mencionan por "la clave pública utilizada en P2SH-P2WPKH DEBE estar comprimida, es decir, 33 bytes de tamaño, y comenzando con 0x02 o 0x03".
@fortesp hash160(x) es ripemd160(sha256(x)). Puede encontrar más información sobre qué es una clave pública comprimida aquí bitcoin.stackexchange.com/questions/3059/…