Pular para o conteúdo
ecdsa.com
← Todos os artigos

secp256k1 explicada: a curva do Bitcoin e do Ethereum

y² = x³ + 7 sobre um primo de 256 bits. Conferimos cada constante do SEC 2 em poucas linhas de Python, mostramos o atalho em que a libsecp256k1 se apoia e testamos quais bibliotecas aceitam uma assinatura high-S, tudo executado em 20 de outubro de 2026.

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âmetroValorSignificado
p2²⁵⁶ − 2³² − 977 (0xffff…fffefffffc2f)Primo do campo; a forma dele torna barata a redução módulo p
a, b0, 7A curva y² = x³ + 7
Gx = 0x79be667e…16f81798, y = 0x483ada77…fb10d4b8Ponto base
n0xffff…fffebaaedce6af48a03bbfd25e8cd0364141Ordem de G, primo
h1Cofator: 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:

curve.py (trecho): verificações dos parâmetros do SEC 2, Python 3.14.8
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")
Saída (python:3.14.8-slim-trixie)
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:

curve.py (trecho): o endomorfismo
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))
Saída
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 SafeCurvessecp256k1O que isso significa para você
rho, transferência, rigidezaprovadaO logaritmo discreto é difícil, e as constantes têm explicação
discriminante CMreprovada (D = −3)É o discriminante pequeno que cria o endomorfismo; nenhum ataque decorre disso
segurança do twistaprovada, 2¹⁰⁹·⁵Menor que a da própria curva; só importa para código que pula a validação de pontos
ladder, fórmulas completasreprovadaCódigo de tempo constante e sem casos excepcionais exige mais cuidado que na Curve25519
indistinguibilidadereprovadaOs 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):

keys.py: uma chave, várias codificações (pycryptodome 3.23.0 para Keccak e RIPEMD-160)
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): 02 ou 03 para 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 ‖ y de 64 bytes, sem o prefixo 04. O Keccak-256 não é o SHA3-256: o padding é diferente, e usar hashlib.sha3_256 produz 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:

lows.mjs (trecho): node 24.22.0, @noble/curves 2.4.0, ethers 6.17.0
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 })}`);
Saída (node:24.22.0-bookworm-slim)
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á

Plataformasecp256k1
WebCrypto (navegadores, Deno, crypto.subtle do Node)Não; use @noble/curves ou um build WASM da libsecp256k1
OpenSSL e crypto do Node.jsSim, com código genérico (mais lento)
BoringSSLNã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 PIVNão
KMS de nuvemAWS 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, WebAuthnNã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 ‖ s de 64 bytes e um r ‖ s ‖ v de 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