go-blockchain/crypto/proof.go
Virgil cb43082d18
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run
feat(crypto): add Zarcanum verification context API
Co-Authored-By: Charon <charon@lethean.io>
2026-04-04 21:28:12 +00:00

208 lines
5.8 KiB
Go

// SPDX-Licence-Identifier: EUPL-1.2
package crypto
/*
#include "bridge.h"
*/
import "C"
import (
"unsafe"
coreerr "dappco.re/go/core/log"
)
// ZarcanumRingMember is one flat ring entry for Zarcanum verification.
// All fields are stored premultiplied by 1/8, matching the on-chain form.
type ZarcanumRingMember struct {
StealthAddress [32]byte
AmountCommitment [32]byte
BlindedAssetID [32]byte
ConcealingPoint [32]byte
}
// ZarcanumVerificationContext groups the full context required by the
// upstream C++ verifier.
type ZarcanumVerificationContext struct {
ContextHash [32]byte
KernelHash [32]byte
Ring []ZarcanumRingMember
LastPowBlockIDHashed [32]byte
StakeKeyImage [32]byte
Proof []byte
PosDifficulty uint64
}
// GenerateDoubleSchnorr creates a generic_double_schnorr_sig from zarcanum.h.
// aIsX selects the generator pair:
//
// false -> (G, G)
// true -> (X, G)
func GenerateDoubleSchnorr(hash [32]byte, aIsX bool, secretA [32]byte, secretB [32]byte) ([96]byte, error) {
var proof [96]byte
var flag C.int
if aIsX {
flag = 1
}
rc := C.cn_double_schnorr_generate(
flag,
(*C.uint8_t)(unsafe.Pointer(&hash[0])),
(*C.uint8_t)(unsafe.Pointer(&secretA[0])),
(*C.uint8_t)(unsafe.Pointer(&secretB[0])),
(*C.uint8_t)(unsafe.Pointer(&proof[0])),
C.size_t(len(proof)),
)
if rc != 0 {
return proof, coreerr.E("GenerateDoubleSchnorr", "double_schnorr_generate failed", nil)
}
return proof, nil
}
// 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
}
// VerifyDoubleSchnorr verifies a generic_double_schnorr_sig from zarcanum.h.
// aIsX selects the generator pair:
//
// false -> (G, G)
// true -> (X, G)
//
// The proof blob is the 96-byte wire encoding: c(32) + y0(32) + y1(32).
func VerifyDoubleSchnorr(hash [32]byte, aIsX bool, a [32]byte, b [32]byte, proof []byte) bool {
if len(proof) != 96 {
return false
}
var flag C.int
if aIsX {
flag = 1
}
return C.cn_double_schnorr_verify(
flag,
(*C.uint8_t)(unsafe.Pointer(&hash[0])),
(*C.uint8_t)(unsafe.Pointer(&a[0])),
(*C.uint8_t)(unsafe.Pointer(&b[0])),
(*C.uint8_t)(unsafe.Pointer(&proof[0])),
C.size_t(len(proof)),
) == 0
}
// VerifyZarcanum verifies a Zarcanum PoS proof.
// This compatibility wrapper remains for the historical proof blob API.
// Use VerifyZarcanumWithContext for full verification.
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
}
// VerifyZarcanumWithContext verifies a Zarcanum PoS proof with the full
// consensus context required by the upstream verifier.
//
// Example:
//
// crypto.VerifyZarcanumWithContext(crypto.ZarcanumVerificationContext{
// ContextHash: txHash,
// KernelHash: kernelHash,
// Ring: ring,
// LastPowBlockIDHashed: lastPowHash,
// StakeKeyImage: stakeKeyImage,
// PosDifficulty: posDifficulty,
// Proof: proofBlob,
// })
func VerifyZarcanumWithContext(ctx ZarcanumVerificationContext) bool {
if len(ctx.Ring) == 0 || len(ctx.Proof) == 0 {
return false
}
flat := make([]byte, len(ctx.Ring)*128)
for i, member := range ctx.Ring {
copy(flat[i*128:], member.StealthAddress[:])
copy(flat[i*128+32:], member.AmountCommitment[:])
copy(flat[i*128+64:], member.BlindedAssetID[:])
copy(flat[i*128+96:], member.ConcealingPoint[:])
}
return C.cn_zarcanum_verify_full(
(*C.uint8_t)(unsafe.Pointer(&ctx.ContextHash[0])),
(*C.uint8_t)(unsafe.Pointer(&ctx.KernelHash[0])),
(*C.uint8_t)(unsafe.Pointer(&flat[0])),
C.size_t(len(ctx.Ring)),
(*C.uint8_t)(unsafe.Pointer(&ctx.LastPowBlockIDHashed[0])),
(*C.uint8_t)(unsafe.Pointer(&ctx.StakeKeyImage[0])),
C.uint64_t(ctx.PosDifficulty),
(*C.uint8_t)(unsafe.Pointer(&ctx.Proof[0])),
C.size_t(len(ctx.Proof)),
) == 0
}