WIP: migrate ECDSA/ECDH off OpenSSL EC to libsecp256k1

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>
This commit is contained in:
2026-04-29 02:55:18 -07:00
parent 4fa30abb4b
commit 55c202516d
12 changed files with 966 additions and 430 deletions
+3
View File
@@ -1,3 +1,6 @@
[submodule "src/tor/tor-src"]
path = src/tor/tor-src
url = https://gitlab.torproject.org/tpo/core/tor.git
[submodule "src/secp256k1"]
path = src/secp256k1
url = https://github.com/bitcoin-core/secp256k1
+24
View File
@@ -133,6 +133,30 @@ if(NOT TARGET RocksDB::rocksdb AND NOT TARGET PkgConfig::RocksDB)
message(STATUS "Found RocksDB (manual probe): ${ROCKSDB_LIBRARY}")
endif()
# libsecp256k1 — vendored as a git submodule under src/secp256k1. Provides
# ECDSA signing/verification, pubkey recovery (via the recovery module), and
# ECDH for secure messaging. Configure the submodule's build for our needs:
# only ECDH + recovery, none of the test/benchmark/extra-module bloat, and
# don't install (we link statically against the in-tree target).
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/src/secp256k1/CMakeLists.txt")
message(FATAL_ERROR
"src/secp256k1 is empty. Run: git submodule update --init --recursive")
endif()
set(SECP256K1_DISABLE_SHARED ON CACHE INTERNAL "")
set(SECP256K1_INSTALL OFF CACHE INTERNAL "")
set(SECP256K1_BUILD_BENCHMARK OFF CACHE INTERNAL "")
set(SECP256K1_BUILD_TESTS OFF CACHE INTERNAL "")
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE INTERNAL "")
set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE INTERNAL "")
set(SECP256K1_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_ECDH ON CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS OFF CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG OFF CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE INTERNAL "")
set(SECP256K1_ENABLE_MODULE_ELLSWIFT OFF CACHE INTERNAL "")
add_subdirectory(src/secp256k1 EXCLUDE_FROM_ALL)
if(BUILD_QT)
find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets)
find_package(Qt5 COMPONENTS LinguistTools QUIET)
+6
View File
@@ -42,6 +42,8 @@ set(CORE_SOURCES
bootstrap.cpp
checkpoints.cpp
crypter.cpp
crypto_ecdh.cpp
crypto_ecdsa.cpp
db.cpp
key.cpp
keystore.cpp
@@ -147,6 +149,10 @@ if(USE_ZMQ)
target_link_libraries(triangles_common PUBLIC PkgConfig::ZMQ)
endif()
# libsecp256k1 (mandatory) — ECDH / ECDSA replacement for OpenSSL EC.
# Provided by add_subdirectory(src/secp256k1) in the top-level CMakeLists.
target_link_libraries(triangles_common PUBLIC secp256k1)
# RocksDB (mandatory)
if(TARGET RocksDB::rocksdb)
target_link_libraries(triangles_common PUBLIC RocksDB::rocksdb)
+56
View File
@@ -0,0 +1,56 @@
// 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.
#include "crypto_ecdh.h"
#include <cstring>
#include <mutex>
#include <secp256k1.h>
#include <secp256k1_ecdh.h>
namespace {
// One process-wide context is sufficient for ECDH — no signing or verification
// flags needed. Created lazily on first use; libsecp256k1 contexts are
// thread-safe for read-only operations like ECDH.
secp256k1_context* GetECDHContext()
{
static std::once_flag once;
static secp256k1_context* ctx = nullptr;
std::call_once(once, []() {
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
});
return ctx;
}
// Hash function callback that returns the raw X coordinate of the shared
// point. Mirrors OpenSSL's ECDH_compute_key behaviour when the KDF is NULL.
int hash_xonly(unsigned char* output,
const unsigned char* x32,
const unsigned char* /*y32*/,
void* /*data*/)
{
std::memcpy(output, x32, 32);
return 1;
}
} // namespace
bool ECDH_xonly_secp256k1(unsigned char out32[32],
const unsigned char privkey32[32],
const unsigned char* pubkey,
std::size_t pubkey_len)
{
if (pubkey_len != 33 && pubkey_len != 65) return false;
secp256k1_context* ctx = GetECDHContext();
if (!ctx) return false;
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len))
return false;
return secp256k1_ecdh(ctx, out32, &pk, privkey32, hash_xonly, nullptr) == 1;
}
+31
View File
@@ -0,0 +1,31 @@
// 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_ECDH_H
#define TRIANGLES_CRYPTO_ECDH_H
#include <cstddef>
/**
* Compute the shared secret X coordinate via secp256k1 ECDH.
*
* Output matches OpenSSL's ECDH_compute_key(buf, 32, peer_pub, our_priv, NULL)
* — i.e. the raw X coordinate of the shared point, with no KDF applied. This
* preserves bit-for-bit compatibility with smessage's existing key derivation
* (which feeds the X coordinate into SHA-512 itself), so historical encrypted
* messages remain decryptable after the migration off OpenSSL EC.
*
* @param out32 32-byte buffer for the shared X coordinate.
* @param privkey32 32-byte secret scalar (big-endian).
* @param pubkey Peer public key, serialized as either 33 bytes (compressed)
* or 65 bytes (uncompressed).
* @param pubkey_len 33 or 65; any other length fails immediately.
* @return true on success, false if the public key is malformed or the
* private key is invalid (zero / >= curve order).
*/
bool ECDH_xonly_secp256k1(unsigned char out32[32],
const unsigned char privkey32[32],
const unsigned char* pubkey,
std::size_t pubkey_len);
#endif // TRIANGLES_CRYPTO_ECDH_H
+389
View File
@@ -0,0 +1,389 @@
// Copyright (c) 2026 The Triangles developers
// Copyright (c) 2015 Pieter Wuille (lax DER parser, MIT licence)
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "crypto_ecdsa.h"
#include <cstring>
#include <mutex>
#include <secp256k1.h>
#include <secp256k1_recovery.h>
namespace {
// Combined VERIFY + SIGN context. libsecp256k1 contexts are thread-safe for
// signing and verification once created. In libsecp256k1 >= 0.2 these flags
// are accepted but increasingly no-ops; passing both keeps us compatible with
// older versions still in distro packages.
secp256k1_context* GetEcdsaContext()
{
static std::once_flag once;
static secp256k1_context* ctx = nullptr;
std::call_once(once, []() {
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
});
return ctx;
}
// ─────────────────────────────────────────────────────────────────────────────
// Lax DER parser, vendored from Bitcoin Core (contrib/lax_der_parsing.c).
//
// libsecp256k1's strict parser rejects DER encodings that OpenSSL has
// historically accepted: non-minimal length bytes, extra leading zeros on R/S,
// negative integers, etc. Many such signatures already exist on chain. This
// parser tolerates them, normalises (R, S) into a 64-byte compact buffer, and
// hands that to libsecp256k1's compact-signature parser. Anything that still
// fails to fit (e.g. R or S exceeding 32 bytes after stripping leading zeros)
// is treated as zero so the verify call returns a clean failure rather than
// crashing.
// ─────────────────────────────────────────────────────────────────────────────
int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx,
secp256k1_ecdsa_signature* sig,
const unsigned char* input,
std::size_t inputlen)
{
std::size_t rpos, rlen, spos, slen;
std::size_t pos = 0;
std::size_t lenbyte;
unsigned char tmpsig[64] = {0};
int overflow = 0;
// Initialise sig with a parseable but invalid signature so the caller
// always gets a defined value back even on early-exit paths.
secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
// SEQUENCE tag.
if (pos == inputlen || input[pos] != 0x30) return 0;
pos++;
// SEQUENCE length (skipped — we trust the inner element lengths).
if (pos == inputlen) return 0;
lenbyte = input[pos++];
if (lenbyte & 0x80) {
lenbyte -= 0x80;
if (lenbyte > inputlen - pos) return 0;
pos += lenbyte;
}
// R: INTEGER tag.
if (pos == inputlen || input[pos] != 0x02) return 0;
pos++;
// R: length.
if (pos == inputlen) return 0;
lenbyte = input[pos++];
if (lenbyte & 0x80) {
lenbyte -= 0x80;
if (lenbyte > inputlen - pos) return 0;
while (lenbyte > 0 && input[pos] == 0) { pos++; lenbyte--; }
if (lenbyte >= sizeof(std::size_t)) return 0;
rlen = 0;
while (lenbyte > 0) { rlen = (rlen << 8) + input[pos]; pos++; lenbyte--; }
} else {
rlen = lenbyte;
}
if (rlen > inputlen - pos) return 0;
rpos = pos;
pos += rlen;
// S: INTEGER tag.
if (pos == inputlen || input[pos] != 0x02) return 0;
pos++;
// S: length.
if (pos == inputlen) return 0;
lenbyte = input[pos++];
if (lenbyte & 0x80) {
lenbyte -= 0x80;
if (lenbyte > inputlen - pos) return 0;
while (lenbyte > 0 && input[pos] == 0) { pos++; lenbyte--; }
if (lenbyte >= sizeof(std::size_t)) return 0;
slen = 0;
while (lenbyte > 0) { slen = (slen << 8) + input[pos]; pos++; lenbyte--; }
} else {
slen = lenbyte;
}
if (slen > inputlen - pos) return 0;
spos = pos;
// Strip leading zeros from R and place right-aligned in tmpsig[0..32).
while (rlen > 0 && input[rpos] == 0) { rlen--; rpos++; }
if (rlen > 32) {
overflow = 1;
} else {
std::memcpy(tmpsig + 32 - rlen, input + rpos, rlen);
}
// Strip leading zeros from S and place right-aligned in tmpsig[32..64).
while (slen > 0 && input[spos] == 0) { slen--; spos++; }
if (slen > 32) {
overflow = 1;
} else {
std::memcpy(tmpsig + 64 - slen, input + spos, slen);
}
if (!overflow) {
overflow = !secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
}
if (overflow) {
std::memset(tmpsig, 0, 64);
secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
}
return 1;
}
} // namespace
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)
{
if (sig_len == 0) return false;
if (pubkey_len != 33 && pubkey_len != 65) return false;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len))
return false;
secp256k1_ecdsa_signature parsed_sig;
if (!ecdsa_signature_parse_der_lax(ctx, &parsed_sig, sig, sig_len))
return false;
return secp256k1_ecdsa_verify(ctx, &parsed_sig, hash32, &pk) == 1;
}
bool ECDSA_sign_secp256k1(unsigned char* out, std::size_t* out_len,
const unsigned char hash32[32],
const unsigned char privkey32[32])
{
if (!out || !out_len) return false;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_ecdsa_signature sig;
if (!secp256k1_ecdsa_sign(ctx, &sig, hash32, privkey32, nullptr, nullptr))
return false;
return secp256k1_ecdsa_signature_serialize_der(ctx, out, out_len, &sig) == 1;
}
bool ECDSA_sign_compact_secp256k1(unsigned char out65[65],
const unsigned char hash32[32],
const unsigned char privkey32[32],
bool fCompressed)
{
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_ecdsa_recoverable_signature recsig;
if (!secp256k1_ecdsa_sign_recoverable(ctx, &recsig, hash32, privkey32, nullptr, nullptr))
return false;
int recid = -1;
if (!secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, &out65[1], &recid, &recsig))
return false;
if (recid < 0 || recid > 3) return false;
out65[0] = static_cast<unsigned char>(27 + recid + (fCompressed ? 4 : 0));
return true;
}
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])
{
if (!pubkey_out || !pubkey_len_out) return false;
int header = sig65[0];
if (header < 27 || header >= 35) return false;
bool fCompressed = (header >= 31);
int recid = (header - 27) & 0x3;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_ecdsa_recoverable_signature recsig;
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &recsig, &sig65[1], recid))
return false;
secp256k1_pubkey pk;
if (!secp256k1_ecdsa_recover(ctx, &pk, &recsig, hash32))
return false;
std::size_t out_len = fCompressed ? 33 : 65;
if (!secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &out_len, &pk,
fCompressed ? SECP256K1_EC_COMPRESSED
: SECP256K1_EC_UNCOMPRESSED))
return false;
*pubkey_len_out = out_len;
return true;
}
bool ECDSA_seckey_verify_secp256k1(const unsigned char privkey32[32])
{
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
return secp256k1_ec_seckey_verify(ctx, privkey32) == 1;
}
bool ECDSA_pubkey_verify_secp256k1(const unsigned char* pubkey, std::size_t pubkey_len)
{
if (pubkey_len != 33 && pubkey_len != 65) return false;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_pubkey pk;
return secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len) == 1;
}
bool ECDSA_pubkey_from_privkey_secp256k1(unsigned char* out, std::size_t* out_len_out,
const unsigned char privkey32[32],
bool fCompressed)
{
if (!out || !out_len_out) return false;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_create(ctx, &pk, privkey32))
return false;
std::size_t len = fCompressed ? 33 : 65;
if (!secp256k1_ec_pubkey_serialize(ctx, out, &len, &pk,
fCompressed ? SECP256K1_EC_COMPRESSED
: SECP256K1_EC_UNCOMPRESSED))
return false;
*out_len_out = len;
return true;
}
// ─────────────────────────────────────────────────────────────────────────────
// SEC1 / RFC-5915 DER codec for secp256k1 ECPrivateKey
//
// Vendored from Bitcoin Core (src/key.cpp), MIT-licensed. The decoder is lax
// about details (matches OpenSSL's d2i_ECPrivateKey lenience); the encoder
// writes the exact byte layout that OpenSSL's i2d_ECPrivateKey produces for
// this curve so wallet.dat records remain interchangeable across versions.
//
// Compressed pubkey: 214 bytes
// Uncompressed pubkey: 279 bytes
//
// The static templates below carry every byte except the 32-byte private
// scalar and the public key bytes, which are spliced into the precomputed
// offsets at encode time.
// ─────────────────────────────────────────────────────────────────────────────
namespace {
const unsigned char der_template_compressed[214] = {
0x30,0x81,0xD3,0x02,0x01,0x01,0x04,0x20,
/* private key (32 bytes) at offset 8 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0xA0,0x81,0x85,0x30,0x81,0x82,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48,
0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,
0x04,0x01,0x07,0x04,0x21,0x02,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,
0x62,0x95,0xCE,0x87,0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,
0x81,0x5B,0x16,0xF8,0x17,0x98,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,
0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x24,0x03,0x22,
0x00,
/* compressed pubkey (33 bytes) at offset 181 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
const unsigned char der_template_uncompressed[279] = {
0x30,0x82,0x01,0x13,0x02,0x01,0x01,0x04,0x20,
/* private key (32 bytes) at offset 9 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0xA0,0x81,0xA5,0x30,0x81,0xA2,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48,
0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,
0x04,0x01,0x07,0x04,0x41,0x04,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,
0x62,0x95,0xCE,0x87,0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,
0x81,0x5B,0x16,0xF8,0x17,0x98,0x48,0x3A,0xDA,0x77,0x26,0xA3,0xC4,0x65,0x5D,0xA4,
0xFB,0xFC,0x0E,0x11,0x08,0xA8,0xFD,0x17,0xB4,0x48,0xA6,0x85,0x54,0x19,0x9C,0x47,
0xD0,0x8F,0xFB,0x10,0xD4,0xB8,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,
0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x44,0x03,0x42,
0x00,
/* uncompressed pubkey (65 bytes) at offset 214 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0
};
} // namespace
bool ECDSA_privkey_export_der_secp256k1(unsigned char* out, std::size_t* out_len_out,
const unsigned char privkey32[32],
bool fCompressed)
{
if (!out || !out_len_out) return false;
secp256k1_context* ctx = GetEcdsaContext();
if (!ctx) return false;
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_create(ctx, &pk, privkey32))
return false;
if (fCompressed) {
std::memcpy(out, der_template_compressed, sizeof(der_template_compressed));
std::memcpy(out + 8, privkey32, 32);
std::size_t pub_len = 33;
if (!secp256k1_ec_pubkey_serialize(ctx, out + 181, &pub_len, &pk, SECP256K1_EC_COMPRESSED))
return false;
*out_len_out = sizeof(der_template_compressed);
} else {
std::memcpy(out, der_template_uncompressed, sizeof(der_template_uncompressed));
std::memcpy(out + 9, privkey32, 32);
std::size_t pub_len = 65;
if (!secp256k1_ec_pubkey_serialize(ctx, out + 214, &pub_len, &pk, SECP256K1_EC_UNCOMPRESSED))
return false;
*out_len_out = sizeof(der_template_uncompressed);
}
return true;
}
bool ECDSA_privkey_import_der_secp256k1(unsigned char privkey32_out[32],
const unsigned char* der, std::size_t der_len)
{
// Lax SEC1/RFC-5915 ECPrivateKey parser. We only need to find the OCTET
// STRING containing the private key scalar; everything else (curve params,
// optional public key) is informational. Mirrors Bitcoin Core's
// ec_privkey_import_der.
const unsigned char* end = der + der_len;
if (end < der + 1 || *(der++) != 0x30) return false;
// Outer SEQUENCE length — variable length encoding.
if (der >= end) return false;
int lenb = *(der++);
if (lenb < 0x80) {
// short form, ignore
} else {
int n = lenb & 0x7F;
if (n == 0 || n > 2) return false;
if (der + n > end) return false;
der += n;
}
// Version INTEGER (1).
if (der + 3 > end || der[0] != 0x02 || der[1] != 0x01 || der[2] != 0x01) return false;
der += 3;
// privateKey OCTET STRING (length 32).
if (der + 2 > end || der[0] != 0x04 || der[1] != 0x20) return false;
der += 2;
if (der + 32 > end) return false;
std::memcpy(privkey32_out, der, 32);
// Validate the result against the curve order; reject zero / >= n.
return ECDSA_seckey_verify_secp256k1(privkey32_out);
}
+121
View File
@@ -0,0 +1,121 @@
// 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
+269 -394
View File
@@ -1,213 +1,33 @@
// Copyright (c) 2009-2012 The Bitcoin developers
// 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.
#include <map>
#include <cstring>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/crypto.h> // OPENSSL_cleanse for secure wipe of secret bytes
#include <openssl/rand.h> // RAND_bytes for new-key entropy
#include "crypto_ecdsa.h"
#include "key.h"
// Generate a private key from just the secret parameter
int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key)
// ─────────────────────────────────────────────────────────────────────────────
// Order-of-generator constants (still used by CheckSignatureElement, the only
// caller into the BigEndian comparison helper below). Kept here so the file
// remains self-contained.
// ─────────────────────────────────────────────────────────────────────────────
namespace {
int CompareBigEndian(const unsigned char* c1, std::size_t c1len,
const unsigned char* c2, std::size_t c2len)
{
int ok = 0;
BN_CTX *ctx = NULL;
EC_POINT *pub_key = NULL;
if (!eckey) return 0;
const EC_GROUP *group = EC_KEY_get0_group(eckey);
if ((ctx = BN_CTX_new()) == NULL)
goto err;
pub_key = EC_POINT_new(group);
if (pub_key == NULL)
goto err;
if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx))
goto err;
EC_KEY_set_private_key(eckey,priv_key);
EC_KEY_set_public_key(eckey,pub_key);
ok = 1;
err:
if (pub_key)
EC_POINT_free(pub_key);
if (ctx != NULL)
BN_CTX_free(ctx);
return(ok);
}
// Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
// recid selects which key is recovered
// if check is non-zero, additional checks are performed
int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check)
{
if (!eckey) return 0;
int ret = 0;
BN_CTX *ctx = NULL;
BIGNUM *x = NULL;
BIGNUM *e = NULL;
BIGNUM *order = NULL;
BIGNUM *sor = NULL;
BIGNUM *eor = NULL;
BIGNUM *field = NULL;
EC_POINT *R = NULL;
EC_POINT *O = NULL;
EC_POINT *Q = NULL;
BIGNUM *rr = NULL;
BIGNUM *zero = NULL;
int n = 0;
int i = recid / 2;
const EC_GROUP *group = EC_KEY_get0_group(eckey);
if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; }
BN_CTX_start(ctx);
const BIGNUM *sig_r, *sig_s;
ECDSA_SIG_get0(ecsig, &sig_r, &sig_s);
order = BN_CTX_get(ctx);
if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; }
x = BN_CTX_get(ctx);
if (!BN_copy(x, order)) { ret=-1; goto err; }
if (!BN_mul_word(x, i)) { ret=-1; goto err; }
if (!BN_add(x, x, sig_r)) { ret=-1; goto err; }
field = BN_CTX_get(ctx);
if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; }
if (BN_cmp(x, field) >= 0) { ret=0; goto err; }
if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; }
if (check)
{
if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; }
if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; }
}
if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
n = EC_GROUP_get_degree(group);
e = BN_CTX_get(ctx);
if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; }
if (8*msglen > n) BN_rshift(e, e, 8-(n & 7));
zero = BN_CTX_get(ctx);
BN_zero(zero);
if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; }
rr = BN_CTX_get(ctx);
if (!BN_mod_inverse(rr, sig_r, order, ctx)) { ret=-1; goto err; }
sor = BN_CTX_get(ctx);
if (!BN_mod_mul(sor, sig_s, rr, order, ctx)) { ret=-1; goto err; }
eor = BN_CTX_get(ctx);
if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; }
if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; }
if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; }
ret = 1;
err:
if (ctx) {
BN_CTX_end(ctx);
BN_CTX_free(ctx);
}
if (R != NULL) EC_POINT_free(R);
if (O != NULL) EC_POINT_free(O);
if (Q != NULL) EC_POINT_free(Q);
return ret;
}
void CKey::SetCompressedPubKey()
{
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
fCompressedPubKey = true;
}
void CKey::SetUnCompressedPubKey()
{
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_UNCOMPRESSED);
fCompressedPubKey = false;
}
EC_KEY* CKey::GetECKey()
{
return pkey;
}
void CKey::Reset()
{
fCompressedPubKey = false;
if (pkey != NULL)
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed");
fSet = false;
}
CKey::CKey()
{
pkey = NULL;
Reset();
}
CKey::CKey(const CKey& b)
{
pkey = EC_KEY_dup(b.pkey);
if (pkey == NULL)
throw key_error("CKey::CKey(const CKey&) : EC_KEY_dup failed");
fSet = b.fSet;
}
CKey& CKey::operator=(const CKey& b)
{
if (!EC_KEY_copy(pkey, b.pkey))
throw key_error("CKey::operator=(const CKey&) : EC_KEY_copy failed");
fSet = b.fSet;
return (*this);
}
CKey::~CKey()
{
EC_KEY_free(pkey);
}
bool CKey::IsNull() const
{
return !fSet;
}
bool CKey::IsCompressed() const
{
return fCompressedPubKey;
}
int CompareBigEndian(const unsigned char *c1, size_t c1len, const unsigned char *c2, size_t c2len) {
while (c1len > c2len) {
if (*c1)
return 1;
c1++;
c1len--;
}
while (c2len > c1len) {
if (*c2)
return -1;
c2++;
c2len--;
}
while (c1len > c2len) { if (*c1) return 1; c1++; c1len--; }
while (c2len > c1len) { if (*c2) return -1; c2++; c2len--; }
while (c1len > 0) {
if (*c1 > *c2)
return 1;
if (*c2 > *c1)
return -1;
c1++;
c2++;
c1len--;
if (*c1 > *c2) return 1;
if (*c2 > *c1) return -1;
c1++; c2++; c1len--;
}
return 0;
}
@@ -228,277 +48,332 @@ const unsigned char vchMaxModHalfOrder[32] = {
0xDF,0xE9,0x2F,0x46,0x68,0x1B,0x20,0xA0
};
const unsigned char vchZero[0] = {};
const unsigned char vchZero[1] = { 0 };
bool CKey::CheckSignatureElement(const unsigned char *vch, int len, bool half) {
return CompareBigEndian(vch, len, vchZero, 0) > 0 &&
CompareBigEndian(vch, len, half ? vchMaxModHalfOrder : vchMaxModOrder, 32) <= 0;
} // namespace
bool CKey::CheckSignatureElement(const unsigned char* vchIn, int len, bool half)
{
return CompareBigEndian(vchIn, len, vchZero, 0) > 0 &&
CompareBigEndian(vchIn, len, half ? vchMaxModHalfOrder : vchMaxModOrder, 32) <= 0;
}
// ─────────────────────────────────────────────────────────────────────────────
// Lifecycle
// ─────────────────────────────────────────────────────────────────────────────
void CKey::Reset()
{
OPENSSL_cleanse(vch, sizeof(vch));
vchPubKey.clear();
fSet = false;
fHavePrivKey = false;
fCompressedPubKey = false;
}
CKey::CKey()
{
std::memset(vch, 0, sizeof(vch));
vchPubKey.clear();
fSet = false;
fHavePrivKey = false;
fCompressedPubKey = false;
}
CKey::CKey(const CKey& b)
{
*this = b;
}
CKey& CKey::operator=(const CKey& b)
{
if (this == &b) return *this;
std::memcpy(vch, b.vch, sizeof(vch));
vchPubKey = b.vchPubKey;
fSet = b.fSet;
fHavePrivKey = b.fHavePrivKey;
fCompressedPubKey = b.fCompressedPubKey;
return *this;
}
CKey::~CKey()
{
OPENSSL_cleanse(vch, sizeof(vch));
}
bool CKey::IsNull() const { return !fSet; }
bool CKey::IsCompressed() const { return fCompressedPubKey; }
// ─────────────────────────────────────────────────────────────────────────────
// Compression toggle
//
// In the new model the pubkey is always cached at the current compression. If
// we hold the private key we can re-derive trivially; if we only hold a public
// key, callers don't toggle compression in practice in this codebase, so we
// just flip the flag and rely on the next SetPubKey/SetSecret to refresh the
// cache.
// ─────────────────────────────────────────────────────────────────────────────
void CKey::SetCompressedPubKey()
{
if (fCompressedPubKey) return;
fCompressedPubKey = true;
if (fSet && fHavePrivKey) {
std::size_t len = 33;
vchPubKey.resize(len);
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, /*fCompressed=*/true)) {
Reset();
return;
}
vchPubKey.resize(len);
}
}
void CKey::SetUnCompressedPubKey()
{
if (!fCompressedPubKey && fSet) return;
fCompressedPubKey = false;
if (fSet && fHavePrivKey) {
std::size_t len = 65;
vchPubKey.resize(len);
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, /*fCompressed=*/false)) {
Reset();
return;
}
vchPubKey.resize(len);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Key generation / load / store
// ─────────────────────────────────────────────────────────────────────────────
void CKey::MakeNewKey(bool fCompressed)
{
if (!EC_KEY_generate_key(pkey))
throw key_error("CKey::MakeNewKey() : EC_KEY_generate_key failed");
if (fCompressed)
SetCompressedPubKey();
// Sample 32 bytes of entropy and reject any that fall outside (0, n).
// Probability of needing a retry is ~2^-128.
do {
if (RAND_bytes(vch, sizeof(vch)) != 1)
throw key_error("CKey::MakeNewKey() : RAND_bytes failed");
} while (!ECDSA_seckey_verify_secp256k1(vch));
fSet = true;
fHavePrivKey = true;
fCompressedPubKey = fCompressed;
std::size_t len = fCompressed ? 33 : 65;
vchPubKey.resize(len);
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, fCompressed)) {
Reset();
throw key_error("CKey::MakeNewKey() : failed to derive public key");
}
vchPubKey.resize(len);
}
bool CKey::SetPrivKey(const CPrivKey& vchPrivKey)
{
const unsigned char* pbegin = &vchPrivKey[0];
if (d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
{
// In testing, d2i_ECPrivateKey can return true
// but fill in pkey with a key that fails
// EC_KEY_check_key, so:
if (EC_KEY_check_key(pkey))
{
fSet = true;
return true;
}
}
// If vchPrivKey data is bad d2i_ECPrivateKey() can
// leave pkey in a state where calling EC_KEY_free()
// crashes. To avoid that, set pkey to NULL and
// leak the memory (a leak is better than a crash)
pkey = NULL;
unsigned char raw[32];
if (!ECDSA_privkey_import_der_secp256k1(raw, &vchPrivKey[0], vchPrivKey.size())) {
OPENSSL_cleanse(raw, sizeof(raw));
Reset();
return false;
}
// Carry the compressed flag out of the DER blob. The two valid sizes
// produced by ECDSA_privkey_export_der_secp256k1 are 214 (compressed) and
// 279 (uncompressed); foreign DER blobs are best-effort but those two
// cover every record this codebase has ever written.
bool fCompressed = (vchPrivKey.size() == 214);
CSecret secret(raw, raw + 32);
OPENSSL_cleanse(raw, sizeof(raw));
return SetSecret(secret, fCompressed);
}
bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed)
{
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::SetSecret() : EC_KEY_new_by_curve_name failed");
if (vchSecret.size() != 32)
throw key_error("CKey::SetSecret() : secret must be 32 bytes");
BIGNUM *bn = BN_bin2bn(&vchSecret[0],32,BN_new());
if (bn == NULL)
throw key_error("CKey::SetSecret() : BN_bin2bn failed");
if (!EC_KEY_regenerate_key(pkey,bn))
{
BN_clear_free(bn);
throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed");
}
BN_clear_free(bn);
if (!ECDSA_seckey_verify_secp256k1(&vchSecret[0]))
throw key_error("CKey::SetSecret() : secret is not a valid scalar");
std::memcpy(vch, &vchSecret[0], 32);
fSet = true;
if (fCompressed || fCompressedPubKey)
SetCompressedPubKey();
fHavePrivKey = true;
// Preserve sticky-compression behaviour from the OpenSSL implementation:
// if either the explicit argument or the previously-set flag is true,
// the result is compressed.
bool fComp = fCompressed || fCompressedPubKey;
fCompressedPubKey = fComp;
std::size_t len = fComp ? 33 : 65;
vchPubKey.resize(len);
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, fComp)) {
Reset();
return false;
}
vchPubKey.resize(len);
return true;
}
CSecret CKey::GetSecret(bool& fCompressed) const
{
CSecret vchRet;
vchRet.resize(32);
const BIGNUM *bn = EC_KEY_get0_private_key(pkey);
int nBytes = BN_num_bytes(bn);
if (bn == NULL)
throw key_error("CKey::GetSecret() : EC_KEY_get0_private_key failed");
int n=BN_bn2bin(bn,&vchRet[32 - nBytes]);
if (n != nBytes)
throw key_error("CKey::GetSecret(): BN_bn2bin failed");
if (!fSet || !fHavePrivKey)
throw key_error("CKey::GetSecret() : key is not set or has no private component");
CSecret out(vch, vch + 32);
fCompressed = fCompressedPubKey;
return vchRet;
return out;
}
CPrivKey CKey::GetPrivKey() const
{
int nSize = i2d_ECPrivateKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey failed");
CPrivKey vchPrivKey(nSize, 0);
unsigned char* pbegin = &vchPrivKey[0];
if (i2d_ECPrivateKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey returned unexpected size");
return vchPrivKey;
if (!fSet || !fHavePrivKey)
throw key_error("CKey::GetPrivKey() : key is not set or has no private component");
// Max possible output: 279 bytes (uncompressed).
CPrivKey out(279, 0);
std::size_t out_len = out.size();
if (!ECDSA_privkey_export_der_secp256k1(&out[0], &out_len, vch, fCompressedPubKey))
throw key_error("CKey::GetPrivKey() : DER export failed");
out.resize(out_len);
return out;
}
bool CKey::SetPubKey(const CPubKey& vchPubKey)
bool CKey::SetPubKey(const CPubKey& cpub)
{
const unsigned char* pbegin = &vchPubKey.vchPubKey[0];
if (o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size()))
{
fSet = true;
if (vchPubKey.vchPubKey.size() == 33)
SetCompressedPubKey();
return true;
}
pkey = NULL;
const std::vector<unsigned char>& vchPub = cpub.vchPubKey;
if (vchPub.size() != 33 && vchPub.size() != 65) {
Reset();
return false;
}
if (!ECDSA_pubkey_verify_secp256k1(&vchPub[0], vchPub.size())) {
Reset();
return false;
}
vchPubKey = vchPub;
fSet = true;
fHavePrivKey = false;
fCompressedPubKey = (vchPub.size() == 33);
return true;
}
CPubKey CKey::GetPubKey() const
{
int nSize = i2o_ECPublicKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey failed");
std::vector<unsigned char> vchPubKey(nSize, 0);
unsigned char* pbegin = &vchPubKey[0];
if (i2o_ECPublicKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey returned unexpected size");
return CPubKey(vchPubKey);
}
// ─────────────────────────────────────────────────────────────────────────────
// Sign / verify / recover (all delegate to crypto_ecdsa wrappers)
// ─────────────────────────────────────────────────────────────────────────────
bool CKey::Sign(uint256 hash, std::vector<unsigned char>& vchSig)
{
vchSig.clear();
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
if (sig == NULL)
if (!fSet || !fHavePrivKey) return false;
// libsecp256k1's max DER output is 72 bytes; allocate that and shrink.
vchSig.resize(72);
std::size_t sig_len = vchSig.size();
if (!ECDSA_sign_secp256k1(&vchSig[0], &sig_len,
reinterpret_cast<const unsigned char*>(&hash),
vch))
{
vchSig.clear();
return false;
BN_CTX *ctx = BN_CTX_new();
BN_CTX_start(ctx);
const EC_GROUP *group = EC_KEY_get0_group(pkey);
BIGNUM *order = BN_CTX_get(ctx);
BIGNUM *halforder = BN_CTX_get(ctx);
EC_GROUP_get_order(group, order, ctx);
BN_rshift1(halforder, order);
const BIGNUM *sig_r, *sig_s;
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
if (BN_cmp(sig_s, halforder) > 0) {
// enforce low S values, by negating the value (modulo the order) if above order/2.
BIGNUM *new_s = BN_new();
BN_sub(new_s, order, sig_s);
BIGNUM *dup_r = BN_dup(sig_r);
ECDSA_SIG_set0(sig, dup_r, new_s);
}
BN_CTX_end(ctx);
BN_CTX_free(ctx);
unsigned int nSize = ECDSA_size(pkey);
vchSig.resize(nSize); // Make sure it is big enough
unsigned char *pos = &vchSig[0];
nSize = i2d_ECDSA_SIG(sig, &pos);
ECDSA_SIG_free(sig);
vchSig.resize(nSize); // Shrink to fit actual size
vchSig.resize(sig_len);
return true;
}
// create a compact signature (65 bytes), which allows reconstructing the used public key
// The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
// 0x1D = second key with even y, 0x1E = second key with odd y
// Compact signature (65 bytes): one header byte (encoding recid + compression)
// followed by 32-byte r and 32-byte s.
bool CKey::SignCompact(uint256 hash, std::vector<unsigned char>& vchSig)
{
bool fOk = false;
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
if (sig==NULL)
return false;
vchSig.clear();
if (!fSet || !fHavePrivKey) return false;
vchSig.resize(65, 0);
const BIGNUM *sig_r, *sig_s;
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
int nBitsR = BN_num_bits(sig_r);
int nBitsS = BN_num_bits(sig_s);
if (nBitsR <= 256 && nBitsS <= 256)
if (!ECDSA_sign_compact_secp256k1(&vchSig[0],
reinterpret_cast<const unsigned char*>(&hash),
vch,
fCompressedPubKey))
{
int nRecId = -1;
for (int i=0; i<4; i++)
{
CKey keyRec;
keyRec.fSet = true;
if (fCompressedPubKey)
keyRec.SetCompressedPubKey();
if (ECDSA_SIG_recover_key_GFp(keyRec.pkey, sig, (unsigned char*)&hash, sizeof(hash), i, 1) == 1)
if (keyRec.GetPubKey() == this->GetPubKey())
{
nRecId = i;
break;
}
}
if (nRecId == -1)
{
ECDSA_SIG_free(sig);
throw key_error("CKey::SignCompact() : unable to construct recoverable key");
}
vchSig[0] = nRecId+27+(fCompressedPubKey ? 4 : 0);
BN_bn2bin(sig_r,&vchSig[33-(nBitsR+7)/8]);
BN_bn2bin(sig_s,&vchSig[65-(nBitsS+7)/8]);
fOk = true;
}
ECDSA_SIG_free(sig);
return fOk;
}
// reconstruct public key from a compact signature
// This is only slightly more CPU intensive than just verifying it.
// If this function succeeds, the recovered public key is guaranteed to be valid
// (the signature is a valid signature of the given data for that key)
bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig)
{
if (vchSig.size() != 65)
vchSig.clear();
return false;
int nV = vchSig[0];
if (nV<27 || nV>=35)
return false;
ECDSA_SIG *sig = ECDSA_SIG_new();
BIGNUM *sig_r = BN_bin2bn(&vchSig[1],32,NULL);
BIGNUM *sig_s = BN_bin2bn(&vchSig[33],32,NULL);
ECDSA_SIG_set0(sig, sig_r, sig_s);
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (nV >= 31)
{
SetCompressedPubKey();
nV -= 4;
}
if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), nV - 27, 0) == 1)
{
fSet = true;
ECDSA_SIG_free(sig);
return true;
}
ECDSA_SIG_free(sig);
bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig)
{
if (vchSig.size() != 65) return false;
int nV = vchSig[0];
if (nV < 27 || nV >= 35) return false;
unsigned char pubkey[65];
std::size_t pubkey_len = 0;
if (!ECDSA_recover_compact_secp256k1(pubkey, &pubkey_len,
reinterpret_cast<const unsigned char*>(&hash),
&vchSig[0]))
return false;
std::vector<unsigned char> vchPub(pubkey, pubkey + pubkey_len);
return SetPubKey(CPubKey(vchPub));
}
bool CKey::Verify(uint256 hash, const std::vector<unsigned char>& vchSig)
{
// -1 = error, 0 = bad sig, 1 = good
if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1)
return false;
if (vchSig.empty() || !fSet) return false;
return true;
return ECDSA_verify_secp256k1(
reinterpret_cast<const unsigned char*>(&hash),
&vchSig[0], vchSig.size(),
&vchPubKey[0], vchPubKey.size());
}
bool CKey::VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig)
{
CKey key;
if (!key.SetCompactSignature(hash, vchSig))
return false;
if (GetPubKey() != key.GetPubKey())
return false;
return true;
if (!key.SetCompactSignature(hash, vchSig)) return false;
return GetPubKey() == key.GetPubKey();
}
bool CKey::IsValid()
{
if (!fSet)
return false;
if (!fSet) return false;
if (!EC_KEY_check_key(pkey))
return false;
if (fHavePrivKey) {
if (!ECDSA_seckey_verify_secp256k1(vch)) return false;
bool fCompr;
CSecret secret = GetSecret(fCompr);
CKey key2;
key2.SetSecret(secret, fCompr);
return GetPubKey() == key2.GetPubKey();
// Re-derive the pubkey and check it matches the cache. This is the
// libsecp256k1 equivalent of OpenSSL's "consistency between priv and
// pub" check the original implementation performed.
unsigned char rederived[65];
std::size_t rederived_len = 0;
if (!ECDSA_pubkey_from_privkey_secp256k1(rederived, &rederived_len, vch, fCompressedPubKey))
return false;
if (rederived_len != vchPubKey.size()) return false;
return std::memcmp(rederived, &vchPubKey[0], rederived_len) == 0;
}
bool ECC_InitSanityCheck() {
EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if(pkey == NULL)
return false;
EC_KEY_free(pkey);
return ECDSA_pubkey_verify_secp256k1(&vchPubKey[0], vchPubKey.size());
}
// TODO Is there more EC functionality that could be missing?
// ─────────────────────────────────────────────────────────────────────────────
// Startup smoke test for the cryptography backend.
// ─────────────────────────────────────────────────────────────────────────────
bool ECC_InitSanityCheck()
{
// Verify that libsecp256k1 can validate a trivially-known good secret
// (the scalar 1) and reject zero. If either of these fails, the linked
// library is broken and we should refuse to start.
static const unsigned char one[32] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
};
static const unsigned char zero[32] = {0};
if (!ECDSA_seckey_verify_secp256k1(one)) return false;
if ( ECDSA_seckey_verify_secp256k1(zero)) return false;
return true;
}
+6 -6
View File
@@ -13,8 +13,6 @@
#include "uint256.h"
#include "util.h"
#include <openssl/ec.h> // for EC_KEY definition
// secp160k1
// const unsigned int PRIVATE_KEY_SIZE = 192;
// const unsigned int PUBLIC_KEY_SIZE = 41;
@@ -105,20 +103,22 @@ typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
// CSecret is a serialization of just the secret parameter (32 bytes)
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CSecret;
/** An encapsulated OpenSSL Elliptic Curve key (public and/or private) */
/** An encapsulated secp256k1 elliptic-curve key (public and/or private). */
class CKey
{
protected:
EC_KEY* pkey;
// 32-byte private scalar. Valid iff fSet && fHavePrivKey.
unsigned char vch[32];
// Cached serialized public key (33 or 65 bytes). Valid iff fSet.
std::vector<unsigned char> vchPubKey;
bool fSet;
bool fCompressedPubKey;
bool fHavePrivKey;
public:
void SetCompressedPubKey();
void SetUnCompressedPubKey();
EC_KEY* GetECKey();
void Reset();
CKey();
+17
View File
@@ -6039,6 +6039,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
const unsigned int nInFlight = CountHeaderSyncInFlight();
static int64_t nLastHeaderPlannerControl = 0;
static int64_t nLastHeaderWatchdog = 0;
static int64_t nLastBlockPlannerControl = 0;
if (nLastNewHeaderTime == 0)
nLastNewHeaderTime = nNowSec;
@@ -6076,6 +6077,22 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
RequestHeaderSyncRefill(pto, hashBestHeaderSync, nMinInterval, "heartbeat");
// Keep the block planner alive even when no new headers arrive and
// no blocks are being accepted. Without this periodic kick, the
// redundant-request and timeout logic inside QueueHeaderSyncBlocksParallel()
// only runs on header arrivals or block acceptance, so IBD can park
// indefinitely behind one missing frontier block.
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
hashBestHeaderSync != 0 &&
nPlannerDepth > 0)
{
const unsigned int nRequeued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
if (nRequeued > 0)
printf("IBD-DIAG: block-planner control queued %u block requests (plannerDepth=%u inflight=%u)\n",
nRequeued, nPlannerDepth, nInFlight);
nLastBlockPlannerControl = nNowSec;
}
}
//
Submodule
+1
Submodule src/secp256k1 added at ea174fe045
+37 -24
View File
@@ -41,8 +41,6 @@ Notes:
#include <errno.h>
#include <openssl/crypto.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
@@ -53,6 +51,7 @@ Notes:
#include "base58.h"
#include "crypto_ecdh.h"
#include "db.h"
#include "init.h" // pwalletMain
#include "txdb.h"
@@ -3682,7 +3681,7 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
3 addressFrom is invalid.
4 addressTo is invalid.
5 Could not get public key for addressTo.
6 ECDH_compute_key failed
6 ECDH key derivation failed
7 Could not get private key for addressFrom.
8 Could not allocate memory.
9 Could not compress message data.
@@ -3777,22 +3776,26 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
std::vector<unsigned char> vchP;
vchP.resize(32);
EC_KEY* pkeyr = keyR.GetECKey();
EC_KEY* pkeyK = keyK.GetECKey();
// always seems to be 32, worth checking?
//int field_size = EC_GROUP_get_degree(EC_KEY_get0_group(pkeyr));
//int secret_len = (field_size+7)/8;
//printf("secret_len %d.\n", secret_len);
// -- ECDH_compute_key returns the same P if fed compressed or uncompressed public keys
int lenP = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyK), pkeyr, NULL);
if (lenP != 32)
bool fCompressedR = false;
CSecret secretR = keyR.GetSecret(fCompressedR);
if (secretR.size() != 32)
{
printf("ECDH_compute_key failed, lenP: %d.\n", lenP);
printf("ECDH: keyR secret has unexpected size %zu.\n", secretR.size());
return 6;
};
}
std::vector<unsigned char> vchPubK = keyK.GetPubKey().Raw();
if (vchPubK.size() != 33 && vchPubK.size() != 65)
{
printf("ECDH: keyK pubkey has unexpected size %zu.\n", vchPubK.size());
return 6;
}
if (!ECDH_xonly_secp256k1(&vchP[0], &secretR[0], &vchPubK[0], vchPubK.size()))
{
printf("ECDH (encrypt): secp256k1_ecdh failed.\n");
return 6;
}
CPubKey cpkR = keyR.GetPubKey();
if (!cpkR.IsValid()
@@ -3980,7 +3983,7 @@ int SecureMsgSend(std::string& addressFrom, std::string& addressTo, std::string&
case 3: sError = "Invalid addressFrom."; break;
case 4: sError = "Invalid addressTo."; break;
case 5: sError = "Could not get public key for addressTo."; break;
case 6: sError = "ECDH_compute_key failed."; break;
case 6: sError = "ECDH key derivation failed."; break;
case 7: sError = "Could not get private key for addressFrom."; break;
case 8: sError = "Could not allocate memory."; break;
case 9: sError = "Could not compress message data."; break;
@@ -4193,16 +4196,26 @@ int SecureMsgDecrypt(bool fTestOnly, std::string& address, unsigned char *pHeade
// -- Do an EC point multiply with private key k and public key R. This gives you public key P.
std::vector<unsigned char> vchP;
vchP.resize(32);
EC_KEY* pkeyk = keyDest.GetECKey();
EC_KEY* pkeyR = keyR.GetECKey();
int lenPdec = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyR), pkeyk, NULL);
if (lenPdec != 32)
bool fCompressedDest = false;
CSecret secretDest = keyDest.GetSecret(fCompressedDest);
if (secretDest.size() != 32)
{
printf("ECDH_compute_key failed, lenPdec: %d.\n", lenPdec);
printf("ECDH: keyDest secret has unexpected size %zu.\n", secretDest.size());
return 1;
};
}
std::vector<unsigned char> vchPubR = keyR.GetPubKey().Raw();
if (vchPubR.size() != 33 && vchPubR.size() != 65)
{
printf("ECDH: keyR pubkey has unexpected size %zu.\n", vchPubR.size());
return 1;
}
if (!ECDH_xonly_secp256k1(&vchP[0], &secretDest[0], &vchPubR[0], vchPubR.size()))
{
printf("ECDH (decrypt): secp256k1_ecdh failed.\n");
return 1;
}
// -- Use public key P to calculate the SHA512 hash H.