refactor(bridge): remove dead custodial lock-mint gateway + MPC signer

The bridge package carried a full custodial lock-mint cross-chain bridge
(BridgeGateway: liquidity pools, daily limits, a 2/3 signer set) and an
in-memory MPC attestation layer (BridgeSigner) — neither wired to any
precompile address or Run dispatch, both guarding fund movement with STUB
verifiers (verifySignature / VerifyThresholdSignature returned
len(signature) > 0, accepting any non-empty bytes).

Cross-chain value movement is now the sole responsibility of the HTLC
atomic-swap precompile LP-90A0 (swap/), where every payout is backed by a
real transferFrom-observed lock and released only on a SHA-256 preimage or
timeout — non-custody is structural, not policy. Keeping a second, dormant
custodial path violated 'exactly one way' and left a mint surface that
could later be wired behind a fake verifier.

Removes gateway.go, signer.go, types.go and their tests; bridge/ is now a
pure chain registry (registrar precompile, registry rows — no fund
movement). No reachable code path changes: the deleted machinery was
depended on by nothing outside its own files (verified by build + grep).

go vet + go test -race ./bridge/... green; registry LP-90A0 intact.
This commit is contained in:
2026-06-26 04:04:45 +00:00
parent 01305d5a50
commit bb88bae915
5 changed files with 0 additions and 1834 deletions
-557
View File
@@ -1,557 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bridge
import (
"crypto/sha256"
"errors"
"math/big"
"sync"
"time"
"github.com/luxfi/geth/common"
)
// BridgeGateway is the main bridge interface for cross-chain transfers
// This precompile at 0x0440 handles bridge initiation and completion
type BridgeGateway struct {
// State
Requests map[[32]byte]*BridgeRequest
Nonces map[common.Address]uint64 // Per-address nonces
SupportedTokens map[common.Address]*BridgedToken
SupportedChains map[uint32]bool
// Liquidity pools
Pools map[uint32]map[common.Address]*LiquidityPool // ChainID -> Token -> Pool
// Signer set (interface to B-Chain)
SignerSet *SignerSet
// Configuration
Config *BridgeFeeConfig
Enabled bool
Paused bool
mu sync.RWMutex
}
// NewBridgeGateway creates a new bridge gateway
func NewBridgeGateway() *BridgeGateway {
gw := &BridgeGateway{
Requests: make(map[[32]byte]*BridgeRequest),
Nonces: make(map[common.Address]uint64),
SupportedTokens: make(map[common.Address]*BridgedToken),
SupportedChains: make(map[uint32]bool),
Pools: make(map[uint32]map[common.Address]*LiquidityPool),
SignerSet: &SignerSet{
Signers: make([]*SignerInfo, 0),
Waitlist: make([][20]byte, 0),
Threshold: 67, // 2/3 default
},
Config: &BridgeFeeConfig{
BaseFee: big.NewInt(1e15), // 0.001 token
PercentFee: 30, // 0.3%
MinFee: big.NewInt(1e15), // 0.001 token
MaxFee: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100)), // 100 tokens
LiquidityFee: 20, // 0.2% to LPs
ProtocolFee: 10, // 0.1% protocol
},
Enabled: true,
Paused: false,
}
// Initialize supported chains
gw.initSupportedChains()
return gw
}
// NewBridgeGatewayWithRegistry constructs a gateway whose supported-chain set
// is sourced from r. Used in tests and on networks that want to pin the set to
// a curated list rather than the hard-coded ecosystem defaults.
//
// The gateway's SupportedChains map mirrors r.All() at construction time.
// Live updates to r are not reflected; callers that need dynamic membership
// should use a state-backed registry behind the precompile entry point.
func NewBridgeGatewayWithRegistry(r Registry) *BridgeGateway {
gw := &BridgeGateway{
Requests: make(map[[32]byte]*BridgeRequest),
Nonces: make(map[common.Address]uint64),
SupportedTokens: make(map[common.Address]*BridgedToken),
SupportedChains: make(map[uint32]bool),
Pools: make(map[uint32]map[common.Address]*LiquidityPool),
SignerSet: &SignerSet{
Signers: make([]*SignerInfo, 0),
Waitlist: make([][20]byte, 0),
Threshold: 67,
},
Config: &BridgeFeeConfig{
BaseFee: big.NewInt(1e15),
PercentFee: 30,
MinFee: big.NewInt(1e15),
MaxFee: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100)),
LiquidityFee: 20,
ProtocolFee: 10,
},
Enabled: true,
Paused: false,
}
for _, c := range r.All() {
gw.SupportedChains[uint32(c.ID)] = true
}
return gw
}
// Supports reports whether id is in the gateway's supported-chain set.
func (gw *BridgeGateway) Supports(id uint32) bool {
gw.mu.RLock()
defer gw.mu.RUnlock()
return gw.SupportedChains[id]
}
func (gw *BridgeGateway) initSupportedChains() {
// Lux ecosystem
gw.SupportedChains[ChainLux] = true
gw.SupportedChains[ChainLuxTest] = true
gw.SupportedChains[ChainHanzo] = true
gw.SupportedChains[ChainHanzoTest] = true
gw.SupportedChains[ChainZoo] = true
gw.SupportedChains[ChainZooTest] = true
gw.SupportedChains[ChainSPC] = true
gw.SupportedChains[ChainSPCTest] = true
// External chains
gw.SupportedChains[ChainEthereum] = true
gw.SupportedChains[ChainArbitrum] = true
gw.SupportedChains[ChainOptimism] = true
gw.SupportedChains[ChainBase] = true
gw.SupportedChains[ChainPolygon] = true
gw.SupportedChains[ChainBSC] = true
gw.SupportedChains[ChainAvalanche] = true
}
// InitiateBridge starts a cross-chain transfer
func (gw *BridgeGateway) InitiateBridge(
sender common.Address,
recipient common.Address,
token common.Address,
amount *big.Int,
sourceChain uint32,
destChain uint32,
deadline uint64,
data []byte,
) (*BridgeRequest, error) {
gw.mu.Lock()
defer gw.mu.Unlock()
// Validations
if !gw.Enabled || gw.Paused {
return nil, ErrBridgeDisabled
}
if !gw.SupportedChains[destChain] {
return nil, ErrChainNotSupported
}
// Check token is supported
tokenInfo := gw.SupportedTokens[token]
if tokenInfo == nil || !tokenInfo.Enabled {
return nil, ErrTokenNotSupported
}
// Check amount limits
if amount.Cmp(tokenInfo.MinBridge) < 0 {
return nil, ErrAmountTooLow
}
if tokenInfo.MaxBridge.Sign() > 0 && amount.Cmp(tokenInfo.MaxBridge) > 0 {
return nil, ErrAmountTooHigh
}
// Check daily limit
gw.resetDailyLimitIfNeeded(tokenInfo)
newTotal := new(big.Int).Add(tokenInfo.BridgedToday, amount)
if tokenInfo.DailyLimit.Sign() > 0 && newTotal.Cmp(tokenInfo.DailyLimit) > 0 {
return nil, ErrDailyLimitExceeded
}
// Check liquidity on destination chain
pool := gw.getPool(destChain, token)
if pool == nil || pool.Available.Cmp(amount) < 0 {
return nil, ErrInsufficientLiquidity
}
// Generate request ID
nonce := gw.Nonces[sender]
gw.Nonces[sender] = nonce + 1
requestID := gw.generateRequestID(sender, recipient, token, amount, sourceChain, destChain, nonce)
// Calculate fee
fee := gw.calculateFee(amount)
// Create request
request := &BridgeRequest{
ID: requestID,
Sender: sender,
Recipient: recipient,
Token: token,
Amount: new(big.Int).Sub(amount, fee), // Amount after fee
SourceChain: sourceChain,
DestChain: destChain,
Nonce: nonce,
Deadline: deadline,
Data: data,
Status: StatusPending,
Signatures: make([][]byte, 0),
CreatedAt: uint64(time.Now().Unix()),
}
// Update state
gw.Requests[requestID] = request
tokenInfo.BridgedToday.Add(tokenInfo.BridgedToday, amount)
// Reserve liquidity on destination
pool.Available.Sub(pool.Available, request.Amount)
return request, nil
}
// CompleteBridge completes a bridge on the destination chain
func (gw *BridgeGateway) CompleteBridge(
requestID [32]byte,
signatures [][]byte,
) error {
gw.mu.Lock()
defer gw.mu.Unlock()
request := gw.Requests[requestID]
if request == nil {
return ErrRequestNotFound
}
if request.Status == StatusCompleted {
return ErrRequestAlreadyDone
}
if request.Deadline > 0 && uint64(time.Now().Unix()) > request.Deadline {
request.Status = StatusExpired
return ErrRequestExpired
}
// Verify signatures meet threshold
if uint32(len(signatures)) < gw.getThreshold() {
return ErrSignatureThreshold
}
// Verify each signature
message := gw.encodeMessage(request)
for _, sig := range signatures {
if !gw.verifySignature(message, sig) {
return ErrInvalidSignature
}
}
// Mark completed
request.Status = StatusCompleted
request.Signatures = signatures
request.CompletedAt = uint64(time.Now().Unix())
return nil
}
// GetRequest returns a bridge request by ID
func (gw *BridgeGateway) GetRequest(requestID [32]byte) (*BridgeRequest, error) {
gw.mu.RLock()
defer gw.mu.RUnlock()
request := gw.Requests[requestID]
if request == nil {
return nil, ErrRequestNotFound
}
return request, nil
}
// RefundExpired refunds an expired bridge request
func (gw *BridgeGateway) RefundExpired(requestID [32]byte) error {
gw.mu.Lock()
defer gw.mu.Unlock()
request := gw.Requests[requestID]
if request == nil {
return ErrRequestNotFound
}
if request.Status == StatusCompleted || request.Status == StatusRefunded {
return ErrRequestAlreadyDone
}
if request.Deadline > 0 && uint64(time.Now().Unix()) <= request.Deadline {
return errors.New("request not yet expired")
}
// Return liquidity to pool
pool := gw.getPool(request.DestChain, request.Token)
if pool != nil {
pool.Available.Add(pool.Available, request.Amount)
}
request.Status = StatusRefunded
return nil
}
// AddLiquidity adds liquidity to a bridge pool
func (gw *BridgeGateway) AddLiquidity(
provider common.Address,
token common.Address,
chainID uint32,
amount *big.Int,
) (*LPPosition, error) {
gw.mu.Lock()
defer gw.mu.Unlock()
if !gw.SupportedChains[chainID] {
return nil, ErrChainNotSupported
}
pool := gw.getOrCreatePool(chainID, token)
// Calculate share ratio
var shareRatio *big.Int
if pool.TotalLiq.Sign() == 0 {
shareRatio = new(big.Int).Mul(amount, big.NewInt(1e18))
} else {
shareRatio = new(big.Int).Mul(amount, big.NewInt(1e18))
shareRatio.Div(shareRatio, pool.TotalLiq)
}
// Update or create position
position := pool.Providers[provider]
if position == nil {
position = &LPPosition{
Provider: provider,
Amount: big.NewInt(0),
ShareRatio: big.NewInt(0),
DepositTime: uint64(time.Now().Unix()),
PendingFees: big.NewInt(0),
}
pool.Providers[provider] = position
}
position.Amount.Add(position.Amount, amount)
position.ShareRatio.Add(position.ShareRatio, shareRatio)
// Update pool totals
pool.TotalLiq.Add(pool.TotalLiq, amount)
pool.Available.Add(pool.Available, amount)
return position, nil
}
// RemoveLiquidity removes liquidity from a bridge pool
func (gw *BridgeGateway) RemoveLiquidity(
provider common.Address,
token common.Address,
chainID uint32,
amount *big.Int,
) (*big.Int, error) {
gw.mu.Lock()
defer gw.mu.Unlock()
pool := gw.getPool(chainID, token)
if pool == nil {
return nil, ErrInsufficientLiquidity
}
position := pool.Providers[provider]
if position == nil || position.Amount.Cmp(amount) < 0 {
return nil, ErrInsufficientLiquidity
}
if pool.Available.Cmp(amount) < 0 {
return nil, errors.New("liquidity currently in use")
}
// Calculate fees earned
fees := gw.calculateLPFees(pool, position)
// Update position
position.Amount.Sub(position.Amount, amount)
if position.Amount.Sign() == 0 {
delete(pool.Providers, provider)
}
// Update pool totals
pool.TotalLiq.Sub(pool.TotalLiq, amount)
pool.Available.Sub(pool.Available, amount)
// Return amount + fees
total := new(big.Int).Add(amount, fees)
return total, nil
}
// RegisterToken registers a new token for bridging
func (gw *BridgeGateway) RegisterToken(
localAddress common.Address,
decimals uint8,
symbol string,
name string,
minBridge *big.Int,
maxBridge *big.Int,
dailyLimit *big.Int,
) error {
gw.mu.Lock()
defer gw.mu.Unlock()
if gw.SupportedTokens[localAddress] != nil {
return errors.New("token already registered")
}
gw.SupportedTokens[localAddress] = &BridgedToken{
LocalAddress: localAddress,
RemoteAddress: make(map[uint32]common.Address),
Decimals: decimals,
Symbol: symbol,
Name: name,
MinBridge: minBridge,
MaxBridge: maxBridge,
DailyLimit: dailyLimit,
BridgedToday: big.NewInt(0),
LastReset: uint64(time.Now().Unix()),
Enabled: true,
}
return nil
}
// Helper functions
func (gw *BridgeGateway) generateRequestID(
sender, recipient common.Address,
token common.Address,
amount *big.Int,
sourceChain, destChain uint32,
nonce uint64,
) [32]byte {
data := append(sender.Bytes(), recipient.Bytes()...)
data = append(data, token.Bytes()...)
data = append(data, amount.Bytes()...)
data = append(data, big.NewInt(int64(sourceChain)).Bytes()...)
data = append(data, big.NewInt(int64(destChain)).Bytes()...)
data = append(data, big.NewInt(int64(nonce)).Bytes()...)
return sha256.Sum256(data)
}
func (gw *BridgeGateway) calculateFee(amount *big.Int) *big.Int {
// Percent fee
fee := new(big.Int).Mul(amount, big.NewInt(int64(gw.Config.PercentFee)))
fee.Div(fee, big.NewInt(10000))
// Add base fee
fee.Add(fee, gw.Config.BaseFee)
// Apply min/max
if fee.Cmp(gw.Config.MinFee) < 0 {
fee.Set(gw.Config.MinFee)
}
if gw.Config.MaxFee.Sign() > 0 && fee.Cmp(gw.Config.MaxFee) > 0 {
fee.Set(gw.Config.MaxFee)
}
return fee
}
func (gw *BridgeGateway) getPool(chainID uint32, token common.Address) *LiquidityPool {
chainPools := gw.Pools[chainID]
if chainPools == nil {
return nil
}
return chainPools[token]
}
func (gw *BridgeGateway) getOrCreatePool(chainID uint32, token common.Address) *LiquidityPool {
if gw.Pools[chainID] == nil {
gw.Pools[chainID] = make(map[common.Address]*LiquidityPool)
}
pool := gw.Pools[chainID][token]
if pool == nil {
pool = &LiquidityPool{
Token: token,
ChainID: chainID,
TotalLiq: big.NewInt(0),
Available: big.NewInt(0),
Providers: make(map[common.Address]*LPPosition),
FeeRate: gw.Config.LiquidityFee,
TotalFees: big.NewInt(0),
}
gw.Pools[chainID][token] = pool
}
return pool
}
func (gw *BridgeGateway) resetDailyLimitIfNeeded(token *BridgedToken) {
now := uint64(time.Now().Unix())
daySeconds := uint64(86400)
if now-token.LastReset >= daySeconds {
token.BridgedToday = big.NewInt(0)
token.LastReset = now
}
}
func (gw *BridgeGateway) getThreshold() uint32 {
if gw.SignerSet == nil || len(gw.SignerSet.Signers) == 0 {
return 1
}
// 2/3 + 1 of active signers
return (uint32(len(gw.SignerSet.Signers)) * 2 / 3) + 1
}
func (gw *BridgeGateway) encodeMessage(request *BridgeRequest) []byte {
msg := &BridgeMessage{
Version: 1,
MessageType: MsgTypeTransfer,
SourceChain: request.SourceChain,
DestChain: request.DestChain,
Nonce: request.Nonce,
Sender: request.Sender,
Recipient: request.Recipient,
Token: request.Token,
Amount: request.Amount,
Data: request.Data,
Timestamp: request.CreatedAt,
}
// Encode message (simplified - would use proper encoding)
data := []byte{msg.Version, msg.MessageType}
data = append(data, big.NewInt(int64(msg.SourceChain)).Bytes()...)
data = append(data, big.NewInt(int64(msg.DestChain)).Bytes()...)
data = append(data, big.NewInt(int64(msg.Nonce)).Bytes()...)
data = append(data, msg.Sender.Bytes()...)
data = append(data, msg.Recipient.Bytes()...)
data = append(data, msg.Token.Bytes()...)
data = append(data, msg.Amount.Bytes()...)
data = append(data, msg.Data...)
return data
}
func (gw *BridgeGateway) verifySignature(message []byte, signature []byte) bool {
// In production, verify against signer set public keys
// This would call into B-Chain's MPC verification
return len(signature) > 0
}
func (gw *BridgeGateway) calculateLPFees(pool *LiquidityPool, position *LPPosition) *big.Int {
if pool.TotalFees.Sign() == 0 || position.ShareRatio.Sign() == 0 {
return big.NewInt(0)
}
// fees = totalFees * shareRatio / 1e18
fees := new(big.Int).Mul(pool.TotalFees, position.ShareRatio)
fees.Div(fees, big.NewInt(1e18))
return fees
}
-655
View File
@@ -1,655 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bridge
import (
"math/big"
"testing"
"time"
"github.com/luxfi/geth/common"
)
// Helper functions for large big.Int values (avoid overflow in big.NewInt)
func bigExp(base, exp int64) *big.Int {
result := big.NewInt(1)
b := big.NewInt(base)
for range exp {
result.Mul(result, b)
}
return result
}
// e19 returns 10^19
func e19() *big.Int { return bigExp(10, 19) }
// e20 returns 10^20
func e20() *big.Int { return bigExp(10, 20) }
// e21 returns 10^21
func e21() *big.Int { return bigExp(10, 21) }
// e24 returns 10^24
func e24() *big.Int { return bigExp(10, 24) }
// e25 returns 10^25
func e25() *big.Int { return bigExp(10, 25) }
// e22 returns 10^22
func e22() *big.Int { return bigExp(10, 22) }
// e30 returns 10^30
func e30() *big.Int { return bigExp(10, 30) }
// fiveE19 returns 5 * 10^19
func fiveE19() *big.Int { return new(big.Int).Mul(big.NewInt(5), e19()) }
// TestNewBridgeGateway tests gateway creation
func TestNewBridgeGateway(t *testing.T) {
gw := NewBridgeGateway()
if gw == nil {
t.Fatal("Expected non-nil BridgeGateway")
}
if gw.Requests == nil {
t.Error("Expected Requests map to be initialized")
}
if gw.Nonces == nil {
t.Error("Expected Nonces map to be initialized")
}
if gw.SupportedTokens == nil {
t.Error("Expected SupportedTokens map to be initialized")
}
if gw.SupportedChains == nil {
t.Error("Expected SupportedChains map to be initialized")
}
if gw.Pools == nil {
t.Error("Expected Pools map to be initialized")
}
if gw.SignerSet == nil {
t.Error("Expected SignerSet to be initialized")
}
if gw.Config == nil {
t.Error("Expected Config to be initialized")
}
if !gw.Enabled {
t.Error("Expected gateway to be enabled")
}
}
// TestSupportedChains tests chain support initialization
func TestSupportedChains(t *testing.T) {
gw := NewBridgeGateway()
expectedChains := []uint32{
ChainLux, ChainLuxTest,
ChainHanzo, ChainHanzoTest,
ChainZoo, ChainZooTest,
ChainSPC, ChainSPCTest,
ChainEthereum, ChainArbitrum, ChainOptimism,
ChainBase, ChainPolygon, ChainBSC, ChainAvalanche,
}
for _, chainID := range expectedChains {
if !gw.SupportedChains[chainID] {
t.Errorf("Expected chain %d to be supported", chainID)
}
}
// Verify unsupported chain
if gw.SupportedChains[99999] {
t.Error("Chain 99999 should not be supported")
}
}
// TestRegisterToken tests token registration
func TestRegisterToken(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
err := gw.RegisterToken(
token,
18,
"TEST",
"Test Token",
big.NewInt(1e17), // min bridge
new(big.Int).Mul(big.NewInt(1e12), big.NewInt(1e12)), // max bridge (1e24)
new(big.Int).Mul(big.NewInt(1e13), big.NewInt(1e12)), // daily limit (1e25)
)
if err != nil {
t.Fatalf("RegisterToken failed: %v", err)
}
// Verify token was registered
tokenInfo := gw.SupportedTokens[token]
if tokenInfo == nil {
t.Fatal("Token not stored")
}
if tokenInfo.Symbol != "TEST" {
t.Errorf("Expected symbol TEST, got %s", tokenInfo.Symbol)
}
if tokenInfo.Decimals != 18 {
t.Errorf("Expected decimals 18, got %d", tokenInfo.Decimals)
}
if !tokenInfo.Enabled {
t.Error("Expected token to be enabled")
}
}
// TestRegisterTokenDuplicate tests duplicate token registration
func TestRegisterTokenDuplicate(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
// First registration
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1), e24(), e25())
// Duplicate registration
err := gw.RegisterToken(token, 18, "TEST2", "Test2", big.NewInt(1), e24(), e25())
if err == nil {
t.Error("Expected error for duplicate token registration")
}
}
// TestInitiateBridge tests bridge initiation
func TestInitiateBridge(t *testing.T) {
gw := NewBridgeGateway()
// Setup token
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
// Add liquidity on destination chain
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
// Initiate bridge
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
recipient := common.HexToAddress("0xABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD")
amount := big.NewInt(1e18)
request, err := gw.InitiateBridge(
sender,
recipient,
token,
amount,
ChainLux,
ChainEthereum,
uint64(time.Now().Add(time.Hour).Unix()),
nil,
)
if err != nil {
t.Fatalf("InitiateBridge failed: %v", err)
}
if request == nil {
t.Fatal("Expected non-nil request")
}
if request.ID == [32]byte{} {
t.Error("Expected non-zero request ID")
}
if request.Sender != sender {
t.Error("Sender mismatch")
}
if request.Recipient != recipient {
t.Error("Recipient mismatch")
}
if request.Status != StatusPending {
t.Errorf("Expected pending status, got %v", request.Status)
}
// Verify nonce incremented
if gw.Nonces[sender] != 1 {
t.Errorf("Expected nonce 1, got %d", gw.Nonces[sender])
}
}
// TestInitiateBridgeDisabled tests bridging when gateway is disabled
func TestInitiateBridgeDisabled(t *testing.T) {
gw := NewBridgeGateway()
gw.Enabled = false
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), 1, 2, 0, nil)
if err != ErrBridgeDisabled {
t.Errorf("Expected ErrBridgeDisabled, got %v", err)
}
}
// TestInitiateBridgePaused tests bridging when gateway is paused
func TestInitiateBridgePaused(t *testing.T) {
gw := NewBridgeGateway()
gw.Paused = true
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), 1, 2, 0, nil)
if err != ErrBridgeDisabled {
t.Errorf("Expected ErrBridgeDisabled, got %v", err)
}
}
// TestInitiateBridgeChainNotSupported tests unsupported destination chain
func TestInitiateBridgeChainNotSupported(t *testing.T) {
gw := NewBridgeGateway()
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), ChainLux, 99999, 0, nil)
if err != ErrChainNotSupported {
t.Errorf("Expected ErrChainNotSupported, got %v", err)
}
}
// TestInitiateBridgeTokenNotSupported tests unsupported token
func TestInitiateBridgeTokenNotSupported(t *testing.T) {
gw := NewBridgeGateway()
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
unsupportedToken := common.HexToAddress("0xDEADDEADDEADDEADDEADDEADDEADDEADDEADDEAD")
_, err := gw.InitiateBridge(sender, sender, unsupportedToken, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
if err != ErrTokenNotSupported {
t.Errorf("Expected ErrTokenNotSupported, got %v", err)
}
}
// TestInitiateBridgeAmountTooLow tests minimum amount validation
func TestInitiateBridgeAmountTooLow(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, token, big.NewInt(1e16), ChainLux, ChainEthereum, 0, nil) // Below min
if err != ErrAmountTooLow {
t.Errorf("Expected ErrAmountTooLow, got %v", err)
}
}
// TestInitiateBridgeAmountTooHigh tests maximum amount validation
func TestInitiateBridgeAmountTooHigh(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e20(), e25()) // Max 100 tokens
// Add liquidity
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, token, e21(), ChainLux, ChainEthereum, 0, nil) // Above max
if err != ErrAmountTooHigh {
t.Errorf("Expected ErrAmountTooHigh, got %v", err)
}
}
// TestInitiateBridgeInsufficientLiquidity tests liquidity check
func TestInitiateBridgeInsufficientLiquidity(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
// No liquidity added
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
if err != ErrInsufficientLiquidity {
t.Errorf("Expected ErrInsufficientLiquidity, got %v", err)
}
}
// TestInitiateBridgeDailyLimitExceeded tests daily limit
func TestInitiateBridgeDailyLimitExceeded(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e19()) // Daily limit 10 tokens
// Add liquidity
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
_, err := gw.InitiateBridge(sender, sender, token, e20(), ChainLux, ChainEthereum, 0, nil) // Exceeds daily limit
if err != ErrDailyLimitExceeded {
t.Errorf("Expected ErrDailyLimitExceeded, got %v", err)
}
}
// TestCompleteBridge tests bridge completion
func TestCompleteBridge(t *testing.T) {
gw := NewBridgeGateway()
// Setup
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
// Initiate
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
// Complete with signatures
signatures := [][]byte{[]byte("sig1")}
err := gw.CompleteBridge(request.ID, signatures)
if err != nil {
t.Fatalf("CompleteBridge failed: %v", err)
}
// Verify status
completedRequest, _ := gw.GetRequest(request.ID)
if completedRequest.Status != StatusCompleted {
t.Errorf("Expected completed status, got %v", completedRequest.Status)
}
if completedRequest.CompletedAt == 0 {
t.Error("Expected non-zero CompletedAt")
}
}
// TestCompleteBridgeNotFound tests error for non-existent request
func TestCompleteBridgeNotFound(t *testing.T) {
gw := NewBridgeGateway()
nonExistent := [32]byte{0xFF}
err := gw.CompleteBridge(nonExistent, [][]byte{[]byte("sig")})
if err != ErrRequestNotFound {
t.Errorf("Expected ErrRequestNotFound, got %v", err)
}
}
// TestCompleteBridgeAlreadyDone tests double completion
func TestCompleteBridgeAlreadyDone(t *testing.T) {
gw := NewBridgeGateway()
// Setup and complete
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
_ = gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
// Try to complete again
err := gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
if err != ErrRequestAlreadyDone {
t.Errorf("Expected ErrRequestAlreadyDone, got %v", err)
}
}
// TestCompleteBridgeExpired tests expired request
func TestCompleteBridgeExpired(t *testing.T) {
gw := NewBridgeGateway()
// Setup
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 1, nil) // Already expired
err := gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
if err != ErrRequestExpired {
t.Errorf("Expected ErrRequestExpired, got %v", err)
}
}
// TestRefundExpired tests refunding expired requests
func TestRefundExpired(t *testing.T) {
gw := NewBridgeGateway()
// Setup
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 1, nil) // Already expired
err := gw.RefundExpired(request.ID)
if err != nil {
t.Fatalf("RefundExpired failed: %v", err)
}
// Verify status
refundedRequest, _ := gw.GetRequest(request.ID)
if refundedRequest.Status != StatusRefunded {
t.Errorf("Expected refunded status, got %v", refundedRequest.Status)
}
}
// TestAddLiquidity tests adding liquidity
func TestAddLiquidity(t *testing.T) {
gw := NewBridgeGateway()
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
amount := e20()
position, err := gw.AddLiquidity(provider, token, ChainEthereum, amount)
if err != nil {
t.Fatalf("AddLiquidity failed: %v", err)
}
if position == nil {
t.Fatal("Expected non-nil position")
}
if position.Provider != provider {
t.Error("Provider mismatch")
}
if position.Amount.Cmp(amount) != 0 {
t.Errorf("Expected amount %v, got %v", amount, position.Amount)
}
// Verify pool state
pool := gw.Pools[ChainEthereum][token]
if pool == nil {
t.Fatal("Pool not created")
}
if pool.TotalLiq.Cmp(amount) != 0 {
t.Errorf("Expected total liquidity %v, got %v", amount, pool.TotalLiq)
}
if pool.Available.Cmp(amount) != 0 {
t.Errorf("Expected available %v, got %v", amount, pool.Available)
}
}
// TestAddLiquidityChainNotSupported tests error for unsupported chain
func TestAddLiquidityChainNotSupported(t *testing.T) {
gw := NewBridgeGateway()
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_, err := gw.AddLiquidity(provider, token, 99999, big.NewInt(1e18))
if err != ErrChainNotSupported {
t.Errorf("Expected ErrChainNotSupported, got %v", err)
}
}
// TestAddLiquidityMultiple tests adding liquidity multiple times
func TestAddLiquidityMultiple(t *testing.T) {
gw := NewBridgeGateway()
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
pool := gw.Pools[ChainEthereum][token]
expected := new(big.Int).Mul(big.NewInt(2), e20())
if pool.TotalLiq.Cmp(expected) != 0 {
t.Errorf("Expected total %v, got %v", expected, pool.TotalLiq)
}
}
// TestRemoveLiquidity tests removing liquidity
func TestRemoveLiquidity(t *testing.T) {
gw := NewBridgeGateway()
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
total, err := gw.RemoveLiquidity(provider, token, ChainEthereum, fiveE19())
if err != nil {
t.Fatalf("RemoveLiquidity failed: %v", err)
}
// Should return amount + fees
if total.Cmp(fiveE19()) < 0 {
t.Errorf("Expected at least %v, got %v", fiveE19(), total)
}
// Verify pool updated
pool := gw.Pools[ChainEthereum][token]
if pool.TotalLiq.Cmp(fiveE19()) != 0 {
t.Errorf("Expected remaining %v, got %v", fiveE19(), pool.TotalLiq)
}
}
// TestRemoveLiquidityInsufficientBalance tests removing more than deposited
func TestRemoveLiquidityInsufficientBalance(t *testing.T) {
gw := NewBridgeGateway()
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, big.NewInt(1e18))
_, err := gw.RemoveLiquidity(provider, token, ChainEthereum, e20()) // More than deposited
if err != ErrInsufficientLiquidity {
t.Errorf("Expected ErrInsufficientLiquidity, got %v", err)
}
}
// TestGetRequest tests request retrieval
func TestGetRequest(t *testing.T) {
gw := NewBridgeGateway()
nonExistent := [32]byte{0xFF}
_, err := gw.GetRequest(nonExistent)
if err != ErrRequestNotFound {
t.Errorf("Expected ErrRequestNotFound, got %v", err)
}
}
// TestCalculateFee tests fee calculation
func TestCalculateFee(t *testing.T) {
gw := NewBridgeGateway()
tests := []struct {
name string
amount *big.Int
minFee *big.Int
}{
{"Small amount", big.NewInt(1e17), gw.Config.MinFee}, // Fee would be below min
{"Large amount", e22(), gw.Config.MaxFee}, // Fee would exceed max
{"Medium amount", e19(), nil}, // Normal calculation
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fee := gw.calculateFee(tt.amount)
if fee.Sign() <= 0 {
t.Error("Expected positive fee")
}
if gw.Config.MinFee.Sign() > 0 && fee.Cmp(gw.Config.MinFee) < 0 {
t.Errorf("Fee %v below minimum %v", fee, gw.Config.MinFee)
}
if gw.Config.MaxFee.Sign() > 0 && fee.Cmp(gw.Config.MaxFee) > 0 {
t.Errorf("Fee %v above maximum %v", fee, gw.Config.MaxFee)
}
})
}
}
// TestNonceIncrement tests nonce handling
func TestNonceIncrement(t *testing.T) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
for i := range uint64(5) {
_, _ = gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
if gw.Nonces[sender] != i+1 {
t.Errorf("Expected nonce %d, got %d", i+1, gw.Nonces[sender])
}
}
}
// TestSignerThreshold tests threshold calculation
func TestSignerThreshold(t *testing.T) {
gw := NewBridgeGateway()
// No signers
threshold := gw.getThreshold()
if threshold != 1 {
t.Errorf("Expected threshold 1 with no signers, got %d", threshold)
}
// Add signers
gw.SignerSet.Signers = make([]*SignerInfo, 10)
threshold = gw.getThreshold()
expected := uint32((10 * 2 / 3) + 1) // 2/3 + 1 = 7
if threshold != expected {
t.Errorf("Expected threshold %d, got %d", expected, threshold)
}
}
// Benchmark tests
func BenchmarkInitiateBridge(b *testing.B) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e30())
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, new(big.Int).Mul(e24(), big.NewInt(int64(b.N)+1)))
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
amount := big.NewInt(1e18)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = gw.InitiateBridge(sender, sender, token, amount, ChainLux, ChainEthereum, 0, nil)
}
}
func BenchmarkCalculateFee(b *testing.B) {
gw := NewBridgeGateway()
amount := e20()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = gw.calculateFee(amount)
}
}
func BenchmarkAddLiquidity(b *testing.B) {
gw := NewBridgeGateway()
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
amount := big.NewInt(1e18)
b.ResetTimer()
for i := 0; i < b.N; i++ {
provider := common.BigToAddress(big.NewInt(int64(i)))
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, amount)
}
}
-15
View File
@@ -161,18 +161,3 @@ func TestStateRegistry_RejectsLongName(t *testing.T) {
t.Fatalf("expected ErrChainNameTooLong, got %v", err)
}
}
func TestGateway_RegistryDriven(t *testing.T) {
// A gateway built over an ad-hoc registry only supports the ids in it.
r := NewStatic([]Chain{
{ID: 42, Name: "answer", EVM: true},
})
gw := NewBridgeGatewayWithRegistry(r)
if !gw.Supports(42) {
t.Fatal("expected 42 to be supported")
}
if gw.Supports(96369) {
t.Fatal("expected 96369 to be unsupported (not in registry)")
}
}
-378
View File
@@ -1,378 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bridge
import (
"crypto/sha256"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/luxfi/geth/common"
)
// BridgeSigner provides the interface to B-Chain MPC signing
// This precompile at 0x0445 allows EVM contracts to request MPC signatures
type BridgeSigner struct {
// Signer set management
SignerSet *SignerSet
PendingSigns map[[32]byte]*SigningSession
// B-Chain connection (in production, would be cross-chain call)
BChainEndpoint string
// Configuration
SignTimeout time.Duration
MaxPendingSigns int
mu sync.RWMutex
}
// SigningSession represents an active signing request
type SigningSession struct {
SessionID [32]byte
MessageHash [32]byte
RequestedBy common.Address
RequestedAt uint64
ExpiresAt uint64
Status SigningStatus
Signatures map[[20]byte][]byte // NodeID -> signature
FinalSig []byte // Combined threshold signature
CallbackAddr common.Address // Contract to call when done
CallbackData []byte // Data for callback
}
// SigningStatus represents the status of a signing session
type SigningStatus uint8
const (
SigningPending SigningStatus = iota
SigningInProgress
SigningComplete
SigningFailed
SigningExpired
)
// NewBridgeSigner creates a new bridge signer interface
func NewBridgeSigner() *BridgeSigner {
return &BridgeSigner{
SignerSet: &SignerSet{
Signers: make([]*SignerInfo, 0),
Waitlist: make([][20]byte, 0),
Threshold: 67,
},
PendingSigns: make(map[[32]byte]*SigningSession),
SignTimeout: 5 * time.Minute,
MaxPendingSigns: 1000,
}
}
// RequestSignature requests an MPC signature from the signer set
func (bs *BridgeSigner) RequestSignature(
requester common.Address,
messageHash [32]byte,
callbackAddr common.Address,
callbackData []byte,
) ([32]byte, error) {
bs.mu.Lock()
defer bs.mu.Unlock()
if len(bs.PendingSigns) >= bs.MaxPendingSigns {
return [32]byte{}, errors.New("too many pending signing requests")
}
if len(bs.SignerSet.Signers) == 0 {
return [32]byte{}, errors.New("no active signers")
}
// Generate session ID
now := uint64(time.Now().Unix())
sessionData := append(messageHash[:], requester.Bytes()...)
sessionData = append(sessionData, big.NewInt(int64(now)).Bytes()...)
sessionID := sha256.Sum256(sessionData)
session := &SigningSession{
SessionID: sessionID,
MessageHash: messageHash,
RequestedBy: requester,
RequestedAt: now,
ExpiresAt: now + uint64(bs.SignTimeout.Seconds()),
Status: SigningPending,
Signatures: make(map[[20]byte][]byte),
CallbackAddr: callbackAddr,
CallbackData: callbackData,
}
bs.PendingSigns[sessionID] = session
// In production, this would send a message to B-Chain
// to initiate MPC signing protocol
go bs.initiateSigningProtocol(session)
return sessionID, nil
}
// GetSignature retrieves a completed signature
func (bs *BridgeSigner) GetSignature(sessionID [32]byte) ([]byte, SigningStatus, error) {
bs.mu.RLock()
defer bs.mu.RUnlock()
session := bs.PendingSigns[sessionID]
if session == nil {
return nil, 0, errors.New("signing session not found")
}
return session.FinalSig, session.Status, nil
}
// SubmitPartialSignature allows a signer to submit their signature share
func (bs *BridgeSigner) SubmitPartialSignature(
sessionID [32]byte,
signerNodeID [20]byte,
signature []byte,
) error {
bs.mu.Lock()
defer bs.mu.Unlock()
session := bs.PendingSigns[sessionID]
if session == nil {
return errors.New("signing session not found")
}
if session.Status != SigningPending && session.Status != SigningInProgress {
return errors.New("signing session not accepting signatures")
}
if uint64(time.Now().Unix()) > session.ExpiresAt {
session.Status = SigningExpired
return errors.New("signing session expired")
}
// Verify signer is in the active set
if !bs.isActiveSigner(signerNodeID) {
return ErrUnauthorizedSigner
}
// Store partial signature
session.Signatures[signerNodeID] = signature
session.Status = SigningInProgress
// Check if we have enough signatures
threshold := bs.getThreshold()
if uint32(len(session.Signatures)) >= threshold {
// Combine signatures
finalSig, err := bs.combineSignatures(session)
if err != nil {
session.Status = SigningFailed
return err
}
session.FinalSig = finalSig
session.Status = SigningComplete
// Execute callback if specified
if session.CallbackAddr != (common.Address{}) {
go bs.executeCallback(session)
}
}
return nil
}
// GetSignerSet returns the current signer set information
func (bs *BridgeSigner) GetSignerSet() (*SignerSet, error) {
bs.mu.RLock()
defer bs.mu.RUnlock()
return bs.SignerSet, nil
}
// GetPublicKey returns the combined threshold public key
func (bs *BridgeSigner) GetPublicKey() ([]byte, error) {
bs.mu.RLock()
defer bs.mu.RUnlock()
if bs.SignerSet == nil || len(bs.SignerSet.PublicKey) == 0 {
return nil, errors.New("no public key available")
}
return bs.SignerSet.PublicKey, nil
}
// RegisterSigner registers a new signer (implements LP-333 opt-in model)
func (bs *BridgeSigner) RegisterSigner(
nodeID [20]byte,
evmAddress common.Address,
publicKeyShare []byte,
bond *big.Int,
) error {
bs.mu.Lock()
defer bs.mu.Unlock()
// Check minimum bond
if bond.Cmp(MinSignerBond) < 0 {
return ErrInsufficientBond
}
// Check if already a signer
for _, signer := range bs.SignerSet.Signers {
if signer.NodeID == nodeID {
return ErrAlreadySigner
}
}
signer := &SignerInfo{
NodeID: nodeID,
Address: evmAddress,
PublicKey: publicKeyShare,
Bond: bond,
JoinedAt: uint64(time.Now().Unix()),
LastActive: uint64(time.Now().Unix()),
SignCount: 0,
SlashCount: 0,
Status: SignerActive,
}
// First 100 validators join without reshare (LP-333)
if len(bs.SignerSet.Signers) < MaxSigners {
bs.SignerSet.Signers = append(bs.SignerSet.Signers, signer)
bs.updateThreshold()
} else {
// Add to waitlist
signer.Status = SignerWaitlist
bs.SignerSet.Waitlist = append(bs.SignerSet.Waitlist, nodeID)
}
return nil
}
// RemoveSigner removes a signer from the set
func (bs *BridgeSigner) RemoveSigner(nodeID [20]byte) error {
bs.mu.Lock()
defer bs.mu.Unlock()
found := false
newSigners := make([]*SignerInfo, 0, len(bs.SignerSet.Signers))
for _, signer := range bs.SignerSet.Signers {
if signer.NodeID != nodeID {
newSigners = append(newSigners, signer)
} else {
found = true
}
}
if !found {
return ErrSignerNotFound
}
bs.SignerSet.Signers = newSigners
bs.updateThreshold()
// Promote from waitlist if available
if len(bs.SignerSet.Waitlist) > 0 {
// This would trigger a reshare in production
bs.SignerSet.Epoch++
bs.SignerSet.LastReshare = uint64(time.Now().Unix())
}
return nil
}
// SlashSigner reduces a signer's bond due to misbehavior
func (bs *BridgeSigner) SlashSigner(nodeID [20]byte, slashPercent uint32) error {
bs.mu.Lock()
defer bs.mu.Unlock()
for _, signer := range bs.SignerSet.Signers {
if signer.NodeID == nodeID {
// Calculate slash amount
slashAmount := new(big.Int).Mul(signer.Bond, big.NewInt(int64(slashPercent)))
slashAmount.Div(slashAmount, big.NewInt(100))
// Reduce bond
signer.Bond.Sub(signer.Bond, slashAmount)
signer.SlashCount++
// Remove if bond drops below minimum
if signer.Bond.Cmp(MinSignerBond) < 0 {
signer.Status = SignerSlashed
return bs.RemoveSigner(nodeID)
}
return nil
}
}
return ErrSignerNotFound
}
// Helper functions
func (bs *BridgeSigner) isActiveSigner(nodeID [20]byte) bool {
for _, signer := range bs.SignerSet.Signers {
if signer.NodeID == nodeID && signer.Status == SignerActive {
return true
}
}
return false
}
func (bs *BridgeSigner) getThreshold() uint32 {
if len(bs.SignerSet.Signers) == 0 {
return 1
}
// 2/3 + 1 of active signers
return (uint32(len(bs.SignerSet.Signers)) * 2 / 3) + 1
}
func (bs *BridgeSigner) updateThreshold() {
bs.SignerSet.Threshold = bs.getThreshold()
}
func (bs *BridgeSigner) initiateSigningProtocol(session *SigningSession) {
// Signing protocol initiation is handled by the Warp messaging layer.
// This method is called for side effects (session state update) and
// the actual cross-chain MPC coordination happens in the B-Chain's
// threshold coordinator via luxfi/threshold protocols.
}
func (bs *BridgeSigner) combineSignatures(session *SigningSession) ([]byte, error) {
// Concatenate signature shares for the threshold coordinator to combine.
// The actual FROST/CGGMP21/Corona combination is performed by the
// B-Chain's MPC coordinator in luxfi/threshold.
if len(session.Signatures) == 0 {
return nil, fmt.Errorf("no signatures to combine")
}
var combined []byte
for _, sig := range session.Signatures {
combined = append(combined, sig...)
}
return combined, nil
}
func (bs *BridgeSigner) executeCallback(session *SigningSession) {
// In production, this would call the callback contract
// with the signature result
}
// VerifyThresholdSignature verifies a threshold signature against the public key
func (bs *BridgeSigner) VerifyThresholdSignature(
messageHash [32]byte,
signature []byte,
) (bool, error) {
bs.mu.RLock()
defer bs.mu.RUnlock()
if len(bs.SignerSet.PublicKey) == 0 {
return false, errors.New("no public key available")
}
// In production, verify using the appropriate threshold scheme
// This would call into lux/threshold package
return len(signature) > 0, nil
}
-229
View File
@@ -1,229 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bridge
import (
"errors"
"math/big"
"github.com/luxfi/geth/common"
)
// Precompile addresses for B-Chain bridge operations
const (
// Bridge core operations
BridgeGatewayAddress = "0x0440" // Main bridge gateway
BridgeRouterAddress = "0x0441" // Cross-chain routing
BridgeVerifierAddress = "0x0442" // Message verification
BridgeLiquidityAddress = "0x0443" // Liquidity pools
BridgeFeeManagerAddress = "0x0444" // Fee management
BridgeSignerAddress = "0x0445" // MPC signer interface
// Gas costs
GasBridgeInitiate = uint64(100000) // Initiate bridge transfer
GasBridgeComplete = uint64(50000) // Complete bridge on destination
GasBridgeVerify = uint64(25000) // Verify bridge message
GasBridgeGetStatus = uint64(5000) // Query bridge status
GasBridgeAddLiq = uint64(75000) // Add liquidity
GasBridgeRemoveLiq = uint64(75000) // Remove liquidity
GasSignerGetPubKey = uint64(10000) // Get MPC public key
GasSignerRequestSign = uint64(150000) // Request MPC signature
)
// Supported chain IDs
const (
ChainLux uint32 = 96369 // Lux mainnet C-Chain
ChainLuxTest uint32 = 96368 // Lux testnet
ChainHanzo uint32 = 36963 // Hanzo AI mainnet
ChainHanzoTest uint32 = 36962 // Hanzo testnet
ChainZoo uint32 = 200200 // Zoo mainnet
ChainZooTest uint32 = 200201 // Zoo testnet
ChainSPC uint32 = 36911 // SPC mainnet
ChainSPCTest uint32 = 36910 // SPC testnet
ChainEthereum uint32 = 1 // Ethereum mainnet
ChainArbitrum uint32 = 42161 // Arbitrum One
ChainOptimism uint32 = 10 // Optimism
ChainBase uint32 = 8453 // Base
ChainPolygon uint32 = 137 // Polygon PoS
ChainBSC uint32 = 56 // BNB Smart Chain
ChainAvalanche uint32 = 43114 // Avalanche C-Chain
ChainPars uint32 = 6133 // Pars Network mainnet
ChainParsTest uint32 = 6132 // Pars Network testnet
// Non-EVM chains (use virtual chain IDs for bridge tracking)
// These don't have native EVM chain IDs but are tracked by the bridge
ChainSolana uint32 = 900001 // Solana mainnet
ChainBitcoin uint32 = 900002 // Bitcoin mainnet
ChainXRP uint32 = 900003 // XRP Ledger mainnet
ChainTON uint32 = 900004 // TON mainnet
)
// BridgeRequest represents a cross-chain transfer request
type BridgeRequest struct {
ID [32]byte // Unique request ID
Sender common.Address // Source chain sender
Recipient common.Address // Destination chain recipient
Token common.Address // Token address (address(0) for native)
Amount *big.Int // Amount to bridge
SourceChain uint32 // Source chain ID
DestChain uint32 // Destination chain ID
Nonce uint64 // Sender nonce for replay protection
Deadline uint64 // Timestamp deadline
Data []byte // Optional calldata for recipient
Status BridgeStatus // Current status
SourceTxHash common.Hash // Source chain transaction hash
DestTxHash common.Hash // Destination chain transaction hash
Signatures [][]byte // MPC signatures
CreatedAt uint64 // Creation timestamp
CompletedAt uint64 // Completion timestamp
}
// BridgeStatus represents the status of a bridge request
type BridgeStatus uint8
const (
StatusPending BridgeStatus = iota
StatusSigning
StatusSigned
StatusRelaying
StatusCompleted
StatusFailed
StatusExpired
StatusRefunded
)
// BridgedToken represents a token that can be bridged
type BridgedToken struct {
LocalAddress common.Address // Address on this chain
RemoteAddress map[uint32]common.Address // Address on remote chains
Decimals uint8 // Token decimals
Symbol string // Token symbol
Name string // Token name
MinBridge *big.Int // Minimum bridge amount
MaxBridge *big.Int // Maximum bridge amount (per tx)
DailyLimit *big.Int // Daily bridge limit
BridgedToday *big.Int // Amount bridged today
LastReset uint64 // Last daily reset timestamp
Enabled bool // Whether bridging is enabled
}
// LiquidityPool represents a bridge liquidity pool
type LiquidityPool struct {
Token common.Address // Pool token
ChainID uint32 // Chain ID
TotalLiq *big.Int // Total liquidity
Available *big.Int // Available liquidity (not in transit)
Providers map[common.Address]*LPPosition
FeeRate uint32 // Fee in basis points (100 = 1%)
TotalFees *big.Int // Total fees collected
}
// LPPosition represents a liquidity provider position
type LPPosition struct {
Provider common.Address
Amount *big.Int
ShareRatio *big.Int // Share of pool (scaled by 1e18)
DepositTime uint64
PendingFees *big.Int
}
// BridgeMessage is the message format for cross-chain communication
type BridgeMessage struct {
Version uint8 // Message version
MessageType uint8 // Type of message
SourceChain uint32 // Source chain ID
DestChain uint32 // Destination chain ID
Nonce uint64 // Message nonce
Sender common.Address // Original sender
Recipient common.Address // Destination recipient
Token common.Address // Token address
Amount *big.Int // Amount
Data []byte // Additional data
Timestamp uint64 // Message timestamp
}
// MessageType constants
const (
MsgTypeTransfer uint8 = 1 // Token transfer
MsgTypeMint uint8 = 2 // Mint wrapped token
MsgTypeBurn uint8 = 3 // Burn wrapped token
MsgTypeUnlock uint8 = 4 // Unlock native token
MsgTypeLiquidity uint8 = 5 // Liquidity operation
MsgTypeGovernance uint8 = 6 // Governance message
MsgTypeEmergency uint8 = 7 // Emergency pause/unpause
)
// SignerInfo represents an MPC signer in the bridge set
type SignerInfo struct {
NodeID [20]byte // Validator node ID
Address common.Address // EVM address
PublicKey []byte // MPC public key share
Bond *big.Int // Staked bond (min 100M LUX)
JoinedAt uint64 // When joined signer set
LastActive uint64 // Last activity timestamp
SignCount uint64 // Total signatures produced
SlashCount uint32 // Times slashed
Status SignerStatus
}
// SignerStatus represents MPC signer status
type SignerStatus uint8
const (
SignerActive SignerStatus = iota
SignerWaitlist
SignerSlashed
SignerExited
)
// SignerSet represents the current MPC signer set
type SignerSet struct {
Signers []*SignerInfo // Active signers (max 100)
Waitlist [][20]byte // Waitlisted node IDs
Threshold uint32 // Required signatures (2/3 of signers)
Epoch uint64 // Current epoch
PublicKey []byte // Combined threshold public key
LastReshare uint64 // Last reshare timestamp
}
// BridgeFeeConfig represents fee configuration
type BridgeFeeConfig struct {
BaseFee *big.Int // Base fee per bridge
PercentFee uint32 // Fee percentage (basis points)
MinFee *big.Int // Minimum fee
MaxFee *big.Int // Maximum fee
LiquidityFee uint32 // Fee to liquidity providers (basis points)
ProtocolFee uint32 // Protocol fee (basis points)
EmergencyPenalty uint32 // Emergency withdrawal penalty (basis points)
}
// Bridge errors
var (
ErrBridgeDisabled = errors.New("bridge is disabled")
ErrTokenNotSupported = errors.New("token not supported for bridging")
ErrChainNotSupported = errors.New("destination chain not supported")
ErrAmountTooLow = errors.New("amount below minimum")
ErrAmountTooHigh = errors.New("amount exceeds maximum")
ErrDailyLimitExceeded = errors.New("daily bridge limit exceeded")
ErrInsufficientLiquidity = errors.New("insufficient bridge liquidity")
ErrInvalidSignature = errors.New("invalid bridge signature")
ErrSignatureThreshold = errors.New("signature threshold not met")
ErrRequestNotFound = errors.New("bridge request not found")
ErrRequestExpired = errors.New("bridge request expired")
ErrRequestAlreadyDone = errors.New("bridge request already completed")
ErrInvalidMessage = errors.New("invalid bridge message")
ErrUnauthorizedSigner = errors.New("unauthorized signer")
ErrSignerNotFound = errors.New("signer not found")
ErrInsufficientBond = errors.New("insufficient signer bond")
ErrAlreadySigner = errors.New("already in signer set")
ErrSignerSetFull = errors.New("signer set is full")
ErrInvalidNonce = errors.New("invalid nonce")
ErrReplayAttack = errors.New("replay attack detected")
)
// MinSignerBond is the minimum bond required to be a signer (100M LUX)
var MinSignerBond = new(big.Int).Mul(big.NewInt(100_000_000), big.NewInt(1e18))
// MaxSigners is the maximum number of active signers
const MaxSigners = 100