// 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
}
