convertir el script RUBY en PYTHON (Recuperar la clave privada cuando alguien usa el mismo k dos veces)

require 'ecdsa'

public_key_hex = '02a50eb66887d03fe186b608f477d99bc7631c56e64bb3af7dc97e71b917c5b364'
msghash1_hex = '01b125d18422cdfa7b153f5bcf5b01927cf59791d1d9810009c70cd37b14f4e6'
msghash2_hex = '339ff7b1ced3a45c988b3e4e239ea745db3b2b3fda6208134691bd2e4a37d6e1'
sig1_hex = '304402200861cce1da15fc2dd79f1164c4f7b3e6c1526e7e8d85716578689ca9a5dc349d02206cf26e2776f7c94cafcee05cc810471ddca16fa864d13d57bee1c06ce39a3188'
sig2_hex = '304402200861cce1da15fc2dd79f1164c4f7b3e6c1526e7e8d85716578689ca9a5dc349d02204ba75bdda43b3aab84b895cfd9ef13a477182657faaf286a7b0d25f0cb9a7de2'

group = ECDSA::Group::Secp256k1

def hex_to_binary(str)
str.scan(/../).map(&:hex).pack('C*')
end

public_key_str = hex_to_binary(public_key_hex)
public_key = ECDSA::Format::PointOctetString.decode(public_key_str, group)

puts 'public key x: %#x' % public_key.x
puts 'public key y: %#x' % public_key.y

msghash1 = hex_to_binary(msghash1_hex)
msghash2 = hex_to_binary(msghash2_hex)
sig1 = ECDSA::Format::SignatureDerString.decode(hex_to_binary(sig1_hex))
sig2 = ECDSA::Format::SignatureDerString.decode(hex_to_binary(sig2_hex))

raise 'R values are not the same' if sig1.r != sig2.r

r = sig1.r
puts 'sig r: %#x' % r
puts 'sig1 s: %#x' % sig1.s
puts 'sig2 s: %#x' % sig2.s

sig1_valid = ECDSA.valid_signature?(public_key, msghash1, sig1)
sig2_valid = ECDSA.valid_signature?(public_key, msghash2, sig2)
puts "sig1 valid: #{sig1_valid}"
puts "sig2 valid: #{sig2_valid}"

# Step 1: k = (z1 - z2)/(s1 - s2)
field = ECDSA::PrimeField.new(group.order)
z1 = ECDSA::Format::IntegerOctetString.decode(msghash1)
z2 = ECDSA::Format::IntegerOctetString.decode(msghash2)

k_candidates = [
field.mod((z1 - z2) * field.inverse(sig1.s - sig2.s)),
field.mod((z1 - z2) * field.inverse(sig1.s + sig2.s)),
field.mod((z1 - z2) * field.inverse(-sig1.s - sig2.s)),
field.mod((z1 - z2) * field.inverse(-sig1.s + sig2.s)),
]

private_key = nil
k_candidates.each do |k|
 next unless group.new_point(k).x == r
 private_key_maybe = field.mod(field.mod(sig1.s * k - z1) * field.inverse(r))
 if public_key == group.new_point(private_key_maybe)
  private_key = private_key_maybe
 end
end

puts 'private key: %#x' % private_key

alguien, por favor, convierta el código anterior en python, por favor, de Recuperación de clave privada cuando alguien usa la misma k dos veces en las firmas ECDSA

Creo que deberías conservar la sangría que tenía el guión original para que sea más fácil de leer para las personas.
por qué esta pregunta obtiene -1 necesito el script ruby ​​en python, entonces qué tiene de malo, -1 es muy malo

Respuestas (1)

Esto es lo que he estado haciendo recientemente en Python. Esta no es una solución muy completa (no valida su entrada, requiere que ya haya decodificado la firma en r & s, no deriva una clave pública o dirección de la clave privada, no No se ocupa de los problemas de maleabilidad de la firma, solo funciona con ciertos tipos de curvas, como la secp256k1 de Bitcoin), pero debería ser adecuada en la mayoría de los casos.

n  = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141  # order of base point G
r  = 0xd47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1 = 0x78c9d47ef31caf0102f9ae2489d7c78ab51692ddd898b6eb20b16a0d25b01c78
z1 = 0x4435b0704795962ac9efe71b841a5366434f552d8b5beca04a48426c15fd9ad7
s2 = 0x240bcda3967d66c71c92ffc4c4486d99968183f198c5fe1612a5cc99a05ba99a
z2 = 0x6b8bb3201a7ce4c7ed72eddc46d9b6d7350bc2eb8c28df9763518de8d66b0b52

def modinv(x, n=n): return pow(x, n-2, n)  # modular multiplicative inverse (requires that n is prime)

k = (z1 - z2) * modinv(s1 - s2) % n ; print('k = {:x}'.format(k))
print('privkey = {:x}'.format( (s1 * k - z1) * modinv(r) % n ))  # these two should
print('privkey = {:x}'.format( (s2 * k - z2) * modinv(r) % n ))  # be the same

Código actualizado

Aquí hay una versión más completa (pero también más difícil de leer) que (a) muestra diferentes posibilidades para compensar los valores de s negados (como lo señaló David Grayson en esta respuesta) , y ( b) verifica la clave privada contra la firma- claves públicas derivadas si tiene pycoin instalado.

# order of base point G of secp256k1
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

# modular multiplicative inverse (requires that n is prime)
def modinv(x, n=n):
    return pow(x, n-2, n)

# the two k candidates which aren't just negations of themselves
def k_candidates(s1, z1, s2, z2, n=n):
    z1_z2 = z1 - z2
    yield z1_z2 * modinv(s1 - s2, n) % n
    yield z1_z2 * modinv(s1 + s2, n) % n

# generates two tuples, each with (privkey, k_possibility_1, k_possibility_2)
def privkey_k_candidates(r, s1, z1, s2, z2, n=n):
    modinv_r = modinv(r, n)
    for k in k_candidates(s1, z1, s2, z2, n):
        yield (s1 * k - z1) * modinv_r % n,  k,  -k % n


r  = 0xd47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1 = 0x78c9d47ef31caf0102f9ae2489d7c78ab51692ddd898b6eb20b16a0d25b01c78
z1 = 0x4435b0704795962ac9efe71b841a5366434f552d8b5beca04a48426c15fd9ad7
s2 = 0x240bcda3967d66c71c92ffc4c4486d99968183f198c5fe1612a5cc99a05ba99a
z2 = 0x6b8bb3201a7ce4c7ed72eddc46d9b6d7350bc2eb8c28df9763518de8d66b0b52

try:
    from pycoin.ecdsa import *
    pubkeys = possible_public_pairs_for_signature(generator_secp256k1, z1, (r, s1))
    for privkey, k1, k2 in privkey_k_candidates(r, s1, z1, s2, z2):
        if public_pair_for_secret_exponent(generator_secp256k1, privkey) in pubkeys:
            print('k       = {:x}'.format(k1))
            print('or k    = {:x}'.format(k2))
            print('privkey = {:x}'.format(privkey))
            break
    else:
        print('privkey not found')

except ImportError:
    for privkey, k1, k2 in privkey_k_candidates(r, s1, z1, s2, z2):
        print('possible k       = {:x}'  .format(k1))
        print('possible k       = {:x}'  .format(k2))
        print('possible privkey = {:x}\n'.format(privkey))
A diferencia de la secuencia de comandos en la publicación original (que escribí), la secuencia de comandos en esta respuesta no tiene en cuenta el hecho de que los inicios de sesión s1y s2podrían haberse invertido, lo que requiere que pruebe 4 valores posibles diferentes para k. Creo que está al tanto de esto porque mencionó "problemas de maleabilidad de la firma".
@DavidGrayson Tiene razón, no tiene en cuenta que s1 y s2 pueden haber sido negados por un tercero. Estaba siendo un poco perezoso cuando publiqué esto; Lo actualizaré en un momento.
¿Qué está pasando con todas estas publicaciones? ¿Supongo que la debacle de BCI random.org?
@WizardOfOzzie Quizás, pero los txs a los que OP ha estado haciendo referencia como ejemplos se remontan a 2012, mucho antes de los dos (suspiro ...) fallas recientes de BCI.
@ChristopherGurnee solo tenía curiosidad, ya que es la quinta mención del valor r hoy (para mí, no solo aquí). EDITAR, espera, 2? ¿El problema de java bouncycastle de agosto de 2013 es el otro?
@WizardOfOzzie BCI no usa Bouncy Castle (BCI es JavaScript, Bouncy Castle es Java). Me refería a este problema en el que BCI arruinó su RNG, y si no fuera por los heroicos esfuerzos del usuario johoe , habrían perdido más de 260 de btc de sus usuarios. También han tenido problemas de ataques de degradación de SSL, problemas de HSTS y probablemente otros...
@ChristopherGurnee oh wow, me perdí ese error de BCI. Estaba recordando el problema de RNG de agosto de 2013, por el cual se afectaron las billeteras BCI, así que pensé que era un problema de Java. Sin embargo, no estoy muy versado en Java o JavaScript, por lo que es posible que tenga lagunas
@WizardOfOzzie En realidad, apuesto a que tiene razón, olvidé que BCI tiene una aplicación de Android que puede haberse visto afectada, solo estaba pensando en la interfaz web.