本文へスキップ
ecdsa.com
C

libsecp256k1 (Bitcoin Core)

Bitcoin Core の libsecp256k1 は、secp256k1 のリファレンス実装です。署名は不透明な構造体で、コンパクト形式か厳格な DER にシリアライズされます。nonce は RFC 6979 に従い、署名側でも検証側でも low-S を適用し、公開鍵の復元と BIP-340 Schnorr 署名にも対応しています。
ライセンス
MIT
確認したバージョン
0.5.0 ·
ソースコード
github.com/bitcoin-core/secp256k1
ドキュメント
github.com/bitcoin-core/secp256k1/tree/master/include

互換性の概要

ECDSA

EdDSA
none
公開鍵の形式
sec1-uncompressedsec1-compressed

libsecp256k1 は 1 つの曲線だけを、細心の注意を払って実装しています。署名は定数時間で行われ、網羅的なテストがあり、有限体演算の一部は形式検証されています。Bitcoin Core、多くのウォレット、coincurve などのバインディング、go-ethereum の cgo バックエンドがこのライブラリを使っています。ここに挙げる事実は、Alpine 3.24.2 のパッケージ(バージョン 0.5.0、recovery、extrakeys、schnorrsig モジュール付き)で確認したものです。当時、アップストリームは 0.8.0 に達していました。

署名は不透明な構造体に格納され、エンコーディングはシリアライズの時点で選びます。serialize_compact は 64 バイトの r‖s を、serialize_der は DER を返し、解析も同様に明示的です。parse_der は厳格で、Bitcoin の BIP-66 のルールに従い、最小限の DER は受け入れましたが、r に冗長な先頭のゼロを付けた同じ署名は拒否しました。過去の緩い形式の署名には、リポジトリの 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 モジュールは署名から公開鍵を復元し(Ethereum のアドレスはこの仕組みに基づきます)、schnorrsig モジュールは BIP-340 を実装しています。Bitcoin のメッセージ署名は Bitcoin メッセージ検証ツールで、Nostr のイベントは Nostr イベント検証ツールで確認してください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/libsecp256k1/run.sh がコンテナイメージ 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 です。署名は、s を正規化した状態で、独立した RFC 6979 実装の署名と一致しました。

    チェックによる記録
    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 座標のみの公開鍵で検証できました。

    チェックによる記録
    fact schnorr = BIP-340
  • EdDSA

    none

    EdDSA には対応していません。

    チェックによる記録
    fact eddsa = none
  • ポスト量子アルゴリズム

    none

    ポスト量子アルゴリズムには対応していません。

    チェックによる記録
    fact pqc = none
  • 公開鍵の形式

    sec1-uncompressedsec1-compressed

    ec_pubkey_parse は、65 バイトの非圧縮形式と 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

ライブラリ