Respuesta breve. secp256k1 es la curva elíptica y² = x³ + 7 sobre el campo primo p = 2²⁵⁶ − 2³² − 977, definida en SEC 2 en el año 2000. Su grupo tiene un número primo de puntos (cofactor 1) y ofrece unos 128 bits de seguridad. Sus coeficientes se eligieron por velocidad, no se generaron a partir de una semilla: a = 0 simplifica las fórmulas, y la curva tiene un endomorfismo que se calcula de forma eficiente y acelera la multiplicación escalar. Bitcoin la usó desde su primer commit, en 2009; Ethereum siguió el mismo camino, y hoy la usan Nostr y la mayoría de las cadenas EVM. No está en WebCrypto, ni en TLS 1.3, ni en la mayor parte del hardware seguro; fuera de los protocolos de cadenas de bloques, opta por P-256 o Ed25519. Sobre el ECDSA básico, sus ecosistemas añaden reglas propias: solo firmas low-S en la política de retransmisión de Bitcoin y en las transacciones de Ethereum, recuperación de la clave pública en lugar de transmitir las claves, y Keccak-256 o doble SHA-256 como hash.
Los parámetros
SEC 2 v2.0 §2.4.1 define la curva mediante seis números (SEC 2):
| Parámetro | Valor | Significado |
|---|---|---|
| p | 2²⁵⁶ − 2³² − 977 (0xffff…fffefffffc2f) | Primo del campo; su forma abarata la reducción módulo p |
| a, b | 0, 7 | La curva y² = x³ + 7 |
| G | x = 0x79be667e…16f81798, y = 0x483ada77…fb10d4b8 | Punto base |
| n | 0xffff…fffebaaedce6af48a03bbfd25e8cd0364141 | Orden de G, primo |
| h | 1 | Cofactor: todo punto salvo el punto en el infinito genera el grupo completo |
Aquí no hay nada que aceptar por fe, y nada procede de una semilla sin explicar. El script siguiente comprueba las constantes con enteros normales de Python:
G = (Gx, Gy)
print("p == 2^256 - 2^32 - 977:", hex(p) == "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f")
print("G is on y^2 = x^3 + 7: ", (Gy * Gy - Gx**3 - b) % p == 0)
print("n is a probable prime: ", probable_prime(n))
print("n * G is infinity: ", mul(n, G) is None)
t = p + 1 - n
print("trace t = p + 1 - n: ", t)
print("Hasse bound |t| <= 2*sqrt(p):", t * t <= 4 * p)
print("n < p, p - n has", (p - n).bit_length(), "bits: the group has n points, cofactor 1")
p == 2^256 - 2^32 - 977: True
G is on y^2 = x^3 + 7: True
n is a probable prime: True
n * G is infinity: True
trace t = p + 1 - n: 432420386565659656852420866390673177327
Hasse bound |t| <= 2*sqrt(p): True
n < p, p - n has 129 bits: the group has n points, cofactor 1
add y mul son la suma de puntos en coordenadas afines y el método de duplicar y sumar (double-and-add) de cualquier manual (sirven para comprobar constantes, no para manejar secretos: no son de tiempo constante). La traza coincide con el valor publicado por SafeCurves. Como n es primo e igual al número de puntos, no hay subgrupos pequeños: los problemas de cofactor que complican los protocolos sobre Curve25519 aquí no existen.
Por qué su forma la hace rápida
Tres propiedades de estos números son deliberadas:
- El primo es 2²⁵⁶ menos un número pequeño, así que reducir un producto de 512 bits módulo p cuesta unas pocas multiplicaciones por 2³² + 977 en lugar de una división general.
- a = 0 elimina una multiplicación en cada duplicación de un punto.
- El endomorfismo. Como p ≡ 1 (mod 3), existe una raíz cúbica de la unidad β módulo p, y la aplicación (x, y) → (βx, y) lleva cada punto a otro punto de la curva. Esa aplicación equivale a multiplicar por una raíz cúbica de la unidad λ módulo n. Así, un escalar k puede dividirse en dos mitades de unos 128 bits, k = k₁ + k₂λ, y kP puede calcularse como k₁P + k₂(λP), donde λP cuesta una sola multiplicación en el campo. Es el método GLV (Gallant, Lambert, Vanstone, 2001).
El mismo script encuentra β y λ y comprueba la identidad:
print("p % 3 == 1 (cube roots of unity exist mod p):", p % 3 == 1)
beta = pow(2, (p - 1) // 3, p)
lam = pow(3, (n - 1) // 3, n)
# Pick the lambda that matches this beta (there are two non-trivial cube roots of unity mod n).
if mul(lam, G) != (beta * Gx % p, Gy):
lam = lam * lam % n
print("beta^3 == 1 mod p, lambda^3 == 1 mod n:", pow(beta, 3, p) == 1, pow(lam, 3, n) == 1)
print("lambda * G == (beta * Gx, Gy): ", mul(lam, G) == (beta * Gx % p, Gy))
print("beta =", hex(beta))
print("lambda =", hex(lam))
p % 3 == 1 (cube roots of unity exist mod p): True
beta^3 == 1 mod p, lambda^3 == 1 mod n: True True
lambda * G == (beta * Gx, Gy): True
beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee
lambda = 0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72
Son las constantes que usa libsecp256k1. Hal Finney describió el truco para Bitcoin en un hilo de bitcointalk de febrero de 2011 y calculó que la verificación sería entre un 20 y un 25 % más rápida; su código fue el punto de partida de libsecp256k1, de Pieter Wuille. Bitcoin Core activó la optimización GLV de forma predeterminada con la actualización de libsecp256k1 del PR #20147 (Bitcoin Core 0.21, 2021), donde se midió una sincronización inicial alrededor de un 16 % más rápida con la comprobación de firmas activada. Cuánto importa todo esto depende de la biblioteca: OpenSSL no tiene código específico para secp256k1 y es mucho más rápido con P-256 (las cifras están en P-256 o secp256k1).
Por qué la usan Bitcoin y Ethereum
El primer commit de Bitcoin (agosto de 2009) crea las claves con EC_KEY_new_by_curve_name(NID_secp256k1) de OpenSSL. Satoshi Nakamoto nunca explicó la elección. Los motivos que suelen darse son propiedades de la curva, no declaraciones de quien la usó: la wiki de Bitcoin señala que la curva se «construyó de una forma especial, no aleatoria, que permite un cálculo especialmente eficiente» y que, «a diferencia de las populares curvas del NIST, las constantes de secp256k1 se seleccionaron de forma predecible» (wiki de Bitcoin). Ethereum conservó la curva de Bitcoin y cambió el hash: el hash de las transacciones y los mensajes se calcula con Keccak-256, y con esa misma función se deriva la dirección a partir de la clave.
Los organismos de normalización se pusieron al día más tarde. El RFC 8812 (2020) registró ES256K para JOSE y COSE, y NIST SP 800-186 (2023), §H.2, dice que la curva «se permite para aplicaciones relacionadas con cadenas de bloques» (no figura entre las curvas recomendadas para uso general).
Qué seguridad ofrece
El mejor ataque conocido contra el problema del logaritmo discreto en este grupo es el rho de Pollard, con unas 0,886·√n ≈ 2¹²⁷·⁸ sumas según SafeCurves: la misma cifra que para P-256 y los habituales «128 bits de seguridad». (En esta curva, el endomorfismo de orden 3 permite acelerar rho un poco más, en un factor de aproximadamente √3, lo que no cambia el panorama). SafeCurves no clasifica secp256k1 como «segura», pero por motivos que afectan a las implementaciones, no al logaritmo discreto:
| Criterio de SafeCurves | secp256k1 | Qué significa para ti |
|---|---|---|
| rho, transferencia, rigidez | superado | El logaritmo discreto es difícil y las constantes están explicadas |
| discriminante CM | no superado (D = −3) | El discriminante pequeño es lo que da origen al endomorfismo; de él no se deriva ningún ataque |
| seguridad del twist | superado, 2¹⁰⁹·⁵ | Menor que la de la propia curva; solo importa al código que omite la validación de puntos |
| escalera (ladder), fórmulas completas | no superado | Escribir código de tiempo constante y sin casos excepcionales exige más cuidado que en Curve25519 |
| indistinguibilidad | no superado | Los puntos no parecen cadenas aleatorias, algo que importa en los protocolos resistentes a la censura, no en las firmas |
La lección práctica es la misma que para P-256: usa una biblioteca escrita para la curva (libsecp256k1, @noble/curves, k256 en Rust, el binding de go-ethereum), valida las claves públicas que recibes como entrada y nunca escribas tu propia multiplicación escalar para secretos. Antes de usar una clave que llega como punto, hay que comprobar que está en y² = x³ + 7; omitir esa comprobación abre la puerta al clásico ataque de curva no válida, y ahí es donde entra en juego la cifra del twist. Y, como en toda curva ECDSA, un nonce repetido o sesgado revela la clave (Reutilización del nonce en ECDSA).
Claves y direcciones
Una clave privada es un número d en [1, n − 1]; la clave pública es el punto Q = dG. El mismo Q se escribe de varias formas, y confundirlas es una fuente habitual de errores de «dirección equivocada». Con d = 1, cuya clave pública es el propio G (una clave que todo el mundo conoce, así que nunca la uses):
compressed (33 bytes): 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
uncompressed (65 bytes): 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
x-only, BIP-340 (32 bytes): 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Ethereum address, keccak256(x || y)[12:]: 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
Bitcoin P2PKH from the compressed key: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
Bitcoin P2PKH from the uncompressed key: 1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm
- Comprimida (SEC 1 §2.3.3):
02o03según y sea par o impar, seguido de x. Bitcoin usa claves comprimidas de forma predeterminada desde hace más de una década. Para descomprimir hace falta una raíz cuadrada módulo p, que para este p (p ≡ 3 mod 4) es una sola exponenciación, y = (x³ + 7)^((p+1)/4). - Sin comprimir:
04 ‖ x ‖ y. En esta forma, la misma clave da una dirección de Bitcoin distinta, y por eso las billeteras antiguas que usaban claves sin comprimir muestran otras direcciones para el mismo secreto. - x-only (32 bytes): Taproot y Nostr usan firmas Schnorr BIP-340, cuyas claves descartan la coordenada y, que se supone par.
- Dirección de Ethereum: los últimos 20 bytes del Keccak-256 calculado sobre los 64 bytes de
x ‖ y, sin el prefijo04. Keccak-256 no es SHA3-256: el relleno es distinto, y usarhashlib.sha3_256da direcciones incorrectas.
Low-S: una firma, dos formas válidas
Una firma ECDSA (r, s) tiene una gemela (r, n − s) que se verifica igual de bien, porque negar el punto del nonce no cambia r. Con P-256 en TLS, a nadie le importa. En Bitcoin sí importaba: la gemela producía un ID de transacción distinto para la misma transacción, y se podía engañar a los servicios que seguían los pagos por su ID. Bitcoin Core 0.11.1 (2015) hizo que el nodo pasara a «exigir la codificación canónica “low-s” de las firmas ECDSA al retransmitir o minar» (notas de la versión): una política de retransmisión, no una regla de consenso. Ethereum fue más allá con EIP-2 (Homestead, 2016): «todas las firmas de transacción cuyo valor s sea mayor que secp256k1n/2 se consideran ahora no válidas», mientras que «el contrato precompilado de recuperación ECDSA no cambia y seguirá aceptando valores s altos».
Las bibliotecas genéricas no siguen estas reglas; las de cadenas de bloques, sí. El laboratorio firmó 1000 mensajes aleatorios con el OpenSSL de Node.js y después verificó una firma high-S y su gemela low-S con tres bibliotecas:
const { msg, sig, s } = sample;
const low = Buffer.concat([sig.subarray(0, 32), Buffer.from((n - s).toString(16).padStart(64, "0"), "hex")]);
const opensslOk = (x) => verify("sha256", msg, { key: publicKey, dsaEncoding: "ieee-p1363" }, x);
console.log(`node:crypto verify high-S: ${opensslOk(sig)} low-S twin: ${opensslOk(low)}`);
console.log(`noble default high-S: ${secp256k1.verify(sig, msg, pub)} low-S twin: ${secp256k1.verify(low, msg, pub)}`);
console.log(`noble lowS: false high-S: ${secp256k1.verify(sig, msg, pub, { lowS: false })}`);
OpenSSL 3.5.8: 494 of 1000 signatures have s > n/2
node:crypto verify high-S: true low-S twin: true
noble default high-S: false low-S twin: true
noble lowS: false high-S: true
ethers 6.17.0 v=27 high-S: non-canonical s; use ._s
Aproximadamente la mitad de las firmas de OpenSSL son high-S; OpenSSL acepta ambas formas; @noble/curves rechaza las high-S en secp256k1 salvo que se le indique lo contrario, y ethers se niega incluso a construir un objeto de firma a partir de una de ellas. Un sistema que firma con una biblioteca de propósito general o con un HSM y envía a una cadena de bloques tiene que normalizar: si s > n/2, sustitúyelo por n − s e invierte el bit de recuperación. Google Cloud KMS documenta que sus claves secp256k1 solo devuelven firmas low-S; la documentación de ECC_SECG_P256K1 de AWS KMS no promete nada parecido, así que normaliza su salida por tu cuenta. Las codificaciones DER y en bruto en las que viajan estas firmas se tratan en Formatos de firma ECDSA.
Recuperación de la clave pública
A partir de una firma, del hash del mensaje y de uno o dos bits adicionales se puede calcular la clave pública del firmante, por lo que las transacciones de Ethereum no llevan ninguna clave pública y los mensajes firmados de Bitcoin se verifican contra una dirección. Esos bits adicionales son el ID de recuperación (v, yParity o el byte de encabezado de la firma de un mensaje de Bitcoin). Cómo funciona la recuperación, qué puede fallar en ella y cómo se comporta ecrecover se explica en Recuperación de clave pública ECDSA.
Dónde se admite y dónde no
| Plataforma | secp256k1 |
|---|---|
WebCrypto (navegadores, Deno, crypto.subtle de Node) | No; usa @noble/curves o una compilación WASM de libsecp256k1 |
OpenSSL y crypto de Node.js | Sí, con código genérico (más lento) |
| BoringSSL | No: solo incorpora P-224, P-256, P-384 y P-521 |
| Java (SunEC) | Eliminada en JDK 16: Curve not supported: secp256k1; usa Bouncy Castle |
| Apple CryptoKit, Secure Enclave, Android Keystore, TPM 2.0, tarjetas PIV | No |
| KMS en la nube | AWS KMS ECC_SECG_P256K1; Google Cloud KMS EC_SIGN_SECP256K1_SHA256 (solo con protección HSM); Azure Key Vault P-256K con ES256K |
| TLS 1.3, X.509 en la PKI web, WebAuthn | No |
Esta ausencia en el hardware seguro de teléfonos y equipos portátiles es la razón por la que las billeteras móviles guardan las claves secp256k1 en software (protegidas por una clave que sí reside en hardware) o recurren a una billetera de hardware aparte.
Errores frecuentes
- Calcular el hash con SHA3-256 en lugar de Keccak-256 para Ethereum, o con un único SHA-256 en lugar del doble SHA-256 que Bitcoin usa en mensajes y transacciones. La firma se verifica contra otro mensaje, y la recuperación devuelve una dirección distinta que parece válida.
- Enviar firmas high-S de OpenSSL, de un HSM o de un KMS en la nube a Ethereum o a la red de Bitcoin. Normaliza antes.
- Mezclar claves comprimidas y sin comprimir al derivar direcciones de Bitcoin.
- Longitudes en bytes del formato en bruto. Un
r ‖ sde 64 bytes y unr ‖ s ‖ vde 65 bytes son formatos distintos; las bibliotecas lo dejan muy claro, como el error invalid raw signature length de ethers. - Dar por hecha la compatibilidad de la plataforma. El código que funcionaba en Java 11 falla en Java 17; WebCrypto nunca ha incluido la curva.
- Usar secp256k1 donde nada lo exige. Para tokens, TLS, identidad de dispositivos o llaves de acceso no aporta ninguna ventaja y se pierde la compatibilidad con el hardware.
Fuentes
- SEC 2 v2.0: parámetros de dominio recomendados para curvas elípticas, §2.4.1; SEC 1 v2.0, §2.3.3 (codificación de puntos)
- NIST SP 800-186, apéndice H.2
- SafeCurves: tablas de rho, discriminante y twist
- Gallant, Lambert, Vanstone, «Faster Point Multiplication on Elliptic Curves with Efficient Endomorphisms», CRYPTO 2001; Hal Finney, Speeding up signature verification (2011); PR #20147 de Bitcoin Core
- Notas de la versión 0.11.1 de Bitcoin Core (política de retransmisión low-S); EIP-2
- RFC 8812 (
ES256K) - Servicios de claves: especificaciones de clave de AWS KMS, algoritmos de Google Cloud KMS, tipos de clave de Azure Key Vault