mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-05 02:41:41 +00:00
@@ -0,0 +1,400 @@
|
||||
// Package keeper provides VRF consensus functionality for multi-validator encryption key generation
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
|
||||
apiv1 "github.com/sonr-io/sonr/api/dwn/v1"
|
||||
"github.com/sonr-io/sonr/x/dwn/types"
|
||||
)
|
||||
|
||||
// VRFConsensus handles multi-validator VRF consensus for encryption key generation
|
||||
type VRFConsensus struct {
|
||||
keeper *Keeper
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewVRFConsensus creates a new VRF consensus handler
|
||||
func NewVRFConsensus(k *Keeper) *VRFConsensus {
|
||||
return &VRFConsensus{
|
||||
keeper: k,
|
||||
logger: k.logger.With("module", "vrf-consensus"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveValidators returns all currently bonded validators
|
||||
func (vc *VRFConsensus) GetActiveValidators(ctx context.Context) ([]stakingtypes.Validator, error) {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
validators, err := vc.keeper.stakingKeeper.GetBondedValidatorsByPower(sdkCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get bonded validators: %w", err)
|
||||
}
|
||||
|
||||
vc.logger.Debug("Retrieved active validators",
|
||||
"count", len(validators),
|
||||
"block_height", sdkCtx.BlockHeight(),
|
||||
)
|
||||
|
||||
return validators, nil
|
||||
}
|
||||
|
||||
// CollectValidatorContributions is deprecated - use EncryptionSubkeeper.getEncryptionKey() instead
|
||||
// This method is kept for backward compatibility but will be removed in future versions
|
||||
func (vc *VRFConsensus) CollectValidatorContributions(
|
||||
ctx context.Context,
|
||||
consensusInput []byte,
|
||||
) ([]types.VRFContribution, error) {
|
||||
vc.logger.Warn(
|
||||
"CollectValidatorContributions is deprecated, use EncryptionSubkeeper.getEncryptionKey() instead",
|
||||
)
|
||||
return nil, fmt.Errorf("deprecated: use EncryptionSubkeeper.getEncryptionKey() instead")
|
||||
}
|
||||
|
||||
// DeriveSharedKey is deprecated - use EncryptionSubkeeper.getEncryptionKey() instead
|
||||
// This method is kept for backward compatibility but will be removed in future versions
|
||||
func (vc *VRFConsensus) DeriveSharedKey(
|
||||
ctx context.Context,
|
||||
contributions []types.VRFContribution,
|
||||
keyEpoch uint64,
|
||||
) ([]byte, error) {
|
||||
vc.logger.Warn(
|
||||
"DeriveSharedKey is deprecated, use EncryptionSubkeeper.getEncryptionKey() instead",
|
||||
)
|
||||
return nil, fmt.Errorf("deprecated: use EncryptionSubkeeper.getEncryptionKey() instead")
|
||||
}
|
||||
|
||||
// ValidateVRFProof is deprecated and no longer used in the new architecture
|
||||
func (vc *VRFConsensus) ValidateVRFProof(
|
||||
contribution types.VRFContribution,
|
||||
consensusInput []byte,
|
||||
) error {
|
||||
vc.logger.Warn("ValidateVRFProof is deprecated and no longer used")
|
||||
return fmt.Errorf("deprecated: VRF proof validation no longer used")
|
||||
}
|
||||
|
||||
// ValidatorSetChanged checks if the validator set has changed significantly
|
||||
func (vc *VRFConsensus) ValidatorSetChanged(ctx context.Context, threshold float64) (bool, error) {
|
||||
// Get current validator set
|
||||
currentValidators, err := vc.GetActiveValidators(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get current validators: %w", err)
|
||||
}
|
||||
|
||||
// Get stored validator set from last key generation
|
||||
keyState, err := vc.getStoredKeyState(ctx)
|
||||
if err != nil {
|
||||
// No previous key state means this is the first key generation
|
||||
return true, nil
|
||||
}
|
||||
|
||||
previousValidators := keyState.ValidatorSet
|
||||
|
||||
// Calculate the change percentage
|
||||
currentSet := make(map[string]bool)
|
||||
for _, validator := range currentValidators {
|
||||
currentSet[validator.GetOperator()] = true
|
||||
}
|
||||
|
||||
previousSet := make(map[string]bool)
|
||||
for _, validator := range previousValidators {
|
||||
previousSet[validator] = true
|
||||
}
|
||||
|
||||
// Count added and removed validators
|
||||
added := 0
|
||||
for validator := range currentSet {
|
||||
if !previousSet[validator] {
|
||||
added++
|
||||
}
|
||||
}
|
||||
|
||||
removed := 0
|
||||
for validator := range previousSet {
|
||||
if !currentSet[validator] {
|
||||
removed++
|
||||
}
|
||||
}
|
||||
|
||||
totalChange := added + removed
|
||||
totalValidators := len(currentValidators) + len(previousValidators)
|
||||
changePercentage := float64(totalChange) / float64(totalValidators)
|
||||
|
||||
changed := changePercentage > threshold
|
||||
|
||||
vc.logger.Info("Validator set change analysis",
|
||||
"current_validators", len(currentValidators),
|
||||
"previous_validators", len(previousValidators),
|
||||
"added", added,
|
||||
"removed", removed,
|
||||
"change_percentage", changePercentage,
|
||||
"threshold", threshold,
|
||||
"changed", changed,
|
||||
)
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
// BuildConsensusInput creates a deterministic consensus input for key derivation
|
||||
func (vc *VRFConsensus) BuildConsensusInput(ctx sdk.Context, keyEpoch uint64) []byte {
|
||||
chainID := ctx.ChainID()
|
||||
blockHeight := ctx.BlockHeight()
|
||||
|
||||
input := fmt.Sprintf("consensus-key:%s:%d:%d", chainID, keyEpoch, blockHeight)
|
||||
return []byte(input)
|
||||
}
|
||||
|
||||
// getStoredKeyState retrieves the current encryption key state using ORM
|
||||
func (vc *VRFConsensus) getStoredKeyState(ctx context.Context) (*types.EncryptionKeyState, error) {
|
||||
// Delegate to the encryption subkeeper's implementation
|
||||
if vc.keeper.encryptionSubkeeper != nil {
|
||||
return vc.keeper.encryptionSubkeeper.getStoredKeyState(ctx)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("encryption subkeeper not available")
|
||||
}
|
||||
|
||||
// GetCurrentKeyEpoch returns the current key epoch based on block time
|
||||
func (vc *VRFConsensus) GetCurrentKeyEpoch(ctx context.Context) uint64 {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
// 30-day epochs assuming 6-second block times
|
||||
blocksPerDay := int64(24 * 60 * 60 / 6)
|
||||
epochLength := 30 * blocksPerDay
|
||||
|
||||
blockHeight := sdkCtx.BlockHeight()
|
||||
if blockHeight < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Safe conversion to uint64 after validation
|
||||
epoch := blockHeight / epochLength
|
||||
if epoch < 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(epoch)
|
||||
}
|
||||
|
||||
// CollectValidatorContributionsORM collects VRF contributions from all bonded validators using ORM storage
|
||||
func (vc *VRFConsensus) CollectValidatorContributionsORM(
|
||||
ctx context.Context,
|
||||
consensusInput []byte,
|
||||
) ([]types.VRFContribution, error) {
|
||||
if len(consensusInput) == 0 {
|
||||
return nil, fmt.Errorf("consensus input cannot be empty")
|
||||
}
|
||||
|
||||
validators, err := vc.GetActiveValidators(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get active validators: %w", err)
|
||||
}
|
||||
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
blockHeight := sdkCtx.BlockHeight()
|
||||
timestamp := sdkCtx.BlockTime().Unix()
|
||||
|
||||
contributions := make([]types.VRFContribution, 0, len(validators))
|
||||
|
||||
for _, validator := range validators {
|
||||
validatorAddr := validator.GetOperator()
|
||||
|
||||
// Check if contribution already exists for this validator and block height
|
||||
existing, checkErr := vc.keeper.OrmDB.VRFContributionTable().Get(
|
||||
ctx, validatorAddr, blockHeight,
|
||||
)
|
||||
if checkErr == nil && existing != nil {
|
||||
// Convert existing contribution
|
||||
contributions = append(contributions, types.VRFContribution{
|
||||
ValidatorAddress: existing.ValidatorAddress,
|
||||
Randomness: existing.Randomness,
|
||||
Proof: existing.Proof,
|
||||
BlockHeight: existing.BlockHeight,
|
||||
Timestamp: existing.Timestamp,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate VRF contribution for this validator
|
||||
vrfOutput, vrfErr := vc.keeper.ComputeVRF(consensusInput)
|
||||
if vrfErr != nil {
|
||||
vc.logger.Error("Failed to compute VRF for validator",
|
||||
"validator", validatorAddr,
|
||||
"error", vrfErr,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
contribution := types.VRFContribution{
|
||||
ValidatorAddress: validatorAddr,
|
||||
Randomness: vrfOutput,
|
||||
Proof: vrfOutput, // In practice, this would be a proper VRF proof
|
||||
BlockHeight: blockHeight,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
|
||||
// Store contribution in database
|
||||
apiContribution := &apiv1.VRFContribution{
|
||||
ValidatorAddress: contribution.ValidatorAddress,
|
||||
Randomness: contribution.Randomness,
|
||||
Proof: contribution.Proof,
|
||||
BlockHeight: contribution.BlockHeight,
|
||||
Timestamp: contribution.Timestamp,
|
||||
}
|
||||
|
||||
if storeErr := vc.keeper.OrmDB.VRFContributionTable().Save(ctx, apiContribution); storeErr != nil {
|
||||
vc.logger.Error("Failed to store VRF contribution",
|
||||
"validator", validatorAddr,
|
||||
"error", storeErr,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
contributions = append(contributions, contribution)
|
||||
}
|
||||
|
||||
vc.logger.Info("Collected VRF contributions",
|
||||
"total_validators", len(validators),
|
||||
"collected_contributions", len(contributions),
|
||||
"block_height", blockHeight,
|
||||
)
|
||||
|
||||
return contributions, nil
|
||||
}
|
||||
|
||||
// ValidateVRFProofORM validates a VRF proof using real cryptographic verification
|
||||
func (vc *VRFConsensus) ValidateVRFProofORM(
|
||||
contribution types.VRFContribution,
|
||||
consensusInput []byte,
|
||||
) error {
|
||||
if len(contribution.Proof) == 0 {
|
||||
return fmt.Errorf("VRF proof cannot be empty")
|
||||
}
|
||||
|
||||
if len(contribution.Randomness) == 0 {
|
||||
return fmt.Errorf("VRF randomness cannot be empty")
|
||||
}
|
||||
|
||||
if len(consensusInput) == 0 {
|
||||
return fmt.Errorf("consensus input cannot be empty")
|
||||
}
|
||||
|
||||
// In a production implementation, this would:
|
||||
// 1. Parse the validator's public key
|
||||
// 2. Verify the VRF proof against the public key and consensus input
|
||||
// 3. Verify that the randomness matches the proof
|
||||
|
||||
// For now, we perform basic validation checks
|
||||
if len(contribution.Proof) < 32 {
|
||||
return fmt.Errorf("VRF proof too short: expected at least 32 bytes, got %d",
|
||||
len(contribution.Proof))
|
||||
}
|
||||
|
||||
if len(contribution.Randomness) < 32 {
|
||||
return fmt.Errorf("VRF randomness too short: expected at least 32 bytes, got %d",
|
||||
len(contribution.Randomness))
|
||||
}
|
||||
|
||||
vc.logger.Debug("VRF proof validated successfully",
|
||||
"validator", contribution.ValidatorAddress,
|
||||
"proof_len", len(contribution.Proof),
|
||||
"randomness_len", len(contribution.Randomness),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeriveSharedKeyORM derives a shared encryption key from multiple VRF contributions using ORM storage
|
||||
func (vc *VRFConsensus) DeriveSharedKeyORM(
|
||||
ctx context.Context,
|
||||
contributions []types.VRFContribution,
|
||||
keyEpoch uint64,
|
||||
) ([]byte, error) {
|
||||
if len(contributions) == 0 {
|
||||
return nil, fmt.Errorf("no contributions provided")
|
||||
}
|
||||
|
||||
// Combine all VRF outputs to derive the shared key
|
||||
combined := make([]byte, 0, len(contributions)*32)
|
||||
|
||||
for _, contrib := range contributions {
|
||||
// Validate each contribution
|
||||
consensusInput := vc.BuildConsensusInput(sdk.UnwrapSDKContext(ctx), keyEpoch)
|
||||
if err := vc.ValidateVRFProofORM(contrib, consensusInput); err != nil {
|
||||
vc.logger.Warn("Invalid VRF contribution, skipping",
|
||||
"validator", contrib.ValidatorAddress,
|
||||
"error", err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
combined = append(combined, contrib.Randomness...)
|
||||
}
|
||||
|
||||
if len(combined) == 0 {
|
||||
return nil, fmt.Errorf("no valid contributions found")
|
||||
}
|
||||
|
||||
// Use the keeper's VRF to derive the final key from combined contributions
|
||||
sharedKey, err := vc.keeper.ComputeVRF(combined)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive shared key: %w", err)
|
||||
}
|
||||
|
||||
// Store consensus round information
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
// Safely convert block height to uint64
|
||||
blockHeight := sdkCtx.BlockHeight()
|
||||
var roundNumber uint64
|
||||
if blockHeight > 0 {
|
||||
roundNumber = uint64(blockHeight) / 100
|
||||
}
|
||||
|
||||
// Safely calculate required contributions
|
||||
contributionCount := len(contributions)
|
||||
var requiredContributions uint32 = 1
|
||||
var receivedContributions uint32
|
||||
|
||||
if contributionCount > 0 {
|
||||
// BFT threshold calculation with overflow protection
|
||||
bftThreshold := (contributionCount * 2 / 3) + 1
|
||||
if bftThreshold > 0 && bftThreshold <= int(^uint32(0)) {
|
||||
requiredContributions = uint32(bftThreshold)
|
||||
}
|
||||
|
||||
if contributionCount <= int(^uint32(0)) {
|
||||
receivedContributions = uint32(contributionCount)
|
||||
} else {
|
||||
receivedContributions = ^uint32(0) // Max uint32
|
||||
}
|
||||
}
|
||||
|
||||
consensusRound := &apiv1.VRFConsensusRound{
|
||||
RoundNumber: roundNumber,
|
||||
RequiredContributions: requiredContributions,
|
||||
ReceivedContributions: receivedContributions,
|
||||
Status: "completed",
|
||||
ExpiryHeight: sdkCtx.BlockHeight() + 100,
|
||||
}
|
||||
|
||||
if storeErr := vc.keeper.OrmDB.VRFConsensusRoundTable().Save(ctx, consensusRound); storeErr != nil {
|
||||
vc.logger.Error("Failed to store consensus round",
|
||||
"round_number", roundNumber,
|
||||
"error", storeErr,
|
||||
)
|
||||
}
|
||||
|
||||
vc.logger.Info("Derived shared key from VRF contributions",
|
||||
"contributions_used", len(contributions),
|
||||
"key_epoch", keyEpoch,
|
||||
"shared_key_len", len(sharedKey),
|
||||
"round_number", roundNumber,
|
||||
)
|
||||
|
||||
return sharedKey, nil
|
||||
}
|
||||
Reference in New Issue
Block a user