Obtener clave pública sin comprimir de forma comprimida

Una pregunta similar tiene una respuesta en Python que enlaza con el foro de bitcointalk:

Pero me gustaría saber cómo se puede escribir un programa Java para la misma conversión. Noté que Java usa el tipo de datos byte y no puedo entender cómo operar con valores.

Parece que el algoritmo que necesita está publicado en bitcointalk. ¿Está preguntando si Java tiene las mismas capacidades para convertir claves públicas que Python?
¡Bien! En realidad, comencé a hacer el código. Sin embargo, no entiendo cómo hacer la parte p//4 (división de piso) ya que estoy trabajando con biginteger. También intenté hacer y=((x^3+7)^1/2)mod p donde p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F pero tampoco funcionó.
Si su pregunta es sobre cómo hacer algo específico en Java, podría valer la pena publicar la pregunta en StackOverflow. Estoy seguro de que hay algunas personas aquí que han trabajado con BigIntegers en Java, pero ciertamente hay más en SO.

Respuestas (3)

Sí, puede convertir una clave pública comprimida de 33 bytes en una clave pública sin comprimir de 65 bytes en Java.

Aquí está el código para realizar la operación. Es correcto, robusto y solo requiere clases de Java SE (no otras bibliotecas), pero me disculpo por la longitud de la implementación.

import java.math.BigInteger;
import java.util.Arrays;

static final BigInteger MODULUS =
    new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
static final BigInteger CURVE_A = new BigInteger("0");
static final BigInteger CURVE_B = new BigInteger("7");


// Given a 33-byte compressed public key, this returns a 65-byte uncompressed key.
byte[] decompressPubkey(byte[] compKey) {
    // Check array length and type indicator byte
    if (compKey.length != 33 || compKey[0] != 2 && compKey[0] != 3)
        throw new IllegalArgumentException();

    final byte[] xCoordBytes = Arrays.copyOfRange(compKey, 1, compKey.length);
    final BigInteger xCoord = new BigInteger(1, xCoordBytes);  // Range [0, 2^256)

    BigInteger temp = xCoord.pow(2).add(CURVE_A);
    temp = sqrtMod(temp.add(CURVE_B));
    boolean tempIsOdd = temp.testBit(0);
    boolean yShouldBeOdd = compKey[0] == 3;
    if (tempIsOdd != yShouldBeOdd)
        temp = temp.negate().mod(MODULUS);
    final BigInteger yCoord = temp;

    // Copy the x coordinate into the new
    // uncompressed key, and change the type byte
    byte[] result = Arrays.copyOf(compKey, 65);
    result[0] = 4;

    // Carefully copy the y coordinate into uncompressed key
    final byte[] yCoordBytes = yCoord.toByteArray();
    for (int i = 0; i < 32 && i < yCoordBytes.length; i++)
        result[result.length - 1 - i] = yCoordBytes[yCoordBytes.length - 1 - i];

    return result;
}


// Given x, this returns a value y such that y^2 % MODULUS == x.
BigInteger sqrtMod(BigInteger value) {
    assert (MODULUS.intValue() & 3) == 3;
    BigInteger pow = MODULUS.add(BigInteger.ONE).shiftRight(2);
    BigInteger result = value.modPow(pow, MODULUS);
    assert result.pow(2).mod(MODULUS).equals(value);
    return result;
}

Mi biblioteca de criptografía de Bitcoin implementa la aritmética de campo modulo-prime, pero también debería agregar la funcionalidad para descomprimir claves públicas...

¡Gracias! El programa se ve limpio. Consulto y te aviso.
No puedo votar esta respuesta debido a la baja reputación.
Falla para esta clave comprimida. 022A779D25B43F04C3DD8A27B079FF4C6BECFBDE1419F1CF0B5CDA2AB001517884¿podrías comprobarlo? falla enassert result.pow(2).mod(MODULUS).equals(value);
@light_keeper ¿Está seguro de que su coordenada x de 2A77...7884 en realidad tiene un punto en la curva elíptica?
Debe multiplicar su primer temp(x ^ 2 + a) por x antes de agregar b y llamar a sqrtMod. Además, para que la segunda afirmación funcione correctamente, debe reducir el argumento al sqrtModmódulo p o hacer que la afirmación verifique la congruencia en lugar de la igualdad.

Puede usar bouncycastle ECPoint para hacer esta conversión:

static ECParameterSpec SPEC = ECNamedCurveTable.getParameterSpec("secp256k1");

static byte[] compressedToUncompressed(byte[] compKey) {
    ECPoint point = SPEC.getCurve().decodePoint(compKey);
    byte[] x = point.getXCoord().getEncoded();
    byte[] y = point.getYCoord().getEncoded();
    // concat 0x04, x, and y, make sure x and y has 32-bytes:
    return concat(new byte[] {0x04}, x, y);
}

En openssl, puede usar las funciones EC_POINT_point2oct y EC_POINT_oct2point para convertir entre comprimido y sin comprimir.

Compruebe si el primer octeto contiene POINT_CONVERSION_UNCOMPRESSED, si desea saber si está comprimido.