mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
369 lines
9.8 KiB
Go
369 lines
9.8 KiB
Go
//go:build !gpu || !cgo
|
|
|
|
// Package gpu provides CPU-only ZK cryptographic operations when GPU support
|
|
// is disabled or CGO is unavailable.
|
|
package gpu
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/consensys/gnark-crypto/ecc/bn254/fr/poseidon2"
|
|
gnarkHash "github.com/consensys/gnark-crypto/hash"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Threshold Constants
|
|
// =============================================================================
|
|
|
|
const (
|
|
// ThresholdPoseidon2 is the minimum batch size for GPU Poseidon2 hashing.
|
|
ThresholdPoseidon2 = 64
|
|
|
|
// ThresholdMerkle is the minimum leaf pairs for GPU Merkle layer.
|
|
ThresholdMerkle = 128
|
|
|
|
// ThresholdMSM is the minimum point-scalar pairs for GPU MSM.
|
|
ThresholdMSM = 256
|
|
|
|
// ThresholdCommitment is the minimum batch size for GPU commitments.
|
|
ThresholdCommitment = 128
|
|
|
|
// ThresholdFRI is the minimum evaluations for GPU FRI folding.
|
|
ThresholdFRI = 512
|
|
)
|
|
|
|
// =============================================================================
|
|
// Error Types
|
|
// =============================================================================
|
|
|
|
var (
|
|
ErrInvalidInput = errors.New("invalid input")
|
|
ErrSizeMismatch = errors.New("input size mismatch")
|
|
ErrNotPowerOfTwo = errors.New("size must be power of two")
|
|
ErrGPUUnavailable = errors.New("GPU acceleration unavailable")
|
|
)
|
|
|
|
// =============================================================================
|
|
// Field Element Type (BN254 Fr - 256-bit)
|
|
// =============================================================================
|
|
|
|
// Fr256 represents a 256-bit field element (BN254 scalar field).
|
|
// Uses 4 x 64-bit limbs in little-endian order.
|
|
type Fr256 [4]uint64
|
|
|
|
// =============================================================================
|
|
// ZK Context
|
|
// =============================================================================
|
|
|
|
// ZKContext provides CPU-only ZK operations with automatic routing.
|
|
type ZKContext struct {
|
|
mu sync.RWMutex
|
|
|
|
gpuEnabled bool
|
|
deviceName string
|
|
|
|
// Stats for monitoring
|
|
gpuCalls int64
|
|
cpuCalls int64
|
|
}
|
|
|
|
// DefaultZKContext is the global ZK context (lazy initialized).
|
|
var (
|
|
defaultZKContext *ZKContext
|
|
initOnce sync.Once
|
|
)
|
|
|
|
// GetZKContext returns the global ZK context.
|
|
func GetZKContext() *ZKContext {
|
|
initOnce.Do(func() {
|
|
defaultZKContext = &ZKContext{
|
|
gpuEnabled: false,
|
|
deviceName: "CPU (gnark-crypto)",
|
|
}
|
|
})
|
|
return defaultZKContext
|
|
}
|
|
|
|
// GPUEnabled returns true if GPU acceleration is enabled.
|
|
func (z *ZKContext) GPUEnabled() bool {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
return z.gpuEnabled
|
|
}
|
|
|
|
// DeviceName returns the GPU device name.
|
|
func (z *ZKContext) DeviceName() string {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
return z.deviceName
|
|
}
|
|
|
|
// Stats returns GPU/CPU call statistics.
|
|
func (z *ZKContext) Stats() (gpuCalls, cpuCalls int64) {
|
|
z.mu.RLock()
|
|
defer z.mu.RUnlock()
|
|
return z.gpuCalls, z.cpuCalls
|
|
}
|
|
|
|
// =============================================================================
|
|
// Poseidon2 Hash Operations (CPU Implementation - gnark-crypto)
|
|
// =============================================================================
|
|
|
|
// poseidon2HasherPool is a pool of Poseidon2 hashers for concurrent use.
|
|
var poseidon2HasherPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return poseidon2.NewMerkleDamgardHasher()
|
|
},
|
|
}
|
|
|
|
// poseidon2HashPairCPU computes Poseidon2(left, right) using gnark-crypto.
|
|
func poseidon2HashPairCPU(left, right *Fr256) Fr256 {
|
|
h := poseidon2HasherPool.Get().(gnarkHash.StateStorer)
|
|
defer poseidon2HasherPool.Put(h)
|
|
|
|
h.Reset()
|
|
|
|
// Write left and right as bytes
|
|
leftBytes := fr256ToBytes(left)
|
|
rightBytes := fr256ToBytes(right)
|
|
|
|
_, _ = h.Write(leftBytes)
|
|
_, _ = h.Write(rightBytes)
|
|
|
|
// Get the hash result
|
|
resultBytes := h.Sum(nil)
|
|
|
|
// Convert back to Fr256
|
|
var out Fr256
|
|
_ = fr256FromBytes(&out, resultBytes[:32])
|
|
return out
|
|
}
|
|
|
|
// fr256ToBytes converts Fr256 to 32-byte big-endian slice.
|
|
func fr256ToBytes(f *Fr256) []byte {
|
|
buf := make([]byte, 32)
|
|
binary.BigEndian.PutUint64(buf[0:8], f[3])
|
|
binary.BigEndian.PutUint64(buf[8:16], f[2])
|
|
binary.BigEndian.PutUint64(buf[16:24], f[1])
|
|
binary.BigEndian.PutUint64(buf[24:32], f[0])
|
|
return buf
|
|
}
|
|
|
|
// fr256FromBytes sets Fr256 from 32-byte big-endian slice.
|
|
func fr256FromBytes(f *Fr256, buf []byte) error {
|
|
if len(buf) != 32 {
|
|
return ErrInvalidInput
|
|
}
|
|
f[3] = binary.BigEndian.Uint64(buf[0:8])
|
|
f[2] = binary.BigEndian.Uint64(buf[8:16])
|
|
f[1] = binary.BigEndian.Uint64(buf[16:24])
|
|
f[0] = binary.BigEndian.Uint64(buf[24:32])
|
|
return nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Public API - Poseidon2
|
|
// =============================================================================
|
|
|
|
// Poseidon2HashPair computes Poseidon2(left, right) with automatic routing.
|
|
func (z *ZKContext) Poseidon2HashPair(left, right *Fr256) Fr256 {
|
|
z.mu.Lock()
|
|
z.cpuCalls++
|
|
z.mu.Unlock()
|
|
return poseidon2HashPairCPU(left, right)
|
|
}
|
|
|
|
// Poseidon2BatchHashPair computes batch Poseidon2 hashes with CPU routing.
|
|
func (z *ZKContext) Poseidon2BatchHashPair(left, right []Fr256) ([]Fr256, error) {
|
|
n := len(left)
|
|
if n != len(right) {
|
|
return nil, ErrSizeMismatch
|
|
}
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
z.mu.Lock()
|
|
z.cpuCalls++
|
|
z.mu.Unlock()
|
|
|
|
result := make([]Fr256, n)
|
|
for i := 0; i < n; i++ {
|
|
result[i] = poseidon2HashPairCPU(&left[i], &right[i])
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Poseidon2 Merkle Tree Operations
|
|
// =============================================================================
|
|
|
|
// Poseidon2MerkleLayer computes one layer of a Merkle tree.
|
|
func (z *ZKContext) Poseidon2MerkleLayer(nodes []Fr256) ([]Fr256, error) {
|
|
n := len(nodes)
|
|
if n == 0 || n%2 != 0 {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
|
|
parentCount := n / 2
|
|
|
|
z.mu.Lock()
|
|
z.cpuCalls++
|
|
z.mu.Unlock()
|
|
|
|
parents := make([]Fr256, parentCount)
|
|
for i := 0; i < parentCount; i++ {
|
|
parents[i] = poseidon2HashPairCPU(&nodes[i*2], &nodes[i*2+1])
|
|
}
|
|
|
|
return parents, nil
|
|
}
|
|
|
|
// Poseidon2MerkleRoot computes the Merkle root from leaves.
|
|
func (z *ZKContext) Poseidon2MerkleRoot(leaves []Fr256) (Fr256, error) {
|
|
n := len(leaves)
|
|
if n == 0 || (n&(n-1)) != 0 {
|
|
return Fr256{}, ErrNotPowerOfTwo
|
|
}
|
|
|
|
if n == 1 {
|
|
return leaves[0], nil
|
|
}
|
|
|
|
// CPU path - build tree layer by layer
|
|
current := leaves
|
|
for len(current) > 1 {
|
|
next, err := z.Poseidon2MerkleLayer(current)
|
|
if err != nil {
|
|
return Fr256{}, err
|
|
}
|
|
current = next
|
|
}
|
|
|
|
return current[0], nil
|
|
}
|
|
|
|
// Poseidon2MerkleTree builds a complete Merkle tree.
|
|
func (z *ZKContext) Poseidon2MerkleTree(leaves []Fr256) ([]Fr256, error) {
|
|
n := len(leaves)
|
|
if n == 0 || (n&(n-1)) != 0 {
|
|
return nil, ErrNotPowerOfTwo
|
|
}
|
|
|
|
if n == 1 {
|
|
return []Fr256{leaves[0]}, nil
|
|
}
|
|
|
|
// CPU path - collect all internal nodes
|
|
allNodes := make([]Fr256, 0, n-1)
|
|
current := leaves
|
|
|
|
for len(current) > 1 {
|
|
next, err := z.Poseidon2MerkleLayer(current)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allNodes = append(allNodes, next...)
|
|
current = next
|
|
}
|
|
|
|
return allNodes, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Commitment and Nullifier Operations
|
|
// =============================================================================
|
|
|
|
// Poseidon2Commitment computes commitment = Poseidon2(Poseidon2(value, blinding), salt).
|
|
func (z *ZKContext) Poseidon2Commitment(value, blinding, salt *Fr256) Fr256 {
|
|
intermediate := poseidon2HashPairCPU(value, blinding)
|
|
return poseidon2HashPairCPU(&intermediate, salt)
|
|
}
|
|
|
|
// Poseidon2Nullifier computes nullifier = Poseidon2(Poseidon2(key, commitment), index).
|
|
func (z *ZKContext) Poseidon2Nullifier(key, commitment, index *Fr256) Fr256 {
|
|
intermediate := poseidon2HashPairCPU(key, commitment)
|
|
return poseidon2HashPairCPU(&intermediate, index)
|
|
}
|
|
|
|
// BatchCommitment computes batch commitments with CPU routing.
|
|
func (z *ZKContext) BatchCommitment(values, blindings, salts []Fr256) ([]Fr256, error) {
|
|
n := len(values)
|
|
if n != len(blindings) || n != len(salts) {
|
|
return nil, ErrSizeMismatch
|
|
}
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
z.mu.Lock()
|
|
z.cpuCalls++
|
|
z.mu.Unlock()
|
|
|
|
result := make([]Fr256, n)
|
|
for i := 0; i < n; i++ {
|
|
result[i] = z.Poseidon2Commitment(&values[i], &blindings[i], &salts[i])
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// BatchNullifier computes batch nullifiers with CPU routing.
|
|
func (z *ZKContext) BatchNullifier(keys, commitments, indices []Fr256) ([]Fr256, error) {
|
|
n := len(keys)
|
|
if n != len(commitments) || n != len(indices) {
|
|
return nil, ErrSizeMismatch
|
|
}
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
z.mu.Lock()
|
|
z.cpuCalls++
|
|
z.mu.Unlock()
|
|
|
|
result := make([]Fr256, n)
|
|
for i := 0; i < n; i++ {
|
|
result[i] = z.Poseidon2Nullifier(&keys[i], &commitments[i], &indices[i])
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Convenience Functions (use default context)
|
|
// =============================================================================
|
|
|
|
// Poseidon2Hash computes Poseidon2(left, right).
|
|
func Poseidon2Hash(left, right *Fr256) Fr256 {
|
|
return GetZKContext().Poseidon2HashPair(left, right)
|
|
}
|
|
|
|
// Poseidon2BatchHash computes batch Poseidon2 hashes.
|
|
func Poseidon2BatchHash(left, right []Fr256) ([]Fr256, error) {
|
|
return GetZKContext().Poseidon2BatchHashPair(left, right)
|
|
}
|
|
|
|
// MerkleRoot computes the Poseidon2 Merkle root from leaves.
|
|
func MerkleRoot(leaves []Fr256) (Fr256, error) {
|
|
return GetZKContext().Poseidon2MerkleRoot(leaves)
|
|
}
|
|
|
|
// MerkleTree builds a complete Poseidon2 Merkle tree.
|
|
func MerkleTree(leaves []Fr256) ([]Fr256, error) {
|
|
return GetZKContext().Poseidon2MerkleTree(leaves)
|
|
}
|
|
|
|
// Commitment computes a Poseidon2 commitment.
|
|
func Commitment(value, blinding, salt *Fr256) Fr256 {
|
|
return GetZKContext().Poseidon2Commitment(value, blinding, salt)
|
|
}
|
|
|
|
// Nullifier computes a Poseidon2 nullifier.
|
|
func Nullifier(key, commitment, index *Fr256) Fr256 {
|
|
return GetZKContext().Poseidon2Nullifier(key, commitment, index)
|
|
}
|