Resposta curta. A secp256k1 é a curva elíptica y² = x³ + 7 sobre o campo primo p = 2²⁵⁶ − 2³² − 977, definida no SEC 2 em 2000. O grupo dela tem um número primo de pontos (cofator 1) e oferece cerca de 128 bits de segurança. Os coeficientes foram escolhidos pela velocidade, e não gerados a partir de uma semente: a = 0 simplifica as fórmulas, e a curva tem um endomorfismo de cálculo eficiente que acelera a multiplicação escalar. O Bitcoin a usa desde o primeiro commit, em 2009, o Ethereum veio em seguida, e hoje o Nostr e a maioria das cadeias EVM também a usam. Ela não está no WebCrypto, no TLS 1.3 nem na maior parte do hardware seguro; fora dos protocolos de blockchain, prefira a P-256 ou o Ed25519. Por cima do ECDSA puro, os ecossistemas dela acrescentam regras próprias: apenas assinaturas low-S na política de retransmissão do Bitcoin e nas transações do Ethereum, recuperação da chave pública em vez da transmissão de chaves, e Keccak-256 ou SHA-256 duplo como hash.
Os parâmetros
O SEC 2 v2.0, §2.4.1, define a curva por seis números (SEC 2):
| Parâmetro | Valor | Significado |
|---|---|---|
| p | 2²⁵⁶ − 2³² − 977 (0xffff…fffefffffc2f) | Primo do campo; a forma dele torna barata a redução módulo p |
| a, b | 0, 7 | A curva y² = x³ + 7 |
| G | x = 0x79be667e…16f81798, y = 0x483ada77…fb10d4b8 | Ponto base |
| n | 0xffff…fffebaaedce6af48a03bbfd25e8cd0364141 | Ordem de G, primo |
| h | 1 | Cofator: todo ponto, exceto o ponto no infinito, gera o grupo inteiro |
Nada aqui precisa ser aceito na base da confiança, e nada veio de uma semente sem explicação. O script abaixo confere as constantes com inteiros comuns do 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 e mul são a adição de pontos em coordenadas afins e o double-and-add dos livros didáticos (servem para conferir constantes, mas não para lidar com segredos: não são de tempo constante). O traço confere com o valor publicado pelo SafeCurves. Como n é primo e igual ao número de pontos, não há subgrupos pequenos: os problemas de cofator que complicam os protocolos baseados na Curve25519 não existem aqui.
Por que essa forma a torna rápida
Três propriedades desses números são intencionais:
- O primo é 2²⁵⁶ menos um número pequeno, então reduzir um produto de 512 bits módulo p exige algumas multiplicações por 2³² + 977, em vez de uma divisão genérica.
- a = 0 elimina uma multiplicação de cada duplicação de ponto.
- O endomorfismo. Como p ≡ 1 (mod 3), existe uma raiz cúbica da unidade β módulo p, e a aplicação (x, y) → (βx, y) leva todo ponto a outro ponto da curva. Essa aplicação equivale a multiplicar por uma raiz cúbica da unidade λ módulo n. Assim, um escalar k pode ser dividido em duas metades de cerca de 128 bits, k = k₁ + k₂λ, e kP pode ser calculado como k₁P + k₂(λP), em que λP custa uma multiplicação no campo. Este é o método GLV (Gallant, Lambert, Vanstone, 2001).
O mesmo script encontra β e λ e confere a identidade:
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
São essas as constantes que a libsecp256k1 usa. Hal Finney descreveu o truque para o Bitcoin em um tópico do bitcointalk de fevereiro de 2011 e esperava que a verificação ficasse de 20 a 25% mais rápida; o código dele foi o ponto de partida da libsecp256k1 de Pieter Wuille. O Bitcoin Core ativou a otimização GLV por padrão com a atualização da libsecp256k1 no PR #20147 (Bitcoin Core 0.21, 2021), onde a medição mostrou uma sincronização inicial cerca de 16% mais rápida com a verificação de assinaturas ativada. Quanto tudo isso importa depende da biblioteca: o OpenSSL não tem código especial para a secp256k1 e é muito mais rápido com a P-256 (os números estão em P-256 vs secp256k1).
Por que o Bitcoin e o Ethereum a usam
O primeiro commit do Bitcoin (agosto de 2009) cria chaves com a função EC_KEY_new_by_curve_name(NID_secp256k1) do OpenSSL. Satoshi Nakamoto nunca explicou a escolha. Os motivos que costumam ser apontados são propriedades da curva, e não declarações de quem a usou: a wiki do Bitcoin observa que ela foi “construída de uma forma especial, não aleatória, que permite um cálculo especialmente eficiente” e que, “ao contrário das populares curvas do NIST, as constantes da secp256k1 foram escolhidas de forma previsível” (wiki do Bitcoin). O Ethereum manteve a curva do Bitcoin e trocou o hash: o hash de transações e mensagens é calculado com Keccak-256, e o endereço é derivado da chave com ele.
Os órgãos de padronização vieram depois. A RFC 8812 (2020) registrou ES256K para JOSE e COSE, e o NIST SP 800-186 (2023), §H.2, diz que a curva “tem uso permitido em aplicações relacionadas a blockchain” (ela não está entre as curvas recomendadas para uso geral).
Quão forte ela é
O melhor ataque conhecido ao problema do logaritmo discreto neste grupo é o rho de Pollard, com cerca de 0,886·√n ≈ 2¹²⁷·⁸ adições segundo o SafeCurves, o mesmo número da P-256 e os habituais “128 bits de segurança”. (O endomorfismo de ordem 3 pode ser usado para acelerar o rho um pouco mais nesta curva, por um fator de cerca de √3, o que não muda o quadro.) O SafeCurves não considera a secp256k1 “segura”, mas por motivos que dizem respeito às implementações, e não ao logaritmo discreto:
| Critério do SafeCurves | secp256k1 | O que isso significa para você |
|---|---|---|
| rho, transferência, rigidez | aprovada | O logaritmo discreto é difícil, e as constantes têm explicação |
| discriminante CM | reprovada (D = −3) | É o discriminante pequeno que cria o endomorfismo; nenhum ataque decorre disso |
| segurança do twist | aprovada, 2¹⁰⁹·⁵ | Menor que a da própria curva; só importa para código que pula a validação de pontos |
| ladder, fórmulas completas | reprovada | Código de tempo constante e sem casos excepcionais exige mais cuidado que na Curve25519 |
| indistinguibilidade | reprovada | Os pontos não se parecem com strings aleatórias, o que importa para protocolos resistentes à censura, e não para assinaturas |
A lição prática é a mesma da P-256: use uma biblioteca escrita para a curva (libsecp256k1, @noble/curves, k256 no Rust, o binding do go-ethereum), valide as chaves públicas na entrada e nunca escreva sua própria multiplicação escalar para segredos. Uma chave que chega como ponto precisa ser verificada antes do uso: ela tem de estar em y² = x³ + 7. Pular essa verificação abre caminho para o clássico ataque de curva inválida, e é aí que entra o valor do twist. E, como em toda curva do ECDSA, um nonce repetido ou enviesado revela a chave (reutilização de nonce no ECDSA).
Chaves e endereços
Uma chave privada é um número d em [1, n − 1]; a chave pública é o ponto Q = dG. O mesmo Q pode ser escrito de várias formas, e confundi-las é uma fonte comum de bugs de “endereço errado”. Com d = 1, cuja chave pública é o próprio G (uma chave que todo mundo conhece, então nunca a use):
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):
02ou03para y par ou ímpar, seguido de x. O Bitcoin usa chaves comprimidas por padrão há mais de uma década. A descompressão exige uma raiz quadrada módulo p, que, para este p (p ≡ 3 mod 4), é uma única exponenciação, y = (x³ + 7)^((p+1)/4). - Não comprimida:
04 ‖ x ‖ y. Nessa forma, a mesma chave gera um endereço Bitcoin diferente, e é por isso que carteiras antigas que usavam chaves não comprimidas mostram outros endereços para o mesmo segredo. - x-only (32 bytes): o Taproot e o Nostr usam assinaturas Schnorr BIP-340, cujas chaves descartam a coordenada y e pressupõem y par.
- Endereço Ethereum: os últimos 20 bytes do Keccak-256 calculado sobre o
x ‖ yde 64 bytes, sem o prefixo04. O Keccak-256 não é o SHA3-256: o padding é diferente, e usarhashlib.sha3_256produz endereços errados.
Low-S: uma assinatura, duas formas válidas
Uma assinatura ECDSA (r, s) tem uma gêmea (r, n − s) que passa na verificação da mesma forma, porque trocar o ponto do nonce pelo oposto dele não altera r. Com a P-256 no TLS, ninguém se importa com isso. No Bitcoin, isso fez diferença: a gêmea gerava um ID de transação diferente para a mesma transação, e serviços que acompanhavam pagamentos pelo ID podiam ser enganados. O Bitcoin Core 0.11.1 (2015) fez o nó “exigir a codificação canônica ‘low-s’ para assinaturas ECDSA ao retransmitir ou minerar” (notas de versão), uma política de retransmissão, e não uma regra de consenso. O Ethereum foi além com a EIP-2 (Homestead, 2016): “Todas as assinaturas de transação cujo valor s seja maior que secp256k1n/2 passam a ser consideradas inválidas”, enquanto “o contrato pré-compilado de recuperação do ECDSA permanece inalterado e continuará aceitando valores s altos”.
As bibliotecas genéricas não seguem essas regras; as de blockchain seguem. O laboratório assinou 1.000 mensagens aleatórias com o OpenSSL do Node.js e depois verificou uma assinatura high-S e a gêmea low-S dela com três 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
Cerca de metade das assinaturas do OpenSSL é high-S; o OpenSSL aceita as duas formas, a @noble/curves rejeita high-S na secp256k1, a menos que receba instrução em contrário, e o ethers se recusa até a construir um objeto de assinatura a partir de uma delas. Um sistema que assina com uma biblioteca de uso geral ou um HSM e envia para uma blockchain precisa normalizar: se s > n/2, substitua s por n − s e inverta o bit de recuperação. O Google Cloud KMS documenta que as chaves secp256k1 dele retornam apenas assinaturas low-S; a documentação do ECC_SECG_P256K1 do AWS KMS não faz essa promessa, então normalize a saída dele você mesmo. As codificações DER e bruta em que essas assinaturas trafegam são tratadas em formatos de assinatura ECDSA.
Recuperação da chave pública
Com uma assinatura, o hash da mensagem e um ou dois bits extras, é possível calcular a chave pública do signatário; por isso, as transações do Ethereum não carregam chave pública nenhuma, e as mensagens assinadas do Bitcoin são verificadas contra um endereço. Os bits extras são o ID de recuperação (v, yParity ou o byte de cabeçalho de uma assinatura de mensagem do Bitcoin). Como a recuperação funciona, o que pode dar errado nela e como o ecrecover se comporta é explicado em recuperação da chave pública ECDSA.
Onde há suporte e onde não há
| Plataforma | secp256k1 |
|---|---|
WebCrypto (navegadores, Deno, crypto.subtle do Node) | Não; use @noble/curves ou um build WASM da libsecp256k1 |
OpenSSL e crypto do Node.js | Sim, com código genérico (mais lento) |
| BoringSSL | Não: ele inclui apenas P-224, P-256, P-384 e P-521 |
| Java (SunEC) | Removida no JDK 16: Curve not supported: secp256k1; use o Bouncy Castle |
| Apple CryptoKit, Secure Enclave, Android Keystore, TPM 2.0, cartões PIV | Não |
| KMS de nuvem | AWS KMS ECC_SECG_P256K1; Google Cloud KMS EC_SIGN_SECP256K1_SHA256 (somente com proteção HSM); Azure Key Vault P-256K com ES256K |
| TLS 1.3, X.509 na Web PKI, WebAuthn | Não |
Essa lacuna no hardware seguro de celulares e notebooks é o motivo pelo qual as carteiras móveis guardam chaves secp256k1 em software (protegidas por uma chave que, essa sim, fica no hardware) ou dependem de uma carteira de hardware separada.
Erros comuns
- Calcular o hash com SHA3-256 em vez de Keccak-256 no Ethereum, ou com SHA-256 simples em vez do SHA-256 duplo que o Bitcoin usa em mensagens e transações. A assinatura é verificada contra outra mensagem, e a recuperação devolve um endereço diferente, de aparência válida.
- Enviar assinaturas high-S do OpenSSL, de um HSM ou de um KMS de nuvem para o Ethereum ou para a rede Bitcoin. Normalize antes.
- Misturar chaves comprimidas e não comprimidas ao derivar endereços Bitcoin.
- Tamanho em bytes do formato bruto. Um
r ‖ sde 64 bytes e umr ‖ s ‖ vde 65 bytes são formatos diferentes; as bibliotecas avisam isso em alto e bom som, como no erro invalid raw signature length do ethers. - Supor que a plataforma oferece suporte. Código que funcionava no Java 11 falha no Java 17; o WebCrypto nunca teve essa curva.
- Usar a secp256k1 onde nada a exige. Para tokens, TLS, identidade de dispositivos ou chaves de acesso (passkeys), ela não traz benefício nenhum e faz perder o suporte de hardware.
Fontes
- SEC 2 v2.0: parâmetros de domínio recomendados para curvas elípticas, §2.4.1; SEC 1 v2.0, §2.3.3 (codificação de pontos)
- NIST SP 800-186, apêndice H.2
- SafeCurves: tabelas de rho, discriminante e twist
- Gallant, Lambert, Vanstone, “Faster Point Multiplication on Elliptic Curves with Efficient Endomorphisms”, CRYPTO 2001; Hal Finney, Speeding up signature verification (2011); PR #20147 do Bitcoin Core
- Notas de versão do Bitcoin Core 0.11.1 (política de retransmissão low-S); EIP-2
- RFC 8812 (
ES256K) - Serviços de chaves: especificações de chave do AWS KMS, algoritmos do Google Cloud KMS, tipos de chave do Azure Key Vault