feat(crypto): wire BPP/BPPE/BGE proof verification with real testnet data
Add cn_bpp_verify for Bulletproofs++ (1 delta, bpp_crypto_trait_ZC_out) used by zc_outs_range_proof in post-HF4 transactions. Distinguish from cn_bppe_verify (2 deltas, bpp_crypto_trait_Zarcanum) used for Zarcanum PoS proofs. Key changes: - Add deserialise_bpp() and cn_bpp_verify() to bridge.cpp - Add VerifyBPP() Go wrapper in proof.go - Wire BPPE and BGE stubs into real C++ verify functions - Add try/catch to all proof verifiers (C++ throws on invalid points) - Add nil/empty input guards to all Go proof functions - Test with real BPP proof from testnet block 101 coinbase tx The BPP proof from tx 543bc3c2... (first post-HF4 coinbase) verifies successfully through the full CGo pipeline, confirming wire format deserialisation matches the C++ daemon output. Co-Authored-By: Charon <charon@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cef9a34bea
commit
dd04cc9dee
5 changed files with 470 additions and 32 deletions
|
|
@ -11,8 +11,132 @@
|
|||
#include "crypto-ops.h"
|
||||
#include "clsag.h"
|
||||
#include "hash-ops.h"
|
||||
#include "range_proofs.h"
|
||||
#include "range_proof_bpp.h"
|
||||
#include "range_proof_bppe.h"
|
||||
#include "one_out_of_many_proofs.h"
|
||||
#include "zarcanum.h"
|
||||
#include "randomx.h"
|
||||
|
||||
// ── Wire-format varint decoder (LEB128, matches Go wire.DecodeVarint) ──
|
||||
namespace {
|
||||
|
||||
// Reads a varint from buf at *offset, advances *offset past the varint.
|
||||
// Returns false if the buffer is too short or the varint is malformed.
|
||||
bool read_varint(const uint8_t *buf, size_t buf_len, size_t *offset, uint64_t *out) {
|
||||
uint64_t val = 0;
|
||||
unsigned shift = 0;
|
||||
while (*offset < buf_len) {
|
||||
uint8_t b = buf[*offset];
|
||||
(*offset)++;
|
||||
val |= (uint64_t)(b & 0x7F) << shift;
|
||||
if ((b & 0x80) == 0) {
|
||||
*out = val;
|
||||
return true;
|
||||
}
|
||||
shift += 7;
|
||||
if (shift >= 63) return false; // overflow
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reads count * 32-byte blobs into a vector of public_keys.
|
||||
bool read_pubkey_vec(const uint8_t *buf, size_t buf_len, size_t *offset,
|
||||
std::vector<crypto::public_key> &out) {
|
||||
uint64_t count;
|
||||
if (!read_varint(buf, buf_len, offset, &count)) return false;
|
||||
if (count > 10000) return false; // sanity
|
||||
if (*offset + count * 32 > buf_len) return false;
|
||||
out.resize(count);
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
memcpy(&out[i], buf + *offset, 32);
|
||||
*offset += 32;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads count * 32-byte blobs into a vector of scalar_t.
|
||||
bool read_scalar_vec(const uint8_t *buf, size_t buf_len, size_t *offset,
|
||||
crypto::scalar_vec_t &out) {
|
||||
uint64_t count;
|
||||
if (!read_varint(buf, buf_len, offset, &count)) return false;
|
||||
if (count > 10000) return false;
|
||||
if (*offset + count * 32 > buf_len) return false;
|
||||
out.resize(count);
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
memcpy(out[i].m_s, buf + *offset, 32);
|
||||
*offset += 32;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads a single 32-byte public_key blob.
|
||||
bool read_pubkey(const uint8_t *buf, size_t buf_len, size_t *offset,
|
||||
crypto::public_key &out) {
|
||||
if (*offset + 32 > buf_len) return false;
|
||||
memcpy(&out, buf + *offset, 32);
|
||||
*offset += 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads a single 32-byte scalar_t blob.
|
||||
bool read_scalar(const uint8_t *buf, size_t buf_len, size_t *offset,
|
||||
crypto::scalar_t &out) {
|
||||
if (*offset + 32 > buf_len) return false;
|
||||
memcpy(out.m_s, buf + *offset, 32);
|
||||
*offset += 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deserialise a bpp_signature from wire bytes (Bulletproofs++, 1 delta).
|
||||
// Layout: varint(len(L)) + L[]*32 + varint(len(R)) + R[]*32
|
||||
// + A0(32) + A(32) + B(32) + r(32) + s(32) + delta(32)
|
||||
bool deserialise_bpp(const uint8_t *buf, size_t len, crypto::bpp_signature &sig) {
|
||||
size_t off = 0;
|
||||
if (!read_pubkey_vec(buf, len, &off, sig.L)) return false;
|
||||
if (!read_pubkey_vec(buf, len, &off, sig.R)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.A0)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.A)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.B)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.r)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.s)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.delta)) return false;
|
||||
return off == len; // must consume all bytes
|
||||
}
|
||||
|
||||
// Deserialise a bppe_signature from wire bytes (Bulletproofs++ Enhanced, 2 deltas).
|
||||
// Layout: varint(len(L)) + L[]*32 + varint(len(R)) + R[]*32
|
||||
// + A0(32) + A(32) + B(32) + r(32) + s(32) + delta_1(32) + delta_2(32)
|
||||
bool deserialise_bppe(const uint8_t *buf, size_t len, crypto::bppe_signature &sig) {
|
||||
size_t off = 0;
|
||||
if (!read_pubkey_vec(buf, len, &off, sig.L)) return false;
|
||||
if (!read_pubkey_vec(buf, len, &off, sig.R)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.A0)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.A)) return false;
|
||||
if (!read_pubkey(buf, len, &off, sig.B)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.r)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.s)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.delta_1)) return false;
|
||||
if (!read_scalar(buf, len, &off, sig.delta_2)) return false;
|
||||
return off == len; // must consume all bytes
|
||||
}
|
||||
|
||||
// Deserialise a BGE_proof from wire bytes.
|
||||
// Layout: A(32) + B(32) + varint(len(Pk)) + Pk[]*32
|
||||
// + varint(len(f)) + f[]*32 + y(32) + z(32)
|
||||
bool deserialise_bge(const uint8_t *buf, size_t len, crypto::BGE_proof &proof) {
|
||||
size_t off = 0;
|
||||
if (!read_pubkey(buf, len, &off, proof.A)) return false;
|
||||
if (!read_pubkey(buf, len, &off, proof.B)) return false;
|
||||
if (!read_pubkey_vec(buf, len, &off, proof.Pk)) return false;
|
||||
if (!read_scalar_vec(buf, len, &off, proof.f)) return false;
|
||||
if (!read_scalar(buf, len, &off, proof.y)) return false;
|
||||
if (!read_scalar(buf, len, &off, proof.z)) return false;
|
||||
return off == len;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
void bridge_fast_hash(const uint8_t *data, size_t len, uint8_t hash[32]) {
|
||||
|
|
@ -401,21 +525,114 @@ int cn_clsag_ggxxg_verify(const uint8_t hash[32], const uint8_t *ring,
|
|||
return crypto::verify_CLSAG_GGXXG(h, ring_refs, po_commitment, po_asset_id, ext_commitment, key_img, clsag_sig) ? 0 : 1;
|
||||
}
|
||||
|
||||
// ── Range Proofs (stubs — need on-chain binary format deserialiser) ──
|
||||
// ── Range Proofs (BPP — Bulletproofs++) ──────────────────
|
||||
// Used for zc_outs_range_proof in post-HF4 transactions.
|
||||
// Trait: bpp_crypto_trait_ZC_out (generators UGX, N=64, values_max=32).
|
||||
// Commitments are amount_commitments_for_rp_aggregation (E'_j, premultiplied by 1/8).
|
||||
|
||||
int cn_bppe_verify(const uint8_t * /*proof*/, size_t /*proof_len*/,
|
||||
const uint8_t * /*commitments*/, size_t /*num_commitments*/) {
|
||||
return -1; // not implemented
|
||||
int cn_bpp_verify(const uint8_t *proof, size_t proof_len,
|
||||
const uint8_t *commitments, size_t num_commitments) {
|
||||
if (proof == nullptr || proof_len == 0 || commitments == nullptr || num_commitments == 0)
|
||||
return 1;
|
||||
|
||||
try {
|
||||
crypto::bpp_signature sig;
|
||||
if (!deserialise_bpp(proof, proof_len, sig))
|
||||
return 1;
|
||||
|
||||
// Build commitment points (premultiplied by 1/8 on-chain).
|
||||
std::vector<crypto::point_t> commit_pts(num_commitments);
|
||||
for (size_t i = 0; i < num_commitments; i++) {
|
||||
crypto::public_key pk;
|
||||
memcpy(&pk, commitments + i * 32, 32);
|
||||
commit_pts[i] = crypto::point_t(pk);
|
||||
}
|
||||
|
||||
std::vector<crypto::bpp_sig_commit_ref_t> sigs = {
|
||||
crypto::bpp_sig_commit_ref_t(sig, commit_pts)
|
||||
};
|
||||
|
||||
uint8_t err = 0;
|
||||
bool ok = crypto::bpp_verify<crypto::bpp_crypto_trait_ZC_out>(sigs, &err);
|
||||
return ok ? 0 : 1;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int cn_bge_verify(const uint8_t /*context*/[32], const uint8_t * /*ring*/,
|
||||
size_t /*ring_size*/, const uint8_t * /*proof*/, size_t /*proof_len*/) {
|
||||
return -1; // not implemented
|
||||
// ── Range Proofs (BPPE — Bulletproofs++ Enhanced) ────────
|
||||
// Used for Zarcanum PoS E_range_proof.
|
||||
// Trait: bpp_crypto_trait_Zarcanum (generators HGX, N=128, values_max=16).
|
||||
|
||||
int cn_bppe_verify(const uint8_t *proof, size_t proof_len,
|
||||
const uint8_t *commitments, size_t num_commitments) {
|
||||
if (proof == nullptr || proof_len == 0 || commitments == nullptr || num_commitments == 0)
|
||||
return 1;
|
||||
|
||||
try {
|
||||
crypto::bppe_signature sig;
|
||||
if (!deserialise_bppe(proof, proof_len, sig))
|
||||
return 1;
|
||||
|
||||
// Build commitment points (premultiplied by 1/8 on-chain).
|
||||
std::vector<crypto::point_t> commit_pts(num_commitments);
|
||||
for (size_t i = 0; i < num_commitments; i++) {
|
||||
crypto::public_key pk;
|
||||
memcpy(&pk, commitments + i * 32, 32);
|
||||
commit_pts[i] = crypto::point_t(pk);
|
||||
}
|
||||
|
||||
std::vector<crypto::bppe_sig_commit_ref_t> sigs = {
|
||||
crypto::bppe_sig_commit_ref_t(sig, commit_pts)
|
||||
};
|
||||
|
||||
uint8_t err = 0;
|
||||
bool ok = crypto::bppe_verify<crypto::bpp_crypto_trait_Zarcanum>(sigs, &err);
|
||||
return ok ? 0 : 1;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ── BGE One-out-of-Many ─────────────────────────────────
|
||||
|
||||
int cn_bge_verify(const uint8_t context[32], const uint8_t *ring,
|
||||
size_t ring_size, const uint8_t *proof, size_t proof_len) {
|
||||
if (context == nullptr || ring == nullptr || ring_size == 0 ||
|
||||
proof == nullptr || proof_len == 0)
|
||||
return 1;
|
||||
|
||||
try {
|
||||
crypto::BGE_proof bge;
|
||||
if (!deserialise_bge(proof, proof_len, bge))
|
||||
return 1;
|
||||
|
||||
crypto::hash ctx_hash;
|
||||
memcpy(&ctx_hash, context, 32);
|
||||
|
||||
// Build ring of public key pointers.
|
||||
std::vector<crypto::public_key> ring_storage(ring_size);
|
||||
std::vector<const crypto::public_key*> ring_ptrs(ring_size);
|
||||
for (size_t i = 0; i < ring_size; i++) {
|
||||
memcpy(&ring_storage[i], ring + i * 32, 32);
|
||||
ring_ptrs[i] = &ring_storage[i];
|
||||
}
|
||||
|
||||
uint8_t err = 0;
|
||||
bool ok = crypto::verify_BGE_proof(ctx_hash, ring_ptrs, bge, &err);
|
||||
return ok ? 0 : 1;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Zarcanum PoS ────────────────────────────────────────
|
||||
// Zarcanum verification requires many parameters beyond what the current
|
||||
// bridge API exposes (kernel_hash, ring, last_pow_block_id, stake_ki,
|
||||
// pos_difficulty). Returns -1 until the API is extended.
|
||||
int cn_zarcanum_verify(const uint8_t /*hash*/[32], const uint8_t * /*proof*/,
|
||||
size_t /*proof_len*/) {
|
||||
return -1; // not implemented
|
||||
return -1; // needs extended API — see bridge.h TODO
|
||||
}
|
||||
|
||||
// ── RandomX PoW Hashing ──────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -90,18 +90,41 @@ int cn_clsag_ggxxg_verify(const uint8_t hash[32], const uint8_t *ring,
|
|||
const uint8_t extended_commitment[32],
|
||||
const uint8_t ki[32], const uint8_t *sig);
|
||||
|
||||
// ── Range Proofs (Bulletproofs+ Enhanced) ─────────────────
|
||||
// Proof verification requires deserialising variable-length BPPE structs from
|
||||
// on-chain binary format. Implementation deferred to Phase 4 (needs RPC + chain data).
|
||||
// Returns 0 on success, 1 on verification failure, -1 if not implemented.
|
||||
// ── Range Proofs (BPP — Bulletproofs++) ──────────────────
|
||||
// Verifies a BPP range proof (1 delta). Used for zc_outs_range_proof in
|
||||
// post-HF4 transactions. proof is the wire-serialised bpp_signature:
|
||||
// varint(len(L)) + L[]*32 + varint(len(R)) + R[]*32
|
||||
// + A0(32) + A(32) + B(32) + r(32) + s(32) + delta(32)
|
||||
// commitments is a flat array of 32-byte public keys — the
|
||||
// amount_commitments_for_rp_aggregation (E'_j, premultiplied by 1/8).
|
||||
// Uses bpp_crypto_trait_ZC_out (generators UGX, N=64, values_max=32).
|
||||
// Returns 0 on success, 1 on verification failure or deserialisation error.
|
||||
int cn_bpp_verify(const uint8_t *proof, size_t proof_len,
|
||||
const uint8_t *commitments, size_t num_commitments);
|
||||
|
||||
// ── Range Proofs (BPPE — Bulletproofs++ Enhanced) ────────
|
||||
// Verifies a BPPE range proof (2 deltas). Used for Zarcanum PoS E_range_proof.
|
||||
// proof is the wire-serialised bppe_signature:
|
||||
// varint(len(L)) + L[]*32 + varint(len(R)) + R[]*32
|
||||
// + A0(32) + A(32) + B(32) + r(32) + s(32) + delta_1(32) + delta_2(32)
|
||||
// commitments is a flat array of 32-byte public keys (premultiplied by 1/8).
|
||||
// Uses bpp_crypto_trait_Zarcanum (N=128, values_max=16).
|
||||
// Returns 0 on success, 1 on verification failure or deserialisation error.
|
||||
int cn_bppe_verify(const uint8_t *proof, size_t proof_len,
|
||||
const uint8_t *commitments, size_t num_commitments);
|
||||
|
||||
// ── BGE One-out-of-Many ───────────────────────────────────
|
||||
// Verifies a BGE one-out-of-many proof. proof is wire-serialised BGE_proof:
|
||||
// A(32) + B(32) + varint(len(Pk)) + Pk[]*32
|
||||
// + varint(len(f)) + f[]*32 + y(32) + z(32)
|
||||
// ring is a flat array of 32-byte public keys. context is a 32-byte hash.
|
||||
// Returns 0 on success, 1 on verification failure or deserialisation error.
|
||||
int cn_bge_verify(const uint8_t context[32], const uint8_t *ring,
|
||||
size_t ring_size, const uint8_t *proof, size_t proof_len);
|
||||
|
||||
// ── Zarcanum PoS ──────────────────────────────────────────
|
||||
// TODO: extend API to accept kernel_hash, ring, last_pow_block_id,
|
||||
// stake_ki, pos_difficulty. Currently returns -1 (not implemented).
|
||||
int cn_zarcanum_verify(const uint8_t hash[32], const uint8_t *proof,
|
||||
size_t proof_len);
|
||||
|
||||
|
|
|
|||
|
|
@ -413,16 +413,175 @@ func TestCLSAG_GGXXG_Good_SigSize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Range Proofs / Zarcanum (stubs) ──────────────────────
|
||||
// ── Range Proofs (BPP — Bulletproofs++) ──────────────────
|
||||
|
||||
func TestBPPE_Stub_NotImplemented(t *testing.T) {
|
||||
t.Skip("BPPE verification needs on-chain proof data — Phase 4")
|
||||
func TestBPP_Bad_EmptyProof(t *testing.T) {
|
||||
commitment := [32]byte{0x01}
|
||||
if crypto.VerifyBPP([]byte{}, [][32]byte{commitment}) {
|
||||
t.Fatal("empty BPP proof should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBGE_Stub_NotImplemented(t *testing.T) {
|
||||
t.Skip("BGE verification needs on-chain proof data — Phase 4")
|
||||
func TestBPP_Bad_GarbageProof(t *testing.T) {
|
||||
// Build a minimal valid-shaped proof: L(0) + R(0) + 6 * 32-byte fields.
|
||||
proof := make([]byte, 0, 2+6*32)
|
||||
proof = append(proof, 0x00) // varint 0: L length
|
||||
proof = append(proof, 0x00) // varint 0: R length
|
||||
proof = append(proof, make([]byte, 6*32)...)
|
||||
|
||||
commitment := [32]byte{0x01}
|
||||
if crypto.VerifyBPP(proof, [][32]byte{commitment}) {
|
||||
t.Fatal("garbage BPP proof should fail verification")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBPP_Good_TestnetCoinbase101(t *testing.T) {
|
||||
// Real BPP range proof from testnet block 101 (first post-HF4 coinbase).
|
||||
// TX hash: 543bc3c29e9f4c5d1fc566be03fb4da1f2ce2d70d4312fdcc3e4eed7ca3b61e0
|
||||
//
|
||||
// This is the zc_outs_range_proof.bpp field, verified by bpp_crypto_trait_ZC_out.
|
||||
// Commitments are the amount_commitments_for_rp_aggregation (E'_j) from the
|
||||
// aggregation proof, NOT the raw output amount_commitments.
|
||||
proofHex := "07" +
|
||||
// L[0..6]
|
||||
"47c3d2db565bf368c9879dd1b08899a5e69bc956bc0a89b0cb456a54e066aa85" +
|
||||
"89c6a14ac578409af177d9605f03fe61dfae8067338a40ca551b34580350f694" +
|
||||
"bf389b62ae684146d33632e1ca5bf51dc6fed884780443489ee8fd68a535ddcf" +
|
||||
"5299c9ca2f400fd0a978b1c0ef55a03922549ef78b8b5cd268bd4df5eb32f1fc" +
|
||||
"4d4543466be7e9b9ceb6051955a815427bd773ad9a5cc9fec49fae4c0ab46fc7" +
|
||||
"53d1c86e8c04cea4b903fc8c42f404bc8c85b25eb2468f8de75171e2bf802ae9" +
|
||||
"6daa934cdb68b0609eb8942b3691c2bf1a122068bcd18d8ef4dd08abb2929780" +
|
||||
"07" + // R count
|
||||
// R[0..6]
|
||||
"599626cfd549b317eb92667ab1783a730cab6528326b581034a60b8f2582ff61" +
|
||||
"d8ac1ec8395699a170dc9ca5ae2a87cd70388083475e38763aa327e31b27bd69" +
|
||||
"1ebd37e22b4eda43001e908fdb211de548ed942766139c8197201d9cc53c744a" +
|
||||
"88ee20995c5f0b64d1bc48293f9c3b8799b2866e473915871df9d55ac065c58e" +
|
||||
"bd51887763709d9e9992d317c12bd27ad933452d06b821b4ee282de78e7cc561" +
|
||||
"02ad119d2f1fb9a79a709614cb24dcb83119bb5734a70c923c4c9586afe1e5bd" +
|
||||
"fedc244f7568a3cd9de95d9d240fb01ee0e0695f6d2066d085457054e78dead4" +
|
||||
// A0
|
||||
"b47ed0fcf2de7c5a9802352f7ab65667c468e32329964723a2084ae0dd38ae97" +
|
||||
// A
|
||||
"79ff7e0870d6b664275b8207e545f4f80537e7cc4c9f81098eefa42e5efc1c85" +
|
||||
// B
|
||||
"86bc9e7a48652c1f1e1e28e86b6e7ea80079dd6db7c78d083235ede6ae359631" +
|
||||
// r
|
||||
"dbcb543d3a6e8b8692fb013832acb9b93ee717c72e832fd78c3a2d5f4fcb380f" +
|
||||
// s
|
||||
"e3e21825ce80bda2c30b499ae87ce5e9daedde3f8885bcd7463fccaa88d37500" +
|
||||
// delta
|
||||
"7c8c94c0f95faa4d0c9e14811442ecb3dc78bd46dafba93e648324b7d0a36d0c"
|
||||
|
||||
proof, err := hex.DecodeString(proofHex)
|
||||
if err != nil {
|
||||
t.Fatalf("decode proof hex: %v", err)
|
||||
}
|
||||
|
||||
// Aggregation proof commitments (E'_j, premultiplied by 1/8).
|
||||
var c0, c1 [32]byte
|
||||
h0, _ := hex.DecodeString("c61a3937e37ff91acd74bd2877bb47e236c36315744a8031339a02c41481d52b")
|
||||
h1, _ := hex.DecodeString("5157b6954e712187a14edcd6faf0e6adbb8ec66f2d9260bd238106e076d3098e")
|
||||
copy(c0[:], h0)
|
||||
copy(c1[:], h1)
|
||||
|
||||
if !crypto.VerifyBPP(proof, [][32]byte{c0, c1}) {
|
||||
t.Fatal("real testnet BPP proof should verify successfully")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBPP_Bad_TestnetWrongCommitment(t *testing.T) {
|
||||
// Same proof as above but with corrupted commitment — must fail.
|
||||
proofHex := "07" +
|
||||
"47c3d2db565bf368c9879dd1b08899a5e69bc956bc0a89b0cb456a54e066aa85" +
|
||||
"89c6a14ac578409af177d9605f03fe61dfae8067338a40ca551b34580350f694" +
|
||||
"bf389b62ae684146d33632e1ca5bf51dc6fed884780443489ee8fd68a535ddcf" +
|
||||
"5299c9ca2f400fd0a978b1c0ef55a03922549ef78b8b5cd268bd4df5eb32f1fc" +
|
||||
"4d4543466be7e9b9ceb6051955a815427bd773ad9a5cc9fec49fae4c0ab46fc7" +
|
||||
"53d1c86e8c04cea4b903fc8c42f404bc8c85b25eb2468f8de75171e2bf802ae9" +
|
||||
"6daa934cdb68b0609eb8942b3691c2bf1a122068bcd18d8ef4dd08abb2929780" +
|
||||
"07" +
|
||||
"599626cfd549b317eb92667ab1783a730cab6528326b581034a60b8f2582ff61" +
|
||||
"d8ac1ec8395699a170dc9ca5ae2a87cd70388083475e38763aa327e31b27bd69" +
|
||||
"1ebd37e22b4eda43001e908fdb211de548ed942766139c8197201d9cc53c744a" +
|
||||
"88ee20995c5f0b64d1bc48293f9c3b8799b2866e473915871df9d55ac065c58e" +
|
||||
"bd51887763709d9e9992d317c12bd27ad933452d06b821b4ee282de78e7cc561" +
|
||||
"02ad119d2f1fb9a79a709614cb24dcb83119bb5734a70c923c4c9586afe1e5bd" +
|
||||
"fedc244f7568a3cd9de95d9d240fb01ee0e0695f6d2066d085457054e78dead4" +
|
||||
"b47ed0fcf2de7c5a9802352f7ab65667c468e32329964723a2084ae0dd38ae97" +
|
||||
"79ff7e0870d6b664275b8207e545f4f80537e7cc4c9f81098eefa42e5efc1c85" +
|
||||
"86bc9e7a48652c1f1e1e28e86b6e7ea80079dd6db7c78d083235ede6ae359631" +
|
||||
"dbcb543d3a6e8b8692fb013832acb9b93ee717c72e832fd78c3a2d5f4fcb380f" +
|
||||
"e3e21825ce80bda2c30b499ae87ce5e9daedde3f8885bcd7463fccaa88d37500" +
|
||||
"7c8c94c0f95faa4d0c9e14811442ecb3dc78bd46dafba93e648324b7d0a36d0c"
|
||||
|
||||
proof, _ := hex.DecodeString(proofHex)
|
||||
|
||||
// Corrupted commitment (flipped first byte).
|
||||
var c0, c1 [32]byte
|
||||
h0, _ := hex.DecodeString("d61a3937e37ff91acd74bd2877bb47e236c36315744a8031339a02c41481d52b")
|
||||
h1, _ := hex.DecodeString("5157b6954e712187a14edcd6faf0e6adbb8ec66f2d9260bd238106e076d3098e")
|
||||
copy(c0[:], h0)
|
||||
copy(c1[:], h1)
|
||||
|
||||
if crypto.VerifyBPP(proof, [][32]byte{c0, c1}) {
|
||||
t.Fatal("BPP proof with corrupted commitment should fail")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Range Proofs (BPPE — Bulletproofs++ Enhanced) ────────
|
||||
|
||||
func TestBPPE_Bad_EmptyProof(t *testing.T) {
|
||||
// Empty proof must return false (not crash).
|
||||
commitment := [32]byte{0x01}
|
||||
if crypto.VerifyBPPE([]byte{}, [][32]byte{commitment}) {
|
||||
t.Fatal("empty BPPE proof should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBPPE_Bad_GarbageProof(t *testing.T) {
|
||||
// Garbage bytes should deserialise (valid varint + blobs) but fail verification.
|
||||
// Build a minimal valid-shaped proof: L(0 entries) + R(0 entries) + 7 * 32-byte fields.
|
||||
proof := make([]byte, 0, 2+7*32)
|
||||
proof = append(proof, 0x00) // varint 0: L length
|
||||
proof = append(proof, 0x00) // varint 0: R length
|
||||
proof = append(proof, make([]byte, 7*32)...)
|
||||
|
||||
commitment := [32]byte{0x01}
|
||||
if crypto.VerifyBPPE(proof, [][32]byte{commitment}) {
|
||||
t.Fatal("garbage BPPE proof should fail verification")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBGE_Bad_EmptyProof(t *testing.T) {
|
||||
ctx := [32]byte{0x01}
|
||||
ring := [][32]byte{{0x02}}
|
||||
if crypto.VerifyBGE(ctx, ring, []byte{}) {
|
||||
t.Fatal("empty BGE proof should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBGE_Bad_GarbageProof(t *testing.T) {
|
||||
// Build a minimal valid-shaped proof: A(32) + B(32) + Pk(0) + f(0) + y(32) + z(32).
|
||||
proof := make([]byte, 0, 4*32+2)
|
||||
proof = append(proof, make([]byte, 32)...) // A
|
||||
proof = append(proof, make([]byte, 32)...) // B
|
||||
proof = append(proof, 0x00) // varint 0: Pk length
|
||||
proof = append(proof, 0x00) // varint 0: f length
|
||||
proof = append(proof, make([]byte, 32)...) // y
|
||||
proof = append(proof, make([]byte, 32)...) // z
|
||||
|
||||
ctx := [32]byte{0x01}
|
||||
ring := [][32]byte{{0x02}}
|
||||
if crypto.VerifyBGE(ctx, ring, proof) {
|
||||
t.Fatal("garbage BGE proof should fail verification")
|
||||
}
|
||||
}
|
||||
|
||||
func TestZarcanum_Stub_NotImplemented(t *testing.T) {
|
||||
t.Skip("Zarcanum verification needs on-chain proof data — Phase 4")
|
||||
// Zarcanum bridge API needs extending — verify it returns false.
|
||||
hash := [32]byte{0x01}
|
||||
if crypto.VerifyZarcanum(hash, []byte{0x00}) {
|
||||
t.Fatal("Zarcanum stub should return false")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,37 @@ import "C"
|
|||
|
||||
import "unsafe"
|
||||
|
||||
// VerifyBPPE verifies a Bulletproofs+ Enhanced range proof.
|
||||
// Returns true if the proof is valid, false otherwise.
|
||||
// Currently returns false (not implemented) — needs on-chain binary format
|
||||
// deserialiser. Full implementation arrives in Phase 4 with RPC + chain data.
|
||||
// VerifyBPP verifies a Bulletproofs++ range proof (1 delta).
|
||||
// Used for zc_outs_range_proof in post-HF4 transactions.
|
||||
// proof is the wire-serialised bpp_signature blob.
|
||||
// commitments are the amount_commitments_for_rp_aggregation (E'_j, premultiplied by 1/8).
|
||||
// Uses bpp_crypto_trait_ZC_out (generators UGX, N=64, values_max=32).
|
||||
func VerifyBPP(proof []byte, commitments [][32]byte) bool {
|
||||
if len(proof) == 0 || len(commitments) == 0 {
|
||||
return false
|
||||
}
|
||||
n := len(commitments)
|
||||
flat := make([]byte, n*32)
|
||||
for i, c := range commitments {
|
||||
copy(flat[i*32:], c[:])
|
||||
}
|
||||
return C.cn_bpp_verify(
|
||||
(*C.uint8_t)(unsafe.Pointer(&proof[0])),
|
||||
C.size_t(len(proof)),
|
||||
(*C.uint8_t)(unsafe.Pointer(&flat[0])),
|
||||
C.size_t(n),
|
||||
) == 0
|
||||
}
|
||||
|
||||
// VerifyBPPE verifies a Bulletproofs++ Enhanced range proof (2 deltas).
|
||||
// Used for Zarcanum PoS E_range_proof.
|
||||
// proof is the wire-serialised bppe_signature blob.
|
||||
// commitments are the output amount commitments (premultiplied by 1/8).
|
||||
// Uses bpp_crypto_trait_Zarcanum (N=128, values_max=16).
|
||||
func VerifyBPPE(proof []byte, commitments [][32]byte) bool {
|
||||
if len(proof) == 0 || len(commitments) == 0 {
|
||||
return false
|
||||
}
|
||||
n := len(commitments)
|
||||
flat := make([]byte, n*32)
|
||||
for i, c := range commitments {
|
||||
|
|
@ -28,8 +54,12 @@ func VerifyBPPE(proof []byte, commitments [][32]byte) bool {
|
|||
}
|
||||
|
||||
// VerifyBGE verifies a BGE one-out-of-many proof.
|
||||
// Currently returns false (not implemented).
|
||||
// context is a 32-byte hash. ring is the set of public keys.
|
||||
// proof is the wire-serialised BGE_proof blob.
|
||||
func VerifyBGE(context [32]byte, ring [][32]byte, proof []byte) bool {
|
||||
if len(ring) == 0 || len(proof) == 0 {
|
||||
return false
|
||||
}
|
||||
n := len(ring)
|
||||
flat := make([]byte, n*32)
|
||||
for i, r := range ring {
|
||||
|
|
@ -45,8 +75,12 @@ func VerifyBGE(context [32]byte, ring [][32]byte, proof []byte) bool {
|
|||
}
|
||||
|
||||
// VerifyZarcanum verifies a Zarcanum PoS proof.
|
||||
// Currently returns false (not implemented).
|
||||
// Currently returns false — bridge API needs extending to pass kernel_hash,
|
||||
// ring, last_pow_block_id, stake_ki, and pos_difficulty.
|
||||
func VerifyZarcanum(hash [32]byte, proof []byte) bool {
|
||||
if len(proof) == 0 {
|
||||
return false
|
||||
}
|
||||
return C.cn_zarcanum_verify(
|
||||
(*C.uint8_t)(unsafe.Pointer(&hash[0])),
|
||||
(*C.uint8_t)(unsafe.Pointer(&proof[0])),
|
||||
|
|
|
|||
|
|
@ -183,9 +183,9 @@ CryptoNote crypto library. The upstream code (37 files from Zano commit
|
|||
| `crypto/keyimage.go` | Key image generation and validation |
|
||||
| `crypto/signature.go` | Standard + NLSAG ring signatures |
|
||||
| `crypto/clsag.go` | CLSAG (GG/GGX/GGXXG) + cofactor helpers |
|
||||
| `crypto/proof.go` | BPPE, BGE, Zarcanum verification stubs |
|
||||
| `crypto/proof.go` | BPP, BPPE, BGE, Zarcanum verification wrappers |
|
||||
| `crypto/PROVENANCE.md` | Upstream origin mapping + update workflow |
|
||||
| `crypto/crypto_test.go` | 22 tests (19 pass, 3 skipped proof stubs) |
|
||||
| `crypto/crypto_test.go` | 30 tests (all passing) |
|
||||
|
||||
### Key findings
|
||||
|
||||
|
|
@ -532,12 +532,17 @@ refresh.
|
|||
and verified. The v2+ (Zarcanum) code paths compile but are untested -- they
|
||||
will be validated in Phase 2 when post-HF4 transactions appear on-chain.
|
||||
|
||||
**Proof verification not yet wired.** Key generation, derivation, signatures
|
||||
(standard, NLSAG, CLSAG GG) are fully operational. BPPE, BGE, and Zarcanum
|
||||
proof verification stubs exist but return "not implemented" -- the deserialisation
|
||||
of on-chain proof blobs requires matching the exact binary format from chain data
|
||||
(Phase 4). CLSAG GGX/GGXXG verify functions are wired but untested without real
|
||||
ring data.
|
||||
**BPP range proof verification tested with real data.** The `cn_bpp_verify`
|
||||
bridge function (Bulletproofs++, 1 delta, `bpp_crypto_trait_ZC_out`) is verified
|
||||
against a real testnet coinbase transaction from block 101 (post-HF4). The
|
||||
`cn_bppe_verify` function (Bulletproofs++ Enhanced, 2 deltas,
|
||||
`bpp_crypto_trait_Zarcanum`) is wired but untested with real data -- it is used
|
||||
for Zarcanum PoS range proofs, not regular transaction output proofs. BGE
|
||||
(one-out-of-many) proofs are wired but untested (coinbase transactions have
|
||||
empty BGE proof vectors). CLSAG GGX/GGXXG verify functions are similarly wired
|
||||
but untested without real ring data. Zarcanum proof verification remains
|
||||
stubbed -- the bridge API needs extending to pass kernel_hash, ring,
|
||||
last_pow_block_id, stake_ki, and pos_difficulty.
|
||||
|
||||
**CGo toolchain required.** The `crypto/` package requires CMake, GCC/Clang,
|
||||
OpenSSL, and Boost headers to build `libcryptonote.a`. Pure Go packages
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue