Updates for warp, docs

This commit is contained in:
Zach Kelling
2025-12-26 14:00:00 -08:00
parent a67025abb7
commit 18f88f19da
11 changed files with 3895 additions and 41 deletions
+64
View File
@@ -0,0 +1,64 @@
name: Deploy Docs to GitHub Pages
on:
push:
branches:
- main
paths:
- 'docs/**'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
cache-dependency-path: docs/pnpm-lock.yaml
- name: Install dependencies
working-directory: docs
run: pnpm install --frozen-lockfile
- name: Build docs
working-directory: docs
run: pnpm build
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+1446 -41
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -52,6 +52,20 @@ func SecretKeyToBytes(sk *SecretKey) []byte {
return data
}
// SecretKeyFromSeed derives a secret key from a seed using proper BLS key derivation.
// The seed is passed through internal key derivation, so any 32+ byte input
// will produce a valid secret key.
func SecretKeyFromSeed(seed []byte) (*SecretKey, error) {
if len(seed) < 32 {
return nil, errors.New("seed must be at least 32 bytes")
}
sk, err := blssign.KeyGen[blssign.KeyG1SigG2](seed, nil, nil)
if err != nil {
return nil, err
}
return &SecretKey{sk: sk}, nil
}
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
if len(skBytes) != SecretKeyLen {
return nil, ErrFailedSecretKeyDeserialize
+11
View File
@@ -63,6 +63,17 @@ func SecretKeyToBytes(sk *SecretKey) []byte {
return sk.sk.Serialize()
}
// SecretKeyFromSeed derives a secret key from a seed using proper BLS key derivation.
// The seed is passed through HKDF internally by blst.KeyGen, so any 32+ byte input
// will produce a valid secret key.
func SecretKeyFromSeed(seed []byte) (*SecretKey, error) {
if len(seed) < 32 {
return nil, errors.New("seed must be at least 32 bytes")
}
sk := blst.KeyGen(seed)
return &SecretKey{sk: sk}, nil
}
// SecretKeyFromBytes parses the big-endian format of the secret key into a
// secret key.
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
+13
View File
@@ -47,6 +47,19 @@ func FromBytes(skBytes []byte) (*LocalSigner, error) {
return &LocalSigner{sk: sk, pk: pk}, nil
}
// FromSeed derives a signer from seed bytes using proper BLS key derivation.
// This is the preferred way to create a signer from arbitrary seed material
// (like mnemonic-derived bytes) as it handles the key derivation properly.
func FromSeed(seed []byte) (*LocalSigner, error) {
sk, err := bls.SecretKeyFromSeed(seed)
if err != nil {
return nil, err
}
pk := sk.PublicKey()
return &LocalSigner{sk: sk, pk: pk}, nil
}
// PublicKey returns the public key that corresponds to this secret
// key.
func (s *LocalSigner) PublicKey() *bls.PublicKey {
+420
View File
@@ -0,0 +1,420 @@
---
title: KZG Polynomial Commitments (EIP-4844)
description: Kate-Zaverucha-Goldberg commitments for blob transactions and data availability
---
# KZG Polynomial Commitments (EIP-4844)
KZG (Kate-Zaverucha-Goldberg) polynomial commitments enable efficient verification of polynomial evaluations, critical for EIP-4844 blob transactions and data availability sampling.
## Overview
KZG commitments provide:
- **Constant-Size Commitments**: 48-byte commitment regardless of data size
- **Efficient Verification**: Single pairing check for proof verification
- **Data Availability**: Enable efficient data availability sampling
- **Blob Transactions**: Foundation for Ethereum's scaling roadmap
## Technical Details
### Types and Sizes
| Type | Size | Description |
|------|------|-------------|
| Blob | 131,072 bytes | Data blob (128 KB) |
| Commitment | 48 bytes | BLS12-381 G1 point |
| Proof | 48 bytes | BLS12-381 G1 point |
| Point | 32 bytes | Field element |
| Claim | 32 bytes | Evaluation result |
### Cell Proofs
| Parameter | Value |
|-----------|-------|
| Cell Proofs Per Blob | 128 |
| Cells Per Blob | 128 |
| Cell Size | 1,024 bytes |
## Implementation
### Blob to Commitment
Create a commitment from blob data:
```go
package main
import (
"fmt"
"github.com/luxfi/crypto/kzg4844"
)
func main() {
// Create a blob (128 KB)
var blob kzg4844.Blob
copy(blob[:], []byte("Your data here..."))
// Generate commitment
commitment, err := kzg4844.BlobToCommitment(&blob)
if err != nil {
panic(err)
}
fmt.Printf("Commitment: %x\n", commitment[:])
fmt.Printf("Commitment size: %d bytes\n", len(commitment))
}
```
### Computing Proofs
Generate KZG proofs for blob evaluation:
```go
func computeKZGProof(blob *kzg4844.Blob, point kzg4844.Point) (kzg4844.Proof, kzg4844.Claim, error) {
// Compute proof at specific point
proof, claim, err := kzg4844.ComputeProof(blob, point)
if err != nil {
return kzg4844.Proof{}, kzg4844.Claim{}, err
}
fmt.Printf("Proof: %x\n", proof[:])
fmt.Printf("Claimed value: %x\n", claim[:])
return proof, claim, nil
}
```
### Blob Proofs
Generate proof that blob matches commitment:
```go
func computeBlobProof(blob *kzg4844.Blob, commitment kzg4844.Commitment) (kzg4844.Proof, error) {
// Compute proof for entire blob
proof, err := kzg4844.ComputeBlobProof(blob, commitment)
if err != nil {
return kzg4844.Proof{}, err
}
return proof, nil
}
```
### Verification
Verify KZG proofs:
```go
// Verify point evaluation proof
func verifyPointProof(
commitment kzg4844.Commitment,
point kzg4844.Point,
claim kzg4844.Claim,
proof kzg4844.Proof,
) bool {
err := kzg4844.VerifyProof(commitment, point, claim, proof)
return err == nil
}
// Verify blob matches commitment
func verifyBlobProof(
blob *kzg4844.Blob,
commitment kzg4844.Commitment,
proof kzg4844.Proof,
) bool {
err := kzg4844.VerifyBlobProof(blob, commitment, proof)
return err == nil
}
```
### Cell Proofs
For data availability sampling:
```go
// Compute all cell proofs for a blob
func computeCellProofs(blob *kzg4844.Blob) ([]kzg4844.Proof, error) {
proofs, err := kzg4844.ComputeCellProofs(blob)
if err != nil {
return nil, err
}
fmt.Printf("Generated %d cell proofs\n", len(proofs))
return proofs, nil
}
// Batch verify cell proofs
func verifyCellProofs(
blobs []kzg4844.Blob,
commitments []kzg4844.Commitment,
proofs []kzg4844.Proof,
) bool {
err := kzg4844.VerifyCellProofs(blobs, commitments, proofs)
return err == nil
}
```
### Versioned Blob Hashes
Calculate versioned hashes for blob transactions:
```go
import (
"crypto/sha256"
"github.com/luxfi/crypto/kzg4844"
)
func calculateBlobHash(commitment *kzg4844.Commitment) [32]byte {
hasher := sha256.New()
return kzg4844.CalcBlobHashV1(hasher, commitment)
}
func isValidBlobHash(hash []byte) bool {
return kzg4844.IsValidVersionedHash(hash)
}
```
## Backend Selection
Choose between Go and C implementations:
```go
// Use C-KZG backend (if available)
func useCBackend() error {
return kzg4844.UseCKZG(true)
}
// Use Go-KZG backend
func useGoBackend() error {
return kzg4844.UseCKZG(false)
}
```
## Blob Transactions
### Transaction Structure
```go
type BlobTransaction struct {
// Standard transaction fields
ChainID *big.Int
Nonce uint64
GasTipCap *big.Int
GasFeeCap *big.Int
Gas uint64
To *common.Address
Value *big.Int
Data []byte
// Blob-specific fields
BlobFeeCap *big.Int
BlobHashes []common.Hash
// Sidecar (not part of signed tx)
Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
}
```
### Creating Blob Transactions
```go
func createBlobTx(data [][]byte) (*BlobTransaction, error) {
tx := &BlobTransaction{
Blobs: make([]kzg4844.Blob, len(data)),
Commitments: make([]kzg4844.Commitment, len(data)),
Proofs: make([]kzg4844.Proof, len(data)),
BlobHashes: make([]common.Hash, len(data)),
}
for i, d := range data {
// Copy data to blob
copy(tx.Blobs[i][:], d)
// Compute commitment
commitment, err := kzg4844.BlobToCommitment(&tx.Blobs[i])
if err != nil {
return nil, err
}
tx.Commitments[i] = commitment
// Compute proof
proof, err := kzg4844.ComputeBlobProof(&tx.Blobs[i], commitment)
if err != nil {
return nil, err
}
tx.Proofs[i] = proof
// Calculate versioned hash
hasher := sha256.New()
tx.BlobHashes[i] = kzg4844.CalcBlobHashV1(hasher, &commitment)
}
return tx, nil
}
```
### Validating Blob Transactions
```go
func validateBlobTx(tx *BlobTransaction) error {
if len(tx.Blobs) != len(tx.Commitments) ||
len(tx.Blobs) != len(tx.Proofs) ||
len(tx.Blobs) != len(tx.BlobHashes) {
return errors.New("mismatched blob sidecar lengths")
}
for i := range tx.Blobs {
// Verify commitment matches blob
expectedCommitment, err := kzg4844.BlobToCommitment(&tx.Blobs[i])
if err != nil {
return err
}
if expectedCommitment != tx.Commitments[i] {
return errors.New("commitment mismatch")
}
// Verify proof
if err := kzg4844.VerifyBlobProof(&tx.Blobs[i], tx.Commitments[i], tx.Proofs[i]); err != nil {
return fmt.Errorf("proof verification failed: %w", err)
}
// Verify versioned hash
hasher := sha256.New()
expectedHash := kzg4844.CalcBlobHashV1(hasher, &tx.Commitments[i])
if tx.BlobHashes[i] != expectedHash {
return errors.New("blob hash mismatch")
}
}
return nil
}
```
## Data Availability Sampling
### Random Sampling
```go
type DataAvailabilitySampler struct {
commitments []kzg4844.Commitment
cellProofs [][]kzg4844.Proof
}
func (das *DataAvailabilitySampler) Sample(numSamples int) (bool, error) {
for i := 0; i < numSamples; i++ {
// Random blob index
blobIdx := rand.Intn(len(das.commitments))
// Random cell index
cellIdx := rand.Intn(kzg4844.CellProofsPerBlob)
// Verify cell proof
// This would require the cell data and use VerifyCellProofs
// Simplified here for illustration
}
return true, nil
}
```
## Performance Benchmarks
| Operation | Go Backend | C Backend | Notes |
|-----------|------------|-----------|-------|
| BlobToCommitment | 50 ms | 25 ms | Single blob |
| ComputeProof | 55 ms | 30 ms | Point evaluation |
| ComputeBlobProof | 60 ms | 35 ms | Full blob proof |
| VerifyProof | 3 ms | 2 ms | Single verification |
| VerifyBlobProof | 3 ms | 2 ms | Blob verification |
| ComputeCellProofs | 1.5 s | 0.8 s | 128 proofs |
## Trusted Setup
KZG requires a trusted setup ceremony:
```go
// The trusted setup is embedded in the package
//go:embed trusted_setup.json
var content embed.FS
// Powers of tau ceremony parameters:
// - 4096 G1 points
// - 65 G2 points
// - Secure multi-party computation ceremony
```
## Security Considerations
### Commitment Binding
```go
// KZG commitments are binding - cannot find two different
// polynomials that hash to the same commitment
func demonstrateBinding() {
var blob1, blob2 kzg4844.Blob
// Different data produces different commitments
copy(blob1[:], []byte("data1"))
copy(blob2[:], []byte("data2"))
c1, _ := kzg4844.BlobToCommitment(&blob1)
c2, _ := kzg4844.BlobToCommitment(&blob2)
// c1 != c2 (with overwhelming probability)
}
```
### Proof Soundness
```go
// Cannot forge proofs for incorrect evaluations
func verifyWithSecurityChecks(
commitment kzg4844.Commitment,
point kzg4844.Point,
claim kzg4844.Claim,
proof kzg4844.Proof,
) error {
// Verify commitment is on curve
if !isValidG1Point(commitment[:]) {
return errors.New("invalid commitment point")
}
// Verify proof is on curve
if !isValidG1Point(proof[:]) {
return errors.New("invalid proof point")
}
// Verify the actual proof
return kzg4844.VerifyProof(commitment, point, claim, proof)
}
```
## Integration with Lux
### Blob-Carrying Blocks
```go
type BlobCarryingBlock struct {
Header *BlockHeader
Transactions []*Transaction
BlobSidecars []*BlobSidecar
}
type BlobSidecar struct {
BlobIndex uint64
Blob kzg4844.Blob
Commitment kzg4844.Commitment
Proof kzg4844.Proof
TxHash common.Hash
}
```
## References
- [EIP-4844: Proto-Danksharding](https://eips.ethereum.org/EIPS/eip-4844)
- [KZG Polynomial Commitments](https://dankradfeist.de/ethereum/2020/06/16/kate-polynomial-commitments.html)
- [Ethereum KZG Ceremony](https://ceremony.ethereum.org/)
- [c-kzg-4844](https://github.com/ethereum/c-kzg-4844)
+405
View File
@@ -0,0 +1,405 @@
---
title: Lamport One-Time Signatures
description: Hash-based quantum-resistant one-time signatures
---
# Lamport One-Time Signatures
Lamport signatures are the simplest form of post-quantum cryptography, relying solely on hash functions for security. They are one-time signatures - each private key can only be used to sign a single message.
## Overview
Lamport signatures offer unique properties:
- **Quantum Resistance**: Security relies only on hash functions, not number theory
- **Simplicity**: Conceptually simple and easy to implement correctly
- **Fast Verification**: Verification requires only hash comparisons
- **Large Key Sizes**: Trade-off for simplicity and security
## Technical Details
### Parameters
| Hash Function | Public Key Size | Private Key Size | Signature Size |
|--------------|-----------------|------------------|----------------|
| SHA-256 | 16 KB | 16 KB | 8 KB |
| SHA-512 | 64 KB | 64 KB | 32 KB |
| SHA3-256 | 16 KB | 16 KB | 8 KB |
| SHA3-512 | 64 KB | 64 KB | 32 KB |
### How It Works
1. **Key Generation**: Generate 2×256 random values (for SHA-256), hash each to create public key
2. **Signing**: For each bit of message hash, reveal corresponding private value
3. **Verification**: Hash signature values and compare with public key
## Implementation
### Key Generation
```go
package main
import (
"crypto/rand"
"fmt"
"github.com/luxfi/crypto/lamport"
)
func main() {
// Generate Lamport key pair using SHA-256
privateKey, err := lamport.GenerateKey(rand.Reader, lamport.SHA256)
if err != nil {
panic(err)
}
// Derive public key
publicKey := privateKey.Public()
fmt.Printf("Public Key Size: %d bytes\n", len(publicKey.Bytes()))
fmt.Printf("Hash Function: SHA-256\n")
}
```
### Available Hash Functions
```go
const (
SHA256 HashFunc = iota // 256-bit, standard choice
SHA512 // 512-bit, higher security
SHA3_256 // SHA-3 256-bit
SHA3_512 // SHA-3 512-bit
)
```
### Signing Messages
**Important**: Each Lamport private key can only sign ONE message. The private key is automatically cleared after signing.
```go
func signMessage(privateKey *lamport.PrivateKey, message []byte) (*lamport.Signature, error) {
// Sign the message (CONSUMES the private key!)
signature, err := privateKey.Sign(message)
if err != nil {
return nil, err
}
fmt.Printf("Signature size: %d bytes\n", len(signature.Bytes()))
// WARNING: privateKey is now zeroed and cannot be reused
return signature, nil
}
```
### Signature Verification
```go
func verifySignature(publicKey *lamport.PublicKey, message []byte, signature *lamport.Signature) bool {
// Verify the signature
valid := publicKey.Verify(message, signature)
if valid {
fmt.Println("Lamport signature verified successfully")
}
return valid
}
```
### Serialization
```go
// Serialize public key
func serializePublicKey(pubKey *lamport.PublicKey) []byte {
return pubKey.Bytes()
}
// Deserialize public key
func deserializePublicKey(data []byte) (*lamport.PublicKey, error) {
return lamport.PublicKeyFromBytes(data)
}
// Serialize signature
func serializeSignature(sig *lamport.Signature) []byte {
return sig.Bytes()
}
// Deserialize signature
func deserializeSignature(data []byte) (*lamport.Signature, error) {
return lamport.SignatureFromBytes(data)
}
```
## One-Time Signature Management
Since Lamport keys can only be used once, you need a key management strategy:
### Key Pool
```go
type LamportKeyPool struct {
unused []*lamport.PrivateKey
used [][]byte // Store used public keys for verification
mu sync.Mutex
hashFunc lamport.HashFunc
}
func NewKeyPool(hashFunc lamport.HashFunc, initialSize int) (*LamportKeyPool, error) {
pool := &LamportKeyPool{
unused: make([]*lamport.PrivateKey, 0, initialSize),
used: make([][]byte, 0),
hashFunc: hashFunc,
}
// Pre-generate keys
for i := 0; i < initialSize; i++ {
key, err := lamport.GenerateKey(rand.Reader, hashFunc)
if err != nil {
return nil, err
}
pool.unused = append(pool.unused, key)
}
return pool, nil
}
func (p *LamportKeyPool) GetKey() (*lamport.PrivateKey, *lamport.PublicKey, error) {
p.mu.Lock()
defer p.mu.Unlock()
if len(p.unused) == 0 {
return nil, nil, errors.New("key pool exhausted")
}
// Get and remove key from pool
key := p.unused[len(p.unused)-1]
p.unused = p.unused[:len(p.unused)-1]
pubKey := key.Public()
p.used = append(p.used, pubKey.Bytes())
return key, pubKey, nil
}
```
### Merkle Tree of Keys
For efficient key management, use a Merkle tree:
```go
type MerkleLamport struct {
keys []*lamport.PrivateKey
rootHash []byte
treeDepth int
}
func NewMerkleLamport(numKeys int, hashFunc lamport.HashFunc) (*MerkleLamport, error) {
// Ensure numKeys is a power of 2
if numKeys&(numKeys-1) != 0 {
return nil, errors.New("numKeys must be a power of 2")
}
ml := &MerkleLamport{
keys: make([]*lamport.PrivateKey, numKeys),
treeDepth: int(math.Log2(float64(numKeys))),
}
// Generate all keys
pubKeys := make([][]byte, numKeys)
for i := 0; i < numKeys; i++ {
key, err := lamport.GenerateKey(rand.Reader, hashFunc)
if err != nil {
return nil, err
}
ml.keys[i] = key
pubKeys[i] = key.Public().Bytes()
}
// Compute Merkle root
ml.rootHash = computeMerkleRoot(pubKeys)
return ml, nil
}
// Sign returns signature with Merkle proof
func (ml *MerkleLamport) Sign(index int, message []byte) (*LamportMerkleSignature, error) {
if index >= len(ml.keys) {
return nil, errors.New("index out of range")
}
sig, err := ml.keys[index].Sign(message)
if err != nil {
return nil, err
}
proof := ml.computeMerkleProof(index)
return &LamportMerkleSignature{
Signature: sig,
Index: index,
MerkleProof: proof,
}, nil
}
```
## Use Cases
### 1. Blockchain Finality Signatures
One-time signatures are ideal for block finalization:
```go
type BlockFinalizationSig struct {
BlockHash []byte
ValidatorID uint64
Signature *lamport.Signature
PublicKey *lamport.PublicKey
}
func finalizeBlock(validator *Validator, block *Block) (*BlockFinalizationSig, error) {
// Get fresh Lamport key from validator's key pool
privKey, pubKey, err := validator.keyPool.GetKey()
if err != nil {
return nil, err
}
// Sign block hash
sig, err := privKey.Sign(block.Hash())
if err != nil {
return nil, err
}
return &BlockFinalizationSig{
BlockHash: block.Hash(),
ValidatorID: validator.ID,
Signature: sig,
PublicKey: pubKey,
}, nil
}
```
### 2. Software Update Signing
Critical one-time releases:
```go
type SoftwareRelease struct {
Version string
Hash []byte
Signature *lamport.Signature
PubKey []byte
}
func signRelease(masterKey *lamport.PrivateKey, release *Release) (*SoftwareRelease, error) {
// This key will NEVER be used again
sig, err := masterKey.Sign(release.Hash)
if err != nil {
return nil, err
}
return &SoftwareRelease{
Version: release.Version,
Hash: release.Hash,
Signature: sig,
PubKey: masterKey.Public().Bytes(),
}, nil
}
```
## Performance Benchmarks
| Operation | SHA-256 | SHA-512 | Notes |
|-----------|---------|---------|-------|
| Key Generation | 5 ms | 8 ms | Generates 512 random values |
| Sign | 2 ms | 4 ms | 256 hash operations |
| Verify | 2 ms | 4 ms | 256 hash comparisons |
## Security Considerations
### One-Time Property
**Critical**: Never reuse a Lamport private key!
```go
// BAD - DANGEROUS
key, _ := lamport.GenerateKey(rand.Reader, lamport.SHA256)
sig1, _ := key.Sign(message1) // OK
sig2, _ := key.Sign(message2) // FAILS - key is zeroed
// GOOD - Safe usage
func signMultipleMessages(messages [][]byte) ([]*lamport.Signature, error) {
signatures := make([]*lamport.Signature, len(messages))
for i, msg := range messages {
// Generate fresh key for each message
key, _ := lamport.GenerateKey(rand.Reader, lamport.SHA256)
sig, err := key.Sign(msg)
if err != nil {
return nil, err
}
signatures[i] = sig
}
return signatures, nil
}
```
### Hash Function Security
| Function | Pre-image | Collision | Quantum Security |
|----------|-----------|-----------|------------------|
| SHA-256 | 256 bits | 128 bits | 128 bits |
| SHA-512 | 512 bits | 256 bits | 256 bits |
| SHA3-256 | 256 bits | 128 bits | 128 bits |
### Key Storage
```go
// Secure key generation with entropy check
func secureKeyGen(hashFunc lamport.HashFunc) (*lamport.PrivateKey, error) {
// Use system CSPRNG
key, err := lamport.GenerateKey(rand.Reader, hashFunc)
if err != nil {
return nil, err
}
// Verify public key derivation
pubKey := key.Public()
if len(pubKey.Bytes()) != lamport.GetPublicKeySize(hashFunc) {
return nil, errors.New("key generation verification failed")
}
return key, nil
}
```
## Comparison with Other Post-Quantum Schemes
| Scheme | Key Size | Sig Size | Sign Time | Verify Time | Reusable |
|--------|----------|----------|-----------|-------------|----------|
| Lamport | 16 KB | 8 KB | 2 ms | 2 ms | No |
| ML-DSA-65 | 1.9 KB | 3.3 KB | 0.4 ms | 0.1 ms | Yes |
| SLH-DSA-128f | 32 B | 17 KB | 85 ms | 4 ms | Yes |
## EVM Precompile Integration
Lamport signatures are available as EVM precompiles:
```go
// Precompile addresses (0x0150-0x0159)
const (
LamportVerify = "0x0150" // Verify signature
LamportKeyGen = "0x0151" // On-chain key generation
)
// Gas costs
const (
LamportVerifyGas = 10000 // Per verification
)
```
## References
- [Lamport's Original Paper (1979)](https://www.microsoft.com/en-us/research/publication/constructing-digital-signatures-one-way-function/)
- [NIST Post-Quantum Standards](https://csrc.nist.gov/projects/post-quantum-cryptography)
- [Hash-Based Signatures](https://eprint.iacr.org/2017/349)
+578
View File
@@ -0,0 +1,578 @@
---
title: EVM Precompiled Contracts
description: Post-quantum cryptographic precompiles for the Lux EVM
---
# EVM Precompiled Contracts
Lux provides EVM precompiled contracts for post-quantum cryptographic operations, enabling smart contracts to use quantum-resistant algorithms efficiently.
## Overview
Precompiled contracts offer:
- **Gas Efficiency**: Native implementation is faster than EVM bytecode
- **Post-Quantum Security**: Access to ML-DSA, ML-KEM, SLH-DSA from Solidity
- **Standardized Interfaces**: Consistent API across all crypto operations
- **CGO Optimization**: Automatic use of optimized C libraries when available
## Address Ranges
| Algorithm | Address Range | Description |
|-----------|---------------|-------------|
| ML-DSA (FIPS 204) | 0x0110-0x0119 | Post-quantum signatures |
| ML-KEM (FIPS 203) | 0x0120-0x0129 | Post-quantum key exchange |
| SLH-DSA (FIPS 205) | 0x0130-0x0139 | Hash-based signatures |
| SHAKE (FIPS 202) | 0x0140-0x0149 | Extendable output functions |
| Lamport | 0x0150-0x0159 | One-time signatures |
| BLS | 0x0160-0x0169 | BLS signature operations |
## Precompile Interface
### Go Interface
```go
package precompile
// PrecompiledContract is the interface for EVM precompiled contracts
type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // Calculate gas cost
Run(input []byte) ([]byte, error) // Execute the precompile
}
// Registry contains all available precompiled contracts
type Registry struct {
contracts map[Address]PrecompiledContract
addresses []Address
}
// Get returns a precompiled contract by address
func (r *Registry) Get(addr Address) (PrecompiledContract, bool) {
contract, exists := r.contracts[addr]
return contract, exists
}
```
### Global Registry
```go
// PostQuantumRegistry contains all post-quantum precompiles
var PostQuantumRegistry = NewRegistry()
func init() {
// Register ML-DSA precompiles
PostQuantumRegistry.Register(
Address{0x01, 0x10},
&MLDSAVerify{},
)
// Register ML-KEM precompiles
PostQuantumRegistry.Register(
Address{0x01, 0x20},
&MLKEMDecapsulate{},
)
// Register SHAKE precompiles
PostQuantumRegistry.Register(
Address{0x01, 0x40},
&SHAKE256{},
)
}
```
## ML-DSA Precompiles
### ML-DSA Verify (0x0110)
Verify ML-DSA signatures on-chain:
```go
type MLDSAVerify struct{}
func (m *MLDSAVerify) RequiredGas(input []byte) uint64 {
// Gas cost based on security level
mode := input[0]
switch mode {
case 44: // ML-DSA-44
return 50000
case 65: // ML-DSA-65
return 75000
case 87: // ML-DSA-87
return 100000
default:
return 100000
}
}
func (m *MLDSAVerify) Run(input []byte) ([]byte, error) {
// Input format:
// [0]: mode (44, 65, or 87)
// [1:pubKeyLen+1]: public key
// [pubKeyLen+1:pubKeyLen+msgLen+1]: message
// [rest]: signature
mode := mldsa.Mode(input[0])
// Parse and verify signature
if valid {
return []byte{1}, nil
}
return []byte{0}, nil
}
```
### Solidity Interface
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IMLDSAVerify {
function verify(
uint8 mode,
bytes calldata publicKey,
bytes calldata message,
bytes calldata signature
) external view returns (bool);
}
contract MLDSAVerifier {
address constant MLDSA_VERIFY = address(0x0110);
function verifyMLDSA65(
bytes calldata publicKey,
bytes calldata message,
bytes calldata signature
) external view returns (bool) {
bytes memory input = abi.encodePacked(
uint8(65),
publicKey,
message,
signature
);
(bool success, bytes memory result) = MLDSA_VERIFY.staticcall(input);
require(success, "Precompile call failed");
return result[0] == 1;
}
}
```
## ML-KEM Precompiles
### ML-KEM Decapsulate (0x0120)
Decapsulate shared secrets on-chain:
```go
type MLKEMDecapsulate struct{}
func (m *MLKEMDecapsulate) RequiredGas(input []byte) uint64 {
mode := input[0]
switch mode {
case 0: // ML-KEM-512
return 30000
case 1: // ML-KEM-768
return 45000
case 2: // ML-KEM-1024
return 60000
default:
return 60000
}
}
func (m *MLKEMDecapsulate) Run(input []byte) ([]byte, error) {
// Input format:
// [0]: mode (0=512, 1=768, 2=1024)
// [1:privKeyLen+1]: private key (decapsulation key)
// [rest]: ciphertext
// Decapsulate and return shared secret
sharedSecret, err := mlkem.Decapsulate(privateKey, ciphertext)
if err != nil {
return nil, err
}
return sharedSecret, nil
}
```
### Solidity Interface
```solidity
interface IMLKEMDecapsulate {
function decapsulate(
uint8 mode,
bytes calldata privateKey,
bytes calldata ciphertext
) external view returns (bytes32 sharedSecret);
}
contract QuantumKeyExchange {
address constant MLKEM_DECAP = address(0x0120);
function establishSharedSecret(
bytes calldata encryptedKey,
bytes calldata ciphertext
) external view returns (bytes32) {
// Decrypt the ML-KEM private key
bytes memory privateKey = decrypt(encryptedKey);
bytes memory input = abi.encodePacked(
uint8(1), // ML-KEM-768
privateKey,
ciphertext
);
(bool success, bytes memory result) = MLKEM_DECAP.staticcall(input);
require(success, "Decapsulation failed");
return bytes32(result);
}
}
```
## SHAKE Precompiles
### SHAKE256 (0x0140)
Extendable-output hash function:
```go
type SHAKE256 struct{}
func (s *SHAKE256) RequiredGas(input []byte) uint64 {
// 10 gas per 32 bytes of input
// Plus 5 gas per 32 bytes of output
outputLen := binary.BigEndian.Uint32(input[:4])
inputLen := len(input) - 4
gasInput := uint64((inputLen + 31) / 32 * 10)
gasOutput := uint64((outputLen + 31) / 32 * 5)
return gasInput + gasOutput + 100 // Base cost
}
func (s *SHAKE256) Run(input []byte) ([]byte, error) {
// Input format:
// [0:4]: output length (big endian)
// [4:]: data to hash
outputLen := binary.BigEndian.Uint32(input[:4])
data := input[4:]
// Generate SHAKE256 output
output := make([]byte, outputLen)
sha3.ShakeSum256(output, data)
return output, nil
}
```
### Solidity Interface
```solidity
interface ISHAKE256 {
function hash(
uint32 outputLength,
bytes calldata data
) external view returns (bytes memory);
}
contract ShakeHasher {
address constant SHAKE256 = address(0x0140);
function shake256(
bytes calldata data,
uint32 outputLen
) external view returns (bytes memory) {
bytes memory input = abi.encodePacked(
outputLen,
data
);
(bool success, bytes memory result) = SHAKE256.staticcall(input);
require(success, "SHAKE256 failed");
return result;
}
// Derive key material
function deriveKey(
bytes calldata seed,
bytes calldata info
) external view returns (bytes32) {
bytes memory combined = abi.encodePacked(seed, info);
bytes memory result = this.shake256(combined, 32);
return bytes32(result);
}
}
```
## BLS Precompiles
### BLS Verify (0x0160)
Verify BLS signatures:
```go
type BLSVerify struct{}
func (b *BLSVerify) RequiredGas(input []byte) uint64 {
return 50000 // Base verification cost
}
func (b *BLSVerify) Run(input []byte) ([]byte, error) {
// Input format:
// [0:48]: public key (compressed G1)
// [48:144]: signature (compressed G2)
// [144:]: message
pubKey, err := bls.PublicKeyFromCompressedBytes(input[:48])
if err != nil {
return []byte{0}, nil
}
sig, err := bls.SignatureFromBytes(input[48:144])
if err != nil {
return []byte{0}, nil
}
message := input[144:]
if bls.Verify(pubKey, sig, message) {
return []byte{1}, nil
}
return []byte{0}, nil
}
```
### BLS Aggregate Verify (0x0161)
Verify aggregated signatures:
```go
type BLSAggregateVerify struct{}
func (b *BLSAggregateVerify) RequiredGas(input []byte) uint64 {
// Parse number of public keys
numKeys := binary.BigEndian.Uint32(input[:4])
return 50000 + uint64(numKeys)*10000
}
func (b *BLSAggregateVerify) Run(input []byte) ([]byte, error) {
// Input format:
// [0:4]: number of public keys
// [4:4+48*n]: public keys (compressed G1)
// [4+48*n:4+48*n+96]: aggregated signature (compressed G2)
// [rest]: message
// Parse and verify aggregate signature
if bls.VerifyAggregateCommon(publicKeys, message, aggSig) {
return []byte{1}, nil
}
return []byte{0}, nil
}
```
## Lamport Precompiles
### Lamport Verify (0x0150)
Verify Lamport one-time signatures:
```go
type LamportVerify struct{}
func (l *LamportVerify) RequiredGas(input []byte) uint64 {
hashFunc := input[0]
switch hashFunc {
case 0: // SHA256
return 100000 // 256 hash operations
case 1: // SHA512
return 200000 // 512 hash operations
default:
return 100000
}
}
func (l *LamportVerify) Run(input []byte) ([]byte, error) {
// Input format:
// [0]: hash function (0=SHA256, 1=SHA512)
// [1:pubKeyLen+1]: public key
// [pubKeyLen+1:pubKeyLen+msgLen+1]: message hash
// [rest]: signature
hashFunc := lamport.HashFunc(input[0])
// Parse and verify
if pubKey.VerifyHash(msgHash, signature) {
return []byte{1}, nil
}
return []byte{0}, nil
}
```
## Gas Costs Summary
| Precompile | Address | Gas Cost |
|------------|---------|----------|
| ML-DSA-44 Verify | 0x0110 | 50,000 |
| ML-DSA-65 Verify | 0x0110 | 75,000 |
| ML-DSA-87 Verify | 0x0110 | 100,000 |
| ML-KEM-512 Decap | 0x0120 | 30,000 |
| ML-KEM-768 Decap | 0x0120 | 45,000 |
| ML-KEM-1024 Decap | 0x0120 | 60,000 |
| SHAKE256 | 0x0140 | 100 + 10/32B input + 5/32B output |
| Lamport-SHA256 | 0x0150 | 100,000 |
| BLS Verify | 0x0160 | 50,000 |
| BLS Aggregate Verify | 0x0161 | 50,000 + 10,000/key |
## Smart Contract Example
Complete example using post-quantum precompiles:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PostQuantumAuth {
address constant MLDSA_VERIFY = address(0x0110);
address constant SHAKE256 = address(0x0140);
mapping(address => bytes) public publicKeys;
event KeyRegistered(address indexed user, bytes publicKey);
event ActionAuthorized(address indexed user, bytes32 actionHash);
function registerKey(bytes calldata mldsaPublicKey) external {
require(mldsaPublicKey.length == 1952, "Invalid ML-DSA-65 key size");
publicKeys[msg.sender] = mldsaPublicKey;
emit KeyRegistered(msg.sender, mldsaPublicKey);
}
function authorizeAction(
bytes32 actionHash,
bytes calldata signature
) external {
bytes memory publicKey = publicKeys[msg.sender];
require(publicKey.length > 0, "No key registered");
// Prepare verification input
bytes memory input = abi.encodePacked(
uint8(65), // ML-DSA-65
publicKey,
abi.encodePacked(actionHash),
signature
);
// Call precompile
(bool success, bytes memory result) = MLDSA_VERIFY.staticcall(input);
require(success && result[0] == 1, "Signature verification failed");
emit ActionAuthorized(msg.sender, actionHash);
}
function deriveSessionKey(
bytes calldata masterSecret,
uint256 sessionId
) external view returns (bytes32) {
bytes memory input = abi.encodePacked(
uint32(32), // Output 32 bytes
masterSecret,
sessionId
);
(bool success, bytes memory result) = SHAKE256.staticcall(input);
require(success, "Key derivation failed");
return bytes32(result);
}
}
```
## Testing Precompiles
```go
func TestMLDSAPrecompile(t *testing.T) {
// Generate key pair
pub, priv, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
require.NoError(t, err)
message := []byte("test message")
signature, err := priv.Sign(rand.Reader, message, crypto.Hash(0))
require.NoError(t, err)
// Create precompile input
input := make([]byte, 0)
input = append(input, 65) // Mode
input = append(input, pub.Bytes()...)
input = append(input, message...)
input = append(input, signature...)
// Run precompile
precompile := &MLDSAVerify{}
result, err := precompile.Run(input)
require.NoError(t, err)
require.Equal(t, byte(1), result[0])
// Test gas calculation
gas := precompile.RequiredGas(input)
require.Equal(t, uint64(75000), gas)
}
```
## Security Considerations
### Input Validation
```go
func (m *MLDSAVerify) Run(input []byte) ([]byte, error) {
// Always validate input length
if len(input) < minInputLength {
return nil, errors.New("input too short")
}
// Validate mode
mode := input[0]
if mode != 44 && mode != 65 && mode != 87 {
return nil, errors.New("invalid mode")
}
// Validate key size matches mode
expectedKeySize := getKeySize(mode)
if len(input) < 1+expectedKeySize {
return nil, errors.New("invalid public key size")
}
// Continue with verification...
}
```
### Gas Griefing Prevention
```go
func (s *SHAKE256) RequiredGas(input []byte) uint64 {
// Cap maximum output length to prevent DoS
outputLen := binary.BigEndian.Uint32(input[:4])
if outputLen > maxOutputLength {
outputLen = maxOutputLength
}
// Ensure minimum gas cost
gas := calculateGas(input, outputLen)
if gas < minGas {
return minGas
}
return gas
}
```
## References
- [EIP-2537: BLS12-381 Precompiles](https://eips.ethereum.org/EIPS/eip-2537)
- [NIST FIPS 202: SHA-3](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf)
- [NIST FIPS 204: ML-DSA](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf)
- [Solidity Precompiles](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#precompiled-contracts)
+271
View File
@@ -0,0 +1,271 @@
---
title: Corona (Lattice Threshold Signatures)
description: Lattice-based threshold signature scheme for post-quantum consensus
---
# Corona
Corona is a lattice-based threshold signature scheme used in Lux consensus for post-quantum security. It enables t-of-n validators to collaboratively create signatures without any single party controlling the signing key.
## Overview
Corona provides:
- **Threshold Security**: Requires t-of-n participants to sign
- **Post-Quantum Resistance**: Based on lattice hard problems
- **Consensus Integration**: Designed for Lux Quasar consensus
- **No Trusted Dealer**: Distributed key generation
## How Threshold Signatures Work
Unlike regular signatures where one party signs:
```
Regular: Private Key → Sign(message) → Signature
```
Threshold signatures require collaboration:
```
Threshold: Share_1 + Share_2 + ... + Share_t → Combine → Signature
(t of n participants required)
```
## Architecture
### Threshold Parameters
| Parameter | Description |
|-----------|-------------|
| n | Total number of participants |
| t | Threshold (minimum signers required) |
| Share | Individual participant's signing contribution |
| Certificate | Combined threshold signature |
### Lux Consensus Configuration
In Quasar consensus, typical configuration:
- **n**: Total validators in the validator set
- **t**: 2/3 + 1 of validators (Byzantine fault tolerance)
## Implementation
### Using the Threshold Interface
```go
package main
import (
"context"
"fmt"
"github.com/luxfi/crypto/threshold"
_ "github.com/luxfi/crypto/threshold/bls" // Register BLS scheme
)
func main() {
// Get the threshold scheme (BLS for now, Corona when available)
scheme, err := threshold.GetScheme(threshold.SchemeBLS)
if err != nil {
panic(err)
}
// Configure distributed key generation
config := threshold.DealerConfig{
Threshold: 2, // t in t+1-of-n
TotalParties: 5,
}
// Generate key shares via trusted dealer
dealer, err := scheme.NewTrustedDealer(config)
if err != nil {
panic(err)
}
shares, groupKey, err := dealer.GenerateShares(context.Background())
if err != nil {
panic(err)
}
fmt.Printf("Group Key: %x\n", groupKey.Bytes())
fmt.Printf("Generated %d shares for threshold %d\n", len(shares), config.Threshold+1)
}
```
### Threshold Operations
Corona threshold operations are coordinated through the consensus layer:
```go
// In consensus layer (github.com/luxfi/consensus)
type ThresholdEngine interface {
// Generate precomputed shares for fast signing
Precompute(sk []byte) ([]byte, error)
// Create a signing share
QuickSign(precomp []byte, msg []byte) ([]byte, error)
// Verify an individual share
VerifyShare(pk, msg, share []byte) bool
// Aggregate shares into final certificate
Aggregate(shares [][]byte) ([]byte, error)
// Verify final certificate
Verify(pk, msg, cert []byte) bool
}
```
### Consensus Integration
```go
// Validator creates their share
precomp, _ := corona.Precompute(validatorSK)
share, _ := corona.QuickSign(precomp, blockHash)
// Collect shares from t validators
shares := collectSharesFromValidators()
// Aggregate into final certificate
if len(shares) >= threshold {
cert, _ := corona.Aggregate(shares)
// cert is the threshold signature
}
```
## Use in Lux Consensus
### Block Finalization
```go
type FinalizedBlock struct {
Height uint64
Hash []byte
BLSAggregate []byte // Classical BLS aggregate
CoronaCert []byte // Threshold certificate (post-quantum)
Signers []byte // Bitfield of participating validators
}
```
### Dual-Layer Security
Lux uses both BLS and Corona:
| Layer | Purpose | Security |
|-------|---------|----------|
| BLS | Fast aggregation, classical | 128-bit classical |
| Corona | Threshold, post-quantum | Lattice hard problems |
Both must be valid for block finalization.
## Performance
| Operation | Time | Notes |
|-----------|------|-------|
| Key Generation | ~1 ms | One-time per validator |
| Precompute | ~5 ms | Per signing round |
| QuickSign | ~2 ms | Per validator per block |
| Aggregate | ~10 ms | Combines t shares |
| Verify | ~5 ms | Final certificate |
## Security Properties
### Threshold Security
- **t-of-n**: Requires threshold participants
- **No Single Point**: No party can sign alone
- **Byzantine Tolerance**: Tolerates n-t malicious parties
### Post-Quantum Resistance
Based on lattice problems believed to be hard for quantum computers:
- Learning With Errors (LWE)
- Short Integer Solution (SIS)
### Forward Security
Precomputed shares enable:
- Fast signing in consensus hot path
- Shares are ephemeral per round
## Comparison with Other Schemes
| Scheme | Type | PQ-Safe | Threshold |
|--------|------|---------|-----------|
| ECDSA | Classical | No | With MPC |
| BLS | Classical | No | Native |
| ML-DSA | Post-Quantum | Yes | No |
| Corona | Post-Quantum | Yes | Yes |
## Integration with Signer Package
The `signer` package provides BLS signing with threshold seed derivation:
```go
import "github.com/luxfi/crypto/signer"
s, _ := signer.NewSigner()
// BLS signing for consensus
sig, _ := s.SignBLS(message)
valid := s.VerifyBLS(message, sig)
// Threshold seed for deriving keys in threshold protocols
// Used with github.com/luxfi/threshold for t-of-n signing
seed := s.ThresholdSeed()
```
Full threshold operations use the `threshold` package interfaces.
## Testing
```go
func TestThresholdSigning(t *testing.T) {
ctx := context.Background()
scheme, err := threshold.GetScheme(threshold.SchemeBLS)
require.NoError(t, err)
// Generate 3 shares with threshold 2 (need 2 to sign)
dealer, err := scheme.NewTrustedDealer(threshold.DealerConfig{
Threshold: 1, // t=1 means need t+1=2 shares
TotalParties: 3,
})
require.NoError(t, err)
shares, groupKey, err := dealer.GenerateShares(ctx)
require.NoError(t, err)
require.Len(t, shares, 3)
require.NotNil(t, groupKey)
// Create signers from shares
signer0, _ := scheme.NewSigner(shares[0])
signer1, _ := scheme.NewSigner(shares[1])
// Sign with 2 parties
message := []byte("test message")
participants := []int{0, 1}
share0, _ := signer0.SignShare(ctx, message, participants, nil)
share1, _ := signer1.SignShare(ctx, message, participants, nil)
// Aggregate shares
aggregator, _ := scheme.NewAggregator(groupKey)
signature, err := aggregator.Aggregate(ctx, message,
[]threshold.SignatureShare{share0, share1}, nil)
require.NoError(t, err)
// Verify
verifier, _ := scheme.NewVerifier(groupKey)
require.True(t, verifier.Verify(message, signature))
}
```
## References
- [Threshold Cryptography Survey](https://eprint.iacr.org/2020/1390)
- [Lattice-Based Threshold Signatures](https://eprint.iacr.org/2021/118)
- [NIST Post-Quantum Standards](https://csrc.nist.gov/projects/post-quantum-cryptography)
- [Lux Consensus](https://github.com/luxfi/consensus)
+205
View File
@@ -0,0 +1,205 @@
---
title: Signer
description: Hybrid BLS and Corona signing for Lux consensus
---
# Signer
Hybrid signing combining BLS with Corona (lattice-based threshold signatures) for Lux consensus.
## Overview
- **Hybrid Signatures**: BLS + Corona threshold signing
- **Classical Security**: BLS provides efficient, aggregatable signatures
- **Post-Quantum Ready**: Corona lattice-based threshold scheme
- **Consensus Optimized**: Designed for Lux's Quasar consensus
## Architecture
Lux consensus uses two cryptographic layers:
| Layer | Algorithm | Purpose |
|-------|-----------|---------|
| Classical | BLS12-381 | Efficient aggregatable signatures |
| Threshold | Corona | Lattice-based threshold signatures |
## Implementation
### Creating a Signer
```go
package main
import (
"fmt"
"github.com/luxfi/crypto/signer"
)
func main() {
s, err := signer.NewSigner()
if err != nil {
panic(err)
}
fmt.Printf("BLS Public Key: %x\n", s.GetBLSPublicKey())
fmt.Printf("Corona Public Key: %x\n", s.GetCoronaPublicKey().Bytes())
}
```
### BLS Signing
BLS signatures are used for classical operations and can be aggregated:
```go
func sign(s *signer.Signer, msg []byte) ([]byte, error) {
return s.SignBLS(msg)
}
func verify(s *signer.Signer, msg, sig []byte) bool {
return s.VerifyBLS(msg, sig)
}
```
### Corona Threshold Signing
Corona is a lattice-based threshold signature scheme used for post-quantum security in consensus:
```go
// Corona threshold signing requires coordination through consensus
// The signer package provides key management
pk := s.GetCoronaPublicKey()
// Threshold operations are coordinated by the consensus layer
// See github.com/luxfi/consensus for full implementation
```
## Consensus Integration
### Quasar Consensus
The Lux Quasar consensus uses both BLS and Corona:
```go
type ConsensusMessage struct {
Height uint64
Round int
BlockHash []byte
BLSSignature []byte // Aggregatable
CoronaSig []byte // Threshold contribution
}
// BLS signatures are aggregated across validators
func aggregateBLS(sigs [][]byte) ([]byte, error) {
return bls.AggregateSignatures(sigs)
}
// Corona uses threshold aggregation
// Requires t-of-n validators to create final signature
```
### Block Finalization
```go
type FinalizedBlock struct {
Height uint64
Hash []byte
BLSAggregate []byte // Aggregated BLS from validators
CoronaCert []byte // Threshold certificate
ValidatorSet []byte // Participating validators
}
```
## Performance Comparison
| Operation | BLS | Notes |
|-----------|-----|-------|
| Sign | 1.2 ms | Single validator |
| Verify | 2.5 ms | Single signature |
| Aggregate | 0.1 ms | Per signature |
| Batch Verify | 3.0 ms | 100 signatures |
| Signature Size | 96 B | Constant |
| Public Key Size | 48 B | Compressed |
Corona threshold operations depend on the consensus layer.
## Key Management
### Export Public Keys
```go
func exportPublicKeys(s *signer.Signer) map[string][]byte {
return map[string][]byte{
"bls": s.GetBLSPublicKey(),
"corona": s.GetCoronaPublicKey().Bytes(),
}
}
```
### Access Raw Keys
```go
// For advanced operations
blsKey := s.BLSSecretKey()
coronaKey := s.CoronaPrivateKey()
```
## Security Considerations
### Dual-Layer Security
The hybrid approach provides security even if one algorithm is compromised:
- **BLS only broken**: Corona threshold maintains security
- **Corona only broken**: BLS maintains classical security
- **Both secure**: Defense in depth
### Key Generation
```go
func secureKeyGeneration() (*signer.Signer, error) {
// Signer uses crypto/rand internally
s, err := signer.NewSigner()
if err != nil {
return nil, err
}
// Verify key generation
if len(s.GetBLSPublicKey()) == 0 {
return nil, errors.New("BLS key generation failed")
}
if s.GetCoronaPublicKey() == nil {
return nil, errors.New("Corona key generation failed")
}
return s, nil
}
```
## Testing
```go
func TestSigner(t *testing.T) {
s, err := signer.NewSigner()
require.NoError(t, err)
message := []byte("test message")
// Test BLS
blsSig, err := s.SignBLS(message)
require.NoError(t, err)
require.True(t, s.VerifyBLS(message, blsSig))
// Wrong message should fail
require.False(t, s.VerifyBLS([]byte("wrong"), blsSig))
// Corona key should exist
require.NotNil(t, s.GetCoronaPublicKey())
}
```
## References
- [BLS Signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/)
- [Threshold Cryptography](https://en.wikipedia.org/wiki/Threshold_cryptosystem)
- [Lattice-Based Cryptography](https://pq-crystals.org/)
- [Lux Consensus](https://github.com/luxfi/consensus)
+468
View File
@@ -0,0 +1,468 @@
---
title: Inner Product Arguments (IPA) & Verkle Trees
description: Cryptographic primitives for Verkle tree state commitments and proofs
---
# Inner Product Arguments (IPA) & Verkle Trees
IPA (Inner Product Arguments) provide the cryptographic foundation for Verkle trees, enabling efficient state proofs with smaller proof sizes than Merkle Patricia Trees.
## Overview
Verkle trees with IPA provide:
- **Smaller Proofs**: ~150 bytes vs ~1 KB for Merkle proofs
- **Efficient Updates**: O(log n) complexity for state updates
- **Constant Verification**: Fixed verification cost regardless of tree depth
- **State Commitment**: Compact commitment to entire blockchain state
## Technical Details
### Bandersnatch Curve
The implementation uses the Bandersnatch curve:
| Parameter | Value |
|-----------|-------|
| Curve | Bandersnatch (embedded in BLS12-381) |
| Field Size | 253 bits |
| Group Order | ~2^253 |
| Security Level | 128 bits |
### IPA Parameters
| Parameter | Value |
|-----------|-------|
| Vector Length | 256 |
| Number of Rounds | 8 (log2(256)) |
| Commitment Size | 32 bytes |
| Proof Size | ~544 bytes |
## Implementation
### Setting Up IPA
```go
package main
import (
"fmt"
"github.com/luxfi/crypto/ipa/ipa"
"github.com/luxfi/crypto/ipa/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
func main() {
// Initialize IPA settings (generates SRS)
config, err := ipa.NewIPASettings()
if err != nil {
panic(err)
}
fmt.Printf("SRS contains %d points\n", len(config.SRS))
fmt.Printf("Number of rounds: %d\n", config.NumRounds())
}
```
### Pedersen Commitments
Create commitments to polynomials:
```go
func commitToPolynomial(config *ipa.IPAConfig, polynomial []fr.Element) banderwagon.Element {
// Commit using precomputed MSM
commitment := config.Commit(polynomial)
fmt.Printf("Commitment: %x\n", commitment.Bytes())
return commitment
}
// Create polynomial from data
func createPolynomial(data []byte) []fr.Element {
poly := make([]fr.Element, 256)
for i := 0; i < len(data) && i < 256; i++ {
poly[i].SetUint64(uint64(data[i]))
}
return poly
}
```
### Inner Product Computation
Compute inner products of vectors:
```go
func computeInnerProduct(a, b []fr.Element) (fr.Element, error) {
if len(a) != len(b) {
return fr.Element{}, errors.New("vectors must have same length")
}
result, err := ipa.InnerProd(a, b)
if err != nil {
return fr.Element{}, err
}
fmt.Printf("Inner product: %s\n", result.String())
return result, nil
}
```
### Multi-Scalar Multiplication
Efficient MSM for group operations:
```go
func multiScalarMult(points []banderwagon.Element, scalars []fr.Element) (banderwagon.Element, error) {
result, err := ipa.MultiScalar(points, scalars)
if err != nil {
return banderwagon.Element{}, err
}
return result, nil
}
```
## Verkle Tree Structure
### Node Types
```go
type VerkleNode interface {
Commitment() banderwagon.Element
Hash() []byte
}
type InternalNode struct {
children [256]VerkleNode
commitment banderwagon.Element
}
type LeafNode struct {
stem [31]byte
values [256][]byte
commitment banderwagon.Element
}
type EmptyNode struct{}
```
### Tree Operations
```go
type VerkleTree struct {
root VerkleNode
config *ipa.IPAConfig
}
func NewVerkleTree(config *ipa.IPAConfig) *VerkleTree {
return &VerkleTree{
root: &EmptyNode{},
config: config,
}
}
// Insert a key-value pair
func (t *VerkleTree) Insert(key []byte, value []byte) error {
stem := key[:31]
suffix := key[31]
// Navigate to leaf, creating nodes as needed
// Update commitments along the path
return nil
}
// Get a value by key
func (t *VerkleTree) Get(key []byte) ([]byte, error) {
stem := key[:31]
suffix := key[31]
// Navigate to leaf
// Return value at suffix position
return nil, nil
}
// Generate proof for a key
func (t *VerkleTree) Prove(key []byte) (*VerkleProof, error) {
// Generate IPA proof for the path
return nil, nil
}
```
## IPA Proof Generation
### Prover
```go
type IPAProver struct {
config *ipa.IPAConfig
}
func (p *IPAProver) CreateProof(
commitment banderwagon.Element,
polynomial []fr.Element,
point fr.Element,
) (*IPAProof, error) {
// Generate IPA proof that polynomial evaluates to
// a specific value at the given point
proof := &IPAProof{
L: make([]banderwagon.Element, p.config.NumRounds()),
R: make([]banderwagon.Element, p.config.NumRounds()),
}
// Prover algorithm:
// 1. Split vectors in half
// 2. Compute cross terms L and R
// 3. Get challenge from verifier (Fiat-Shamir)
// 4. Fold vectors
// 5. Repeat for log(n) rounds
return proof, nil
}
type IPAProof struct {
L []banderwagon.Element // Left cross terms
R []banderwagon.Element // Right cross terms
A fr.Element // Final scalar
}
```
### Verifier
```go
type IPAVerifier struct {
config *ipa.IPAConfig
}
func (v *IPAVerifier) VerifyProof(
commitment banderwagon.Element,
point fr.Element,
evaluation fr.Element,
proof *IPAProof,
) bool {
// Verify IPA proof
// Verifier algorithm:
// 1. Recompute challenges from L, R values
// 2. Compute final commitment
// 3. Check against claimed evaluation
return true
}
```
## Multi-Proof System
Efficient proofs for multiple keys:
```go
type MultiProof struct {
D banderwagon.Element // Commitment to quotient polynomial
IPAProof *IPAProof // IPA proof for evaluation
ClaimedValues []fr.Element // Values at queried positions
}
func CreateMultiProof(
config *ipa.IPAConfig,
commitments []banderwagon.Element,
polynomials [][]fr.Element,
points []fr.Element,
) (*MultiProof, error) {
// Create proof for multiple polynomial evaluations
return nil, nil
}
func VerifyMultiProof(
config *ipa.IPAConfig,
commitments []banderwagon.Element,
points []fr.Element,
values []fr.Element,
proof *MultiProof,
) bool {
// Verify multi-proof
return true
}
```
## State Management
### State Commitment
```go
type StateCommitment struct {
Root banderwagon.Element
BlockNum uint64
StateRoot []byte
}
func (s *StateCommitment) Update(changes map[string][]byte) (*StateCommitment, error) {
// Apply changes to state tree
// Recompute affected commitments
// Return new state commitment
return nil, nil
}
```
### Witness Generation
```go
type ExecutionWitness struct {
StateDiff []KeyValuePair
VerkleProof *MultiProof
ParentState []byte
}
func GenerateWitness(
tree *VerkleTree,
accessedKeys [][]byte,
) (*ExecutionWitness, error) {
// Generate witness for accessed state
return nil, nil
}
```
## Performance Benchmarks
| Operation | Time | Notes |
|-----------|------|-------|
| Commit (256 values) | 2.5 ms | Using precomputed MSM |
| IPA Prove | 15 ms | 8 rounds |
| IPA Verify | 8 ms | Single proof |
| Multi-proof (100 keys) | 150 ms | Batched |
| Multi-verify (100 keys) | 50 ms | Batched |
### Proof Sizes
| Proof Type | Size |
|------------|------|
| Single IPA proof | 544 bytes |
| Verkle proof (depth 4) | ~600 bytes |
| Multi-proof (10 keys) | ~1.5 KB |
| Multi-proof (100 keys) | ~3 KB |
## Comparison with Merkle Trees
| Aspect | Merkle Patricia | Verkle |
|--------|-----------------|--------|
| Proof size | ~1-3 KB | ~150-600 bytes |
| Update complexity | O(log n) | O(log n) |
| Verification | O(log n) hashes | O(1) pairings |
| Witness size | Large | Small |
| Statelessness | Difficult | Feasible |
## Security Properties
### Binding
```go
// Commitments are computationally binding
// Cannot find two different polynomials with same commitment
func demonstrateBinding() {
config, _ := ipa.NewIPASettings()
poly1 := make([]fr.Element, 256)
poly2 := make([]fr.Element, 256)
poly1[0].SetUint64(1)
poly2[0].SetUint64(2)
c1 := config.Commit(poly1)
c2 := config.Commit(poly2)
// c1 != c2 with overwhelming probability
}
```
### Soundness
```go
// Cannot create valid proof for incorrect evaluation
// Soundness relies on discrete log hardness in Bandersnatch
func testSoundness(t *testing.T) {
// Create proof for correct evaluation
// Try to verify with wrong evaluation value
// Must fail
require.False(t, verifier.VerifyProof(commitment, point, wrongValue, proof))
}
```
## Integration with Lux
### Block State Root
```go
type LuxBlock struct {
Header *BlockHeader
Transactions []*Transaction
VerkleRoot banderwagon.Element
}
func (b *LuxBlock) ComputeStateRoot(tree *VerkleTree) {
b.VerkleRoot = tree.root.Commitment()
}
```
### Stateless Validation
```go
type StatelessValidator struct {
config *ipa.IPAConfig
}
func (v *StatelessValidator) ValidateBlock(
block *LuxBlock,
witness *ExecutionWitness,
) error {
// Verify witness against parent state
// Execute transactions using witness data
// Verify resulting state matches block state root
return nil
}
```
## Testing
```go
func TestIPACommitment(t *testing.T) {
config, err := ipa.NewIPASettings()
require.NoError(t, err)
// Create polynomial
poly := make([]fr.Element, 256)
for i := 0; i < 256; i++ {
poly[i].SetUint64(uint64(i))
}
// Commit
commitment := config.Commit(poly)
require.False(t, commitment.IsIdentity())
// Same polynomial should give same commitment
commitment2 := config.Commit(poly)
require.True(t, commitment.Equal(&commitment2))
// Different polynomial should give different commitment
poly[0].SetUint64(999)
commitment3 := config.Commit(poly)
require.False(t, commitment.Equal(&commitment3))
}
```
## References
- [Verkle Trees](https://vitalik.ca/general/2021/06/18/verkle.html)
- [Inner Product Arguments](https://eprint.iacr.org/2019/1021)
- [Bandersnatch Curve](https://eprint.iacr.org/2021/1152)
- [go-verkle Implementation](https://github.com/ethereum/go-verkle)
- [EIP-6800: Ethereum state using a Verkle tree](https://eips.ethereum.org/EIPS/eip-6800)