55c202516d
Add libsecp256k1 v0.7.1 as src/secp256k1 submodule and introduce crypto_ecdsa / crypto_ecdh wrappers as drop-in replacements for the OpenSSL ECDSA_verify / ECDSA_sign / ECDH_compute_key call sites used by key.cpp and smessage.cpp. Wrappers preserve on-chain compatibility (lax DER parsing, 65-byte recoverable compact sigs, SEC1 priv-key DER round-trip, raw-X ECDH output for smsg KDF). CMake wires the submodule and new sources into the build. Mid-refactor; landing as a checkpoint before stacking sync-pipeline work on top. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
122 lines
5.7 KiB
C++
122 lines
5.7 KiB
C++
// Copyright (c) 2026 The Triangles developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
#ifndef TRIANGLES_CRYPTO_ECDSA_H
|
|
#define TRIANGLES_CRYPTO_ECDSA_H
|
|
|
|
#include <cstddef>
|
|
|
|
/**
|
|
* Verify a DER-encoded secp256k1 ECDSA signature using libsecp256k1.
|
|
*
|
|
* Drop-in replacement for OpenSSL's
|
|
* ECDSA_verify(0, hash, 32, sig, sig_len, pkey)
|
|
* with one important caveat baked in: the DER input is parsed *laxly*
|
|
* (Bitcoin Core's `lax_der_parsing` algorithm), so historical non-canonical
|
|
* encodings already on chain — extra padding, leading zeros, length-byte
|
|
* quirks that OpenSSL's permissive ASN.1 reader once accepted — continue
|
|
* to verify. Strict-DER-only parsing here would silently fork the chain.
|
|
*
|
|
* High-S signatures are accepted (libsecp256k1's verify behaviour by default).
|
|
* No malleability check is applied; that is policy and lives elsewhere.
|
|
*
|
|
* @param hash32 32-byte message hash to verify against.
|
|
* @param sig DER-encoded signature bytes.
|
|
* @param sig_len Length of `sig`.
|
|
* @param pubkey Serialized public key (33 bytes compressed or 65 uncompressed).
|
|
* @param pubkey_len 33 or 65; any other length fails immediately.
|
|
* @return true iff the signature is valid for (hash32, pubkey).
|
|
*/
|
|
bool ECDSA_verify_secp256k1(const unsigned char hash32[32],
|
|
const unsigned char* sig, std::size_t sig_len,
|
|
const unsigned char* pubkey, std::size_t pubkey_len);
|
|
|
|
/**
|
|
* Sign `hash32` with `privkey32` and write a DER-encoded signature to `out`.
|
|
*
|
|
* libsecp256k1 uses RFC 6979 deterministic nonces, so signature bytes will
|
|
* differ from OpenSSL's random-nonce output for the same key+hash, but any
|
|
* resulting signature is equally valid. Low-S is enforced automatically.
|
|
*
|
|
* @param out Output buffer; must be at least `*out_len` bytes.
|
|
* libsecp256k1 produces at most 72 bytes of DER.
|
|
* @param out_len In: capacity of `out`. Out: bytes actually written.
|
|
* @param hash32 32-byte message hash to sign.
|
|
* @param privkey32 32-byte secret scalar.
|
|
* @return true on success.
|
|
*/
|
|
bool ECDSA_sign_secp256k1(unsigned char* out, std::size_t* out_len,
|
|
const unsigned char hash32[32],
|
|
const unsigned char privkey32[32]);
|
|
|
|
/**
|
|
* Produce a 65-byte recoverable compact signature.
|
|
*
|
|
* Output layout matches the existing wire format:
|
|
* out[0] = 27 + recid + (fCompressed ? 4 : 0)
|
|
* out[1..33) = R (big-endian, 32 bytes)
|
|
* out[33..65) = S (big-endian, 32 bytes)
|
|
*
|
|
* @param out65 65-byte output buffer.
|
|
* @param hash32 32-byte message hash to sign.
|
|
* @param privkey32 32-byte secret scalar.
|
|
* @param fCompressed Whether the matching public key is compressed; affects
|
|
* the recid offset in the header byte.
|
|
* @return true on success.
|
|
*/
|
|
bool ECDSA_sign_compact_secp256k1(unsigned char out65[65],
|
|
const unsigned char hash32[32],
|
|
const unsigned char privkey32[32],
|
|
bool fCompressed);
|
|
|
|
/**
|
|
* Recover the signing public key from a 65-byte compact signature (as produced
|
|
* by ECDSA_sign_compact_secp256k1) and a message hash.
|
|
*
|
|
* The header byte's "compressed" flag determines whether the recovered key is
|
|
* serialized as 33 bytes (compressed) or 65 bytes (uncompressed).
|
|
*
|
|
* @param pubkey_out Output buffer; needs at least 65 bytes capacity.
|
|
* @param pubkey_len_out Receives the actual serialized length (33 or 65).
|
|
* @param hash32 32-byte message hash that was signed.
|
|
* @param sig65 65-byte compact signature.
|
|
* @return true if recovery succeeded.
|
|
*/
|
|
bool ECDSA_recover_compact_secp256k1(unsigned char* pubkey_out,
|
|
std::size_t* pubkey_len_out,
|
|
const unsigned char hash32[32],
|
|
const unsigned char sig65[65]);
|
|
|
|
/** Return true iff `privkey32` is a valid secp256k1 secret (in (0, n)). */
|
|
bool ECDSA_seckey_verify_secp256k1(const unsigned char privkey32[32]);
|
|
|
|
/** Return true iff `pubkey/pubkey_len` parses as a valid secp256k1 point. */
|
|
bool ECDSA_pubkey_verify_secp256k1(const unsigned char* pubkey, std::size_t pubkey_len);
|
|
|
|
/**
|
|
* Derive the public key for `privkey32` and serialize it.
|
|
* @param out Output buffer; must be at least 65 bytes.
|
|
* @param out_len_out Receives the actual length (33 or 65).
|
|
* @param privkey32 32-byte secret scalar.
|
|
* @param fCompressed Whether to serialize compressed (33B) or uncompressed (65B).
|
|
* @return true on success.
|
|
*/
|
|
bool ECDSA_pubkey_from_privkey_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
|
const unsigned char privkey32[32],
|
|
bool fCompressed);
|
|
|
|
/**
|
|
* SEC1/RFC-5915 DER ECPrivateKey encoder/decoder for the secp256k1 curve.
|
|
* Output bytes match the layout produced by OpenSSL's i2d_ECPrivateKey on this
|
|
* curve (compressed = 214 bytes, uncompressed = 279 bytes), so wallet.dat
|
|
* records written by previous OpenSSL-EC builds remain readable, and records
|
|
* we write remain readable by older OpenSSL-based builds.
|
|
*/
|
|
bool ECDSA_privkey_export_der_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
|
const unsigned char privkey32[32],
|
|
bool fCompressed);
|
|
bool ECDSA_privkey_import_der_secp256k1(unsigned char privkey32_out[32],
|
|
const unsigned char* der, std::size_t der_len);
|
|
|
|
#endif // TRIANGLES_CRYPTO_ECDSA_H
|