2025-10-03 14:45:52 -04:00
|
|
|
// Package commands contains utility functions for the snrd command
|
|
|
|
|
package commands
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
|
|
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
|
|
|
|
|
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
|
|
|
|
"github.com/sonr-io/sonr/app"
|
2025-10-10 11:47:18 -04:00
|
|
|
"github.com/sonr-io/crypto/vrf"
|
2025-10-03 14:45:52 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func EnhancedInit(chainApp *app.ChainApp) *cobra.Command {
|
|
|
|
|
baseCmd := genutilcli.InitCmd(chainApp.BasicModuleManager, app.DefaultNodeHome)
|
|
|
|
|
baseCmd.PostRunE = handleInitPostE
|
|
|
|
|
return baseCmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handleInitPostE generates VRF keypair and stores it securely after chain initialization
|
|
|
|
|
func handleInitPostE(cmd *cobra.Command, args []string) error {
|
|
|
|
|
// Extract chain-id from genesis file for network-aware key generation
|
|
|
|
|
chainID, err := getChainIDFromGenesis()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get chain-id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create deterministic entropy source from chainID
|
|
|
|
|
entropySource, err := createChainIDEntropySource(chainID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create entropy source: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate VRF keypair using chainID-derived entropy
|
|
|
|
|
privateKey, err := vrf.GenerateKey(entropySource)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to generate VRF keypair: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate the generated keypair
|
|
|
|
|
if len(privateKey) != vrf.PrivateKeySize {
|
|
|
|
|
return fmt.Errorf(
|
|
|
|
|
"invalid VRF private key size: expected %d, got %d",
|
|
|
|
|
vrf.PrivateKeySize,
|
|
|
|
|
len(privateKey),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get public key to validate keypair
|
|
|
|
|
_, ok := privateKey.Public()
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("failed to derive public key from VRF private key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create secure storage path
|
|
|
|
|
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
|
|
|
|
|
|
|
|
|
|
// Ensure directory exists with proper permissions
|
|
|
|
|
if err := os.MkdirAll(app.DefaultNodeHome, 0o750); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to create node home directory: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store VRF secret key with restrictive permissions (owner read/write only)
|
|
|
|
|
if err := os.WriteFile(vrfKeyPath, privateKey, 0o600); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to save VRF secret key: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("✓ VRF keypair generated for network: %s\n", chainID)
|
|
|
|
|
fmt.Printf("✓ VRF secret key stored securely: %s\n", vrfKeyPath)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getChainIDFromGenesis extracts chain-id from the genesis file
|
|
|
|
|
func getChainIDFromGenesis() (string, error) {
|
|
|
|
|
genesisPath := filepath.Join(app.DefaultNodeHome, "config", "genesis.json")
|
|
|
|
|
|
|
|
|
|
// #nosec G304 - genesisPath is constructed from trusted app.DefaultNodeHome constant
|
|
|
|
|
genesisData, err := os.ReadFile(genesisPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to read genesis file: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var genesis genutiltypes.AppGenesis
|
|
|
|
|
if err := json.Unmarshal(genesisData, &genesis); err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to parse genesis file: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if genesis.ChainID == "" {
|
|
|
|
|
return "", fmt.Errorf("chain-id not found in genesis file")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return genesis.ChainID, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// entropyReader implements io.Reader to provide deterministic entropy from chainID
|
|
|
|
|
type entropyReader struct {
|
|
|
|
|
seed []byte
|
|
|
|
|
pos int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *entropyReader) Read(p []byte) (int, error) {
|
|
|
|
|
n := 0
|
|
|
|
|
for n < len(p) && e.pos < len(e.seed) {
|
|
|
|
|
p[n] = e.seed[e.pos]
|
|
|
|
|
n++
|
|
|
|
|
e.pos++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we need more bytes than available in seed, use crypto/rand for additional randomness
|
|
|
|
|
if n < len(p) {
|
|
|
|
|
remaining, err := rand.Read(p[n:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return n, err
|
|
|
|
|
}
|
|
|
|
|
n += remaining
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return n, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createChainIDEntropySource creates a deterministic entropy source from chainID
|
|
|
|
|
// This provides network-specific but still secure randomness for VRF key generation
|
|
|
|
|
func createChainIDEntropySource(chainID string) (io.Reader, error) {
|
|
|
|
|
if chainID == "" {
|
|
|
|
|
return nil, fmt.Errorf("chainID cannot be empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create deterministic seed from chainID using SHA256
|
|
|
|
|
hash := sha256.Sum256([]byte(chainID))
|
|
|
|
|
|
|
|
|
|
return &entropyReader{
|
|
|
|
|
seed: hash[:],
|
|
|
|
|
pos: 0,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|