跳到主要内容
ecdsa.com
C

libsecp256k1 (Bitcoin Core)

Bitcoin Core 的 libsecp256k1,secp256k1 的参考实现:签名是不透明的结构体,可序列化为紧凑格式或严格 DER;采用 RFC 6979 nonce,签名和验证两端都要求 low-S,并支持公钥恢复和 BIP-340 Schnorr 签名。
许可证
MIT
检查的版本
0.5.0 ·
源代码
github.com/bitcoin-core/secp256k1
文档
github.com/bitcoin-core/secp256k1/tree/master/include

兼容性概览

ECDSA

ECDSA 曲线
secp256k1
EdDSA
none
公钥格式
sec1-uncompressedsec1-compressed
私钥格式
raw-scalar

libsecp256k1 只实现一条曲线,但实现得非常严谨:常量时间签名、详尽的测试,以及对部分域运算的形式化验证。Bitcoin Core、许多钱包,以及 coincurve 和 go-ethereum 的 cgo 后端等绑定都在使用它。本页的事实基于 Alpine 3.24.2 的软件包检查得出,即启用了 recovery、extrakeys 和 schnorrsig 模块的 0.5.0 版;当时上游已发布到 0.8.0。

签名保存在不透明的结构体中,编码在序列化时选择——serialize_compact 给出 64 字节的 r‖s,serialize_der 给出 DER——解析同样是显式的。parse_der 很严格:它接受最简 DER,而拒绝了 r 带有多余前导零的同一签名,这遵循的是比特币的 BIP-66 规则。历史上那些不严格的签名,需要使用仓库 contrib 目录中保留的宽松解析器。

签名的输入是 32 字节的哈希——这个库从不对你的消息计算哈希——它采用 RFC 6979(本次检查的结果与一个独立实现一致),并且总是生成 low-S 签名。严格之处在于验证:由有效签名镜像得到的 high-S 形式验证失败,而 secp256k1_ecdsa_signature_normalize 正是为修正通用签名方生成的签名而提供的。如果来自 OpenSSL、Java 或云 KMS 的签名大约只有一半能验证通过,原因就是 high-S;示例在验证前先做规范化。

公钥是 SEC1 点,未压缩为 65 字节,压缩为 33 字节;64 字节的 x‖y 会被拒绝,也不支持 SPKI、PEM 或 JWK。recovery 模块从签名中恢复公钥——这是以太坊地址的基础——schnorrsig 模块则实现了 BIP-340。可以用比特币消息验证工具检查比特币消息签名,用 Nostr 事件验证工具检查 Nostr 事件。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/libsecp256k1/run.sh 于2026年10月20日在容器镜像 alpine:3.24.2 中运行了 libsecp256k1 (Bitcoin Core) 0.5.0;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    structured

    签名是不透明的结构体;serialize_compact 写出 64 字节的 r‖s,serialize_der 写出 DER。

    检查记录
    fact ecdsa-signature-encoding = structured
    evidence ecdsa-signature-encoding: secp256k1_ecdsa_signature is opaque: serialize_compact gives 64 bytes, serialize_der 70 bytes
  • ECDSA 的 nonce

    rfc6979

    RFC 6979:签名与一个独立的 RFC 6979 实现给出的签名(s 已规范化)相同。

    检查记录
    fact ecdsa-nonce = rfc6979
    evidence ecdsa-nonce: two signatures identical: yes; equal to the reference RFC 6979 signature with s normalized: yes
  • 签名时的 low-S

    low-s

    64 个签名中没有 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = low-s
  • 验证时的 high-S

    rejects-high-s

    secp256k1_ecdsa_verify 拒绝了 (r, n − s);经 secp256k1_ecdsa_signature_normalize 处理后验证通过。

    检查记录
    fact ecdsa-low-s-verify = rejects-high-s
    evidence ecdsa-low-s-verify: verify on (r, n - s): 0; signature_normalize reports it was high: 1; normalized verifies: 1
  • 签名调用对哪些内容做哈希

    takes-digest

    secp256k1_ecdsa_sign 接收 32 字节的消息哈希。

    检查记录
    fact ecdsa-prehash = takes-digest
    evidence ecdsa-prehash: secp256k1_ecdsa_sign takes a 32-byte message hash (msghash32)
  • 默认哈希

    none

    签名和验证过程中都不计算哈希。

    检查记录
    fact ecdsa-default-hash = none
  • DER 严格程度

    rejects-non-minimal

    parse_der 接受最简 DER,拒绝了 r 带有多余前导零字节的编码。

    检查记录
    fact der-strictness = rejects-non-minimal
    evidence der-strictness: parse_der on minimal DER: 1; with a redundant leading zero in r: 0
  • 格式错误的 ECDSA 签名

    returns-error

    格式错误的输入在解析阶段就会失败(parse_der 返回 0);verify 只接收已解析的签名。

    检查记录
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: parsing is a separate call: parse_der on 3 bytes returned 0; verify only ever sees parsed signatures
  • ECDSA 曲线

    secp256k1

    仅支持 secp256k1。

    检查记录
    fact ecdsa-curves = secp256k1
  • 公钥恢复

    supported

    recovery 模块从可恢复签名中恢复出了签名者的公钥。

    检查记录
    fact public-key-recovery = supported
  • Schnorr 签名

    BIP-340

    schnorrsig 模块生成了 BIP-340 签名,并用仅含 x 坐标的公钥(x-only)验证通过。

    检查记录
    fact schnorr = BIP-340
  • EdDSA

    none

    不支持 EdDSA。

    检查记录
    fact eddsa = none
  • 后量子算法

    none

    不支持后量子算法。

    检查记录
    fact pqc = none
  • 公钥格式

    sec1-uncompressedsec1-compressed

    ec_pubkey_parse 接受 65 字节的 SEC1 未压缩点和 33 字节的 SEC1 压缩点;64 字节的 x‖y 被拒绝。

    检查记录
    fact public-key-formats = sec1-uncompressed, sec1-compressed
    evidence public-key-formats: ec_pubkey_parse: 65-byte uncompressed 1, 33-byte compressed 1, 64-byte x||y 0
  • 私钥格式

    raw-scalar

    私钥是 32 字节的原始标量。

    检查记录
    fact private-key-formats = raw-scalar

用法

对 32 字节的哈希签名,按从别处收到时的形式解析签名,规范化 s 后再验证。
#include <secp256k1.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
    unsigned char seckey[32], msghash[32];
    memset(seckey, 0x11, 32);   /* use a random 32-byte key in real code */
    memset(msghash, 0x22, 32);  /* the caller hashes: libsecp256k1 signs a 32-byte digest */

    secp256k1_pubkey pub;
    secp256k1_ecdsa_signature sig;
    if (!secp256k1_ec_pubkey_create(ctx, &pub, seckey) || !secp256k1_ecdsa_sign(ctx, &sig, msghash, seckey, NULL, NULL)) return 1;

    /* A signature from elsewhere: parse (strict DER or 64-byte compact), then normalize s,
       because verify rejects high-S signatures that OpenSSL or Java produce half the time. */
    unsigned char compact[64];
    secp256k1_ecdsa_signature_serialize_compact(ctx, compact, &sig);
    secp256k1_ecdsa_signature parsed;
    if (!secp256k1_ecdsa_signature_parse_compact(ctx, &parsed, compact)) return 1;
    secp256k1_ecdsa_signature_normalize(ctx, &parsed, &parsed);

    printf("verified: %d\n", secp256k1_ecdsa_verify(ctx, &parsed, msghash, &pub));
    secp256k1_context_destroy(ctx);
    return 0;
}

代码片段测试工具会在容器镜像 alpine:3.24.2 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
apk add --no-cache -q build-base libsecp256k1-dev=0.5.0-r1 >/dev/null
命令
cc -o usage usage.c -lsecp256k1 && ./usage
输出必须匹配(正则表达式)
verified: 1

库