coincurve は libsecp256k1 を CFFI でラップし、ビルド済みの wheel を配布しています。そのため Python のコードは、コンパイラーなしで Bitcoin Core と同じ定数時間の secp256k1 実装を使えます。バージョン 21.0.0 が wheel を公開しているのは CPython 3.10〜3.13 だけです。Python 3.14 では pip がソースからのビルドに切り替わり、C のツールチェーンがないスリムなイメージでは失敗します。チェックを Python 3.12 で実行したのはこのためです。
デフォルト設定は Ethereum ではなく Bitcoin 向けです。sign() はメッセージを SHA-256 でハッシュし、RFC 6979 の nonce を使い(チェックでは独立した参照用の署名実装と一致しました)、s が最初から low-S になっている DER 署名を返します。Ethereum、Nostr、多くのウォレットは keccak-256 でハッシュするか 32 バイトのダイジェストに署名するため、hasher を渡すか hasher=None を指定してください。デフォルトのままにすると、一見有効でも誤ったハッシュに対する署名が生成されます。
検証は libsecp256k1 の厳格さをそのまま受け継いでいます。有効な署名の s を n − s に置き換えた high-S 形式に対して、verify() は False を返しました。そのため、OpenSSL、Java、クラウドの KMS による署名は、先に s を正規化しない限り約半分の確率で失敗します。DER ではない入力に対しては、False を返さずに ValueError: The DER-encoded signature could not be parsed. が発生します。64 バイトの生の署名は DER に再エンコードしてから verify() に渡す必要があります。DER ⇄ 生形式の変換ツールで両方の形式を確認できます。
通常の ECDSA に加えて、sign_recoverable() は r ‖ s ‖ リカバリ ID からなる 65 バイトの形式を返し、PublicKey.from_signature_and_message() は公開鍵を復元します。Ethereum のアドレスを署名から導出するのはこの仕組みです。sign_schnorr() は Taproot や Nostr 向けの BIP-340 署名を生成します。公開鍵は SEC1 の点で、デフォルトでは圧縮形式です。秘密鍵は 32 バイトの生のスカラーで、to_pem()/to_der() は PKCS#8 を書き出します。
サンプルでは両方のモードを示しています。デフォルトの SHA-256 と、Ethereum が想定するとおりに keccak のダイジェストに復元可能な形で署名する方法です。結果は Bitcoin メッセージ検証ツールまたは Ethereum 署名検証ツールで確認してください。