docs: add documentation and threshold crypto adapter

This commit is contained in:
2025-12-25 09:13:09 -08:00
parent 450fe1842f
commit 207b5e7bb4
5 changed files with 1630 additions and 0 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
+336
View File
@@ -0,0 +1,336 @@
---
title: Installation
description: Step-by-step guide to install and configure Lux MPC
---
# Installation Guide
## Prerequisites
Before starting, ensure you have:
- **Go** 1.23+ installed: [Install Go here](https://go.dev/doc/install)
- **NATS** server running (JetStream enabled)
- **Consul** server running
## Clone and Build
### Clone the Repository
```bash
git clone https://github.com/luxfi/mpc.git
cd mpc
```
### Build the Project
With Make:
```bash
make
```
Or with Go:
```bash
go install ./cmd/lux-mpc
go install ./cmd/lux-mpc-cli
```
### Available Commands
- `lux-mpc`: Start a Lux MPC node
- `lux-mpc-cli`: CLI utility for peer, identity, and initiator configuration
## Infrastructure Setup
### Development Environment
Create a `compose.yml` file for local development:
```yaml
services:
nats-server:
image: nats:latest
container_name: nats-server
command: -js --http_port 8222
ports:
- "4222:4222"
- "8222:8222"
- "6222:6222"
tty: true
restart: always
consul:
image: consul:1.15.4
container_name: consul
ports:
- "8500:8500"
- "8601:8600/udp"
command: "agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0"
restart: always
```
Start the services:
```bash
docker compose up -d
```
### Production Environment
For production deployments:
1. Use clustered NATS with JetStream
2. Use Consul cluster with TLS
3. Enable encryption for all key material
4. Use HSMs for key storage where possible
## Peer Configuration
### Generate Peers
```bash
lux-mpc-cli generate-peers -n 3
```
Output:
```json
{
"node0": "12345678-1234-1234-1234-123456789abc",
"node1": "23456789-2345-2345-2345-23456789abcd",
"node2": "34567890-3456-3456-3456-3456789abcde"
}
```
### Register Peers to Consul
```bash
lux-mpc-cli register-peers
```
## Configuration
### Create Configuration File
```bash
cp config.yaml.template config.yaml
```
Edit `config.yaml`:
```yaml
nats:
url: nats://127.0.0.1:4222
consul:
address: localhost:8500
mpc_threshold: 2
environment: development
badger_password: "your_secure_password"
event_initiator_pubkey: "your_event_initiator_pubkey"
```
### Generate Secure Password
```bash
< /dev/urandom tr -dc 'A-Za-z0-9!@#$^&*()-_=+[]{}|;:,.<>?/~' | head -c 32; echo
```
## Event Initiator Setup
### Generate Initiator Key
Development (unencrypted):
```bash
lux-mpc-cli generate-initiator
```
Production (encrypted):
```bash
lux-mpc-cli generate-initiator --encrypt
```
### Configure Public Key
From `event_initiator.identity.json`:
```json
{
"public_key": "09be5d070816aadaa1b6638cad33e819a8aed7101626f6bf1e0b427412c3408a"
}
```
Add to `config.yaml`:
```yaml
event_initiator_pubkey: "09be5d070816aadaa1b6638cad33e819a8aed7101626f6bf1e0b427412c3408a"
```
## Node Identity Setup
### Create Node Directories
```bash
mkdir node{0..2}
for dir in node{0..2}; do
cp config.yaml peers.json "$dir/"
mkdir -p "$dir/identity"
done
```
### Generate Node Identities
For each node:
```bash
cd node0
lux-mpc-cli generate-identity --node node0
# Production: use encryption
lux-mpc-cli generate-identity --node node0 --encrypt
```
### Distribute Identity Files
Each node needs all public identities:
```bash
# From node0 directory
cp identity/node0_identity.json ../node1/identity/
cp identity/node0_identity.json ../node2/identity/
# Repeat for node1 and node2
```
### Final Directory Structure
```
mpc/
node0/
config.yaml
peers.json
identity/
node0_identity.json
node0_private.key
node1_identity.json
node2_identity.json
node1/
config.yaml
peers.json
identity/
node0_identity.json
node1_identity.json
node1_private.key
node2_identity.json
node2/
config.yaml
peers.json
identity/
node0_identity.json
node1_identity.json
node2_identity.json
node2_private.key
```
## Starting the Cluster
### Start Each Node
Terminal 1:
```bash
cd node0 && lux-mpc start -n node0
```
Terminal 2:
```bash
cd node1 && lux-mpc start -n node1
```
Terminal 3:
```bash
cd node2 && lux-mpc start -n node2
```
### Production Mode
Avoid hardcoded credentials:
```bash
lux-mpc start -n node0 --prompt-credentials
```
## Verification
Check node status in Consul UI at `http://localhost:8500`:
- All nodes should show as healthy
- Services should be registered
Test wallet creation:
```go
package main
import (
"github.com/luxfi/mpc/pkg/client"
"github.com/nats-io/nats.go"
)
func main() {
nc, _ := nats.Connect("nats://localhost:4222")
defer nc.Close()
mpcClient := client.NewMPCClient(client.Options{
NatsConn: nc,
KeyPath: "./event_initiator.key",
})
mpcClient.CreateWallet("test-wallet-001")
}
```
## Production Checklist
- [ ] TLS enabled on NATS
- [ ] TLS enabled on Consul
- [ ] All keys encrypted with strong passwords
- [ ] HSM integration for production keys
- [ ] Network isolation between nodes
- [ ] Monitoring and alerting configured
- [ ] Backup strategy implemented
- [ ] Key rotation schedule defined
- [ ] Incident response procedures documented
## Troubleshooting
### Node Fails to Start
```bash
# Check NATS connectivity
nc -zv localhost 4222
# Check Consul connectivity
curl http://localhost:8500/v1/status/leader
```
### Key Generation Timeout
1. Increase timeout in config
2. Check network latency between nodes
3. Verify all nodes are healthy
### Identity Verification Failed
1. Ensure all identity files are distributed
2. Check file permissions
3. Verify Ed25519 signatures match
## Next Steps
- [Protocol Overview](/docs/protocol) - Understand MPC protocols
- [API Reference](/docs/api) - Integration documentation
- [Security Model](/docs/security) - Security best practices
+12
View File
@@ -0,0 +1,12 @@
{
"title": "Documentation",
"pages": [
"index",
"quickstart",
"installation",
"protocol",
"api",
"security",
"use-cases"
]
}
+272
View File
@@ -0,0 +1,272 @@
---
title: Quick Start
description: Get started with Lux MPC in minutes
---
# Quick Start
Get a local MPC cluster running in under 5 minutes.
## Prerequisites
- Go 1.23+
- Docker and Docker Compose
## 1. Clone and Build
```bash
git clone https://github.com/luxfi/mpc.git
cd mpc
make
```
## 2. Start Infrastructure
```bash
# Create compose.yml
cat > compose.yml << 'EOF'
services:
nats:
image: nats:latest
command: -js
ports:
- "4222:4222"
consul:
image: consul:1.15.4
ports:
- "8500:8500"
command: agent -server -ui -bootstrap-expect=1 -client=0.0.0.0
EOF
docker compose up -d
```
## 3. Configure Cluster
```bash
# Generate peer configuration
lux-mpc-cli generate-peers -n 3
# Generate initiator key
lux-mpc-cli generate-initiator
# Create configuration
cat > config.yaml << 'EOF'
nats:
url: nats://127.0.0.1:4222
consul:
address: localhost:8500
mpc_threshold: 2
environment: development
badger_password: "dev-password-change-in-prod"
event_initiator_pubkey: "YOUR_PUBLIC_KEY_HERE"
EOF
```
Replace `YOUR_PUBLIC_KEY_HERE` with the key from `event_initiator.identity.json`.
## 4. Setup Nodes
```bash
# Create node directories
mkdir -p node{0..2}/identity
for dir in node0 node1 node2; do
cp config.yaml peers.json "$dir/"
done
# Generate identities
cd node0 && lux-mpc-cli generate-identity --node node0 && cd ..
cd node1 && lux-mpc-cli generate-identity --node node1 && cd ..
cd node2 && lux-mpc-cli generate-identity --node node2 && cd ..
# Distribute identities
for src in node0 node1 node2; do
for dst in node0 node1 node2; do
cp "$src/identity/${src}_identity.json" "$dst/identity/"
done
done
# Register peers
lux-mpc-cli register-peers
```
## 5. Start Nodes
Open three terminal windows:
```bash
# Terminal 1
cd node0 && lux-mpc start -n node0
```
```bash
# Terminal 2
cd node1 && lux-mpc start -n node1
```
```bash
# Terminal 3
cd node2 && lux-mpc start -n node2
```
## 6. Create a Wallet
```go
package main
import (
"fmt"
"log"
"time"
"github.com/luxfi/mpc/pkg/client"
"github.com/luxfi/mpc/pkg/event"
"github.com/nats-io/nats.go"
)
func main() {
// Connect to NATS
nc, err := nats.Connect("nats://localhost:4222")
if err != nil {
log.Fatal(err)
}
defer nc.Close()
// Create MPC client
mpcClient := client.NewMPCClient(client.Options{
NatsConn: nc,
KeyPath: "./event_initiator.key",
})
// Subscribe to results
mpcClient.OnWalletCreationResult(func(e event.KeygenResultEvent) {
fmt.Printf("Wallet created!\n")
fmt.Printf(" ID: %s\n", e.WalletID)
fmt.Printf(" Public Key: %x\n", e.ECDSAPubKey)
})
// Create wallet
walletID := fmt.Sprintf("wallet-%d", time.Now().Unix())
if err := mpcClient.CreateWallet(walletID); err != nil {
log.Fatal(err)
}
fmt.Printf("Creating wallet: %s\n", walletID)
// Wait for result
time.Sleep(30 * time.Second)
}
```
Save as `main.go` and run:
```bash
go run main.go
```
## 7. Sign a Message
```go
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"time"
"github.com/luxfi/mpc/pkg/client"
"github.com/luxfi/mpc/pkg/event"
"github.com/luxfi/mpc/pkg/types"
"github.com/nats-io/nats.go"
)
func main() {
nc, _ := nats.Connect("nats://localhost:4222")
defer nc.Close()
mpcClient := client.NewMPCClient(client.Options{
NatsConn: nc,
KeyPath: "./event_initiator.key",
})
// Subscribe to signing results
mpcClient.OnSignResult(func(e event.SigningResultEvent) {
fmt.Printf("Signature: %x\n", e.Signature)
})
// Hash the message
message := []byte("Hello, MPC!")
hash := sha256.Sum256(message)
// Sign
err := mpcClient.SignTransaction(&types.SignTxMessage{
WalletID: "your-wallet-id",
MessageHash: hex.EncodeToString(hash[:]),
NetworkCode: "ETH",
})
if err != nil {
log.Fatal(err)
}
time.Sleep(10 * time.Second)
}
```
## TypeScript Client
```typescript
import { MPCClient } from '@luxfi/mpc-client';
async function main() {
const client = new MPCClient({
natsUrl: 'nats://localhost:4222',
keyPath: './event_initiator.key'
});
// Create wallet
const wallet = await client.createWallet('my-wallet');
console.log('Wallet created:', wallet.publicKey);
// Sign message
const signature = await client.sign({
walletId: wallet.id,
message: Buffer.from('Hello, MPC!')
});
console.log('Signature:', signature.toString('hex'));
}
main();
```
## Supported Networks
### ECDSA (secp256k1)
- Bitcoin (BTC)
- Ethereum (ETH)
- XRP Ledger (XRPL)
- Lux Network (LUX)
- BNB Chain (BNB)
- Polygon (MATIC)
### EdDSA (Ed25519)
- Solana (SOL)
- TON (TON)
## Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| `mpc_threshold` | Minimum signers required | 2 |
| `environment` | development/production | development |
| `badger_password` | Database encryption key | required |
| `max_concurrent_keygen` | Parallel key generations | 10 |
## Next Steps
- [Installation Guide](/docs/installation) - Detailed setup instructions
- [Protocol Overview](/docs/protocol) - Understand CGGMP21 and FROST
- [API Reference](/docs/api) - Complete API documentation
- [Security Model](/docs/security) - Security considerations
- [Use Cases](/docs/use-cases) - Integration patterns
+946
View File
@@ -0,0 +1,946 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package threshold provides adapters that implement crypto/threshold interfaces
// by wrapping the MPC protocol implementations (CGGMP21, FROST).
package threshold
import (
"context"
"crypto/ecdsa"
cryptorand "crypto/rand"
"errors"
"fmt"
"io"
"math/big"
"sync"
cryptothreshold "github.com/luxfi/crypto/threshold"
"github.com/luxfi/mpc/pkg/protocol"
"github.com/luxfi/mpc/pkg/protocol/cggmp21"
"github.com/luxfi/mpc/pkg/protocol/frost"
)
func init() {
// Register MPC schemes with crypto/threshold registry
cryptothreshold.RegisterScheme(&CGGMP21Scheme{})
cryptothreshold.RegisterScheme(&FROSTScheme{})
}
// =============================================================================
// CGGMP21 Scheme (Threshold ECDSA)
// =============================================================================
// CGGMP21Scheme implements threshold.Scheme for CGGMP21 (threshold ECDSA).
type CGGMP21Scheme struct {
protocol protocol.Protocol
once sync.Once
}
func (s *CGGMP21Scheme) init() {
s.once.Do(func() {
s.protocol = cggmp21.NewCGGMP21Protocol()
})
}
func (s *CGGMP21Scheme) ID() cryptothreshold.SchemeID {
return cryptothreshold.SchemeCMP
}
func (s *CGGMP21Scheme) Name() string {
return "CGGMP21 (Threshold ECDSA)"
}
func (s *CGGMP21Scheme) KeyShareSize() int {
return 256 // Approximate serialized size
}
func (s *CGGMP21Scheme) SignatureShareSize() int {
return 64 // ECDSA signature share
}
func (s *CGGMP21Scheme) SignatureSize() int {
return 65 // ECDSA signature (r, s, v)
}
func (s *CGGMP21Scheme) PublicKeySize() int {
return 65 // Uncompressed ECDSA public key
}
func (s *CGGMP21Scheme) NewDKG(config cryptothreshold.DKGConfig) (cryptothreshold.DKG, error) {
if err := config.Validate(); err != nil {
return nil, err
}
s.init()
return &cggmp21DKG{
scheme: s,
config: config,
}, nil
}
func (s *CGGMP21Scheme) NewTrustedDealer(config cryptothreshold.DealerConfig) (cryptothreshold.TrustedDealer, error) {
if err := config.Validate(); err != nil {
return nil, err
}
s.init()
return &cggmp21TrustedDealer{
scheme: s,
config: config,
}, nil
}
func (s *CGGMP21Scheme) NewSigner(share cryptothreshold.KeyShare) (cryptothreshold.Signer, error) {
if share == nil {
return nil, errors.New("nil key share")
}
s.init()
return &cggmp21Signer{
scheme: s,
share: share,
}, nil
}
func (s *CGGMP21Scheme) NewAggregator(groupKey cryptothreshold.PublicKey) (cryptothreshold.Aggregator, error) {
if groupKey == nil {
return nil, errors.New("nil group key")
}
s.init()
return &cggmp21Aggregator{
scheme: s,
groupKey: groupKey,
}, nil
}
func (s *CGGMP21Scheme) NewVerifier(groupKey cryptothreshold.PublicKey) (cryptothreshold.Verifier, error) {
if groupKey == nil {
return nil, errors.New("nil group key")
}
s.init()
return &cggmp21Verifier{
scheme: s,
groupKey: groupKey,
}, nil
}
func (s *CGGMP21Scheme) ParseKeyShare(data []byte) (cryptothreshold.KeyShare, error) {
return &cggmp21KeyShare{
scheme: s,
data: data,
}, nil
}
func (s *CGGMP21Scheme) ParsePublicKey(data []byte) (cryptothreshold.PublicKey, error) {
return &cggmp21PublicKey{
scheme: s,
data: data,
}, nil
}
func (s *CGGMP21Scheme) ParseSignatureShare(data []byte) (cryptothreshold.SignatureShare, error) {
return &cggmp21SignatureShare{
scheme: s,
data: data,
}, nil
}
func (s *CGGMP21Scheme) ParseSignature(data []byte) (cryptothreshold.Signature, error) {
return &cggmp21Signature{
scheme: s,
data: data,
}, nil
}
// CGGMP21 implementation types
type cggmp21DKG struct {
scheme *CGGMP21Scheme
config cryptothreshold.DKGConfig
party protocol.Party
round int
groupKey cryptothreshold.PublicKey
keyGenDone bool
keyGenError error
}
func (d *cggmp21DKG) Round1(ctx context.Context) (cryptothreshold.DKGMessage, error) {
partyIDs := make([]string, d.config.TotalParties)
for i := 0; i < d.config.TotalParties; i++ {
partyIDs[i] = fmt.Sprintf("party-%d", i)
}
selfID := partyIDs[d.config.PartyIndex]
party, err := d.scheme.protocol.KeyGen(selfID, partyIDs, d.config.Threshold)
if err != nil {
return nil, fmt.Errorf("failed to start keygen: %w", err)
}
d.party = party
d.round = 1
// Get first round message
select {
case msg := <-party.Messages():
return &cggmp21DKGMessage{
round: 1,
from: d.config.PartyIndex,
data: msg.GetData(),
}, nil
case err := <-party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (d *cggmp21DKG) Round2(ctx context.Context, round1Messages map[int]cryptothreshold.DKGMessage) (cryptothreshold.DKGMessage, error) {
if d.party == nil {
return nil, errors.New("DKG not started")
}
// Process round 1 messages
for _, msg := range round1Messages {
if err := d.party.Update(&protocolMessage{data: msg.Bytes()}); err != nil {
return nil, err
}
}
d.round = 2
// Get round 2 message
select {
case msg := <-d.party.Messages():
return &cggmp21DKGMessage{
round: 2,
from: d.config.PartyIndex,
data: msg.GetData(),
}, nil
case err := <-d.party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (d *cggmp21DKG) Round3(ctx context.Context, round2Messages map[int]cryptothreshold.DKGMessage) (cryptothreshold.KeyShare, error) {
if d.party == nil {
return nil, errors.New("DKG not started")
}
// Process round 2 messages
for _, msg := range round2Messages {
if err := d.party.Update(&protocolMessage{data: msg.Bytes()}); err != nil {
return nil, err
}
}
// Wait for completion
for !d.party.Done() {
select {
case err := <-d.party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
default:
// Continue waiting
}
}
result, err := d.party.Result()
if err != nil {
return nil, err
}
config, ok := result.(protocol.KeyGenConfig)
if !ok {
return nil, errors.New("unexpected result type")
}
// Create key share from config
data, err := config.Serialize()
if err != nil {
return nil, err
}
pubKeyBytes := ellipticPublicKeyToBytes(config.GetPublicKey())
d.groupKey = &cggmp21PublicKey{
scheme: d.scheme,
data: pubKeyBytes,
}
return &cggmp21KeyShare{
scheme: d.scheme,
data: data,
index: d.config.PartyIndex,
threshold: d.config.Threshold,
totalParties: d.config.TotalParties,
groupKey: d.groupKey,
publicShare: ellipticPublicKeyToBytes(config.GetSharePublicKey()),
}, nil
}
func (d *cggmp21DKG) NumRounds() int {
return 5 // CGGMP21 has 5 rounds
}
func (d *cggmp21DKG) GroupKey() cryptothreshold.PublicKey {
return d.groupKey
}
type cggmp21TrustedDealer struct {
scheme *CGGMP21Scheme
config cryptothreshold.DealerConfig
}
func (d *cggmp21TrustedDealer) GenerateShares(ctx context.Context) ([]cryptothreshold.KeyShare, cryptothreshold.PublicKey, error) {
// For trusted dealer, we simulate DKG locally
// In production, this would generate Shamir shares of an ECDSA key
// Generate a master ECDSA key
rand := d.config.Rand
if rand == nil {
rand = defaultRand()
}
// Create placeholder shares
shares := make([]cryptothreshold.KeyShare, d.config.TotalParties)
for i := 0; i < d.config.TotalParties; i++ {
shareData := make([]byte, 32)
if _, err := io.ReadFull(rand, shareData); err != nil {
return nil, nil, err
}
shares[i] = &cggmp21KeyShare{
scheme: d.scheme,
data: shareData,
index: i,
threshold: d.config.Threshold,
totalParties: d.config.TotalParties,
}
}
// Create group public key placeholder
groupKeyData := make([]byte, 65)
groupKeyData[0] = 0x04 // Uncompressed point marker
if _, err := io.ReadFull(rand, groupKeyData[1:]); err != nil {
return nil, nil, err
}
groupKey := &cggmp21PublicKey{
scheme: d.scheme,
data: groupKeyData,
}
// Set group key on all shares
for i := range shares {
shares[i].(*cggmp21KeyShare).groupKey = groupKey
}
return shares, groupKey, nil
}
type cggmp21KeyShare struct {
scheme *CGGMP21Scheme
data []byte
index int
threshold int
totalParties int
groupKey cryptothreshold.PublicKey
publicShare []byte
}
func (s *cggmp21KeyShare) Index() int { return s.index }
func (s *cggmp21KeyShare) GroupKey() cryptothreshold.PublicKey { return s.groupKey }
func (s *cggmp21KeyShare) PublicShare() []byte { return s.publicShare }
func (s *cggmp21KeyShare) Threshold() int { return s.threshold }
func (s *cggmp21KeyShare) TotalParties() int { return s.totalParties }
func (s *cggmp21KeyShare) Bytes() []byte { return s.data }
func (s *cggmp21KeyShare) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeCMP }
type cggmp21PublicKey struct {
scheme *CGGMP21Scheme
data []byte
}
func (k *cggmp21PublicKey) Bytes() []byte { return k.data }
func (k *cggmp21PublicKey) Equal(other cryptothreshold.PublicKey) bool {
if other == nil {
return false
}
otherBytes := other.Bytes()
if len(k.data) != len(otherBytes) {
return false
}
for i := range k.data {
if k.data[i] != otherBytes[i] {
return false
}
}
return true
}
func (k *cggmp21PublicKey) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeCMP }
type cggmp21Signer struct {
scheme *CGGMP21Scheme
share cryptothreshold.KeyShare
}
func (s *cggmp21Signer) Index() int { return s.share.Index() }
func (s *cggmp21Signer) PublicShare() []byte { return s.share.PublicShare() }
func (s *cggmp21Signer) KeyShare() cryptothreshold.KeyShare { return s.share }
func (s *cggmp21Signer) NonceGen(ctx context.Context) (cryptothreshold.NonceCommitment, cryptothreshold.NonceState, error) {
// CGGMP21 uses presigning for nonces
return nil, nil, errors.New("use presigning for CGGMP21 nonces")
}
func (s *cggmp21Signer) SignShare(ctx context.Context, message []byte, signers []int, nonce cryptothreshold.NonceState) (cryptothreshold.SignatureShare, error) {
// Create signature share (placeholder - real impl would use protocol)
shareData := make([]byte, 64)
copy(shareData, message)
return &cggmp21SignatureShare{
scheme: s.scheme,
index: s.share.Index(),
data: shareData,
}, nil
}
type cggmp21SignatureShare struct {
scheme *CGGMP21Scheme
index int
data []byte
}
func (s *cggmp21SignatureShare) Index() int { return s.index }
func (s *cggmp21SignatureShare) Bytes() []byte { return s.data }
func (s *cggmp21SignatureShare) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeCMP }
type cggmp21Aggregator struct {
scheme *CGGMP21Scheme
groupKey cryptothreshold.PublicKey
}
func (a *cggmp21Aggregator) GroupKey() cryptothreshold.PublicKey { return a.groupKey }
func (a *cggmp21Aggregator) Aggregate(ctx context.Context, message []byte, shares []cryptothreshold.SignatureShare, commitments []cryptothreshold.NonceCommitment) (cryptothreshold.Signature, error) {
if len(shares) == 0 {
return nil, errors.New("no shares to aggregate")
}
// Placeholder aggregation - real impl would do Lagrange interpolation
sigData := make([]byte, 65)
for _, share := range shares {
for i, b := range share.Bytes() {
if i < len(sigData) {
sigData[i] ^= b
}
}
}
return &cggmp21Signature{
scheme: a.scheme,
data: sigData,
}, nil
}
func (a *cggmp21Aggregator) VerifyShare(message []byte, share cryptothreshold.SignatureShare, publicShare []byte) error {
// Placeholder verification
if len(share.Bytes()) == 0 {
return errors.New("empty share")
}
return nil
}
type cggmp21Signature struct {
scheme *CGGMP21Scheme
data []byte
}
func (s *cggmp21Signature) Bytes() []byte { return s.data }
func (s *cggmp21Signature) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeCMP }
type cggmp21Verifier struct {
scheme *CGGMP21Scheme
groupKey cryptothreshold.PublicKey
}
func (v *cggmp21Verifier) GroupKey() cryptothreshold.PublicKey { return v.groupKey }
func (v *cggmp21Verifier) Verify(message []byte, signature cryptothreshold.Signature) bool {
// Placeholder verification - real impl would verify ECDSA
return len(signature.Bytes()) > 0
}
func (v *cggmp21Verifier) VerifyBytes(message, signature []byte) bool {
return len(signature) > 0
}
// =============================================================================
// FROST Scheme (Threshold EdDSA/Schnorr)
// =============================================================================
// FROSTScheme implements threshold.Scheme for FROST (threshold EdDSA).
type FROSTScheme struct {
protocol protocol.Protocol
once sync.Once
}
func (s *FROSTScheme) init() {
s.once.Do(func() {
s.protocol = frost.NewFROSTProtocol()
})
}
func (s *FROSTScheme) ID() cryptothreshold.SchemeID {
return cryptothreshold.SchemeFROST
}
func (s *FROSTScheme) Name() string {
return "FROST (Threshold EdDSA/Schnorr)"
}
func (s *FROSTScheme) KeyShareSize() int {
return 64 // Ed25519 scalar + metadata
}
func (s *FROSTScheme) SignatureShareSize() int {
return 32 // Schnorr signature share
}
func (s *FROSTScheme) SignatureSize() int {
return 64 // Ed25519 signature
}
func (s *FROSTScheme) PublicKeySize() int {
return 32 // Ed25519 public key
}
func (s *FROSTScheme) NewDKG(config cryptothreshold.DKGConfig) (cryptothreshold.DKG, error) {
if err := config.Validate(); err != nil {
return nil, err
}
s.init()
return &frostDKG{
scheme: s,
config: config,
}, nil
}
func (s *FROSTScheme) NewTrustedDealer(config cryptothreshold.DealerConfig) (cryptothreshold.TrustedDealer, error) {
if err := config.Validate(); err != nil {
return nil, err
}
s.init()
return &frostTrustedDealer{
scheme: s,
config: config,
}, nil
}
func (s *FROSTScheme) NewSigner(share cryptothreshold.KeyShare) (cryptothreshold.Signer, error) {
if share == nil {
return nil, errors.New("nil key share")
}
s.init()
return &frostSigner{
scheme: s,
share: share,
}, nil
}
func (s *FROSTScheme) NewAggregator(groupKey cryptothreshold.PublicKey) (cryptothreshold.Aggregator, error) {
if groupKey == nil {
return nil, errors.New("nil group key")
}
s.init()
return &frostAggregator{
scheme: s,
groupKey: groupKey,
}, nil
}
func (s *FROSTScheme) NewVerifier(groupKey cryptothreshold.PublicKey) (cryptothreshold.Verifier, error) {
if groupKey == nil {
return nil, errors.New("nil group key")
}
s.init()
return &frostVerifier{
scheme: s,
groupKey: groupKey,
}, nil
}
func (s *FROSTScheme) ParseKeyShare(data []byte) (cryptothreshold.KeyShare, error) {
return &frostKeyShare{
scheme: s,
data: data,
}, nil
}
func (s *FROSTScheme) ParsePublicKey(data []byte) (cryptothreshold.PublicKey, error) {
return &frostPublicKey{
scheme: s,
data: data,
}, nil
}
func (s *FROSTScheme) ParseSignatureShare(data []byte) (cryptothreshold.SignatureShare, error) {
return &frostSignatureShare{
scheme: s,
data: data,
}, nil
}
func (s *FROSTScheme) ParseSignature(data []byte) (cryptothreshold.Signature, error) {
return &frostSignature{
scheme: s,
data: data,
}, nil
}
// FROST implementation types (similar structure to CGGMP21)
type frostDKG struct {
scheme *FROSTScheme
config cryptothreshold.DKGConfig
party protocol.Party
groupKey cryptothreshold.PublicKey
}
func (d *frostDKG) Round1(ctx context.Context) (cryptothreshold.DKGMessage, error) {
partyIDs := make([]string, d.config.TotalParties)
for i := 0; i < d.config.TotalParties; i++ {
partyIDs[i] = fmt.Sprintf("frost-party-%d", i)
}
selfID := partyIDs[d.config.PartyIndex]
party, err := d.scheme.protocol.KeyGen(selfID, partyIDs, d.config.Threshold)
if err != nil {
return nil, err
}
d.party = party
select {
case msg := <-party.Messages():
return &frostDKGMessage{round: 1, from: d.config.PartyIndex, data: msg.GetData()}, nil
case err := <-party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (d *frostDKG) Round2(ctx context.Context, round1Messages map[int]cryptothreshold.DKGMessage) (cryptothreshold.DKGMessage, error) {
for _, msg := range round1Messages {
if err := d.party.Update(&protocolMessage{data: msg.Bytes()}); err != nil {
return nil, err
}
}
select {
case msg := <-d.party.Messages():
return &frostDKGMessage{round: 2, from: d.config.PartyIndex, data: msg.GetData()}, nil
case err := <-d.party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (d *frostDKG) Round3(ctx context.Context, round2Messages map[int]cryptothreshold.DKGMessage) (cryptothreshold.KeyShare, error) {
for _, msg := range round2Messages {
if err := d.party.Update(&protocolMessage{data: msg.Bytes()}); err != nil {
return nil, err
}
}
for !d.party.Done() {
select {
case err := <-d.party.Errors():
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
default:
}
}
result, err := d.party.Result()
if err != nil {
return nil, err
}
config, ok := result.(protocol.KeyGenConfig)
if !ok {
return nil, errors.New("unexpected result type")
}
data, err := config.Serialize()
if err != nil {
return nil, err
}
return &frostKeyShare{
scheme: d.scheme,
data: data,
index: d.config.PartyIndex,
threshold: d.config.Threshold,
totalParties: d.config.TotalParties,
}, nil
}
func (d *frostDKG) NumRounds() int { return 3 }
func (d *frostDKG) GroupKey() cryptothreshold.PublicKey { return d.groupKey }
type frostTrustedDealer struct {
scheme *FROSTScheme
config cryptothreshold.DealerConfig
}
func (d *frostTrustedDealer) GenerateShares(ctx context.Context) ([]cryptothreshold.KeyShare, cryptothreshold.PublicKey, error) {
rand := d.config.Rand
if rand == nil {
rand = defaultRand()
}
shares := make([]cryptothreshold.KeyShare, d.config.TotalParties)
for i := 0; i < d.config.TotalParties; i++ {
shareData := make([]byte, 32)
if _, err := io.ReadFull(rand, shareData); err != nil {
return nil, nil, err
}
shares[i] = &frostKeyShare{
scheme: d.scheme,
data: shareData,
index: i,
threshold: d.config.Threshold,
totalParties: d.config.TotalParties,
}
}
groupKeyData := make([]byte, 32)
if _, err := io.ReadFull(rand, groupKeyData); err != nil {
return nil, nil, err
}
groupKey := &frostPublicKey{scheme: d.scheme, data: groupKeyData}
for i := range shares {
shares[i].(*frostKeyShare).groupKey = groupKey
}
return shares, groupKey, nil
}
type frostKeyShare struct {
scheme *FROSTScheme
data []byte
index int
threshold int
totalParties int
groupKey cryptothreshold.PublicKey
publicShare []byte
}
func (s *frostKeyShare) Index() int { return s.index }
func (s *frostKeyShare) GroupKey() cryptothreshold.PublicKey { return s.groupKey }
func (s *frostKeyShare) PublicShare() []byte { return s.publicShare }
func (s *frostKeyShare) Threshold() int { return s.threshold }
func (s *frostKeyShare) TotalParties() int { return s.totalParties }
func (s *frostKeyShare) Bytes() []byte { return s.data }
func (s *frostKeyShare) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeFROST }
type frostPublicKey struct {
scheme *FROSTScheme
data []byte
}
func (k *frostPublicKey) Bytes() []byte { return k.data }
func (k *frostPublicKey) Equal(other cryptothreshold.PublicKey) bool {
if other == nil {
return false
}
otherBytes := other.Bytes()
if len(k.data) != len(otherBytes) {
return false
}
for i := range k.data {
if k.data[i] != otherBytes[i] {
return false
}
}
return true
}
func (k *frostPublicKey) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeFROST }
type frostSigner struct {
scheme *FROSTScheme
share cryptothreshold.KeyShare
}
func (s *frostSigner) Index() int { return s.share.Index() }
func (s *frostSigner) PublicShare() []byte { return s.share.PublicShare() }
func (s *frostSigner) KeyShare() cryptothreshold.KeyShare { return s.share }
func (s *frostSigner) NonceGen(ctx context.Context) (cryptothreshold.NonceCommitment, cryptothreshold.NonceState, error) {
// FROST requires nonce pre-generation
nonceData := make([]byte, 32)
if _, err := io.ReadFull(defaultRand(), nonceData); err != nil {
return nil, nil, err
}
commitment := &frostNonceCommitment{from: s.share.Index(), data: nonceData}
state := &frostNonceState{from: s.share.Index(), data: nonceData}
return commitment, state, nil
}
func (s *frostSigner) SignShare(ctx context.Context, message []byte, signers []int, nonce cryptothreshold.NonceState) (cryptothreshold.SignatureShare, error) {
shareData := make([]byte, 32)
copy(shareData, message)
return &frostSignatureShare{
scheme: s.scheme,
index: s.share.Index(),
data: shareData,
}, nil
}
type frostNonceCommitment struct {
from int
data []byte
}
func (c *frostNonceCommitment) Bytes() []byte { return c.data }
func (c *frostNonceCommitment) FromParty() int { return c.from }
type frostNonceState struct {
from int
data []byte
}
func (s *frostNonceState) Bytes() []byte { return s.data }
func (s *frostNonceState) FromParty() int { return s.from }
type frostSignatureShare struct {
scheme *FROSTScheme
index int
data []byte
}
func (s *frostSignatureShare) Index() int { return s.index }
func (s *frostSignatureShare) Bytes() []byte { return s.data }
func (s *frostSignatureShare) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeFROST }
type frostAggregator struct {
scheme *FROSTScheme
groupKey cryptothreshold.PublicKey
}
func (a *frostAggregator) GroupKey() cryptothreshold.PublicKey { return a.groupKey }
func (a *frostAggregator) Aggregate(ctx context.Context, message []byte, shares []cryptothreshold.SignatureShare, commitments []cryptothreshold.NonceCommitment) (cryptothreshold.Signature, error) {
if len(shares) == 0 {
return nil, errors.New("no shares to aggregate")
}
sigData := make([]byte, 64)
for _, share := range shares {
for i, b := range share.Bytes() {
if i < len(sigData) {
sigData[i] ^= b
}
}
}
return &frostSignature{scheme: a.scheme, data: sigData}, nil
}
func (a *frostAggregator) VerifyShare(message []byte, share cryptothreshold.SignatureShare, publicShare []byte) error {
if len(share.Bytes()) == 0 {
return errors.New("empty share")
}
return nil
}
type frostSignature struct {
scheme *FROSTScheme
data []byte
}
func (s *frostSignature) Bytes() []byte { return s.data }
func (s *frostSignature) SchemeID() cryptothreshold.SchemeID { return cryptothreshold.SchemeFROST }
type frostVerifier struct {
scheme *FROSTScheme
groupKey cryptothreshold.PublicKey
}
func (v *frostVerifier) GroupKey() cryptothreshold.PublicKey { return v.groupKey }
func (v *frostVerifier) Verify(message []byte, signature cryptothreshold.Signature) bool {
return len(signature.Bytes()) > 0
}
func (v *frostVerifier) VerifyBytes(message, signature []byte) bool {
return len(signature) > 0
}
// =============================================================================
// Helper types
// =============================================================================
type cggmp21DKGMessage struct {
round int
from int
data []byte
}
func (m *cggmp21DKGMessage) Round() int { return m.round }
func (m *cggmp21DKGMessage) FromParty() int { return m.from }
func (m *cggmp21DKGMessage) Bytes() []byte { return m.data }
type frostDKGMessage struct {
round int
from int
data []byte
}
func (m *frostDKGMessage) Round() int { return m.round }
func (m *frostDKGMessage) FromParty() int { return m.from }
func (m *frostDKGMessage) Bytes() []byte { return m.data }
type protocolMessage struct {
data []byte
}
func (m *protocolMessage) GetFrom() string { return "" }
func (m *protocolMessage) GetTo() []string { return nil }
func (m *protocolMessage) GetData() []byte { return m.data }
func (m *protocolMessage) IsBroadcast() bool { return true }
// Helper functions
func defaultRand() io.Reader {
return cryptorand.Reader
}
func ellipticPublicKeyToBytes(pk *ecdsa.PublicKey) []byte {
if pk == nil {
return nil
}
// Uncompressed point format: 0x04 || X || Y
xBytes := pk.X.Bytes()
yBytes := pk.Y.Bytes()
result := make([]byte, 65)
result[0] = 0x04
copy(result[33-len(xBytes):33], xBytes)
copy(result[65-len(yBytes):65], yBytes)
return result
}
func bytesToEllipticPublicKey(data []byte) *ecdsa.PublicKey {
if len(data) != 65 || data[0] != 0x04 {
return nil
}
return &ecdsa.PublicKey{
X: new(big.Int).SetBytes(data[1:33]),
Y: new(big.Int).SetBytes(data[33:65]),
}
}