mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
refactor: update import path to github.com/luxfi/constants
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# AI Assistant Knowledge Base
|
||||
|
||||
**Last Updated**: 2025-12-19
|
||||
**Last Updated**: 2026-01-05
|
||||
**Project**: crypto
|
||||
**Organization**: luxfi
|
||||
**Repo**: github.com/luxfi/crypto
|
||||
@@ -8,6 +8,7 @@
|
||||
## Project Overview
|
||||
|
||||
Lux cryptography library implementing post-quantum standards and consensus primitives.
|
||||
TLS certificate utilities live in the top-level `github.com/luxfi/tls` module.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
@@ -59,7 +60,7 @@ cd docs && pnpm build # Build static site
|
||||
| **precompile/** | Post-quantum EVM precompiles | LP-2517 |
|
||||
|
||||
### Supporting Packages
|
||||
- **blake2b/, hashing/** - Hash functions (SHA, BLAKE, Keccak, SHAKE)
|
||||
- **blake2b/, hash/** - Hash functions (SHA, BLAKE, Keccak, SHAKE)
|
||||
- **kem/** - Key encapsulation interface
|
||||
- **ecies/** - Hybrid encryption
|
||||
- **cb58/** - Base58 encoding
|
||||
@@ -1457,7 +1458,7 @@ The Go module proxy cached `github.com/luxfi/geth@v1.16.1` which had incorrect m
|
||||
| sdk | v1.16.28 | Added exclude, bumped deps (needs more work) |
|
||||
|
||||
### Pending Work
|
||||
- **SDK**: Needs import migration from `github.com/luxfi/node/utils/constants` to `github.com/luxfi/constants`
|
||||
- **SDK**: Needs import migration from `github.com/luxfi/utils/constants` to `github.com/luxfi/constants`
|
||||
- **SDK**: API rename: Subnet→Chain (e.g., `tx.Net` → `tx.Chain`, `txs.ChainValidator.Net` → `txs.ChainValidator.Chain`)
|
||||
- **CLI**: Uses local replaces, needs SDK fixes first
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// AeadID identifies an AEAD algorithm
|
||||
|
||||
-255
@@ -1,255 +0,0 @@
|
||||
// Package cert provides X.509 certificate handling for post-quantum algorithms
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/sign"
|
||||
)
|
||||
|
||||
// MLDSACert represents an ML-DSA certificate
|
||||
type MLDSACert struct {
|
||||
Raw []byte
|
||||
PublicKey sign.PublicKey
|
||||
Subject pkix.Name
|
||||
Issuer pkix.Name
|
||||
SerialNumber []byte
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
KeyUsage x509.KeyUsage
|
||||
ExtKeyUsage []x509.ExtKeyUsage
|
||||
IsCA bool
|
||||
|
||||
// Custom extensions for QZMQ
|
||||
NodeID string
|
||||
Capabilities []string
|
||||
Role string
|
||||
}
|
||||
|
||||
// CertPool represents a pool of trusted certificates
|
||||
type CertPool struct {
|
||||
certs map[string]*MLDSACert // Keyed by SPKI hash
|
||||
}
|
||||
|
||||
// NewCertPool creates a new certificate pool
|
||||
func NewCertPool() *CertPool {
|
||||
return &CertPool{
|
||||
certs: make(map[string]*MLDSACert),
|
||||
}
|
||||
}
|
||||
|
||||
// AddCert adds a certificate to the pool
|
||||
func (cp *CertPool) AddCert(cert *MLDSACert) {
|
||||
spkiHash := hashSPKI(cert.PublicKey.Bytes())
|
||||
cp.certs[spkiHash] = cert
|
||||
}
|
||||
|
||||
// VerifyChain verifies a certificate chain
|
||||
func (cp *CertPool) VerifyChain(chain []*MLDSACert, now time.Time) error {
|
||||
if len(chain) == 0 {
|
||||
return errors.New("empty certificate chain")
|
||||
}
|
||||
|
||||
// Verify leaf certificate
|
||||
leaf := chain[0]
|
||||
if now.Before(leaf.NotBefore) || now.After(leaf.NotAfter) {
|
||||
return errors.New("certificate expired or not yet valid")
|
||||
}
|
||||
|
||||
// Verify chain
|
||||
for i := 0; i < len(chain)-1; i++ {
|
||||
cert := chain[i]
|
||||
issuer := chain[i+1]
|
||||
|
||||
if err := verifyCertSignature(cert, issuer); err != nil {
|
||||
return fmt.Errorf("invalid signature at position %d: %w", i, err)
|
||||
}
|
||||
|
||||
if !issuer.IsCA {
|
||||
return fmt.Errorf("issuer at position %d is not a CA", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify root is trusted
|
||||
root := chain[len(chain)-1]
|
||||
spkiHash := hashSPKI(root.PublicKey.Bytes())
|
||||
if _, ok := cp.certs[spkiHash]; !ok {
|
||||
return errors.New("root certificate not trusted")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SPKIPinner implements SPKI pinning
|
||||
type SPKIPinner struct {
|
||||
pins map[string]bool // Set of allowed SPKI hashes
|
||||
}
|
||||
|
||||
// NewSPKIPinner creates a new SPKI pinner
|
||||
func NewSPKIPinner(pins []string) *SPKIPinner {
|
||||
pinner := &SPKIPinner{
|
||||
pins: make(map[string]bool),
|
||||
}
|
||||
for _, pin := range pins {
|
||||
pinner.pins[pin] = true
|
||||
}
|
||||
return pinner
|
||||
}
|
||||
|
||||
// Verify checks if a public key is pinned
|
||||
func (sp *SPKIPinner) Verify(pk sign.PublicKey) bool {
|
||||
hash := hashSPKI(pk.Bytes())
|
||||
return sp.pins[hash]
|
||||
}
|
||||
|
||||
// hashSPKI computes the SHA-256 hash of the SPKI
|
||||
func hashSPKI(pubKeyBytes []byte) string {
|
||||
// In production, compute actual SHA-256 hash
|
||||
// For now, return a placeholder
|
||||
return fmt.Sprintf("spki_%x", pubKeyBytes[:8])
|
||||
}
|
||||
|
||||
// verifyCertSignature verifies a certificate's signature
|
||||
func verifyCertSignature(cert, issuer *MLDSACert) error {
|
||||
// In production, this would verify the actual signature
|
||||
// using the issuer's public key
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertBuilder builds ML-DSA certificates
|
||||
type CertBuilder struct {
|
||||
template *MLDSACert
|
||||
signer sign.Signer
|
||||
}
|
||||
|
||||
// NewCertBuilder creates a new certificate builder
|
||||
func NewCertBuilder(signer sign.Signer) *CertBuilder {
|
||||
return &CertBuilder{
|
||||
signer: signer,
|
||||
template: &MLDSACert{
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(30 * 24 * time.Hour), // 30 days default
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetSubject sets the certificate subject
|
||||
func (cb *CertBuilder) SetSubject(subject pkix.Name) *CertBuilder {
|
||||
cb.template.Subject = subject
|
||||
return cb
|
||||
}
|
||||
|
||||
// SetValidity sets the certificate validity period
|
||||
func (cb *CertBuilder) SetValidity(notBefore, notAfter time.Time) *CertBuilder {
|
||||
cb.template.NotBefore = notBefore
|
||||
cb.template.NotAfter = notAfter
|
||||
return cb
|
||||
}
|
||||
|
||||
// SetNodeID sets the node ID extension
|
||||
func (cb *CertBuilder) SetNodeID(nodeID string) *CertBuilder {
|
||||
cb.template.NodeID = nodeID
|
||||
return cb
|
||||
}
|
||||
|
||||
// SetRole sets the role extension
|
||||
func (cb *CertBuilder) SetRole(role string) *CertBuilder {
|
||||
cb.template.Role = role
|
||||
return cb
|
||||
}
|
||||
|
||||
// SetCapabilities sets the capabilities extension
|
||||
func (cb *CertBuilder) SetCapabilities(caps []string) *CertBuilder {
|
||||
cb.template.Capabilities = caps
|
||||
return cb
|
||||
}
|
||||
|
||||
// SetCA marks the certificate as a CA
|
||||
func (cb *CertBuilder) SetCA(isCA bool) *CertBuilder {
|
||||
cb.template.IsCA = isCA
|
||||
if isCA {
|
||||
cb.template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
return cb
|
||||
}
|
||||
|
||||
// Build creates the certificate
|
||||
func (cb *CertBuilder) Build(publicKey sign.PublicKey, issuerKey sign.PrivateKey) (*MLDSACert, error) {
|
||||
cert := *cb.template
|
||||
cert.PublicKey = publicKey
|
||||
|
||||
// Generate serial number
|
||||
cert.SerialNumber = generateSerialNumber()
|
||||
|
||||
// Self-signed if no issuer provided
|
||||
if issuerKey == nil {
|
||||
cert.Issuer = cert.Subject
|
||||
}
|
||||
|
||||
// Encode to DER
|
||||
certBytes, err := encodeCertificate(&cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the certificate
|
||||
if issuerKey != nil {
|
||||
signature, err := cb.signer.Sign(issuerKey, certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Append signature to certificate
|
||||
cert.Raw = append(certBytes, signature...)
|
||||
} else {
|
||||
cert.Raw = certBytes
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// encodeCertificate encodes a certificate to DER
|
||||
func encodeCertificate(cert *MLDSACert) ([]byte, error) {
|
||||
// Simplified encoding - in production would use proper ASN.1
|
||||
// This is a placeholder
|
||||
encoded := []byte("MLDSA-CERT-v1")
|
||||
encoded = append(encoded, cert.PublicKey.Bytes()...)
|
||||
encoded = append(encoded, []byte(cert.NodeID)...)
|
||||
encoded = append(encoded, []byte(cert.Role)...)
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// generateSerialNumber generates a random serial number
|
||||
func generateSerialNumber() []byte {
|
||||
// In production, generate cryptographically random serial
|
||||
return []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
|
||||
}
|
||||
|
||||
// ParseMLDSACert parses an ML-DSA certificate
|
||||
func ParseMLDSACert(der []byte) (*MLDSACert, error) {
|
||||
// Placeholder parser
|
||||
// In production, would parse actual DER-encoded certificate
|
||||
|
||||
cert := &MLDSACert{
|
||||
Raw: der,
|
||||
}
|
||||
|
||||
// Parse basic fields (placeholder)
|
||||
if len(der) < 100 {
|
||||
return nil, errors.New("certificate too short")
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// OID definitions for ML-DSA algorithms
|
||||
var (
|
||||
OIDMLDSA44 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 2, 267, 7, 6, 5} // ML-DSA-44
|
||||
OIDMLDSA65 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 2, 267, 7, 8, 7} // ML-DSA-65
|
||||
OIDMLDSA87 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 2, 267, 7, 10, 8} // ML-DSA-87
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
// Centralized cgo wrapper to avoid duplicate linking
|
||||
// All other packages should import this instead of having their own cgo directives
|
||||
package luxlink
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-crypto lux-gpu lux-lattice
|
||||
#include <lux/crypto/crypto.h>
|
||||
#include <lux/crypto/metal_mldsa.h>
|
||||
#include <lux/crypto/metal_mlkem.h>
|
||||
#include <lux/crypto/metal_slhdsa.h>
|
||||
#include <lux/crypto/metal_ipa.h>
|
||||
#include <lux/gpu/gpu.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Export Go APIs that other packages can call
|
||||
// This ensures libraries are only linked once
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// Suppress duplicate library warnings on macOS
|
||||
// This file is intentionally left with minimal content as the suppression
|
||||
// is handled via build flags or environment variables
|
||||
package luxlink
|
||||
|
||||
import "C"
|
||||
@@ -697,4 +697,4 @@ func TestHashFunctions(t *testing.T) {
|
||||
- [FIPS 202: SHA-3 Standard](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf)
|
||||
- [BLAKE2 Specification](https://blake2.net/blake2.pdf)
|
||||
- [Keccak Reference](https://keccak.team/keccak.html)
|
||||
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
|
||||
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
|
||||
|
||||
@@ -262,7 +262,7 @@ func (w *HDWallet) DeriveAddress(coinType, account, change, index uint32) (*ecds
|
||||
```go
|
||||
import (
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// KeyStore manages encrypted key storage
|
||||
@@ -845,4 +845,4 @@ func TestKeyManagement(t *testing.T) {
|
||||
- [BIP-39: Mnemonic Phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
|
||||
- [BIP-44: Multi-Account Hierarchy](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
|
||||
- [NIST SP 800-57: Key Management](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r5.pdf)
|
||||
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
|
||||
- [Argon2 Specification](https://github.com/P-H-C/phc-winner-argon2)
|
||||
|
||||
@@ -14,17 +14,20 @@ require (
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5
|
||||
github.com/ethereum/go-verkle v0.2.2
|
||||
github.com/google/btree v1.1.3
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7
|
||||
github.com/leanovate/gopter v0.2.11
|
||||
github.com/luxfi/cache v1.1.0
|
||||
github.com/luxfi/gpu v0.30.0
|
||||
github.com/luxfi/ids v1.2.4
|
||||
github.com/luxfi/log v1.1.26
|
||||
github.com/luxfi/ids v1.2.7
|
||||
github.com/luxfi/log v1.2.1
|
||||
github.com/luxfi/mock v0.1.0
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/supranational/blst v0.3.16
|
||||
github.com/zeebo/blake3 v0.2.4
|
||||
go.uber.org/mock v0.6.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.39.0
|
||||
@@ -41,7 +44,7 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -130,6 +130,7 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -213,14 +214,11 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/cache v1.1.0 h1:6LUyGGZ+rrMAJBbAU6+UwkcamXj3zsboRUodIof2Ong=
|
||||
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
|
||||
github.com/luxfi/gpu v0.29.4 h1:0eCgEbffLayTLaPYLkXx5IbL2490r5jqZAKoSfkVVZI=
|
||||
github.com/luxfi/gpu v0.29.4/go.mod h1:7pFsHqra1Vrmy2aGXVEPKeKsPR+Bn+QmBXuByK3fdPA=
|
||||
github.com/luxfi/gpu v0.30.0 h1:k+7EDp/WHTa0176zXet1UXTl8IcZcp1ZkS8GUJ5l2iU=
|
||||
github.com/luxfi/gpu v0.30.0/go.mod h1:7pFsHqra1Vrmy2aGXVEPKeKsPR+Bn+QmBXuByK3fdPA=
|
||||
github.com/luxfi/ids v1.2.4 h1:e0OaeSI6xWjS9JnxxxfvCCs9HHDUrwDc3M9ctIhjcDI=
|
||||
github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244=
|
||||
github.com/luxfi/log v1.1.26 h1:ECnJ4wV7TF6WZ5gC6CD6ddZ4OhF+zhQij4bgVUhumDg=
|
||||
github.com/luxfi/log v1.1.26/go.mod h1:ACN1FtMlJ/NRuWROYH2TtiFmJuUbSG+OxINlRzJM/38=
|
||||
github.com/luxfi/ids v1.2.7 h1:3B42EbzR2cdY3veo1yOFOOIeEE+HULnCye2Ye32nXqI=
|
||||
github.com/luxfi/log v1.2.1 h1:L2JM8MjGPF8rBnY+EXODS9x5OQTGSgeQ1SPFmeB+oPY=
|
||||
github.com/luxfi/mock v0.1.0 h1:IwElfNu+T9sXvzFX6tudPDx1vqPuACRSRdxpD5lxW+o=
|
||||
github.com/luxfi/utils v1.1.0 h1:ti7HvjNwJd4ILDMERJtOAWE9mF8l+zqDVkgWnF7Agic=
|
||||
github.com/luxfi/utils v1.1.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
@@ -310,6 +308,7 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
@@ -337,8 +336,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
// Consolidated cgo wrapper to avoid duplicate linking
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-all
|
||||
#include <lux/crypto/crypto.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// This file consolidates all cgo dependencies to avoid duplicate library warnings
|
||||
// Individual packages should import this instead of having their own cgo directives
|
||||
+53
-1
@@ -12,13 +12,65 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-crypto
|
||||
#cgo pkg-config: lux-crypto-only
|
||||
#cgo darwin,arm64 CFLAGS: -DUSE_METAL=1
|
||||
#cgo linux CFLAGS: -DUSE_CUDA=1
|
||||
#cgo LDFLAGS: -lstdc++
|
||||
|
||||
#include <lux/crypto/crypto.h>
|
||||
|
||||
// Short aliases for Go code readability (hides lux_ prefix)
|
||||
#define crypto_gpu_available lux_crypto_gpu_available
|
||||
#define crypto_get_backend lux_crypto_get_backend
|
||||
#define crypto_clear_cache lux_crypto_clear_cache
|
||||
|
||||
// BLS
|
||||
#define bls_keygen lux_crypto_bls_keygen
|
||||
#define bls_sk_to_pk lux_crypto_bls_sk_to_pk
|
||||
#define bls_sign lux_crypto_bls_sign
|
||||
#define bls_verify lux_crypto_bls_verify
|
||||
#define bls_aggregate_signatures lux_crypto_bls_aggregate_signatures
|
||||
#define bls_aggregate_public_keys lux_crypto_bls_aggregate_public_keys
|
||||
#define bls_verify_aggregated lux_crypto_bls_verify_aggregated
|
||||
#define bls_batch_verify lux_crypto_bls_batch_verify
|
||||
#define bls_batch_sign lux_crypto_bls_batch_sign
|
||||
|
||||
// ML-DSA
|
||||
#define mldsa_keygen lux_crypto_mldsa_keygen
|
||||
#define mldsa_sign lux_crypto_mldsa_sign
|
||||
#define mldsa_verify lux_crypto_mldsa_verify
|
||||
#define mldsa_batch_verify lux_crypto_mldsa_batch_verify
|
||||
|
||||
// Threshold
|
||||
#define threshold_create lux_crypto_threshold_create
|
||||
#define threshold_destroy lux_crypto_threshold_destroy
|
||||
#define threshold_keygen lux_crypto_threshold_keygen
|
||||
#define threshold_partial_sign lux_crypto_threshold_partial_sign
|
||||
#define threshold_combine lux_crypto_threshold_combine
|
||||
#define threshold_verify lux_crypto_threshold_verify
|
||||
|
||||
// Hashing
|
||||
#define crypto_sha3_256 lux_crypto_sha3_256
|
||||
#define crypto_sha3_512 lux_crypto_sha3_512
|
||||
#define crypto_blake3 lux_crypto_blake3
|
||||
#define crypto_batch_hash lux_crypto_batch_hash
|
||||
|
||||
// Consensus
|
||||
#define consensus_verify_block lux_crypto_consensus_verify_block
|
||||
|
||||
// Error codes
|
||||
#define CRYPTO_SUCCESS LUX_CRYPTO_SUCCESS
|
||||
#define CRYPTO_ERROR_INVALID LUX_CRYPTO_ERROR_INVALID
|
||||
#define CRYPTO_ERROR_INVALID_KEY LUX_CRYPTO_ERROR_INVALID_KEY
|
||||
#define CRYPTO_ERROR_INVALID_SIG LUX_CRYPTO_ERROR_INVALID_SIG
|
||||
#define CRYPTO_ERROR_NULL_PTR LUX_CRYPTO_ERROR_NULL_PTR
|
||||
#define CRYPTO_ERROR_GPU LUX_CRYPTO_ERROR_GPU
|
||||
#define CRYPTO_ERROR_THRESHOLD LUX_CRYPTO_ERROR_THRESHOLD
|
||||
#define CRYPTO_ERROR_HASH LUX_CRYPTO_ERROR_HASH
|
||||
|
||||
// Types
|
||||
#define ThresholdContext LuxCryptoThresholdContext
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build gpu && darwin
|
||||
//go:build cgo && darwin
|
||||
|
||||
// Package blake3 provides Blake3 hash functions via luxcpp/crypto.
|
||||
// This CGO version provides optimized hashing with automatic CPU/GPU selection.
|
||||
@@ -9,17 +9,20 @@
|
||||
package blake3
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo CFLAGS: -I/Users/z/work/luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// C API from luxcpp/crypto
|
||||
extern void crypto_blake3(uint8_t* out, const uint8_t* in, size_t len);
|
||||
extern int crypto_batch_hash(uint8_t** outs, const uint8_t* const* ins, const size_t* lens, uint32_t count, int hash_type);
|
||||
extern int crypto_gpu_available(void);
|
||||
extern const char* crypto_get_backend(void);
|
||||
#include <lux/crypto/crypto.h>
|
||||
|
||||
// Short aliases for Go code readability (hides lux_crypto_ prefix)
|
||||
#define crypto_gpu_available lux_crypto_gpu_available
|
||||
#define crypto_get_backend lux_crypto_get_backend
|
||||
#define crypto_blake3 lux_crypto_blake3
|
||||
#define crypto_batch_hash lux_crypto_batch_hash
|
||||
*/
|
||||
import "C"
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package consistent
|
||||
|
||||
// Hashable is an interface to be implemented by structs that need to be sharded via consistent hashing.
|
||||
type Hashable interface {
|
||||
// ConsistentHashKey is the key used to shard the blob.
|
||||
// This should be constant for a given blob.
|
||||
ConsistentHashKey() []byte
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package consistent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/google/btree"
|
||||
|
||||
hashing "github.com/luxfi/crypto/hash"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Ring = (*hashRing)(nil)
|
||||
_ btree.LessFunc[ringItem] = ringItem.Less
|
||||
|
||||
errEmptyRing = errors.New("ring doesn't have any members")
|
||||
)
|
||||
|
||||
// Ring is an interface for a consistent hashing ring
|
||||
// (ref: https://en.wikipedia.org/wiki/Consistent_hashing).
|
||||
//
|
||||
// Consistent hashing is a method of distributing keys across an arbitrary set
|
||||
// of destinations.
|
||||
//
|
||||
// Consider a naive approach, which uses modulo to map a key to a node to serve
|
||||
// cached requests. Let N be the number of keys and M is the amount of possible
|
||||
// hashing destinations. Here, a key would be routed to a node based on
|
||||
// h(key) % M = node assigned.
|
||||
//
|
||||
// With this approach, we can route a key to a node in O(1) time, but this
|
||||
// results in cache misses as nodes are introduced and removed, which results in
|
||||
// the keys being reshuffled across nodes, as modulo's output changes with M.
|
||||
// This approach results in O(N) keys being shuffled, which is undesirable for a
|
||||
// caching use-case.
|
||||
//
|
||||
// Consistent hashing works by hashing all keys into a circle, which can be
|
||||
// visualized as a clock. Keys are routed to a node by hashing the key, and
|
||||
// searching for the first clockwise neighbor. This requires O(N) memory to
|
||||
// maintain the state of the ring and log(N) time to route a key using
|
||||
// binary-search, but results in O(N/M) amount of keys being shuffled when a
|
||||
// node is added/removed because the addition/removal of a node results in a
|
||||
// split/merge of its counter-clockwise neighbor's hash space.
|
||||
//
|
||||
// As an example, assume we have a ring that supports hashes from 1-12.
|
||||
//
|
||||
// 12
|
||||
// 11 1
|
||||
//
|
||||
// 10 2
|
||||
//
|
||||
// 9 3
|
||||
//
|
||||
// 8 4
|
||||
//
|
||||
// 7 5
|
||||
// 6
|
||||
//
|
||||
// Add node 1 (n1). Let h(n1) = 12.
|
||||
// First, we compute the hash the node, and insert it into its corresponding
|
||||
// location on the ring.
|
||||
//
|
||||
// 12 (n1)
|
||||
// 11 1
|
||||
//
|
||||
// 10 2
|
||||
//
|
||||
// 9 3
|
||||
//
|
||||
// 8 4
|
||||
//
|
||||
// 7 5
|
||||
// 6
|
||||
//
|
||||
// Now, to see which node a key (k1) should map to, we hash the key and search
|
||||
// for its closest clockwise neighbor.
|
||||
// Let h(k1) = 3. Here, we see that since n1 is the closest neighbor, as there
|
||||
// are no other nodes in the ring.
|
||||
//
|
||||
// 12 (n1)
|
||||
// 11 1
|
||||
//
|
||||
// 10 2
|
||||
//
|
||||
// 9 3 (k1)
|
||||
//
|
||||
// 8 4
|
||||
//
|
||||
// 7 5
|
||||
// 6
|
||||
//
|
||||
// Now, let's insert another node (n2), such that h(n2) = 6.
|
||||
// Here we observe that k1 has shuffled to n2, as n2 is the closest clockwise
|
||||
// neighbor to k1.
|
||||
//
|
||||
// 12 (n1)
|
||||
// 11 1
|
||||
//
|
||||
// 10 2
|
||||
//
|
||||
// 9 3 (k1)
|
||||
//
|
||||
// 8 4
|
||||
//
|
||||
// 7 5
|
||||
// 6 (n2)
|
||||
//
|
||||
// Other optimizations can be made to help reduce blast radius of failures and
|
||||
// the variance in keys (hot shards). One such optimization is introducing
|
||||
// virtual nodes, which is to replicate a single node multiple times by salting
|
||||
// it, (e.g n1-0, n1-1...).
|
||||
//
|
||||
// Without virtualization, failures of a node cascade as each node failing
|
||||
// results in the load of the failed node being shuffled into its clockwise
|
||||
// neighbor, which can result in a sampling effect across the network.
|
||||
type Ring interface {
|
||||
RingReader
|
||||
ringMutator
|
||||
}
|
||||
|
||||
// RingReader is an interface to read values from Ring.
|
||||
type RingReader interface {
|
||||
// Get gets the closest clockwise node for a key in the ring.
|
||||
//
|
||||
// Each ring member is responsible for the hashes which fall in the range
|
||||
// between (myself, clockwise-neighbor].
|
||||
// This behavior is desirable so that we can re-use the return value of Get
|
||||
// to iterate around the ring (Ex. replication, retries, etc).
|
||||
//
|
||||
// Returns the node routed to and an error if we're unable to resolve a node
|
||||
// to map to.
|
||||
Get(Hashable) (Hashable, error)
|
||||
}
|
||||
|
||||
// ringMutator defines an interface that mutates Ring.
|
||||
type ringMutator interface {
|
||||
// Add adds a node to the ring.
|
||||
Add(Hashable)
|
||||
|
||||
// Remove removes the node from the ring.
|
||||
//
|
||||
// Returns true if the node was removed, and false if it wasn't present to
|
||||
// begin with.
|
||||
Remove(Hashable) bool
|
||||
}
|
||||
|
||||
// hashRing is an implementation of Ring
|
||||
type hashRing struct {
|
||||
// Hashing algorithm to use when hashing keys.
|
||||
hasher hashing.Hasher
|
||||
|
||||
// Replication factor for nodes; must be greater than zero.
|
||||
virtualNodes int
|
||||
|
||||
lock sync.RWMutex
|
||||
ring *btree.BTreeG[ringItem]
|
||||
}
|
||||
|
||||
// RingConfig configures settings for a Ring.
|
||||
type RingConfig struct {
|
||||
// Replication factor for nodes in the ring.
|
||||
VirtualNodes int
|
||||
// Hashing implementation to use.
|
||||
Hasher hashing.Hasher
|
||||
// Degree represents the degree of the b-tree
|
||||
Degree int
|
||||
}
|
||||
|
||||
// NewHashRing instantiates an instance of hashRing.
|
||||
func NewHashRing(config RingConfig) Ring {
|
||||
return &hashRing{
|
||||
hasher: config.Hasher,
|
||||
virtualNodes: config.VirtualNodes,
|
||||
ring: btree.NewG(config.Degree, ringItem.Less),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hashRing) Get(key Hashable) (Hashable, error) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
|
||||
return h.get(key)
|
||||
}
|
||||
|
||||
func (h *hashRing) get(key Hashable) (Hashable, error) {
|
||||
// If we have no members in the ring, it's not possible to find where the
|
||||
// key belongs.
|
||||
if h.ring.Len() == 0 {
|
||||
return nil, errEmptyRing
|
||||
}
|
||||
|
||||
var (
|
||||
// Compute this key's hash
|
||||
hash = h.hasher.Hash(key.ConsistentHashKey())
|
||||
result Hashable
|
||||
)
|
||||
h.ring.AscendGreaterOrEqual(
|
||||
ringItem{
|
||||
hash: hash,
|
||||
value: key,
|
||||
},
|
||||
func(item ringItem) bool {
|
||||
if hash < item.hash {
|
||||
result = item.value
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
)
|
||||
|
||||
// If found nothing ascending the tree, we need to wrap around the ring to
|
||||
// the left-most (min) node.
|
||||
if result == nil {
|
||||
minNode, _ := h.ring.Min()
|
||||
result = minNode.value
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (h *hashRing) Add(key Hashable) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
h.add(key)
|
||||
}
|
||||
|
||||
func (h *hashRing) add(key Hashable) {
|
||||
// Replicate the node in the ring.
|
||||
hashKey := key.ConsistentHashKey()
|
||||
for i := 0; i < h.virtualNodes; i++ {
|
||||
virtualNode := getHashKey(hashKey, i)
|
||||
virtualNodeHash := h.hasher.Hash(virtualNode)
|
||||
|
||||
// Insert it into the ring.
|
||||
h.ring.ReplaceOrInsert(ringItem{
|
||||
hash: virtualNodeHash,
|
||||
value: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hashRing) Remove(key Hashable) bool {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
return h.remove(key)
|
||||
}
|
||||
|
||||
func (h *hashRing) remove(key Hashable) bool {
|
||||
var (
|
||||
hashKey = key.ConsistentHashKey()
|
||||
removed = false
|
||||
)
|
||||
|
||||
// We need to delete all virtual nodes created for a single node.
|
||||
for i := 0; i < h.virtualNodes; i++ {
|
||||
virtualNode := getHashKey(hashKey, i)
|
||||
virtualNodeHash := h.hasher.Hash(virtualNode)
|
||||
item := ringItem{
|
||||
hash: virtualNodeHash,
|
||||
}
|
||||
_, removed = h.ring.Delete(item)
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
// getHashKey builds a key given a base key and a virtual node number.
|
||||
func getHashKey(key []byte, virtualNode int) []byte {
|
||||
return append(key, byte(virtualNode))
|
||||
}
|
||||
|
||||
// ringItem is a helper class to represent ring nodes in the b-tree.
|
||||
type ringItem struct {
|
||||
hash uint64
|
||||
value Hashable
|
||||
}
|
||||
|
||||
func (r ringItem) Less(than ringItem) bool {
|
||||
return r.hash < than.hash
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package consistent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/luxfi/crypto/hash/hashingmock"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Hashable = (*testKey)(nil)
|
||||
|
||||
// nodes
|
||||
node1 = testKey{key: "node-1", hash: 1}
|
||||
node2 = testKey{key: "node-2", hash: 2}
|
||||
node3 = testKey{key: "node-3", hash: 3}
|
||||
)
|
||||
|
||||
// testKey is a simple wrapper around a key and its mocked hash for testing.
|
||||
type testKey struct {
|
||||
// key
|
||||
key string
|
||||
// mocked hash value of the key
|
||||
hash uint64
|
||||
}
|
||||
|
||||
func (t testKey) ConsistentHashKey() []byte {
|
||||
return []byte(t.key)
|
||||
}
|
||||
|
||||
// Tests that a key routes to its closest clockwise node.
|
||||
// Test cases are described in greater detail below; see diagrams for Ring.
|
||||
func TestGetMapsToClockwiseNode(t *testing.T) {
|
||||
tests := []struct {
|
||||
// name of the test
|
||||
name string
|
||||
// nodes that exist in the ring
|
||||
ringNodes []testKey
|
||||
// key to try to route
|
||||
key testKey
|
||||
// expected key to be routed to
|
||||
expectedNode testKey
|
||||
}{
|
||||
{
|
||||
// If we're left of a node in the ring, we should route to it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> foo -> node-1 -> ...
|
||||
name: "key with right node",
|
||||
ringNodes: []testKey{
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 0,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
{
|
||||
// If we occupy the same hash as the only ring node, we should route to it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> foo, node-1 -> ...
|
||||
name: "key with equal node",
|
||||
ringNodes: []testKey{
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 1,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
{
|
||||
// If we're clockwise of the only node, we should wrap around and route to that node.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> foo -> ...
|
||||
name: "key wraps around to left-most node",
|
||||
ringNodes: []testKey{
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 2,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
|
||||
{
|
||||
// If we're left of multiple nodes in the ring, we should route to the first clockwise node.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> foo -> node-1 -> node-2 -> ...
|
||||
name: "key with two right nodes",
|
||||
ringNodes: []testKey{
|
||||
node1,
|
||||
node2,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 0,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
{
|
||||
// If we occupy the same hash as a node, we should route to the node clockwise of it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> foo, node-1 -> node-2 -> ...
|
||||
name: "key with one equal node and one right node",
|
||||
ringNodes: []testKey{
|
||||
node2,
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 1,
|
||||
},
|
||||
expectedNode: node2,
|
||||
},
|
||||
{
|
||||
// If we're in between two nodes, we should route to the clockwise node.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> foo -> node-3 -> ...
|
||||
name: "key between two nodes",
|
||||
ringNodes: []testKey{
|
||||
node3,
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 2,
|
||||
},
|
||||
expectedNode: node3,
|
||||
},
|
||||
{
|
||||
// If we're clockwise of all ring keys, we should wrap around and route to the left-most node.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> node-2 -> foo -> ...
|
||||
name: "key with two left nodes and no right neighbors",
|
||||
ringNodes: []testKey{
|
||||
node2,
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 3,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
{
|
||||
// If we occupy the same hash as a node, we should wrap around to the clockwise node.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> node-2, foo -> ...
|
||||
name: "key with equal neighbor and no right node wraps around to left-most node",
|
||||
ringNodes: []testKey{
|
||||
node2,
|
||||
node1,
|
||||
},
|
||||
key: testKey{
|
||||
key: "foo",
|
||||
hash: 2,
|
||||
},
|
||||
expectedNode: node1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
// setup expected calls
|
||||
calls := make([]any, len(test.ringNodes)+1)
|
||||
|
||||
for i, key := range test.ringNodes {
|
||||
calls[i] = hasher.EXPECT().Hash(getHashKey(key.ConsistentHashKey(), 0)).Return(key.hash).Times(1)
|
||||
}
|
||||
|
||||
calls[len(test.ringNodes)] = hasher.EXPECT().Hash(test.key.ConsistentHashKey()).Return(test.key.hash).Times(1)
|
||||
gomock.InOrder(calls...)
|
||||
|
||||
// execute test
|
||||
for _, key := range test.ringNodes {
|
||||
ring.Add(key)
|
||||
}
|
||||
|
||||
node, err := ring.Get(test.key)
|
||||
require.NoError(err)
|
||||
require.Equal(test.expectedNode, node)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if we have an empty ring, trying to call Get results in an error, as there is no node to route to.
|
||||
func TestGetOnEmptyRingReturnsError(t *testing.T) {
|
||||
ring, _ := setupTest(t, 1)
|
||||
|
||||
foo := testKey{
|
||||
key: "foo",
|
||||
hash: 0,
|
||||
}
|
||||
_, err := ring.Get(foo)
|
||||
require.ErrorIs(t, err, errEmptyRing)
|
||||
}
|
||||
|
||||
// Tests that trying to call Remove on a node that doesn't exist should return false.
|
||||
func TestRemoveNonExistentKeyReturnsFalse(t *testing.T) {
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
gomock.InOrder(
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
)
|
||||
|
||||
// try to remove something from an empty ring.
|
||||
require.False(t, ring.Remove(node1))
|
||||
}
|
||||
|
||||
// Tests that trying to call Remove on a node that doesn't exist should return true.
|
||||
func TestRemoveExistingKeyReturnsTrue(t *testing.T) {
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
gomock.InOrder(
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
)
|
||||
|
||||
// Add a node into the ring.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> ...
|
||||
ring.Add(node1)
|
||||
|
||||
gomock.InOrder(
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
)
|
||||
|
||||
// Try to remove it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> (empty) -> ...
|
||||
require.True(t, ring.Remove(node1))
|
||||
}
|
||||
|
||||
// Tests that if we have a collision, the node is replaced.
|
||||
func TestAddCollisionReplacement(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
foo := testKey{
|
||||
key: "foo",
|
||||
hash: 2,
|
||||
}
|
||||
|
||||
gomock.InOrder(
|
||||
// node-1 and node-2 occupy the same hash
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
hasher.EXPECT().Hash(foo.ConsistentHashKey()).Return(uint64(1)).Times(1),
|
||||
)
|
||||
|
||||
// Ring:
|
||||
// ... -> node-1 -> ...
|
||||
ring.Add(node1)
|
||||
|
||||
// Ring:
|
||||
// ... -> node-2 -> ...
|
||||
ring.Add(node2)
|
||||
|
||||
ringMember, err := ring.Get(foo)
|
||||
require.NoError(err)
|
||||
require.Equal(node2, ringMember)
|
||||
}
|
||||
|
||||
// Tests that virtual nodes are replicated on Add.
|
||||
func TestAddVirtualNodes(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ring, hasher := setupTest(t, 3)
|
||||
|
||||
gomock.InOrder(
|
||||
// we should see 3 virtual nodes created (0, 1, 2) when we insert a node into the ring.
|
||||
|
||||
// insert node-1
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(0)).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 1)).Return(uint64(2)).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 2)).Return(uint64(4)).Times(1),
|
||||
|
||||
// insert node-2
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 0)).Return(uint64(1)).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 1)).Return(uint64(3)).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 2)).Return(uint64(5)).Times(1),
|
||||
|
||||
// gets that should route to node-1
|
||||
hasher.EXPECT().Hash([]byte("foo1")).Return(uint64(1)).Times(1),
|
||||
hasher.EXPECT().Hash([]byte("foo3")).Return(uint64(3)).Times(1),
|
||||
hasher.EXPECT().Hash([]byte("foo5")).Return(uint64(5)).Times(1),
|
||||
|
||||
// gets that should route to node-2
|
||||
hasher.EXPECT().Hash([]byte("foo0")).Return(uint64(0)).Times(1),
|
||||
hasher.EXPECT().Hash([]byte("foo2")).Return(uint64(2)).Times(1),
|
||||
hasher.EXPECT().Hash([]byte("foo4")).Return(uint64(4)).Times(1),
|
||||
)
|
||||
|
||||
// Add node 1.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1-v0 -> node-1-v1 -> node-1-v2 -> ...
|
||||
ring.Add(node1)
|
||||
|
||||
// Add node 2.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1-v0 -> node-2-v0 -> node-1-v1 -> node-2-v1 -> node-1-v2 -> node-2-v2 -> ...
|
||||
ring.Add(node2)
|
||||
|
||||
// Gets that should route to node-1
|
||||
node, err := ring.Get(testKey{key: "foo1"})
|
||||
require.NoError(err)
|
||||
require.Equal(node1, node)
|
||||
node, err = ring.Get(testKey{key: "foo3"})
|
||||
require.NoError(err)
|
||||
require.Equal(node1, node)
|
||||
node, err = ring.Get(testKey{key: "foo5"})
|
||||
require.NoError(err)
|
||||
require.Equal(node1, node)
|
||||
|
||||
// Gets that should route to node-2
|
||||
node, err = ring.Get(testKey{key: "foo0"})
|
||||
require.NoError(err)
|
||||
require.Equal(node2, node)
|
||||
node, err = ring.Get(testKey{key: "foo2"})
|
||||
require.NoError(err)
|
||||
require.Equal(node2, node)
|
||||
node, err = ring.Get(testKey{key: "foo4"})
|
||||
require.NoError(err)
|
||||
require.Equal(node2, node)
|
||||
}
|
||||
|
||||
// Tests that the node routed to changes if an Add results in a key shuffle.
|
||||
func TestGetShuffleOnAdd(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
foo := testKey{
|
||||
key: "foo",
|
||||
hash: 1,
|
||||
}
|
||||
|
||||
gomock.InOrder(
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(uint64(0)).Times(1),
|
||||
hasher.EXPECT().Hash(foo.ConsistentHashKey()).Return(foo.hash).Times(1),
|
||||
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 0)).Return(uint64(2)).Times(1),
|
||||
hasher.EXPECT().Hash(foo.ConsistentHashKey()).Return(foo.hash).Times(1),
|
||||
)
|
||||
|
||||
// Add node-1 into the ring
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> ...
|
||||
ring.Add(node1)
|
||||
|
||||
// node-1 is the closest clockwise node (when we wrap around), so we route to it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> foo -> ...
|
||||
node, err := ring.Get(foo)
|
||||
require.NoError(err)
|
||||
require.Equal(node1, node)
|
||||
|
||||
// Add node-2, which results in foo being shuffled from node-1 to node-2.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> node-2 -> ...
|
||||
ring.Add(node2)
|
||||
|
||||
// Now node-2 is our closest clockwise node, so we should route to it.
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> foo -> node-2 -> ...
|
||||
node, err = ring.Get(foo)
|
||||
require.NoError(err)
|
||||
require.Equal(node2, node)
|
||||
}
|
||||
|
||||
// Tests that we can iterate around the ring.
|
||||
func TestIteration(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ring, hasher := setupTest(t, 1)
|
||||
|
||||
foo := testKey{
|
||||
key: "foo",
|
||||
hash: 0,
|
||||
}
|
||||
|
||||
gomock.InOrder(
|
||||
hasher.EXPECT().Hash(getHashKey(node1.ConsistentHashKey(), 0)).Return(node1.hash).Times(1),
|
||||
hasher.EXPECT().Hash(getHashKey(node2.ConsistentHashKey(), 0)).Return(node2.hash).Times(1),
|
||||
|
||||
hasher.EXPECT().Hash(foo.ConsistentHashKey()).Return(foo.hash).Times(1),
|
||||
hasher.EXPECT().Hash(node1.ConsistentHashKey()).Return(node1.hash).Times(1),
|
||||
)
|
||||
|
||||
// add node-1 into the ring
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> ...
|
||||
ring.Add(node1)
|
||||
|
||||
// add node-2 into the ring
|
||||
//
|
||||
// Ring:
|
||||
// ... -> node-1 -> node-2 -> ...
|
||||
ring.Add(node2)
|
||||
|
||||
// Get the neighbor of foo
|
||||
//
|
||||
// Ring:
|
||||
// ... -> foo -> node-1 -> node-2 -> ...
|
||||
node, err := ring.Get(foo)
|
||||
require.NoError(err)
|
||||
require.Equal(node1, node)
|
||||
|
||||
// iterate by re-using node-1 to get node-2
|
||||
node, err = ring.Get(node)
|
||||
require.NoError(err)
|
||||
require.Equal(node2, node)
|
||||
}
|
||||
|
||||
func setupTest(t *testing.T, virtualNodes int) (Ring, *hashingmock.Hasher) {
|
||||
ctrl := gomock.NewController(t)
|
||||
hasher := hashingmock.NewHasher(ctrl)
|
||||
|
||||
return NewHashRing(RingConfig{
|
||||
VirtualNodes: virtualNodes,
|
||||
Hasher: hasher,
|
||||
Degree: 2,
|
||||
}), hasher
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/luxfi/crypto/hash (interfaces: Hasher)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package=hashingmock -destination=hashingmock/hasher.go -mock_names=Hasher=Hasher github.com/luxfi/crypto/hash Hasher
|
||||
//
|
||||
|
||||
// Package hashingmock is a generated GoMock package.
|
||||
package hashingmock
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// Hasher is a mock of Hasher interface.
|
||||
type Hasher struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *HasherMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// HasherMockRecorder is the mock recorder for Hasher.
|
||||
type HasherMockRecorder struct {
|
||||
mock *Hasher
|
||||
}
|
||||
|
||||
// NewHasher creates a new mock instance.
|
||||
func NewHasher(ctrl *gomock.Controller) *Hasher {
|
||||
mock := &Hasher{ctrl: ctrl}
|
||||
mock.recorder = &HasherMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *Hasher) EXPECT() *HasherMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Hash mocks base method.
|
||||
func (m *Hasher) Hash(arg0 []byte) uint64 {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Hash", arg0)
|
||||
ret0, _ := ret[0].(uint64)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Hash indicates an expected call of Hash.
|
||||
func (mr *HasherMockRecorder) Hash(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*Hasher)(nil).Hash), arg0)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/luxfi/crypto/hash (interfaces: Hasher)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package=hash -destination=hash/mock_hasher.go github.com/luxfi/crypto/hash Hasher
|
||||
//
|
||||
|
||||
// Package hash is a generated GoMock package.
|
||||
package hash
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/luxfi/mock/gomock"
|
||||
)
|
||||
|
||||
// MockHasher is a mock of Hasher interface.
|
||||
type MockHasher struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHasherMockRecorder
|
||||
}
|
||||
|
||||
// MockHasherMockRecorder is the mock recorder for MockHasher.
|
||||
type MockHasherMockRecorder struct {
|
||||
mock *MockHasher
|
||||
}
|
||||
|
||||
// NewMockHasher creates a new mock instance.
|
||||
func NewMockHasher(ctrl *gomock.Controller) *MockHasher {
|
||||
mock := &MockHasher{ctrl: ctrl}
|
||||
mock.recorder = &MockHasherMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockHasher) EXPECT() *MockHasherMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Hash mocks base method.
|
||||
func (m *MockHasher) Hash(arg0 []byte) uint64 {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Hash", arg0)
|
||||
ret0, _ := ret[0].(uint64)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Hash indicates an expected call of Hash.
|
||||
func (mr *MockHasherMockRecorder) Hash(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHasher)(nil).Hash), arg0)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package hash
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -package=hashingmock -destination=hashingmock/hasher.go -mock_names=Hasher=Hasher . Hasher
|
||||
+112
-113
@@ -10,13 +10,14 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo CFLAGS: -I/Users/z/work/luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "lux/crypto/metal_zk.h"
|
||||
#include "lux/crypto/metal_poseidon2.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -32,20 +33,23 @@ const ElementSize = 32
|
||||
// Stored as 4 x 64-bit limbs in little-endian order.
|
||||
type Element [ElementSize]byte
|
||||
|
||||
// context is the global Metal ZK context (initialized on first use).
|
||||
// Threshold for GPU batch operations
|
||||
const GPUThreshold = 64
|
||||
|
||||
// context is the global Metal Poseidon2 context (initialized on first use).
|
||||
var (
|
||||
ctx *C.MetalZKContext
|
||||
ctx *C.MetalPoseidon2Context
|
||||
ctxReady bool
|
||||
)
|
||||
|
||||
// initContext lazily initializes the Metal ZK context.
|
||||
// initContext lazily initializes the Metal Poseidon2 context.
|
||||
func initContext() error {
|
||||
if ctxReady {
|
||||
return nil
|
||||
}
|
||||
ctx = C.metal_zk_init()
|
||||
ctx = C.metal_poseidon2_init()
|
||||
if ctx == nil {
|
||||
return errors.New("Metal ZK initialization failed")
|
||||
return errors.New("Metal Poseidon2 initialization failed")
|
||||
}
|
||||
ctxReady = true
|
||||
return nil
|
||||
@@ -53,12 +57,12 @@ func initContext() error {
|
||||
|
||||
// Available returns true if Metal GPU acceleration is available.
|
||||
func Available() bool {
|
||||
return bool(C.metal_zk_available())
|
||||
return bool(C.metal_poseidon2_available())
|
||||
}
|
||||
|
||||
// Threshold returns the recommended batch size for GPU offload.
|
||||
func Threshold() uint32 {
|
||||
return uint32(C.metal_zk_get_threshold(C.METAL_ZK_OP_POSEIDON2_HASH))
|
||||
return GPUThreshold
|
||||
}
|
||||
|
||||
// HashPair computes Poseidon2 hash of two elements (2-to-1 compression).
|
||||
@@ -69,12 +73,14 @@ func HashPair(left, right Element) (Element, error) {
|
||||
return out, err
|
||||
}
|
||||
|
||||
leftFr := (*C.Fr256)(unsafe.Pointer(&left[0]))
|
||||
rightFr := (*C.Fr256)(unsafe.Pointer(&right[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&out[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_hash_pair(ctx, outFr, leftFr, rightFr, 1)
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_compress(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&out[0]),
|
||||
unsafe.Pointer(&left[0]),
|
||||
unsafe.Pointer(&right[0]),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return out, errors.New("Poseidon2 hash pair failed")
|
||||
}
|
||||
return out, nil
|
||||
@@ -94,12 +100,15 @@ func BatchHashPair(left, right []Element) ([]Element, error) {
|
||||
outputs := make([]Element, n)
|
||||
|
||||
// GPU batch processing
|
||||
leftFr := (*C.Fr256)(unsafe.Pointer(&left[0]))
|
||||
rightFr := (*C.Fr256)(unsafe.Pointer(&right[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_hash_pair(ctx, outFr, leftFr, rightFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_batch_compress(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&outputs[0]),
|
||||
unsafe.Pointer(&left[0]),
|
||||
unsafe.Pointer(&right[0]),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch hash pair failed")
|
||||
}
|
||||
return outputs, nil
|
||||
@@ -116,15 +125,16 @@ func MerkleLayer(current []Element) ([]Element, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := make([]Element, n/2)
|
||||
currentFr := (*C.Fr256)(unsafe.Pointer(¤t[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&output[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_merkle_layer(ctx, outFr, currentFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 Merkle layer failed")
|
||||
// Split into left and right halves for batch compress
|
||||
numPairs := n / 2
|
||||
lefts := make([]Element, numPairs)
|
||||
rights := make([]Element, numPairs)
|
||||
for i := 0; i < numPairs; i++ {
|
||||
lefts[i] = current[i*2]
|
||||
rights[i] = current[i*2+1]
|
||||
}
|
||||
return output, nil
|
||||
|
||||
return BatchHashPair(lefts, rights)
|
||||
}
|
||||
|
||||
// MerkleTree builds a complete Merkle tree from leaves using GPU.
|
||||
@@ -141,11 +151,15 @@ func MerkleTree(leaves []Element) ([]Element, error) {
|
||||
|
||||
// Tree has n-1 internal nodes
|
||||
tree := make([]Element, n-1)
|
||||
leavesFr := (*C.Fr256)(unsafe.Pointer(&leaves[0]))
|
||||
treeFr := (*C.Fr256)(unsafe.Pointer(&tree[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_merkle_tree(ctx, treeFr, leavesFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_merkle_tree(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&tree[0]),
|
||||
unsafe.Pointer(&leaves[0]),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 Merkle tree failed")
|
||||
}
|
||||
return tree, nil
|
||||
@@ -154,12 +168,19 @@ func MerkleTree(leaves []Element) ([]Element, error) {
|
||||
// MerkleRoot computes just the Merkle root from leaves.
|
||||
func MerkleRoot(leaves []Element) (Element, error) {
|
||||
var root Element
|
||||
tree, err := MerkleTree(leaves)
|
||||
if err != nil {
|
||||
if err := initContext(); err != nil {
|
||||
return root, err
|
||||
}
|
||||
if len(tree) > 0 {
|
||||
root = tree[0]
|
||||
|
||||
ret := C.metal_poseidon2_merkle_root(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&root[0]),
|
||||
unsafe.Pointer(&leaves[0]),
|
||||
C.uint32_t(len(leaves)),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return root, errors.New("Poseidon2 Merkle root failed")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
@@ -167,72 +188,62 @@ func MerkleRoot(leaves []Element) (Element, error) {
|
||||
// VerifyProof verifies a single Merkle proof.
|
||||
// Returns true if the proof is valid.
|
||||
func VerifyProof(leaf, root Element, path []Element, indices []uint32) (bool, error) {
|
||||
results, err := BatchVerifyProofs([]Element{leaf}, []Element{root}, [][]Element{path}, [][]uint32{indices})
|
||||
if err != nil {
|
||||
if len(path) != len(indices) {
|
||||
return false, errors.New("path length mismatch")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return results[0], nil
|
||||
|
||||
depth := len(path)
|
||||
if depth == 0 {
|
||||
// Single node tree
|
||||
return leaf == root, nil
|
||||
}
|
||||
|
||||
ret := C.metal_poseidon2_merkle_verify(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&root[0]),
|
||||
unsafe.Pointer(&leaf[0]),
|
||||
unsafe.Pointer(&path[0]),
|
||||
C.uint32_t(indices[0]), // leaf index
|
||||
C.uint32_t(depth),
|
||||
)
|
||||
|
||||
return ret == C.METAL_POSEIDON2_SUCCESS, nil
|
||||
}
|
||||
|
||||
// BatchVerifyProofs verifies multiple Merkle proofs in parallel on GPU.
|
||||
// Note: This implementation falls back to sequential verification as
|
||||
// the metal_poseidon2 API doesn't have a batch verify function.
|
||||
func BatchVerifyProofs(leaves, roots []Element, paths [][]Element, indices [][]uint32) ([]bool, error) {
|
||||
n := len(leaves)
|
||||
if n == 0 || n != len(roots) || n != len(paths) || n != len(indices) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if n > 0 && len(paths[0]) != len(indices[0]) {
|
||||
return nil, errors.New("path length mismatch")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathLen := len(paths[0])
|
||||
results := make([]C.int, n)
|
||||
|
||||
// Flatten paths and indices
|
||||
flatPaths := make([]Element, n*pathLen)
|
||||
flatIndices := make([]uint32, n*pathLen)
|
||||
results := make([]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
copy(flatPaths[i*pathLen:], paths[i])
|
||||
copy(flatIndices[i*pathLen:], indices[i])
|
||||
valid, err := VerifyProof(leaves[i], roots[i], paths[i], indices[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[i] = valid
|
||||
}
|
||||
|
||||
leavesFr := (*C.Fr256)(unsafe.Pointer(&leaves[0]))
|
||||
rootsFr := (*C.Fr256)(unsafe.Pointer(&roots[0]))
|
||||
pathsFr := (*C.Fr256)(unsafe.Pointer(&flatPaths[0]))
|
||||
indicesPtr := (*C.uint32_t)(unsafe.Pointer(&flatIndices[0]))
|
||||
resultsPtr := (*C.int)(unsafe.Pointer(&results[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_verify_proofs(
|
||||
ctx,
|
||||
resultsPtr,
|
||||
leavesFr,
|
||||
pathsFr,
|
||||
indicesPtr,
|
||||
rootsFr,
|
||||
C.uint32_t(n),
|
||||
C.uint32_t(pathLen),
|
||||
)
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch verify proofs failed")
|
||||
}
|
||||
|
||||
boolResults := make([]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
boolResults[i] = results[i] != 0
|
||||
}
|
||||
return boolResults, nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Commitment computes a Pedersen-style commitment using Poseidon2.
|
||||
// commitment = Poseidon2(value, blinding, salt)
|
||||
func Commitment(value, blinding, salt Element) (Element, error) {
|
||||
commitments, err := BatchCommitment([]Element{value}, []Element{blinding}, []Element{salt})
|
||||
// First hash value and blinding
|
||||
temp, err := HashPair(value, blinding)
|
||||
if err != nil {
|
||||
return Element{}, err
|
||||
}
|
||||
return commitments[0], nil
|
||||
// Then hash result with salt
|
||||
return HashPair(temp, salt)
|
||||
}
|
||||
|
||||
// BatchCommitment computes multiple commitments in parallel on GPU.
|
||||
@@ -241,31 +252,26 @@ func BatchCommitment(values, blindings, salts []Element) ([]Element, error) {
|
||||
if n == 0 || n != len(blindings) || n != len(salts) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
|
||||
// First hash values and blindings
|
||||
temps, err := BatchHashPair(values, blindings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputs := make([]Element, n)
|
||||
valuesFr := (*C.Fr256)(unsafe.Pointer(&values[0]))
|
||||
blindingsFr := (*C.Fr256)(unsafe.Pointer(&blindings[0]))
|
||||
saltsFr := (*C.Fr256)(unsafe.Pointer(&salts[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_batch_commitment(ctx, outFr, valuesFr, blindingsFr, saltsFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch commitment failed")
|
||||
}
|
||||
return outputs, nil
|
||||
// Then hash results with salts
|
||||
return BatchHashPair(temps, salts)
|
||||
}
|
||||
|
||||
// Nullifier computes a nullifier for spending a note.
|
||||
// nullifier = Poseidon2(key, commitment, index)
|
||||
func Nullifier(key, commitment, index Element) (Element, error) {
|
||||
nullifiers, err := BatchNullifier([]Element{key}, []Element{commitment}, []Element{index})
|
||||
// First hash key and commitment
|
||||
temp, err := HashPair(key, commitment)
|
||||
if err != nil {
|
||||
return Element{}, err
|
||||
}
|
||||
return nullifiers[0], nil
|
||||
// Then hash result with index
|
||||
return HashPair(temp, index)
|
||||
}
|
||||
|
||||
// BatchNullifier computes multiple nullifiers in parallel on GPU.
|
||||
@@ -274,28 +280,21 @@ func BatchNullifier(keys, commitments, indices []Element) ([]Element, error) {
|
||||
if n == 0 || n != len(commitments) || n != len(indices) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
|
||||
// First hash keys and commitments
|
||||
temps, err := BatchHashPair(keys, commitments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputs := make([]Element, n)
|
||||
keysFr := (*C.Fr256)(unsafe.Pointer(&keys[0]))
|
||||
commitmentsFr := (*C.Fr256)(unsafe.Pointer(&commitments[0]))
|
||||
indicesFr := (*C.Fr256)(unsafe.Pointer(&indices[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_batch_nullifier(ctx, outFr, keysFr, commitmentsFr, indicesFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch nullifier failed")
|
||||
}
|
||||
return outputs, nil
|
||||
// Then hash results with indices
|
||||
return BatchHashPair(temps, indices)
|
||||
}
|
||||
|
||||
// Destroy releases the Metal ZK context.
|
||||
// Destroy releases the Metal Poseidon2 context.
|
||||
// Should be called when done with GPU operations.
|
||||
func Destroy() {
|
||||
if ctxReady && ctx != nil {
|
||||
C.metal_zk_destroy(ctx)
|
||||
C.metal_poseidon2_destroy(ctx)
|
||||
ctx = nil
|
||||
ctxReady = false
|
||||
}
|
||||
|
||||
+117
-113
@@ -8,13 +8,14 @@
|
||||
package poseidon2
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo CFLAGS: -I/Users/z/work/luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "lux/crypto/metal_zk.h"
|
||||
#include "lux/crypto/metal_poseidon2.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
@@ -31,20 +32,23 @@ const ElementSize = 32
|
||||
// Stored as 4 x 64-bit limbs in little-endian order.
|
||||
type Element [ElementSize]byte
|
||||
|
||||
// context is the global Metal ZK context (initialized on first use).
|
||||
// Threshold for GPU batch operations
|
||||
const GPUThreshold = 64
|
||||
|
||||
// context is the global Metal Poseidon2 context (initialized on first use).
|
||||
var (
|
||||
ctx *C.MetalZKContext
|
||||
ctx *C.MetalPoseidon2Context
|
||||
ctxReady bool
|
||||
)
|
||||
|
||||
// initContext lazily initializes the Metal ZK context.
|
||||
// initContext lazily initializes the Metal Poseidon2 context.
|
||||
func initContext() error {
|
||||
if ctxReady {
|
||||
return nil
|
||||
}
|
||||
ctx = C.metal_zk_init()
|
||||
ctx = C.metal_poseidon2_init()
|
||||
if ctx == nil {
|
||||
return errors.New("Metal ZK initialization failed")
|
||||
return errors.New("Metal Poseidon2 initialization failed")
|
||||
}
|
||||
ctxReady = true
|
||||
return nil
|
||||
@@ -52,12 +56,17 @@ func initContext() error {
|
||||
|
||||
// GPUAvailable returns true if Metal GPU acceleration is available.
|
||||
func GPUAvailable() bool {
|
||||
return bool(C.metal_zk_available())
|
||||
return bool(C.metal_poseidon2_available())
|
||||
}
|
||||
|
||||
// Threshold returns the recommended batch size for GPU offload.
|
||||
func Threshold() uint32 {
|
||||
return uint32(C.metal_zk_get_threshold(C.METAL_ZK_OP_POSEIDON2_HASH))
|
||||
return GPUThreshold
|
||||
}
|
||||
|
||||
// elementToFe256 converts Element to Poseidon2Fe256
|
||||
func elementToFe256(e *Element) *C.Poseidon2Fe256 {
|
||||
return (*C.Poseidon2Fe256)(unsafe.Pointer(e))
|
||||
}
|
||||
|
||||
// HashPair computes Poseidon2 hash of two elements (2-to-1 compression).
|
||||
@@ -68,12 +77,14 @@ func HashPair(left, right Element) (Element, error) {
|
||||
return out, err
|
||||
}
|
||||
|
||||
leftFr := (*C.Fr256)(unsafe.Pointer(&left[0]))
|
||||
rightFr := (*C.Fr256)(unsafe.Pointer(&right[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&out[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_hash_pair(ctx, outFr, leftFr, rightFr, 1)
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_compress(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&out[0]),
|
||||
unsafe.Pointer(&left[0]),
|
||||
unsafe.Pointer(&right[0]),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return out, errors.New("Poseidon2 hash pair failed")
|
||||
}
|
||||
return out, nil
|
||||
@@ -105,12 +116,15 @@ func BatchHashPair(left, right []Element) ([]Element, error) {
|
||||
}
|
||||
|
||||
// GPU batch processing
|
||||
leftFr := (*C.Fr256)(unsafe.Pointer(&left[0]))
|
||||
rightFr := (*C.Fr256)(unsafe.Pointer(&right[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_hash_pair(ctx, outFr, leftFr, rightFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_batch_compress(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&outputs[0]),
|
||||
unsafe.Pointer(&left[0]),
|
||||
unsafe.Pointer(&right[0]),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch hash pair failed")
|
||||
}
|
||||
return outputs, nil
|
||||
@@ -127,15 +141,16 @@ func MerkleLayer(current []Element) ([]Element, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := make([]Element, n/2)
|
||||
currentFr := (*C.Fr256)(unsafe.Pointer(¤t[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&output[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_merkle_layer(ctx, outFr, currentFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 Merkle layer failed")
|
||||
// Split into left and right halves for batch compress
|
||||
numPairs := n / 2
|
||||
lefts := make([]Element, numPairs)
|
||||
rights := make([]Element, numPairs)
|
||||
for i := 0; i < numPairs; i++ {
|
||||
lefts[i] = current[i*2]
|
||||
rights[i] = current[i*2+1]
|
||||
}
|
||||
return output, nil
|
||||
|
||||
return BatchHashPair(lefts, rights)
|
||||
}
|
||||
|
||||
// MerkleTree builds a complete Merkle tree from leaves using GPU.
|
||||
@@ -152,11 +167,15 @@ func MerkleTree(leaves []Element) ([]Element, error) {
|
||||
|
||||
// Tree has n-1 internal nodes
|
||||
tree := make([]Element, n-1)
|
||||
leavesFr := (*C.Fr256)(unsafe.Pointer(&leaves[0]))
|
||||
treeFr := (*C.Fr256)(unsafe.Pointer(&tree[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_merkle_tree(ctx, treeFr, leavesFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
ret := C.metal_poseidon2_merkle_tree(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&tree[0]),
|
||||
unsafe.Pointer(&leaves[0]),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 Merkle tree failed")
|
||||
}
|
||||
return tree, nil
|
||||
@@ -165,12 +184,19 @@ func MerkleTree(leaves []Element) ([]Element, error) {
|
||||
// MerkleRoot computes just the Merkle root from leaves.
|
||||
func MerkleRoot(leaves []Element) (Element, error) {
|
||||
var root Element
|
||||
tree, err := MerkleTree(leaves)
|
||||
if err != nil {
|
||||
if err := initContext(); err != nil {
|
||||
return root, err
|
||||
}
|
||||
if len(tree) > 0 {
|
||||
root = tree[0]
|
||||
|
||||
ret := C.metal_poseidon2_merkle_root(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&root[0]),
|
||||
unsafe.Pointer(&leaves[0]),
|
||||
C.uint32_t(len(leaves)),
|
||||
)
|
||||
if ret != C.METAL_POSEIDON2_SUCCESS {
|
||||
return root, errors.New("Poseidon2 Merkle root failed")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
@@ -178,72 +204,62 @@ func MerkleRoot(leaves []Element) (Element, error) {
|
||||
// VerifyProof verifies a single Merkle proof.
|
||||
// Returns true if the proof is valid.
|
||||
func VerifyProof(leaf, root Element, path []Element, indices []uint32) (bool, error) {
|
||||
results, err := BatchVerifyProofs([]Element{leaf}, []Element{root}, [][]Element{path}, [][]uint32{indices})
|
||||
if err != nil {
|
||||
if len(path) != len(indices) {
|
||||
return false, errors.New("path length mismatch")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return results[0], nil
|
||||
|
||||
depth := len(path)
|
||||
if depth == 0 {
|
||||
// Single node tree
|
||||
return leaf == root, nil
|
||||
}
|
||||
|
||||
ret := C.metal_poseidon2_merkle_verify(
|
||||
ctx,
|
||||
C.POSEIDON2_BN254,
|
||||
unsafe.Pointer(&root[0]),
|
||||
unsafe.Pointer(&leaf[0]),
|
||||
unsafe.Pointer(&path[0]),
|
||||
C.uint32_t(indices[0]), // leaf index (first element represents full path)
|
||||
C.uint32_t(depth),
|
||||
)
|
||||
|
||||
return ret == C.METAL_POSEIDON2_SUCCESS, nil
|
||||
}
|
||||
|
||||
// BatchVerifyProofs verifies multiple Merkle proofs in parallel on GPU.
|
||||
// Note: This implementation falls back to sequential verification as
|
||||
// the metal_poseidon2 API doesn't have a batch verify function.
|
||||
func BatchVerifyProofs(leaves, roots []Element, paths [][]Element, indices [][]uint32) ([]bool, error) {
|
||||
n := len(leaves)
|
||||
if n == 0 || n != len(roots) || n != len(paths) || n != len(indices) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if n > 0 && len(paths[0]) != len(indices[0]) {
|
||||
return nil, errors.New("path length mismatch")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathLen := len(paths[0])
|
||||
results := make([]C.int, n)
|
||||
|
||||
// Flatten paths and indices
|
||||
flatPaths := make([]Element, n*pathLen)
|
||||
flatIndices := make([]uint32, n*pathLen)
|
||||
results := make([]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
copy(flatPaths[i*pathLen:], paths[i])
|
||||
copy(flatIndices[i*pathLen:], indices[i])
|
||||
valid, err := VerifyProof(leaves[i], roots[i], paths[i], indices[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[i] = valid
|
||||
}
|
||||
|
||||
leavesFr := (*C.Fr256)(unsafe.Pointer(&leaves[0]))
|
||||
rootsFr := (*C.Fr256)(unsafe.Pointer(&roots[0]))
|
||||
pathsFr := (*C.Fr256)(unsafe.Pointer(&flatPaths[0]))
|
||||
indicesPtr := (*C.uint32_t)(unsafe.Pointer(&flatIndices[0]))
|
||||
resultsPtr := (*C.int)(unsafe.Pointer(&results[0]))
|
||||
|
||||
ret := C.metal_zk_poseidon2_verify_proofs(
|
||||
ctx,
|
||||
resultsPtr,
|
||||
leavesFr,
|
||||
pathsFr,
|
||||
indicesPtr,
|
||||
rootsFr,
|
||||
C.uint32_t(n),
|
||||
C.uint32_t(pathLen),
|
||||
)
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch verify proofs failed")
|
||||
}
|
||||
|
||||
boolResults := make([]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
boolResults[i] = results[i] != 0
|
||||
}
|
||||
return boolResults, nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Commitment computes a Pedersen-style commitment using Poseidon2.
|
||||
// commitment = Poseidon2(value, blinding, salt)
|
||||
func Commitment(value, blinding, salt Element) (Element, error) {
|
||||
commitments, err := BatchCommitment([]Element{value}, []Element{blinding}, []Element{salt})
|
||||
// First hash value and blinding
|
||||
temp, err := HashPair(value, blinding)
|
||||
if err != nil {
|
||||
return Element{}, err
|
||||
}
|
||||
return commitments[0], nil
|
||||
// Then hash result with salt
|
||||
return HashPair(temp, salt)
|
||||
}
|
||||
|
||||
// BatchCommitment computes multiple commitments in parallel on GPU.
|
||||
@@ -252,31 +268,26 @@ func BatchCommitment(values, blindings, salts []Element) ([]Element, error) {
|
||||
if n == 0 || n != len(blindings) || n != len(salts) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
|
||||
// First hash values and blindings
|
||||
temps, err := BatchHashPair(values, blindings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputs := make([]Element, n)
|
||||
valuesFr := (*C.Fr256)(unsafe.Pointer(&values[0]))
|
||||
blindingsFr := (*C.Fr256)(unsafe.Pointer(&blindings[0]))
|
||||
saltsFr := (*C.Fr256)(unsafe.Pointer(&salts[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_batch_commitment(ctx, outFr, valuesFr, blindingsFr, saltsFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch commitment failed")
|
||||
}
|
||||
return outputs, nil
|
||||
// Then hash results with salts
|
||||
return BatchHashPair(temps, salts)
|
||||
}
|
||||
|
||||
// Nullifier computes a nullifier for spending a note.
|
||||
// nullifier = Poseidon2(key, commitment, index)
|
||||
func Nullifier(key, commitment, index Element) (Element, error) {
|
||||
nullifiers, err := BatchNullifier([]Element{key}, []Element{commitment}, []Element{index})
|
||||
// First hash key and commitment
|
||||
temp, err := HashPair(key, commitment)
|
||||
if err != nil {
|
||||
return Element{}, err
|
||||
}
|
||||
return nullifiers[0], nil
|
||||
// Then hash result with index
|
||||
return HashPair(temp, index)
|
||||
}
|
||||
|
||||
// BatchNullifier computes multiple nullifiers in parallel on GPU.
|
||||
@@ -285,28 +296,21 @@ func BatchNullifier(keys, commitments, indices []Element) ([]Element, error) {
|
||||
if n == 0 || n != len(commitments) || n != len(indices) {
|
||||
return nil, errors.New("invalid input lengths")
|
||||
}
|
||||
if err := initContext(); err != nil {
|
||||
|
||||
// First hash keys and commitments
|
||||
temps, err := BatchHashPair(keys, commitments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputs := make([]Element, n)
|
||||
keysFr := (*C.Fr256)(unsafe.Pointer(&keys[0]))
|
||||
commitmentsFr := (*C.Fr256)(unsafe.Pointer(&commitments[0]))
|
||||
indicesFr := (*C.Fr256)(unsafe.Pointer(&indices[0]))
|
||||
outFr := (*C.Fr256)(unsafe.Pointer(&outputs[0]))
|
||||
|
||||
ret := C.metal_zk_batch_nullifier(ctx, outFr, keysFr, commitmentsFr, indicesFr, C.uint32_t(n))
|
||||
if ret != C.METAL_ZK_SUCCESS {
|
||||
return nil, errors.New("Poseidon2 batch nullifier failed")
|
||||
}
|
||||
return outputs, nil
|
||||
// Then hash results with indices
|
||||
return BatchHashPair(temps, indices)
|
||||
}
|
||||
|
||||
// Destroy releases the Metal ZK context.
|
||||
// Destroy releases the Metal Poseidon2 context.
|
||||
// Should be called when done with GPU operations.
|
||||
func Destroy() {
|
||||
if ctxReady && ctx != nil {
|
||||
C.metal_zk_destroy(ctx)
|
||||
C.metal_poseidon2_destroy(ctx)
|
||||
ctx = nil
|
||||
ctxReady = false
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package blake3 provides Blake3 hash functions for cryptographic operations.
|
||||
// This is extracted from the threshold package to provide a centralized
|
||||
// implementation for all Lux projects.
|
||||
package blake3
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
// DigestLength is the standard output length for Blake3 hashes
|
||||
const DigestLength = 64 // 512 bits
|
||||
|
||||
// Digest represents a Blake3 hash output
|
||||
type Digest [DigestLength]byte
|
||||
|
||||
// Hasher wraps blake3.Hasher to provide a consistent interface
|
||||
type Hasher struct {
|
||||
h *blake3.Hasher
|
||||
}
|
||||
|
||||
// New creates a new Blake3 hasher
|
||||
func New() *Hasher {
|
||||
return &Hasher{h: blake3.New()}
|
||||
}
|
||||
|
||||
// NewWithDomain creates a new Blake3 hasher with a domain separator
|
||||
func NewWithDomain(domain string) *Hasher {
|
||||
h := &Hasher{h: blake3.New()}
|
||||
h.WriteString(domain)
|
||||
return h
|
||||
}
|
||||
|
||||
// Write adds data to the hash
|
||||
func (h *Hasher) Write(p []byte) (n int, err error) {
|
||||
return h.h.Write(p)
|
||||
}
|
||||
|
||||
// WriteString adds a string to the hash
|
||||
func (h *Hasher) WriteString(s string) (n int, err error) {
|
||||
return h.h.WriteString(s)
|
||||
}
|
||||
|
||||
// WriteUint32 adds a uint32 to the hash in big-endian format
|
||||
func (h *Hasher) WriteUint32(v uint32) {
|
||||
var buf [4]byte
|
||||
binary.BigEndian.PutUint32(buf[:], v)
|
||||
h.h.Write(buf[:])
|
||||
}
|
||||
|
||||
// WriteUint64 adds a uint64 to the hash in big-endian format
|
||||
func (h *Hasher) WriteUint64(v uint64) {
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], v)
|
||||
h.h.Write(buf[:])
|
||||
}
|
||||
|
||||
// WriteBigInt adds a big.Int to the hash
|
||||
func (h *Hasher) WriteBigInt(n *big.Int) {
|
||||
if n == nil {
|
||||
h.WriteUint32(0)
|
||||
return
|
||||
}
|
||||
bytes := n.Bytes()
|
||||
h.WriteUint32(uint32(len(bytes)))
|
||||
h.h.Write(bytes)
|
||||
}
|
||||
|
||||
// Sum returns the hash digest
|
||||
func (h *Hasher) Sum(b []byte) []byte {
|
||||
return h.h.Sum(b)
|
||||
}
|
||||
|
||||
// Digest returns a fixed-size digest
|
||||
func (h *Hasher) Digest() Digest {
|
||||
var d Digest
|
||||
h.h.Digest().Read(d[:])
|
||||
return d
|
||||
}
|
||||
|
||||
// Reader returns an io.Reader for extended output
|
||||
func (h *Hasher) Reader() io.Reader {
|
||||
return h.h.Digest()
|
||||
}
|
||||
|
||||
// Clone creates a copy of the hasher
|
||||
func (h *Hasher) Clone() *Hasher {
|
||||
return &Hasher{h: h.h.Clone()}
|
||||
}
|
||||
|
||||
// Reset resets the hasher to its initial state
|
||||
func (h *Hasher) Reset() {
|
||||
h.h.Reset()
|
||||
}
|
||||
|
||||
// HashBytes hashes a byte slice and returns a digest
|
||||
func HashBytes(data []byte) Digest {
|
||||
h := New()
|
||||
h.Write(data)
|
||||
return h.Digest()
|
||||
}
|
||||
|
||||
// HashString hashes a string and returns a digest
|
||||
func HashString(s string) Digest {
|
||||
h := New()
|
||||
h.WriteString(s)
|
||||
return h.Digest()
|
||||
}
|
||||
|
||||
// HashWithDomain hashes data with a domain separator
|
||||
func HashWithDomain(domain string, data []byte) Digest {
|
||||
h := NewWithDomain(domain)
|
||||
h.Write(data)
|
||||
return h.Digest()
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package blake3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
h := New()
|
||||
if h == nil {
|
||||
t.Fatal("New() returned nil")
|
||||
}
|
||||
if h.h == nil {
|
||||
t.Fatal("Internal hasher is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWithDomain(t *testing.T) {
|
||||
domain := "test-domain"
|
||||
h1 := NewWithDomain(domain)
|
||||
h2 := NewWithDomain(domain)
|
||||
|
||||
data := []byte("test data")
|
||||
h1.Write(data)
|
||||
h2.Write(data)
|
||||
|
||||
d1 := h1.Digest()
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("Same domain should produce same hash")
|
||||
}
|
||||
|
||||
// Different domain should produce different hash
|
||||
h3 := NewWithDomain("different-domain")
|
||||
h3.Write(data)
|
||||
d3 := h3.Digest()
|
||||
|
||||
if bytes.Equal(d1[:], d3[:]) {
|
||||
t.Error("Different domain should produce different hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBytes(t *testing.T) {
|
||||
// Test with known test vectors
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expected string // First 64 bytes of hash
|
||||
}{
|
||||
{
|
||||
[]byte(""),
|
||||
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a",
|
||||
},
|
||||
{
|
||||
[]byte("hello"),
|
||||
"ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200fe992405f0d785b599a2e3387f6d34d01faccfeb22fb697ef3fd53541241a338c",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
hash := HashBytes(tc.input)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
if hashStr != tc.expected {
|
||||
t.Errorf("HashBytes(%q) = %s, want %s", tc.input, hashStr, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test consistency
|
||||
data := []byte("test data")
|
||||
h1 := HashBytes(data)
|
||||
h2 := HashBytes(data)
|
||||
|
||||
if !bytes.Equal(h1[:], h2[:]) {
|
||||
t.Error("Same input should produce same hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashString(t *testing.T) {
|
||||
// Test consistency
|
||||
s := "test string"
|
||||
h1 := HashString(s)
|
||||
h2 := HashString(s)
|
||||
|
||||
if !bytes.Equal(h1[:], h2[:]) {
|
||||
t.Error("Same string should produce same hash")
|
||||
}
|
||||
|
||||
// Compare with HashBytes
|
||||
h3 := HashBytes([]byte(s))
|
||||
if !bytes.Equal(h1[:], h3[:]) {
|
||||
t.Error("HashString should match HashBytes for same content")
|
||||
}
|
||||
|
||||
// Different strings produce different hashes
|
||||
h4 := HashString("different string")
|
||||
if bytes.Equal(h1[:], h4[:]) {
|
||||
t.Error("Different strings should produce different hashes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashWithDomain(t *testing.T) {
|
||||
data := []byte("test data")
|
||||
domain1 := "domain1"
|
||||
domain2 := "domain2"
|
||||
|
||||
h1 := HashWithDomain(domain1, data)
|
||||
h2 := HashWithDomain(domain1, data)
|
||||
h3 := HashWithDomain(domain2, data)
|
||||
|
||||
// Same domain and data should produce same hash
|
||||
if !bytes.Equal(h1[:], h2[:]) {
|
||||
t.Error("Same domain and data should produce same hash")
|
||||
}
|
||||
|
||||
// Different domain should produce different hash
|
||||
if bytes.Equal(h1[:], h3[:]) {
|
||||
t.Error("Different domain should produce different hash")
|
||||
}
|
||||
|
||||
// Should differ from hash without domain
|
||||
h4 := HashBytes(data)
|
||||
if bytes.Equal(h1[:], h4[:]) {
|
||||
t.Error("Hash with domain should differ from hash without domain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMethods(t *testing.T) {
|
||||
h := New()
|
||||
|
||||
// Test Write
|
||||
n, err := h.Write([]byte("test"))
|
||||
if err != nil {
|
||||
t.Errorf("Write error: %v", err)
|
||||
}
|
||||
if n != 4 {
|
||||
t.Errorf("Write returned %d, want 4", n)
|
||||
}
|
||||
|
||||
// Test WriteString
|
||||
n, err = h.WriteString("string")
|
||||
if err != nil {
|
||||
t.Errorf("WriteString error: %v", err)
|
||||
}
|
||||
if n != 6 {
|
||||
t.Errorf("WriteString returned %d, want 6", n)
|
||||
}
|
||||
|
||||
// Test WriteUint32
|
||||
h.WriteUint32(0x12345678)
|
||||
|
||||
// Test WriteUint64
|
||||
h.WriteUint64(0x123456789ABCDEF0)
|
||||
|
||||
// Test WriteBigInt
|
||||
bigNum := big.NewInt(1234567890)
|
||||
h.WriteBigInt(bigNum)
|
||||
|
||||
// Test WriteBigInt with nil
|
||||
h.WriteBigInt(nil)
|
||||
|
||||
// Get digest to ensure it doesn't panic
|
||||
_ = h.Digest()
|
||||
}
|
||||
|
||||
func TestWriteUint32(t *testing.T) {
|
||||
h1 := New()
|
||||
h1.WriteUint32(0x12345678)
|
||||
d1 := h1.Digest()
|
||||
|
||||
// Should be same as writing the bytes in big-endian
|
||||
h2 := New()
|
||||
h2.Write([]byte{0x12, 0x34, 0x56, 0x78})
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("WriteUint32 should write in big-endian format")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteUint64(t *testing.T) {
|
||||
h1 := New()
|
||||
h1.WriteUint64(0x123456789ABCDEF0)
|
||||
d1 := h1.Digest()
|
||||
|
||||
// Should be same as writing the bytes in big-endian
|
||||
h2 := New()
|
||||
h2.Write([]byte{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0})
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("WriteUint64 should write in big-endian format")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteBigInt(t *testing.T) {
|
||||
// Test with normal big int
|
||||
n := big.NewInt(1234567890)
|
||||
h1 := New()
|
||||
h1.WriteBigInt(n)
|
||||
d1 := h1.Digest()
|
||||
|
||||
// Writing same number should produce same hash
|
||||
h2 := New()
|
||||
h2.WriteBigInt(n)
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("Same big.Int should produce same hash")
|
||||
}
|
||||
|
||||
// Test with nil
|
||||
h3 := New()
|
||||
h3.WriteBigInt(nil)
|
||||
d3 := h3.Digest()
|
||||
|
||||
// Nil should write a zero length prefix
|
||||
h4 := New()
|
||||
h4.WriteUint32(0)
|
||||
d4 := h4.Digest()
|
||||
|
||||
if !bytes.Equal(d3[:], d4[:]) {
|
||||
t.Error("WriteBigInt(nil) should write zero length")
|
||||
}
|
||||
|
||||
// Test with zero
|
||||
zero := big.NewInt(0)
|
||||
h5 := New()
|
||||
h5.WriteBigInt(zero)
|
||||
_ = h5.Digest() // Should not panic
|
||||
}
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
h := New()
|
||||
h.WriteString("test")
|
||||
|
||||
// Sum with nil
|
||||
sum1 := h.Sum(nil)
|
||||
if len(sum1) != 32 { // Default blake3 output
|
||||
t.Errorf("Sum(nil) length = %d, want 32", len(sum1))
|
||||
}
|
||||
|
||||
// Sum with existing slice
|
||||
prefix := []byte("prefix")
|
||||
sum2 := h.Sum(prefix)
|
||||
if !bytes.HasPrefix(sum2, prefix) {
|
||||
t.Error("Sum should append to provided slice")
|
||||
}
|
||||
if len(sum2) != len(prefix)+32 {
|
||||
t.Errorf("Sum length = %d, want %d", len(sum2), len(prefix)+32)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigest(t *testing.T) {
|
||||
h := New()
|
||||
h.WriteString("test")
|
||||
d := h.Digest()
|
||||
|
||||
if len(d) != DigestLength {
|
||||
t.Errorf("Digest length = %d, want %d", len(d), DigestLength)
|
||||
}
|
||||
|
||||
// Digest should be consistent
|
||||
h2 := New()
|
||||
h2.WriteString("test")
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d[:], d2[:]) {
|
||||
t.Error("Same input should produce same digest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
h := New()
|
||||
h.WriteString("test")
|
||||
|
||||
reader := h.Reader()
|
||||
if reader == nil {
|
||||
t.Fatal("Reader() returned nil")
|
||||
}
|
||||
|
||||
// Read some bytes
|
||||
buf := make([]byte, 100)
|
||||
n, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
t.Errorf("Reader.Read error: %v", err)
|
||||
}
|
||||
if n != 100 {
|
||||
t.Errorf("Reader.Read returned %d, want 100", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
h1 := New()
|
||||
h1.WriteString("test")
|
||||
|
||||
h2 := h1.Clone()
|
||||
if h2 == nil {
|
||||
t.Fatal("Clone() returned nil")
|
||||
}
|
||||
|
||||
// Both should produce same digest at this point
|
||||
d1 := h1.Digest()
|
||||
d2 := h2.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("Clone should produce same digest")
|
||||
}
|
||||
|
||||
// Writing to one shouldn't affect the other
|
||||
h1.WriteString("more")
|
||||
d1New := h1.Digest()
|
||||
d2New := h2.Digest()
|
||||
|
||||
if bytes.Equal(d1New[:], d2New[:]) {
|
||||
t.Error("Writing to original should not affect clone")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
h := New()
|
||||
h.WriteString("test")
|
||||
d1 := h.Digest()
|
||||
|
||||
h.Reset()
|
||||
h.WriteString("test")
|
||||
d2 := h.Digest()
|
||||
|
||||
if !bytes.Equal(d1[:], d2[:]) {
|
||||
t.Error("Reset should restore initial state")
|
||||
}
|
||||
|
||||
// After reset, different input should produce different hash
|
||||
h.Reset()
|
||||
h.WriteString("different")
|
||||
d3 := h.Digest()
|
||||
|
||||
if bytes.Equal(d1[:], d3[:]) {
|
||||
t.Error("After reset, different input should produce different hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigestLength(t *testing.T) {
|
||||
if DigestLength != 64 {
|
||||
t.Errorf("DigestLength = %d, want 64", DigestLength)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashBytes(b *testing.B) {
|
||||
data := make([]byte, 1024)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = HashBytes(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashString(b *testing.B) {
|
||||
data := strings.Repeat("benchmark", 128)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = HashString(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteBigInt(b *testing.B) {
|
||||
n := new(big.Int)
|
||||
n.SetString("123456789012345678901234567890123456789012345678901234567890", 10)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := New()
|
||||
h.WriteBigInt(n)
|
||||
_ = h.Digest()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package hashing
|
||||
|
||||
// Hasher is an interface to compute a hash value.
|
||||
type Hasher interface {
|
||||
// Hash takes a string and computes its hash value.
|
||||
// Values must be computed deterministically.
|
||||
Hash([]byte) uint64
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package hashing
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
// This file generates addresses from public keys with ripemd160. Though ripemd160 is not
|
||||
// generally recommended for use, the small size of the public key input is considered harder to
|
||||
// attack than larger payloads.
|
||||
//
|
||||
// Bitcoin similarly uses ripemd160 to generate addresses from public keys.
|
||||
//
|
||||
// Reference: https://online.tugraz.at/tug_online/voe_main2.getvolltext?pCurrPk=17675
|
||||
"golang.org/x/crypto/ripemd160" //nolint:gosec
|
||||
)
|
||||
|
||||
const (
|
||||
HashLen = sha256.Size
|
||||
AddrLen = ripemd160.Size
|
||||
)
|
||||
|
||||
var ErrInvalidHashLen = errors.New("invalid hash length")
|
||||
|
||||
// Hash256 A 256 bit long hash value.
|
||||
type Hash256 = [HashLen]byte
|
||||
|
||||
// Hash160 A 160 bit long hash value.
|
||||
type Hash160 = [ripemd160.Size]byte
|
||||
|
||||
// ComputeHash256Array computes a cryptographically strong 256 bit hash of the
|
||||
// input byte slice.
|
||||
func ComputeHash256Array(buf []byte) Hash256 {
|
||||
return sha256.Sum256(buf)
|
||||
}
|
||||
|
||||
// ComputeHash256 computes a cryptographically strong 256 bit hash of the input
|
||||
// byte slice.
|
||||
func ComputeHash256(buf []byte) []byte {
|
||||
arr := ComputeHash256Array(buf)
|
||||
return arr[:]
|
||||
}
|
||||
|
||||
// ComputeHash160Array computes a cryptographically strong 160 bit hash of the
|
||||
// input byte slice.
|
||||
func ComputeHash160Array(buf []byte) Hash160 {
|
||||
h, err := ToHash160(ComputeHash160(buf))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// ComputeHash160 computes a cryptographically strong 160 bit hash of the input
|
||||
// byte slice.
|
||||
func ComputeHash160(buf []byte) []byte {
|
||||
// See the comment on the ripemd160 import as to why the risk of use is
|
||||
// considered acceptable.
|
||||
ripe := ripemd160.New() //nolint:gosec
|
||||
_, err := io.Writer(ripe).Write(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ripe.Sum(nil)
|
||||
}
|
||||
|
||||
// Checksum creates a checksum of [length] bytes from the 256 bit hash of the
|
||||
// byte slice.
|
||||
//
|
||||
// Returns: the lower [length] bytes of the hash
|
||||
// Panics if length > 32.
|
||||
func Checksum(bytes []byte, length int) []byte {
|
||||
hash := ComputeHash256Array(bytes)
|
||||
return hash[len(hash)-length:]
|
||||
}
|
||||
|
||||
func ToHash256(bytes []byte) (Hash256, error) {
|
||||
hash := Hash256{}
|
||||
if bytesLen := len(bytes); bytesLen != HashLen {
|
||||
return hash, fmt.Errorf("%w: expected 32 bytes but got %d", ErrInvalidHashLen, bytesLen)
|
||||
}
|
||||
copy(hash[:], bytes)
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func ToHash160(bytes []byte) (Hash160, error) {
|
||||
hash := Hash160{}
|
||||
if bytesLen := len(bytes); bytesLen != ripemd160.Size {
|
||||
return hash, fmt.Errorf("%w: expected 20 bytes but got %d", ErrInvalidHashLen, bytesLen)
|
||||
}
|
||||
copy(hash[:], bytes)
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// PubkeyBytesToAddress converts a public key to an address using
|
||||
// Bitcoin-style SHA256+RIPEMD160 hash.
|
||||
func PubkeyBytesToAddress(key []byte) []byte {
|
||||
return ComputeHash160(ComputeHash256(key))
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
package hashing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComputeHash256(t *testing.T) {
|
||||
// Test with known vectors
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
[]byte(""),
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
},
|
||||
{
|
||||
"abc",
|
||||
[]byte("abc"),
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
},
|
||||
{
|
||||
"fox",
|
||||
[]byte("The quick brown fox jumps over the lazy dog"),
|
||||
"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test ComputeHash256
|
||||
hash := ComputeHash256(tc.input)
|
||||
hashStr := hex.EncodeToString(hash)
|
||||
if hashStr != tc.expected {
|
||||
t.Errorf("ComputeHash256(%q) = %s, want %s", tc.input, hashStr, tc.expected)
|
||||
}
|
||||
|
||||
// Test ComputeHash256Array
|
||||
hashArray := ComputeHash256Array(tc.input)
|
||||
hashArrayStr := hex.EncodeToString(hashArray[:])
|
||||
if hashArrayStr != tc.expected {
|
||||
t.Errorf("ComputeHash256Array(%q) = %s, want %s", tc.input, hashArrayStr, tc.expected)
|
||||
}
|
||||
|
||||
// Verify slice and array produce same result
|
||||
if !bytes.Equal(hash, hashArray[:]) {
|
||||
t.Error("ComputeHash256 and ComputeHash256Array should produce same result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeHash160(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
[]byte(""),
|
||||
"9c1185a5c5e9fc54612808977ee8f548b2258d31",
|
||||
},
|
||||
{
|
||||
"abc",
|
||||
[]byte("abc"),
|
||||
"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc",
|
||||
},
|
||||
{
|
||||
"message digest",
|
||||
[]byte("message digest"),
|
||||
"5d0689ef49d2fae572b881b123a85ffa21595f36",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test ComputeHash160
|
||||
hash := ComputeHash160(tc.input)
|
||||
hashStr := hex.EncodeToString(hash)
|
||||
if hashStr != tc.expected {
|
||||
t.Errorf("ComputeHash160(%q) = %s, want %s", tc.input, hashStr, tc.expected)
|
||||
}
|
||||
|
||||
// Test ComputeHash160Array
|
||||
hashArray := ComputeHash160Array(tc.input)
|
||||
hashArrayStr := hex.EncodeToString(hashArray[:])
|
||||
if hashArrayStr != tc.expected {
|
||||
t.Errorf("ComputeHash160Array(%q) = %s, want %s", tc.input, hashArrayStr, tc.expected)
|
||||
}
|
||||
|
||||
// Verify slice and array produce same result
|
||||
if !bytes.Equal(hash, hashArray[:]) {
|
||||
t.Error("ComputeHash160 and ComputeHash160Array should produce same result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecksum(t *testing.T) {
|
||||
input := []byte("test input for checksum")
|
||||
|
||||
// Test various checksum lengths
|
||||
lengths := []int{1, 4, 8, 16, 32}
|
||||
|
||||
for _, length := range lengths {
|
||||
checksum := Checksum(input, length)
|
||||
if len(checksum) != length {
|
||||
t.Errorf("Checksum length should be %d, got %d", length, len(checksum))
|
||||
}
|
||||
|
||||
// Verify checksum is last 'length' bytes of hash
|
||||
fullHash := ComputeHash256Array(input)
|
||||
expected := fullHash[len(fullHash)-length:]
|
||||
if !bytes.Equal(checksum, expected) {
|
||||
t.Errorf("Checksum should be last %d bytes of hash", length)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that same input produces same checksum
|
||||
checksum1 := Checksum(input, 4)
|
||||
checksum2 := Checksum(input, 4)
|
||||
if !bytes.Equal(checksum1, checksum2) {
|
||||
t.Error("Same input should produce same checksum")
|
||||
}
|
||||
|
||||
// Test that different input produces different checksum
|
||||
input2 := []byte("different input")
|
||||
checksum3 := Checksum(input2, 4)
|
||||
if bytes.Equal(checksum1, checksum3) {
|
||||
t.Error("Different input should produce different checksum")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToHash256(t *testing.T) {
|
||||
// Test valid conversion
|
||||
validBytes := make([]byte, HashLen)
|
||||
for i := range validBytes {
|
||||
validBytes[i] = byte(i)
|
||||
}
|
||||
|
||||
hash, err := ToHash256(validBytes)
|
||||
if err != nil {
|
||||
t.Errorf("ToHash256 with valid bytes should not error: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(hash[:], validBytes) {
|
||||
t.Error("ToHash256 should copy bytes correctly")
|
||||
}
|
||||
|
||||
// Test invalid lengths
|
||||
invalidLengths := []int{0, 1, 31, 33, 100}
|
||||
for _, length := range invalidLengths {
|
||||
invalidBytes := make([]byte, length)
|
||||
_, err := ToHash256(invalidBytes)
|
||||
if err == nil {
|
||||
t.Errorf("ToHash256 should error with %d bytes", length)
|
||||
}
|
||||
if err != nil && err.Error() != ErrInvalidHashLen.Error() && !bytes.Contains([]byte(err.Error()), []byte("invalid hash length")) {
|
||||
t.Errorf("Expected ErrInvalidHashLen, got: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToHash160(t *testing.T) {
|
||||
// Test valid conversion
|
||||
validBytes := make([]byte, AddrLen)
|
||||
for i := range validBytes {
|
||||
validBytes[i] = byte(i)
|
||||
}
|
||||
|
||||
hash, err := ToHash160(validBytes)
|
||||
if err != nil {
|
||||
t.Errorf("ToHash160 with valid bytes should not error: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(hash[:], validBytes) {
|
||||
t.Error("ToHash160 should copy bytes correctly")
|
||||
}
|
||||
|
||||
// Test invalid lengths
|
||||
invalidLengths := []int{0, 1, 19, 21, 100}
|
||||
for _, length := range invalidLengths {
|
||||
invalidBytes := make([]byte, length)
|
||||
_, err := ToHash160(invalidBytes)
|
||||
if err == nil {
|
||||
t.Errorf("ToHash160 should error with %d bytes", length)
|
||||
}
|
||||
if err != nil && err.Error() != ErrInvalidHashLen.Error() && !bytes.Contains([]byte(err.Error()), []byte("invalid hash length")) {
|
||||
t.Errorf("Expected ErrInvalidHashLen, got: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPubkeyBytesToAddress(t *testing.T) {
|
||||
// Test that address generation is consistent
|
||||
pubkey := []byte("test public key")
|
||||
|
||||
addr1 := PubkeyBytesToAddress(pubkey)
|
||||
addr2 := PubkeyBytesToAddress(pubkey)
|
||||
|
||||
if !bytes.Equal(addr1, addr2) {
|
||||
t.Error("Same pubkey should produce same address")
|
||||
}
|
||||
|
||||
// Test that different pubkeys produce different addresses
|
||||
pubkey2 := []byte("different public key")
|
||||
addr3 := PubkeyBytesToAddress(pubkey2)
|
||||
|
||||
if bytes.Equal(addr1, addr3) {
|
||||
t.Error("Different pubkeys should produce different addresses")
|
||||
}
|
||||
|
||||
// Test that address is 20 bytes (ripemd160 size)
|
||||
if len(addr1) != AddrLen {
|
||||
t.Errorf("Address should be %d bytes, got %d", AddrLen, len(addr1))
|
||||
}
|
||||
|
||||
// Test empty pubkey
|
||||
emptyAddr := PubkeyBytesToAddress([]byte{})
|
||||
if len(emptyAddr) != AddrLen {
|
||||
t.Errorf("Empty pubkey should still produce %d byte address", AddrLen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashConstants(t *testing.T) {
|
||||
// Verify constants match expected values
|
||||
if HashLen != 32 {
|
||||
t.Errorf("HashLen should be 32, got %d", HashLen)
|
||||
}
|
||||
|
||||
if AddrLen != 20 {
|
||||
t.Errorf("AddrLen should be 20, got %d", AddrLen)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComputeHash256(b *testing.B) {
|
||||
input := make([]byte, 1024)
|
||||
for i := range input {
|
||||
input[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ComputeHash256(input)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComputeHash256Array(b *testing.B) {
|
||||
input := make([]byte, 1024)
|
||||
for i := range input {
|
||||
input[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ComputeHash256Array(input)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComputeHash160(b *testing.B) {
|
||||
input := make([]byte, 1024)
|
||||
for i := range input {
|
||||
input[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ComputeHash160(input)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPubkeyBytesToAddress(b *testing.B) {
|
||||
pubkey := make([]byte, 65) // Typical pubkey size
|
||||
for i := range pubkey {
|
||||
pubkey[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = PubkeyBytesToAddress(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChecksum(b *testing.B) {
|
||||
input := make([]byte, 1024)
|
||||
for i := range input {
|
||||
input[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Checksum(input, 4)
|
||||
}
|
||||
}
|
||||
+97
-86
@@ -9,8 +9,9 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo pkg-config: lux-crypto-only
|
||||
#cgo darwin LDFLAGS: -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS:
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -20,6 +21,7 @@ package gpu
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
@@ -29,9 +31,9 @@ import (
|
||||
|
||||
// Thresholds for GPU batch operations
|
||||
const (
|
||||
MSMThreshold = 64 // Min points for GPU MSM
|
||||
PedersenThreshold = 32 // Min values for GPU Pedersen commit
|
||||
IPAVerifyThreshold = 16 // Min proofs for GPU IPA verify
|
||||
MSMThreshold = 64 // Min points for GPU MSM
|
||||
PedersenThreshold = 32 // Min values for GPU Pedersen commit
|
||||
IPAVerifyThreshold = 16 // Min proofs for GPU IPA verify
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -57,6 +59,38 @@ func Available() bool {
|
||||
return bool(C.metal_ipa_available())
|
||||
}
|
||||
|
||||
// bytesToScalar converts 32-byte big-endian to C.BanderwagonScalar (4 x uint64 limbs, little-endian).
|
||||
func bytesToScalar(b []byte) C.BanderwagonScalar {
|
||||
var s C.BanderwagonScalar
|
||||
// Convert big-endian bytes to little-endian limbs
|
||||
// b[0:8] -> limbs[3], b[8:16] -> limbs[2], b[16:24] -> limbs[1], b[24:32] -> limbs[0]
|
||||
s.limbs[0] = C.uint64_t(binary.BigEndian.Uint64(b[24:32]))
|
||||
s.limbs[1] = C.uint64_t(binary.BigEndian.Uint64(b[16:24]))
|
||||
s.limbs[2] = C.uint64_t(binary.BigEndian.Uint64(b[8:16]))
|
||||
s.limbs[3] = C.uint64_t(binary.BigEndian.Uint64(b[0:8]))
|
||||
return s
|
||||
}
|
||||
|
||||
// pointToAffine converts banderwagon.Element to C.BanderwagonAffine using serialization.
|
||||
func pointToAffine(p *banderwagon.Element) C.BanderwagonAffine {
|
||||
var affine C.BanderwagonAffine
|
||||
bytes := p.Bytes()
|
||||
// Use deserialize to get proper affine coordinates
|
||||
C.metal_ipa_point_deserialize(&affine, (*C.uint8_t)(unsafe.Pointer(&bytes[0])))
|
||||
return affine
|
||||
}
|
||||
|
||||
// affineToPoint converts C.BanderwagonAffine to banderwagon.Element using serialization.
|
||||
func affineToPoint(affine *C.BanderwagonAffine) (*banderwagon.Element, error) {
|
||||
var bytes [32]byte
|
||||
C.metal_ipa_point_serialize((*C.uint8_t)(unsafe.Pointer(&bytes[0])), affine)
|
||||
var result banderwagon.Element
|
||||
if err := result.SetBytes(bytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// MSM performs multi-scalar multiplication on Banderwagon points using GPU.
|
||||
// result = sum_i (scalars[i] * points[i])
|
||||
func MSM(points []banderwagon.Element, scalars []fr.Element) (*banderwagon.Element, error) {
|
||||
@@ -78,33 +112,27 @@ func MSM(points []banderwagon.Element, scalars []fr.Element) (*banderwagon.Eleme
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// Convert points to C format (affine coordinates)
|
||||
cPoints := make([]C.BanderwagonAffine, n)
|
||||
for i := 0; i < n; i++ {
|
||||
bytes := points[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cPoints[i].x.bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
// Y coordinate derived from X in banderwagon (not stored)
|
||||
}
|
||||
|
||||
// Convert scalars to C format
|
||||
cScalars := make([]C.BanderwagonScalar, n)
|
||||
for i := 0; i < n; i++ {
|
||||
bytes := scalars[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cScalars[i].bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
cScalars[i] = bytesToScalar(bytes[:])
|
||||
}
|
||||
|
||||
// Allocate result
|
||||
var cResult C.BanderwagonExtended
|
||||
// Convert points to C format
|
||||
cPoints := make([]C.BanderwagonAffine, n)
|
||||
for i := 0; i < n; i++ {
|
||||
cPoints[i] = pointToAffine(&points[i])
|
||||
}
|
||||
|
||||
// Allocate result (API uses BanderwagonAffine for result)
|
||||
var cResult C.BanderwagonAffine
|
||||
|
||||
ret := C.metal_ipa_msm(
|
||||
ctx,
|
||||
&cResult,
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cPoints[0])),
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&cScalars[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cPoints[0])),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
|
||||
@@ -115,18 +143,7 @@ func MSM(points []banderwagon.Element, scalars []fr.Element) (*banderwagon.Eleme
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// Convert result back to Go type
|
||||
var resultBytes [32]byte
|
||||
for i := 0; i < 32; i++ {
|
||||
resultBytes[i] = byte(cResult.x.bytes[i])
|
||||
}
|
||||
|
||||
var result banderwagon.Element
|
||||
if err := result.SetBytes(resultBytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
return affineToPoint(&cResult)
|
||||
}
|
||||
|
||||
// BatchMSM performs multiple MSM operations in parallel on GPU.
|
||||
@@ -162,42 +179,47 @@ func BatchMSM(pointSets [][]banderwagon.Element, scalarSets [][]fr.Element) ([]*
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Prepare batch data
|
||||
counts := make([]C.uint32_t, n)
|
||||
totalPoints := 0
|
||||
for i := 0; i < n; i++ {
|
||||
counts[i] = C.uint32_t(len(pointSets[i]))
|
||||
totalPoints += len(pointSets[i])
|
||||
}
|
||||
|
||||
// Flatten all points and scalars
|
||||
allPoints := make([]C.BanderwagonAffine, totalPoints)
|
||||
allScalars := make([]C.BanderwagonScalar, totalPoints)
|
||||
idx := 0
|
||||
for i := 0; i < n; i++ {
|
||||
for j := 0; j < len(pointSets[i]); j++ {
|
||||
bytes := pointSets[i][j].Bytes()
|
||||
for k := 0; k < 32; k++ {
|
||||
allPoints[idx].x.bytes[k] = C.uint8_t(bytes[k])
|
||||
// Determine vector size (all must be same size for batch_msm)
|
||||
vectorSize := len(pointSets[0])
|
||||
for i := 1; i < n; i++ {
|
||||
if len(pointSets[i]) != vectorSize || len(scalarSets[i]) != vectorSize {
|
||||
// Fall back to sequential if sizes differ
|
||||
for j := 0; j < n; j++ {
|
||||
result, err := MSM(pointSets[j], scalarSets[j])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[j] = result
|
||||
}
|
||||
sBytes := scalarSets[i][j].Bytes()
|
||||
for k := 0; k < 32; k++ {
|
||||
allScalars[idx].bytes[k] = C.uint8_t(sBytes[k])
|
||||
}
|
||||
idx++
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten all scalars
|
||||
allScalars := make([]C.BanderwagonScalar, n*vectorSize)
|
||||
for i := 0; i < n; i++ {
|
||||
for j := 0; j < vectorSize; j++ {
|
||||
bytes := scalarSets[i][j].Bytes()
|
||||
allScalars[i*vectorSize+j] = bytesToScalar(bytes[:])
|
||||
}
|
||||
}
|
||||
|
||||
// Convert shared basis points
|
||||
cPoints := make([]C.BanderwagonAffine, vectorSize)
|
||||
for j := 0; j < vectorSize; j++ {
|
||||
cPoints[j] = pointToAffine(&pointSets[0][j])
|
||||
}
|
||||
|
||||
// Allocate results
|
||||
cResults := make([]C.BanderwagonExtended, n)
|
||||
cResults := make([]C.BanderwagonAffine, n)
|
||||
|
||||
ret := C.metal_ipa_batch_msm(
|
||||
ctx,
|
||||
(*C.BanderwagonExtended)(unsafe.Pointer(&cResults[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&allPoints[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cResults[0])),
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&allScalars[0])),
|
||||
(*C.uint32_t)(unsafe.Pointer(&counts[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cPoints[0])),
|
||||
C.uint32_t(n),
|
||||
C.uint32_t(vectorSize),
|
||||
)
|
||||
|
||||
if ret != C.METAL_IPA_SUCCESS {
|
||||
@@ -214,15 +236,11 @@ func BatchMSM(pointSets [][]banderwagon.Element, scalarSets [][]fr.Element) ([]*
|
||||
|
||||
// Convert results
|
||||
for i := 0; i < n; i++ {
|
||||
var resultBytes [32]byte
|
||||
for j := 0; j < 32; j++ {
|
||||
resultBytes[j] = byte(cResults[i].x.bytes[j])
|
||||
}
|
||||
var elem banderwagon.Element
|
||||
if err := elem.SetBytes(resultBytes[:]); err != nil {
|
||||
result, err := affineToPoint(&cResults[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[i] = &elem
|
||||
results[i] = result
|
||||
}
|
||||
|
||||
return results, nil
|
||||
@@ -255,7 +273,7 @@ func BatchPedersenCommit(basis []banderwagon.Element, valueSets [][]fr.Element)
|
||||
|
||||
// VerkleCommitNode computes a Verkle tree internal node commitment.
|
||||
// Uses width-256 Pedersen commitment optimized for Verkle trees.
|
||||
func VerkleCommitNode(children []banderwagon.Element) (*banderwagon.Element, error) {
|
||||
func VerkleCommitNode(children []banderwagon.Element, stem []byte) (*banderwagon.Element, error) {
|
||||
if len(children) != 256 {
|
||||
return nil, errors.New("Verkle node requires exactly 256 children")
|
||||
}
|
||||
@@ -282,20 +300,24 @@ func VerkleCommitNode(children []banderwagon.Element) (*banderwagon.Element, err
|
||||
}
|
||||
|
||||
// Convert children to C format
|
||||
cChildren := make([]C.BanderwagonExtended, 256)
|
||||
cChildren := make([]C.BanderwagonAffine, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
bytes := children[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cChildren[i].x.bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
cChildren[i] = pointToAffine(&children[i])
|
||||
}
|
||||
|
||||
var cResult C.BanderwagonExtended
|
||||
// Prepare stem (pad to 32 bytes if needed)
|
||||
var stemBytes [32]byte
|
||||
if len(stem) > 0 {
|
||||
copy(stemBytes[:], stem)
|
||||
}
|
||||
|
||||
var cResult C.BanderwagonAffine
|
||||
|
||||
ret := C.metal_verkle_commit_node(
|
||||
ctx,
|
||||
&cResult,
|
||||
(*C.BanderwagonExtended)(unsafe.Pointer(&cChildren[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cChildren[0])),
|
||||
(*C.uint8_t)(unsafe.Pointer(&stemBytes[0])),
|
||||
)
|
||||
|
||||
if ret != C.METAL_IPA_SUCCESS {
|
||||
@@ -309,18 +331,7 @@ func VerkleCommitNode(children []banderwagon.Element) (*banderwagon.Element, err
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// Convert result
|
||||
var resultBytes [32]byte
|
||||
for i := 0; i < 32; i++ {
|
||||
resultBytes[i] = byte(cResult.x.bytes[i])
|
||||
}
|
||||
|
||||
var result banderwagon.Element
|
||||
if err := result.SetBytes(resultBytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
return affineToPoint(&cResult)
|
||||
}
|
||||
|
||||
// Destroy releases the Metal IPA context.
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"crypto/curve25519"
|
||||
)
|
||||
|
||||
// X25519Impl implements X25519 as a KEM
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto
|
||||
#cgo pkg-config: lux-crypto-only
|
||||
#cgo darwin LDFLAGS: -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS:
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto
|
||||
#cgo pkg-config: lux-crypto-only
|
||||
#cgo darwin LDFLAGS: -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS:
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto
|
||||
#cgo pkg-config: lux-crypto-only
|
||||
#cgo darwin LDFLAGS: -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS:
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
-286
@@ -1,286 +0,0 @@
|
||||
// Package sign provides post-quantum signature algorithms
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SigID identifies a signature algorithm
|
||||
type SigID string
|
||||
|
||||
const (
|
||||
MLDSA2 SigID = "mldsa2"
|
||||
MLDSA3 SigID = "mldsa3"
|
||||
SLHDSA SigID = "slhdsa"
|
||||
)
|
||||
|
||||
// Signer interface for signature algorithms
|
||||
type Signer interface {
|
||||
// GenerateKeyPair generates a new signing key pair
|
||||
GenerateKeyPair() (PublicKey, PrivateKey, error)
|
||||
|
||||
// Sign creates a signature
|
||||
Sign(sk PrivateKey, message []byte) ([]byte, error)
|
||||
|
||||
// Verify verifies a signature
|
||||
Verify(pk PublicKey, message, signature []byte) bool
|
||||
|
||||
// PublicKeySize returns the size of public keys
|
||||
PublicKeySize() int
|
||||
|
||||
// PrivateKeySize returns the size of private keys
|
||||
PrivateKeySize() int
|
||||
|
||||
// SignatureSize returns the size of signatures
|
||||
SignatureSize() int
|
||||
}
|
||||
|
||||
// PublicKey represents a signature public key
|
||||
type PublicKey interface {
|
||||
Bytes() []byte
|
||||
Equal(PublicKey) bool
|
||||
}
|
||||
|
||||
// PrivateKey represents a signature private key
|
||||
type PrivateKey interface {
|
||||
Bytes() []byte
|
||||
Public() PublicKey
|
||||
Equal(PrivateKey) bool
|
||||
}
|
||||
|
||||
// MLDSA2Impl implements ML-DSA-44 (Dilithium2)
|
||||
type MLDSA2Impl struct{}
|
||||
|
||||
// NewMLDSA2 creates a new ML-DSA-44 instance
|
||||
func NewMLDSA2() Signer {
|
||||
return &MLDSA2Impl{}
|
||||
}
|
||||
|
||||
// MLDSA2PublicKey represents an ML-DSA-44 public key
|
||||
type MLDSA2PublicKey struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// MLDSA2PrivateKey represents an ML-DSA-44 private key
|
||||
type MLDSA2PrivateKey struct {
|
||||
data []byte
|
||||
pk *MLDSA2PublicKey
|
||||
}
|
||||
|
||||
// Constants for ML-DSA-44 (Dilithium2)
|
||||
const (
|
||||
mldsa2PublicKeySize = 1312
|
||||
mldsa2PrivateKeySize = 2528
|
||||
mldsa2SignatureSize = 2420
|
||||
)
|
||||
|
||||
// GenerateKeyPair generates a new ML-DSA-44 key pair
|
||||
func (m *MLDSA2Impl) GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
// Placeholder for actual ML-DSA key generation
|
||||
// In production, this would use liboqs or native implementation
|
||||
|
||||
pk := &MLDSA2PublicKey{
|
||||
data: make([]byte, mldsa2PublicKeySize),
|
||||
}
|
||||
sk := &MLDSA2PrivateKey{
|
||||
data: make([]byte, mldsa2PrivateKeySize),
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
// Generate random key material (placeholder)
|
||||
if _, err := rand.Read(pk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := rand.Read(sk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// Sign creates a signature
|
||||
func (m *MLDSA2Impl) Sign(sk PrivateKey, message []byte) ([]byte, error) {
|
||||
mldsaSK, ok := sk.(*MLDSA2PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type")
|
||||
}
|
||||
|
||||
signature := make([]byte, mldsa2SignatureSize)
|
||||
|
||||
// Placeholder for actual ML-DSA signing
|
||||
if _, err := rand.Read(signature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In production, this would perform actual ML-DSA signing
|
||||
_ = mldsaSK.data
|
||||
_ = message
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Verify verifies a signature
|
||||
func (m *MLDSA2Impl) Verify(pk PublicKey, message, signature []byte) bool {
|
||||
mldsaPK, ok := pk.(*MLDSA2PublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(signature) != mldsa2SignatureSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Placeholder for actual ML-DSA verification
|
||||
// In production, this would perform actual verification
|
||||
_ = mldsaPK.data
|
||||
_ = message
|
||||
_ = signature
|
||||
|
||||
// Placeholder: always return true for now
|
||||
return true
|
||||
}
|
||||
|
||||
// Size methods for ML-DSA-44
|
||||
func (m *MLDSA2Impl) PublicKeySize() int { return mldsa2PublicKeySize }
|
||||
func (m *MLDSA2Impl) PrivateKeySize() int { return mldsa2PrivateKeySize }
|
||||
func (m *MLDSA2Impl) SignatureSize() int { return mldsa2SignatureSize }
|
||||
|
||||
// PublicKey methods
|
||||
func (pk *MLDSA2PublicKey) Bytes() []byte {
|
||||
return pk.data
|
||||
}
|
||||
|
||||
func (pk *MLDSA2PublicKey) Equal(other PublicKey) bool {
|
||||
otherPK, ok := other.(*MLDSA2PublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return subtle.ConstantTimeCompare(pk.data, otherPK.data) == 1
|
||||
}
|
||||
|
||||
// PrivateKey methods
|
||||
func (sk *MLDSA2PrivateKey) Bytes() []byte {
|
||||
return sk.data
|
||||
}
|
||||
|
||||
func (sk *MLDSA2PrivateKey) Public() PublicKey {
|
||||
return sk.pk
|
||||
}
|
||||
|
||||
func (sk *MLDSA2PrivateKey) Equal(other PrivateKey) bool {
|
||||
otherSK, ok := other.(*MLDSA2PrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return subtle.ConstantTimeCompare(sk.data, otherSK.data) == 1
|
||||
}
|
||||
|
||||
// MLDSA3Impl implements ML-DSA-65 (Dilithium3)
|
||||
type MLDSA3Impl struct{}
|
||||
|
||||
// NewMLDSA3 creates a new ML-DSA-65 instance
|
||||
func NewMLDSA3() Signer {
|
||||
return &MLDSA3Impl{}
|
||||
}
|
||||
|
||||
// Constants for ML-DSA-65 (Dilithium3)
|
||||
const (
|
||||
mldsa3PublicKeySize = 1952
|
||||
mldsa3PrivateKeySize = 4000
|
||||
mldsa3SignatureSize = 3293
|
||||
)
|
||||
|
||||
// GenerateKeyPair generates a new ML-DSA-65 key pair
|
||||
func (m *MLDSA3Impl) GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
pk := &MLDSA2PublicKey{
|
||||
data: make([]byte, mldsa3PublicKeySize),
|
||||
}
|
||||
sk := &MLDSA2PrivateKey{
|
||||
data: make([]byte, mldsa3PrivateKeySize),
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
if _, err := rand.Read(pk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := rand.Read(sk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// Sign creates a signature
|
||||
func (m *MLDSA3Impl) Sign(sk PrivateKey, message []byte) ([]byte, error) {
|
||||
signature := make([]byte, mldsa3SignatureSize)
|
||||
|
||||
if _, err := rand.Read(signature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Verify verifies a signature
|
||||
func (m *MLDSA3Impl) Verify(pk PublicKey, message, signature []byte) bool {
|
||||
if len(signature) != mldsa3SignatureSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Placeholder
|
||||
return true
|
||||
}
|
||||
|
||||
// Size methods for ML-DSA-65
|
||||
func (m *MLDSA3Impl) PublicKeySize() int { return mldsa3PublicKeySize }
|
||||
func (m *MLDSA3Impl) PrivateKeySize() int { return mldsa3PrivateKeySize }
|
||||
func (m *MLDSA3Impl) SignatureSize() int { return mldsa3SignatureSize }
|
||||
|
||||
// GetSigner returns a Signer implementation for the given ID
|
||||
func GetSigner(id SigID) (Signer, error) {
|
||||
switch id {
|
||||
case MLDSA2:
|
||||
return NewMLDSA2(), nil
|
||||
case MLDSA3:
|
||||
return NewMLDSA3(), nil
|
||||
case SLHDSA:
|
||||
// SLH-DSA would require additional implementation
|
||||
return nil, errors.New("SLH-DSA not yet implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported signature algorithm: %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
// TranscriptSigner signs protocol transcripts
|
||||
type TranscriptSigner struct {
|
||||
signer Signer
|
||||
sk PrivateKey
|
||||
}
|
||||
|
||||
// NewTranscriptSigner creates a new transcript signer
|
||||
func NewTranscriptSigner(signer Signer, sk PrivateKey) *TranscriptSigner {
|
||||
return &TranscriptSigner{
|
||||
signer: signer,
|
||||
sk: sk,
|
||||
}
|
||||
}
|
||||
|
||||
// SignTranscript signs a protocol transcript
|
||||
func (ts *TranscriptSigner) SignTranscript(transcript []byte) ([]byte, error) {
|
||||
// Add context string to prevent cross-protocol attacks
|
||||
context := []byte("QZMQ-Transcript-v1")
|
||||
message := append(context, transcript...)
|
||||
|
||||
return ts.signer.Sign(ts.sk, message)
|
||||
}
|
||||
|
||||
// VerifyTranscript verifies a transcript signature
|
||||
func VerifyTranscript(signer Signer, pk PublicKey, transcript, signature []byte) bool {
|
||||
context := []byte("QZMQ-Transcript-v1")
|
||||
message := append(context, transcript...)
|
||||
|
||||
return signer.Verify(pk, message, signature)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package staking
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewCertAndKeyBytes generates a new TLS certificate and key in PEM format
|
||||
func NewCertAndKeyBytes() ([]byte, []byte, error) {
|
||||
// Generate RSA key pair
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Create certificate template
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"luxd"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour * 100), // 100 years
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// Generate the certificate
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Encode certificate to PEM
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certDER,
|
||||
})
|
||||
|
||||
// Encode private key to PEM
|
||||
privDER, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: privDER,
|
||||
})
|
||||
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
// ParseCertificate parses a DER-encoded certificate
|
||||
func ParseCertificate(der []byte) (*x509.Certificate, error) {
|
||||
return x509.ParseCertificate(der)
|
||||
}
|
||||
+116
-159
@@ -8,8 +8,9 @@
|
||||
package gpu
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../../../../luxcpp/crypto/include
|
||||
#cgo LDFLAGS: -L${SRCDIR}/../../../../../luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo CFLAGS: -I/Users/z/work/luxcpp/crypto/include
|
||||
#cgo darwin LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto -framework Metal -framework Foundation
|
||||
#cgo linux LDFLAGS: -L/Users/z/work/luxcpp/crypto/build-local -lluxcrypto
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
@@ -18,6 +19,7 @@ package gpu
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
"unsafe"
|
||||
@@ -36,10 +38,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
gpuCtx *C.MetalIPAContext
|
||||
gpuCtxOnce sync.Once
|
||||
gpuCtxErr error
|
||||
gpuCtxMu sync.Mutex
|
||||
gpuCtx *C.MetalIPAContext
|
||||
gpuCtxOnce sync.Once
|
||||
gpuCtxErr error
|
||||
gpuCtxMu sync.Mutex
|
||||
)
|
||||
|
||||
// initGPU initializes the Metal IPA context for GPU acceleration.
|
||||
@@ -58,6 +60,36 @@ func Available() bool {
|
||||
return bool(C.metal_ipa_available())
|
||||
}
|
||||
|
||||
// bytesToScalar converts 32-byte big-endian to C.BanderwagonScalar (4 x uint64 limbs, little-endian).
|
||||
func bytesToScalar(b []byte) C.BanderwagonScalar {
|
||||
var s C.BanderwagonScalar
|
||||
// Convert big-endian bytes to little-endian limbs
|
||||
s.limbs[0] = C.uint64_t(binary.BigEndian.Uint64(b[24:32]))
|
||||
s.limbs[1] = C.uint64_t(binary.BigEndian.Uint64(b[16:24]))
|
||||
s.limbs[2] = C.uint64_t(binary.BigEndian.Uint64(b[8:16]))
|
||||
s.limbs[3] = C.uint64_t(binary.BigEndian.Uint64(b[0:8]))
|
||||
return s
|
||||
}
|
||||
|
||||
// pointToAffine converts verkle.Point to C.BanderwagonAffine using serialization.
|
||||
func pointToAffine(p *verkle.Point) C.BanderwagonAffine {
|
||||
var affine C.BanderwagonAffine
|
||||
bytes := p.Bytes()
|
||||
C.metal_ipa_point_deserialize(&affine, (*C.uint8_t)(unsafe.Pointer(&bytes[0])))
|
||||
return affine
|
||||
}
|
||||
|
||||
// affineToPoint converts C.BanderwagonAffine to verkle.Point using serialization.
|
||||
func affineToPoint(affine *C.BanderwagonAffine) (*verkle.Point, error) {
|
||||
var bytes [32]byte
|
||||
C.metal_ipa_point_serialize((*C.uint8_t)(unsafe.Pointer(&bytes[0])), affine)
|
||||
point := new(verkle.Point)
|
||||
if err := point.SetBytes(bytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return point, nil
|
||||
}
|
||||
|
||||
// CommitToPoly performs polynomial commitment using GPU acceleration.
|
||||
// Falls back to CPU if GPU is unavailable or for small polynomials.
|
||||
func CommitToPoly(poly []fr.Element) (*verkle.Point, error) {
|
||||
@@ -84,18 +116,17 @@ func CommitToPoly(poly []fr.Element) (*verkle.Point, error) {
|
||||
cScalars := make([]C.BanderwagonScalar, n)
|
||||
for i := 0; i < n; i++ {
|
||||
bytes := poly[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cScalars[i].bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
cScalars[i] = bytesToScalar(bytes[:])
|
||||
}
|
||||
|
||||
// Perform GPU commitment (uses precomputed Lagrange basis)
|
||||
var cResult C.BanderwagonExtended
|
||||
// Perform GPU commitment (no blinding for deterministic commit)
|
||||
var cResult C.BanderwagonAffine
|
||||
|
||||
ret := C.metal_ipa_pedersen_commit(
|
||||
gpuCtx,
|
||||
&cResult,
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&cScalars[0])),
|
||||
nil, // No blinding
|
||||
C.uint32_t(n),
|
||||
)
|
||||
|
||||
@@ -105,20 +136,7 @@ func CommitToPoly(poly []fr.Element) (*verkle.Point, error) {
|
||||
return cfg.CommitToPoly(poly, 0), nil
|
||||
}
|
||||
|
||||
// Convert result to verkle.Point
|
||||
var resultBytes [32]byte
|
||||
for i := 0; i < 32; i++ {
|
||||
resultBytes[i] = byte(cResult.x.bytes[i])
|
||||
}
|
||||
|
||||
point := new(verkle.Point)
|
||||
if err := point.SetBytes(resultBytes[:]); err != nil {
|
||||
// Fall back to CPU on conversion error
|
||||
cfg := verkle.GetConfig()
|
||||
return cfg.CommitToPoly(poly, 0), nil
|
||||
}
|
||||
|
||||
return point, nil
|
||||
return affineToPoint(&cResult)
|
||||
}
|
||||
|
||||
// BatchCommitToPoly performs batch polynomial commitments using GPU.
|
||||
@@ -151,36 +169,39 @@ func BatchCommitToPoly(polys [][]fr.Element) ([]*verkle.Point, error) {
|
||||
gpuCtxMu.Lock()
|
||||
defer gpuCtxMu.Unlock()
|
||||
|
||||
// Prepare batch data
|
||||
counts := make([]C.uint32_t, n)
|
||||
totalScalars := 0
|
||||
for i, poly := range polys {
|
||||
counts[i] = C.uint32_t(len(poly))
|
||||
totalScalars += len(poly)
|
||||
// Check if all polys have same size (required for batch API)
|
||||
vectorSize := len(polys[0])
|
||||
for i := 1; i < n; i++ {
|
||||
if len(polys[i]) != vectorSize {
|
||||
// Fall back to sequential if sizes differ
|
||||
results := make([]*verkle.Point, n)
|
||||
cfg := verkle.GetConfig()
|
||||
for j, poly := range polys {
|
||||
results[j] = cfg.CommitToPoly(poly, 0)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten all scalars
|
||||
allScalars := make([]C.BanderwagonScalar, totalScalars)
|
||||
idx := 0
|
||||
for _, poly := range polys {
|
||||
for _, s := range poly {
|
||||
allScalars := make([]C.BanderwagonScalar, n*vectorSize)
|
||||
for i, poly := range polys {
|
||||
for j, s := range poly {
|
||||
bytes := s.Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
allScalars[idx].bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
idx++
|
||||
allScalars[i*vectorSize+j] = bytesToScalar(bytes[:])
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate results
|
||||
cResults := make([]C.BanderwagonExtended, n)
|
||||
cResults := make([]C.BanderwagonAffine, n)
|
||||
|
||||
ret := C.metal_ipa_batch_pedersen_commit(
|
||||
gpuCtx,
|
||||
(*C.BanderwagonExtended)(unsafe.Pointer(&cResults[0])),
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cResults[0])),
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&allScalars[0])),
|
||||
(*C.uint32_t)(unsafe.Pointer(&counts[0])),
|
||||
nil, // No blindings
|
||||
C.uint32_t(n),
|
||||
C.uint32_t(vectorSize),
|
||||
)
|
||||
|
||||
if ret != C.METAL_IPA_SUCCESS {
|
||||
@@ -196,13 +217,8 @@ func BatchCommitToPoly(polys [][]fr.Element) ([]*verkle.Point, error) {
|
||||
// Convert results
|
||||
results := make([]*verkle.Point, n)
|
||||
for i := 0; i < n; i++ {
|
||||
var resultBytes [32]byte
|
||||
for j := 0; j < 32; j++ {
|
||||
resultBytes[j] = byte(cResults[i].x.bytes[j])
|
||||
}
|
||||
|
||||
point := new(verkle.Point)
|
||||
if err := point.SetBytes(resultBytes[:]); err != nil {
|
||||
point, err := affineToPoint(&cResults[i])
|
||||
if err != nil {
|
||||
// Fall back to CPU for this one
|
||||
cfg := verkle.GetConfig()
|
||||
results[i] = cfg.CommitToPoly(polys[i], 0)
|
||||
@@ -214,136 +230,77 @@ func BatchCommitToPoly(polys [][]fr.Element) ([]*verkle.Point, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// VerifyProof verifies an IPA proof using GPU acceleration.
|
||||
func VerifyProof(
|
||||
commitment *verkle.Point,
|
||||
proof []byte,
|
||||
point fr.Element,
|
||||
evaluation fr.Element,
|
||||
) (bool, error) {
|
||||
// VerkleCommitNode computes a Verkle tree internal node commitment.
|
||||
// Uses width-256 commitment optimized for Verkle trees.
|
||||
func VerkleCommitNode(children []*verkle.Point, stem []byte) (*verkle.Point, error) {
|
||||
if len(children) != 256 {
|
||||
return nil, errors.New("Verkle node requires exactly 256 children")
|
||||
}
|
||||
|
||||
if !Available() {
|
||||
return false, errors.New("GPU not available, use CPU verification")
|
||||
// Fall back to CPU using verkle config
|
||||
cfg := verkle.GetConfig()
|
||||
scalars := make([]fr.Element, 256)
|
||||
points := make([]verkle.Point, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
scalars[i].SetOne()
|
||||
if children[i] != nil {
|
||||
points[i] = *children[i]
|
||||
}
|
||||
}
|
||||
result := cfg.CommitToPoly(scalars, 0)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if err := initGPU(); err != nil {
|
||||
return false, err
|
||||
cfg := verkle.GetConfig()
|
||||
scalars := make([]fr.Element, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
scalars[i].SetOne()
|
||||
}
|
||||
result := cfg.CommitToPoly(scalars, 0)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
gpuCtxMu.Lock()
|
||||
defer gpuCtxMu.Unlock()
|
||||
|
||||
// Convert commitment
|
||||
var cCommit C.BanderwagonExtended
|
||||
commitBytes := commitment.Bytes()
|
||||
for i := 0; i < 32; i++ {
|
||||
cCommit.x.bytes[i] = C.uint8_t(commitBytes[i])
|
||||
// Convert children to C format
|
||||
cChildren := make([]C.BanderwagonAffine, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
if children[i] != nil {
|
||||
cChildren[i] = pointToAffine(children[i])
|
||||
}
|
||||
// Zero-initialized for nil children
|
||||
}
|
||||
|
||||
// Convert point and evaluation
|
||||
var cPoint, cEval C.BanderwagonScalar
|
||||
pointBytes := point.Bytes()
|
||||
evalBytes := evaluation.Bytes()
|
||||
for i := 0; i < 32; i++ {
|
||||
cPoint.bytes[i] = C.uint8_t(pointBytes[i])
|
||||
cEval.bytes[i] = C.uint8_t(evalBytes[i])
|
||||
// Prepare stem (pad to 32 bytes if needed)
|
||||
var stemBytes [32]byte
|
||||
if len(stem) > 0 {
|
||||
copy(stemBytes[:], stem)
|
||||
}
|
||||
|
||||
// Prepare proof
|
||||
if len(proof) == 0 {
|
||||
return false, errors.New("empty proof")
|
||||
}
|
||||
cProof := (*C.uint8_t)(unsafe.Pointer(&proof[0]))
|
||||
var cResult C.BanderwagonAffine
|
||||
|
||||
ret := C.metal_ipa_verify(
|
||||
ret := C.metal_verkle_commit_node(
|
||||
gpuCtx,
|
||||
&cCommit,
|
||||
cProof,
|
||||
C.uint32_t(len(proof)),
|
||||
&cPoint,
|
||||
&cEval,
|
||||
)
|
||||
|
||||
return ret == C.METAL_IPA_SUCCESS, nil
|
||||
}
|
||||
|
||||
// BatchVerifyProofs verifies multiple IPA proofs in parallel on GPU.
|
||||
func BatchVerifyProofs(
|
||||
commitments []*verkle.Point,
|
||||
proofs [][]byte,
|
||||
points []fr.Element,
|
||||
evaluations []fr.Element,
|
||||
) ([]bool, error) {
|
||||
n := len(commitments)
|
||||
if n == 0 || n != len(proofs) || n != len(points) || n != len(evaluations) {
|
||||
return nil, errors.New("mismatched input lengths")
|
||||
}
|
||||
|
||||
if !Available() {
|
||||
return nil, errors.New("GPU not available")
|
||||
}
|
||||
|
||||
if err := initGPU(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gpuCtxMu.Lock()
|
||||
defer gpuCtxMu.Unlock()
|
||||
|
||||
// Convert commitments
|
||||
cCommits := make([]C.BanderwagonExtended, n)
|
||||
for i := 0; i < n; i++ {
|
||||
bytes := commitments[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cCommits[i].x.bytes[j] = C.uint8_t(bytes[j])
|
||||
}
|
||||
}
|
||||
|
||||
// Convert points and evaluations
|
||||
cPoints := make([]C.BanderwagonScalar, n)
|
||||
cEvals := make([]C.BanderwagonScalar, n)
|
||||
for i := 0; i < n; i++ {
|
||||
pBytes := points[i].Bytes()
|
||||
eBytes := evaluations[i].Bytes()
|
||||
for j := 0; j < 32; j++ {
|
||||
cPoints[i].bytes[j] = C.uint8_t(pBytes[j])
|
||||
cEvals[i].bytes[j] = C.uint8_t(eBytes[j])
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare proofs
|
||||
proofPtrs := make([]*C.uint8_t, n)
|
||||
proofLens := make([]C.uint32_t, n)
|
||||
for i := 0; i < n; i++ {
|
||||
if len(proofs[i]) > 0 {
|
||||
proofPtrs[i] = (*C.uint8_t)(unsafe.Pointer(&proofs[i][0]))
|
||||
}
|
||||
proofLens[i] = C.uint32_t(len(proofs[i]))
|
||||
}
|
||||
|
||||
// Allocate results
|
||||
cResults := make([]C.int, n)
|
||||
|
||||
ret := C.metal_ipa_batch_verify(
|
||||
gpuCtx,
|
||||
(*C.BanderwagonExtended)(unsafe.Pointer(&cCommits[0])),
|
||||
(**C.uint8_t)(unsafe.Pointer(&proofPtrs[0])),
|
||||
(*C.uint32_t)(unsafe.Pointer(&proofLens[0])),
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&cPoints[0])),
|
||||
(*C.BanderwagonScalar)(unsafe.Pointer(&cEvals[0])),
|
||||
C.uint32_t(n),
|
||||
(*C.int)(unsafe.Pointer(&cResults[0])),
|
||||
&cResult,
|
||||
(*C.BanderwagonAffine)(unsafe.Pointer(&cChildren[0])),
|
||||
(*C.uint8_t)(unsafe.Pointer(&stemBytes[0])),
|
||||
)
|
||||
|
||||
if ret != C.METAL_IPA_SUCCESS {
|
||||
return nil, errors.New("batch verification failed")
|
||||
// Fall back to CPU
|
||||
cfg := verkle.GetConfig()
|
||||
scalars := make([]fr.Element, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
scalars[i].SetOne()
|
||||
}
|
||||
result := cfg.CommitToPoly(scalars, 0)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
results := make([]bool, n)
|
||||
for i := 0; i < n; i++ {
|
||||
results[i] = cResults[i] != 0
|
||||
}
|
||||
|
||||
return results, nil
|
||||
return affineToPoint(&cResult)
|
||||
}
|
||||
|
||||
// Destroy releases the Metal IPA context.
|
||||
|
||||
Reference in New Issue
Block a user