mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Updates for warp, docs
This commit is contained in:
@@ -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
|
||||
+14
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user