Files
p2pool-salvium/src/carrot_crypto.cpp
T
2025-12-21 21:11:38 +00:00

467 lines
16 KiB
C++

#include "common.h"
#include "carrot_crypto.h"
#include "crypto.h"
extern "C" {
#include "crypto-ops.h"
#include "mx25519.h"
#include "blake2/blake2.h"
}
#include <cstring>
#include <mutex>
#include "log.h"
LOG_CATEGORY(CarrotCrypto)
namespace p2pool {
namespace carrot {
// Domain separators (must match Salvium's carrot_core/config.h)
static constexpr const char DOMAIN_SEP_EPHEMERAL_PRIVKEY[] = "Carrot sending key normal";
static constexpr const char DOMAIN_SEP_SENDER_RECEIVER_SECRET[] = "Carrot sender-receiver secret";
static constexpr const char DOMAIN_SEP_VIEW_TAG[] = "Carrot view tag";
static constexpr const char DOMAIN_SEP_ENCRYPTION_MASK_ANCHOR[] = "Carrot encryption mask anchor";
static constexpr const char DOMAIN_SEP_ONETIME_EXTENSION_G[] = "Carrot key extension G";
static constexpr const char DOMAIN_SEP_ONETIME_EXTENSION_T[] = "Carrot key extension T";
// T generator for Carrot (from Salvium generators.cpp)
static const uint8_t generator_T[32] = {
0x96, 0x6f, 0xc6, 0x6b, 0x82, 0xcd, 0x56, 0xcf,
0x85, 0xea, 0xec, 0x80, 0x1c, 0x42, 0x84, 0x5f,
0x5f, 0x40, 0x88, 0x78, 0xd1, 0x56, 0x1e, 0x00,
0xd3, 0xd7, 0xde, 0xd2, 0x79, 0x4d, 0x09, 0x4f
};
// H generator for Pedersen commitments (from RingCT)
static const uint8_t generator_H[32] = {
0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf,
0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea,
0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9,
0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94
};
// Curve order L for Ed25519
static const uint8_t curve_order[32] = {
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
};
static const uint8_t identity_point[32] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Get mx25519 implementation (cached)
static const mx25519_impl* get_mx25519_impl()
{
static std::once_flag of;
static const mx25519_impl *impl;
std::call_once(of, [&](){
impl = mx25519_select_impl(MX25519_TYPE_AUTO);
if (!impl) {
LOGERR(0, "Failed to select mx25519 implementation - unsupported platform");
PANIC_STOP();
}
});
return impl;
}
// Build transcript: [1-byte length][domain_sep][args...]
// Returns total size written to buffer
template<size_t N>
static size_t build_transcript(uint8_t* buf, const char (&domain_sep)[N])
{
// N includes null terminator, actual length is N-1
constexpr size_t len = N - 1;
static_assert(len <= 255, "Domain separator too long");
buf[0] = static_cast<uint8_t>(len);
memcpy(buf + 1, domain_sep, len);
return 1 + len;
}
// Debug helper to print hex
static void debug_hex(const char* label, const uint8_t* data, size_t len)
{
static constexpr char log_category_prefix[] = "CarrotCrypto ";
char hex[130] = {0};
for (size_t i = 0; i < len && i < 64; ++i) {
snprintf(hex + i*2, 3, "%02x", data[i]);
}
LOGINFO(6, label << ": " << static_cast<const char*>(hex));
}
// BLAKE2b keyed hash wrapper
// key can be nullptr for unkeyed hash
static void blake2b_hash(void* out, size_t outlen,
const void* data, size_t datalen,
const void* key, size_t keylen)
{
blake2b(out, outlen, data, datalen, key, keylen);
}
static void derive_scalar(const void* transcript, size_t transcript_len,
const void* key, // 32 bytes or nullptr
uint8_t* scalar_out)
{
uint8_t temp[64];
blake2b_hash(temp, 64, transcript, transcript_len,
key, key ? 32 : 0);
sc_reduce(temp); // Reduce 64 bytes mod l
memcpy(scalar_out, temp, 32);
}
// derive_bytes_32: H_32 (32-byte output, no reduction)
static void derive_bytes_32(const void* transcript, size_t transcript_len,
const void* key, // 32 bytes
uint8_t* out)
{
blake2b_hash(out, 32, transcript, transcript_len, key, 32);
}
// derive_bytes_3: H_3 (3-byte output)
static void derive_bytes_3(const void* transcript, size_t transcript_len,
const void* key, // 32 bytes
uint8_t* out)
{
blake2b_hash(out, 3, transcript, transcript_len, key, 32);
}
// derive_bytes_16: H_16 (16-byte output)
static void derive_bytes_16(const void* transcript, size_t transcript_len,
const void* key, // 32 bytes
uint8_t* out)
{
blake2b_hash(out, 16, transcript, transcript_len, key, 32);
}
void generate_janus_anchor(uint8_t (&anchor)[16])
{
hash tmp;
generate_keys(tmp, tmp); // Use existing random generation
memcpy(anchor, tmp.h, 16);
}
void make_input_context_coinbase(uint64_t block_index, uint8_t (&input_context)[33])
{
// input_context = 'C' || block_index as little-endian 256-bit (32 bytes)
input_context[0] = 'C';
memset(input_context + 1, 0, 32);
// Little-endian 64-bit at start of the 32-byte field
memcpy(input_context + 1, &block_index, sizeof(block_index));
debug_hex("input_context", input_context, 33);
}
void make_ephemeral_privkey(
const uint8_t (&anchor)[16],
const uint8_t (&input_context)[33],
const hash& address_spend_pubkey,
const uint8_t (&payment_id)[8],
hash& ephemeral_privkey_out)
{
// d_e = H_n("Carrot sending key normal" || anchor || input_context || K^j_s || pid)
// Transcript: [len][domain_sep][anchor:16][input_context:33][K_s:32][pid:8]
constexpr size_t domain_len = sizeof(DOMAIN_SEP_EPHEMERAL_PRIVKEY) - 1; // 25
constexpr size_t transcript_size = 1 + domain_len + 16 + 33 + 32 + 8; // 115
uint8_t transcript[transcript_size];
size_t offset = build_transcript(transcript, DOMAIN_SEP_EPHEMERAL_PRIVKEY);
memcpy(transcript + offset, anchor, 16);
offset += 16;
memcpy(transcript + offset, input_context, 33);
offset += 33;
memcpy(transcript + offset, address_spend_pubkey.h, 32);
offset += 32;
memcpy(transcript + offset, payment_id, 8);
// Unkeyed hash (key = nullptr)
derive_scalar(transcript, transcript_size, nullptr, ephemeral_privkey_out.h);
debug_hex("eph_priv anchor", anchor, 16);
debug_hex("eph_priv K_s", address_spend_pubkey.h, 32);
debug_hex("eph_priv d_e", ephemeral_privkey_out.h, 32);
}
void make_ephemeral_pubkey_mainaddress(
const hash& ephemeral_privkey,
hash& ephemeral_pubkey_out)
{
// D_e = d_e * B (X25519 scalar mult with base point)
mx25519_scmul_base(get_mx25519_impl(),
reinterpret_cast<mx25519_pubkey*>(ephemeral_pubkey_out.h),
reinterpret_cast<const mx25519_privkey*>(ephemeral_privkey.h));
debug_hex("eph_pub D_e", ephemeral_pubkey_out.h, 32);
}
bool make_shared_secret_sender(
const hash& ephemeral_privkey,
const hash& address_view_pubkey,
hash& shared_secret_out)
{
// First verify the view pubkey is valid and convert to X25519
ge_p3 view_point;
if (ge_frombytes_vartime(&view_point, address_view_pubkey.h) != 0) {
return false;
}
// Check if point is in prime order subgroup: l*P == identity
ge_p2 check_point;
ge_scalarmult(&check_point, curve_order, &view_point);
uint8_t check_bytes[32];
ge_tobytes(check_bytes, &check_point);
if (memcmp(check_bytes, identity_point, 32) != 0) {
return false; // Point not in prime order subgroup
}
// D^j_v = ConvertPointE(K^j_v) - convert Ed25519 pubkey to X25519
mx25519_pubkey address_view_pubkey_x25519;
ge_p3_to_x25519(address_view_pubkey_x25519.data, &view_point);
// s_sr = d_e * D^j_v (native X25519 scalar multiplication)
mx25519_scmul_key(get_mx25519_impl(),
reinterpret_cast<mx25519_pubkey*>(shared_secret_out.h),
reinterpret_cast<const mx25519_privkey*>(ephemeral_privkey.h),
&address_view_pubkey_x25519);
debug_hex("shared K_v_ed25519", address_view_pubkey.h, 32);
debug_hex("shared K_v_x25519", address_view_pubkey_x25519.data, 32);
debug_hex("shared s_sr_unctx", shared_secret_out.h, 32);
return true;
}
void make_sender_receiver_secret(
const hash& shared_secret_unctx,
const hash& ephemeral_pubkey,
const uint8_t (&input_context)[33],
hash& sender_receiver_out)
{
// s^ctx_sr = H_32[s_sr]("Carrot sender-receiver secret" || D_e || input_context)
// Transcript: [len][domain_sep][D_e:32][input_context:33]
constexpr size_t domain_len = sizeof(DOMAIN_SEP_SENDER_RECEIVER_SECRET) - 1; // 29
constexpr size_t transcript_size = 1 + domain_len + 32 + 33; // 95
uint8_t transcript[transcript_size];
size_t offset = build_transcript(transcript, DOMAIN_SEP_SENDER_RECEIVER_SECRET);
memcpy(transcript + offset, ephemeral_pubkey.h, 32);
offset += 32;
memcpy(transcript + offset, input_context, 33);
// Keyed with shared_secret_unctx
derive_bytes_32(transcript, transcript_size, shared_secret_unctx.h, sender_receiver_out.h);
debug_hex("s_sr_ctx result", sender_receiver_out.h, 32);
}
void make_onetime_address_coinbase(
const hash& address_spend_pubkey,
const hash& sender_receiver_secret,
uint64_t amount,
hash& onetime_address_out)
{
// For coinbase: K_o = K^j_s + k^o_g * G + k^o_t * T
// k^o_g = H_n[s^ctx_sr]("Carrot key extension G" || C_a)
// k^o_t = H_n[s^ctx_sr]("Carrot key extension T" || C_a)
// C_a = 1*G + amount*H (coinbase uses k_a = 1)
// 1. Compute C_a = 1*G + amount*H
// First: 1*G
ge_p3 one_G;
{
uint8_t one_scalar[32] = {0};
one_scalar[0] = 1;
ge_scalarmult_base(&one_G, one_scalar);
}
// Second: amount*H
ge_p3 H_point;
if (ge_frombytes_vartime(&H_point, generator_H) != 0) {
memcpy(onetime_address_out.h, address_spend_pubkey.h, 32);
return;
}
ge_p2 amount_H_p2;
uint8_t amount_le[32] = {0};
memcpy(amount_le, &amount, sizeof(amount)); // little-endian
ge_scalarmult(&amount_H_p2, amount_le, &H_point);
// Convert amount_H to p3
uint8_t amount_H_bytes[32];
ge_tobytes(amount_H_bytes, &amount_H_p2);
ge_p3 amount_H;
ge_frombytes_vartime(&amount_H, amount_H_bytes);
// C_a = 1*G + amount*H
ge_cached one_G_cached;
ge_p3_to_cached(&one_G_cached, &one_G);
ge_p1p1 C_a_p1p1;
ge_add(&C_a_p1p1, &amount_H, &one_G_cached);
ge_p3 C_a_p3;
ge_p1p1_to_p3(&C_a_p3, &C_a_p1p1);
uint8_t C_a[32];
ge_p3_tobytes(C_a, &C_a_p3);
debug_hex("K_o C_a commitment", C_a, 32);
// 2. k^o_g = H_n[s^ctx_sr]("Carrot key extension G" || C_a)
constexpr size_t domain_len_g = sizeof(DOMAIN_SEP_ONETIME_EXTENSION_G) - 1;
constexpr size_t transcript_size_g = 1 + domain_len_g + 32;
uint8_t transcript_g[transcript_size_g];
size_t offset_g = build_transcript(transcript_g, DOMAIN_SEP_ONETIME_EXTENSION_G);
memcpy(transcript_g + offset_g, C_a, 32);
uint8_t k_o_g[32];
derive_scalar(transcript_g, transcript_size_g, sender_receiver_secret.h, k_o_g);
debug_hex("K_o k^o_g scalar", k_o_g, 32);
// 3. k^o_t = H_n[s^ctx_sr]("Carrot key extension T" || C_a)
constexpr size_t domain_len_t = sizeof(DOMAIN_SEP_ONETIME_EXTENSION_T) - 1;
constexpr size_t transcript_size_t = 1 + domain_len_t + 32;
uint8_t transcript_t[transcript_size_t];
size_t offset_t = build_transcript(transcript_t, DOMAIN_SEP_ONETIME_EXTENSION_T);
memcpy(transcript_t + offset_t, C_a, 32);
uint8_t k_o_t[32];
derive_scalar(transcript_t, transcript_size_t, sender_receiver_secret.h, k_o_t);
debug_hex("K_o k^o_t scalar", k_o_t, 32);
// 4. K^o_ext = k^o_g * G + k^o_t * T
// First: k^o_g * G
ge_p3 k_o_g_G;
ge_scalarmult_base(&k_o_g_G, k_o_g);
// Second: k^o_t * T
ge_p3 T_point;
if (ge_frombytes_vartime(&T_point, generator_T) != 0) {
memcpy(onetime_address_out.h, address_spend_pubkey.h, 32);
return;
}
ge_p2 k_o_t_T_p2;
ge_scalarmult(&k_o_t_T_p2, k_o_t, &T_point);
uint8_t k_o_t_T_bytes[32];
ge_tobytes(k_o_t_T_bytes, &k_o_t_T_p2);
ge_p3 k_o_t_T;
ge_frombytes_vartime(&k_o_t_T, k_o_t_T_bytes);
// K^o_ext = k^o_g*G + k^o_t*T
ge_cached k_o_g_G_cached;
ge_p3_to_cached(&k_o_g_G_cached, &k_o_g_G);
ge_p1p1 ext_p1p1;
ge_add(&ext_p1p1, &k_o_t_T, &k_o_g_G_cached);
ge_p3 extension_point;
ge_p1p1_to_p3(&extension_point, &ext_p1p1);
// 5. K_o = K^j_s + K^o_ext
ge_p3 spend_point;
if (ge_frombytes_vartime(&spend_point, address_spend_pubkey.h) != 0) {
memcpy(onetime_address_out.h, address_spend_pubkey.h, 32);
return;
}
ge_cached extension_cached;
ge_p3_to_cached(&extension_cached, &extension_point);
ge_p1p1 result_p1p1;
ge_add(&result_p1p1, &spend_point, &extension_cached);
ge_p3 result;
ge_p1p1_to_p3(&result, &result_p1p1);
ge_p3_tobytes(onetime_address_out.h, &result);
debug_hex("K_o result", onetime_address_out.h, 32);
}
void make_view_tag(
const hash& shared_secret_unctx,
const uint8_t (&input_context)[33],
const hash& onetime_address,
uint8_t (&view_tag)[3])
{
// vt = H_3[s_sr]("Carrot view tag" || input_context || K_o)
// Transcript: [len][domain_sep][input_context:33][K_o:32]
constexpr size_t domain_len = sizeof(DOMAIN_SEP_VIEW_TAG) - 1; // 15
constexpr size_t transcript_size = 1 + domain_len + 33 + 32; // 81
uint8_t transcript[transcript_size];
size_t offset = build_transcript(transcript, DOMAIN_SEP_VIEW_TAG);
memcpy(transcript + offset, input_context, 33);
offset += 33;
memcpy(transcript + offset, onetime_address.h, 32);
// Keyed with shared_secret_unctx (NOT contextualized s_sr)
derive_bytes_3(transcript, transcript_size, shared_secret_unctx.h, view_tag);
debug_hex("view_tag result", view_tag, 3);
}
void encrypt_anchor(
const uint8_t (&anchor)[16],
const hash& sender_receiver_secret,
const hash& onetime_address,
uint8_t (&encrypted_anchor)[16])
{
// anchor_enc = anchor XOR H_16[s^ctx_sr]("Carrot encryption mask anchor" || K_o)
// Transcript: [len][domain_sep][K_o:32]
constexpr size_t domain_len = sizeof(DOMAIN_SEP_ENCRYPTION_MASK_ANCHOR) - 1; // 29
constexpr size_t transcript_size = 1 + domain_len + 32; // 62
uint8_t transcript[transcript_size];
size_t offset = build_transcript(transcript, DOMAIN_SEP_ENCRYPTION_MASK_ANCHOR);
memcpy(transcript + offset, onetime_address.h, 32);
// Keyed with sender_receiver_secret (contextualized)
uint8_t mask[16];
derive_bytes_16(transcript, transcript_size, sender_receiver_secret.h, mask);
for (size_t i = 0; i < 16; ++i) {
encrypted_anchor[i] = anchor[i] ^ mask[i];
}
debug_hex("anchor mask", mask, 16);
debug_hex("anchor encrypted", encrypted_anchor, 16);
}
void derive_deterministic_anchor_from_pubkey(
const hash& tx_key_seed,
const hash& spend_public_key,
uint8_t (&anchor)[16])
{
// anchor = H_16("p2pool_anchor_v2" || tx_key_seed || spend_public_key)
const char domain[] = "p2pool_anchor_v2";
uint8_t data[sizeof(domain) + 32 + 32];
memcpy(data, domain, sizeof(domain));
memcpy(data + sizeof(domain), tx_key_seed.h, 32);
memcpy(data + sizeof(domain) + 32, spend_public_key.h, 32);
hash h;
blake2b_hash(h.h, 16, data, sizeof(data), nullptr, 0);
memcpy(anchor, h.h, 16);
}
void derive_transaction_ephemeral_privkey(
const hash& tx_key_seed,
const uint8_t (&input_context)[33],
hash& ephemeral_privkey_out)
{
// d_e = H_n("p2pool_tx_eph_v1" || tx_key_seed || input_context)
const char domain[] = "p2pool_tx_eph_v1";
uint8_t data[sizeof(domain) + 32 + 33];
memcpy(data, domain, sizeof(domain));
memcpy(data + sizeof(domain), tx_key_seed.h, 32);
memcpy(data + sizeof(domain) + 32, input_context, 33);
// Derive scalar (reduces to valid ed25519 scalar)
derive_scalar(data, sizeof(data), nullptr, ephemeral_privkey_out.h);
debug_hex("tx_eph d_e", ephemeral_privkey_out.h, 32);
}
} // namespace carrot
} // namespace p2pool