go-blockchain/crypto/proof.go
Claude dd04cc9dee
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>
2026-02-21 18:37:08 +00:00

89 lines
2.5 KiB
Go

// SPDX-Licence-Identifier: EUPL-1.2
package crypto
/*
#include "bridge.h"
*/
import "C"
import "unsafe"
// 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 {
copy(flat[i*32:], c[:])
}
return C.cn_bppe_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
}
// VerifyBGE verifies a BGE one-out-of-many proof.
// 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 {
copy(flat[i*32:], r[:])
}
return C.cn_bge_verify(
(*C.uint8_t)(unsafe.Pointer(&context[0])),
(*C.uint8_t)(unsafe.Pointer(&flat[0])),
C.size_t(n),
(*C.uint8_t)(unsafe.Pointer(&proof[0])),
C.size_t(len(proof)),
) == 0
}
// VerifyZarcanum verifies a Zarcanum PoS proof.
// 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])),
C.size_t(len(proof)),
) == 0
}