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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
+267
-392
@@ -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 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();
|
||||
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)
|
||||
{
|
||||
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 (!fSet || !fHavePrivKey) return false;
|
||||
|
||||
if (nRecId == -1)
|
||||
vchSig.resize(65, 0);
|
||||
if (!ECDSA_sign_compact_secp256k1(&vchSig[0],
|
||||
reinterpret_cast<const unsigned char*>(&hash),
|
||||
vch,
|
||||
fCompressedPubKey))
|
||||
{
|
||||
ECDSA_SIG_free(sig);
|
||||
throw key_error("CKey::SignCompact() : unable to construct recoverable key");
|
||||
vchSig.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
return false;
|
||||
if (vchSig.size() != 65) 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);
|
||||
if (nV < 27 || nV >= 35) return false;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
return ECDSA_pubkey_verify_secp256k1(&vchPubKey[0], vchPubKey.size());
|
||||
}
|
||||
|
||||
bool ECC_InitSanityCheck() {
|
||||
EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if(pkey == NULL)
|
||||
return false;
|
||||
EC_KEY_free(pkey);
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Startup smoke test for the cryptography backend.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// TODO Is there more EC functionality that could be missing?
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user