Files
zeekay 65f575285e cli: rename Corona → Corona (production R-LWE threshold);
LSAG ring-signature key → RingSig (descriptive, not Corona-lemur)

Two distinct concepts that shared the "Corona" name in CLI code are
now disambiguated:

  1. Production R-LWE threshold signature library — was "Corona",
     now Corona. Affects consensus / threshold-sign code paths,
     genesis precompile entry, threshold-scheme name, NTT references.

  2. LSAG ring-signature key derivation (independent feature, named
     after the lemur not the threshold sig) — now `RingSig`.
     Affects NewCoronaKeyBytes → NewRingSigKeyBytes,
     ToCoronaPublicKey → ToRingSigPublicKey, the keyset field
     `CoronaPrivateKey/PublicKey` → `RingSigPrivateKey/PublicKey`,
     JSON tag `corona_private_key` → `ringsig_private_key`, and
     the user-facing prose around the `lux key ring` command.

The `rt` subcommand is wired to github.com/luxfi/corona/cli (the
production library), via go.mod bump to luxfi/corona v0.2.1.

No "Corona" identifier remains in production CLI code or docs.
2026-05-12 09:07:44 -07:00

568 lines
16 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package chaincmd
import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/luxfi/cli/pkg/prompts"
"github.com/luxfi/cli/pkg/ux"
"github.com/luxfi/cli/pkg/vm"
"github.com/luxfi/constants"
"github.com/luxfi/evm/core"
"github.com/luxfi/sdk/models"
"github.com/spf13/cobra"
)
var (
chainType string // l1, l2, l3
sequencerType string // lux, ethereum, op, external
forceCreate bool
genesisFile string
customVMBin string
useEVM bool
useCustomVM bool
useParsVM bool
vmVersion string
useLatestVM bool
enablePreconfirm bool
// Genesis configuration flags
evmChainID uint64 // EVM chain ID (default: 200200)
tokenName string // Token name (default: TOKEN)
tokenSymbol string // Token symbol (default: TKN)
airdropAddress string // Address to airdrop tokens to
airdropAmount string // Amount to airdrop (in wei, default: 1000000 ether)
)
func newCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create [chainName]",
Short: "Create a new blockchain configuration",
Long: `Create a new blockchain configuration for deployment.
OVERVIEW:
Creates a blockchain configuration with genesis file and metadata.
The configuration is stored in ~/.lux/chains/<chainName>/ and can
be deployed to any network (local, testnet, mainnet).
CHAIN TYPES:
l1 Sovereign L1 with independent validation
l2 Layer 2 rollup/chain (default)
l3 App-specific L3 chain
SEQUENCER OPTIONS (for L2):
lux Lux-based rollup, 100ms blocks (default, lowest cost)
ethereum Ethereum-based rollup, 12s blocks (highest security)
op OP Stack compatible
external External/custom sequencer
VM OPTIONS:
--evm Use Lux EVM (default)
--pars Use Pars VM (post-quantum messaging)
--custom-vm Use custom VM binary
--vm Path to custom VM binary
--vm-version Specific VM version (default: latest)
--latest Use latest VM version
GENESIS OPTIONS:
--genesis Path to custom genesis.json file
If not provided, generates default EVM genesis
--evm-chain-id EVM chain ID (default: 200200)
--token-name Native token name (default: TOKEN)
--token-symbol Native token symbol (default: TKN)
--airdrop-address Address to airdrop tokens to (default: test account)
--airdrop-amount Amount to airdrop in wei (default: 1000000000000000000000000)
NON-INTERACTIVE MODE:
Non-interactive mode is automatically enabled when:
- NON_INTERACTIVE=1 environment variable is set
- CI=1 environment variable is set (common in CI/CD pipelines)
- stdin is not a TTY (piped input, scripts, etc.)
In non-interactive mode, sensible defaults are used for optional values.
Required values must be provided via flags.
OTHER OPTIONS:
--force, -f Overwrite existing configuration
--enable-preconfirm Enable pre-confirmations (<100ms acknowledgment)
EXAMPLES:
# Create default L2 chain with Lux sequencing
lux chain create mychain
# Create sovereign L1
lux chain create mychain --type=l1
# Create with Ethereum sequencing (12s blocks)
lux chain create mychain --sequencer=ethereum
# Create with custom genesis
lux chain create mychain --genesis=~/custom-genesis.json
# Create L3 on existing L2
lux chain create myapp --type=l3
# Overwrite existing configuration
lux chain create mychain --force
# Create with pre-confirmations enabled
lux chain create mychain --enable-preconfirm
# Non-interactive in CI/CD (env var triggers non-interactive mode)
CI=1 lux chain create mychain
# Non-interactive with custom chain ID
NON_INTERACTIVE=1 lux chain create mychain --evm-chain-id=12345
# Piped input also triggers non-interactive mode
echo "" | lux chain create mychain --evm-chain-id=12345
OUTPUT:
Creates two files in ~/.lux/chains/<chainName>/:
- genesis.json Blockchain genesis configuration
- sidecar.json Metadata (VM type, versions, deployment info)
NEXT STEPS:
After creating a chain configuration:
1. Start a network: lux network start --devnet
2. Deploy the chain: lux chain deploy mychain --devnet
3. Verify deployment: lux network status
NOTES:
- Chain names must be unique and ≤32 characters
- Reserved names: c, p, x, primary, platform
- Default genesis includes funded test account
- Genesis can be customized after creation`,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
RunE: createChain,
}
cmd.Flags().StringVar(&chainType, "type", "l2", "Chain type: l1, l2, l3")
cmd.Flags().StringVar(&sequencerType, "sequencer", "lux", "Sequencer: lux, ethereum, op, external")
cmd.Flags().BoolVarP(&forceCreate, "force", "f", false, "Overwrite existing configuration")
cmd.Flags().StringVar(&genesisFile, "genesis", "", "Path to custom genesis file")
cmd.Flags().StringVar(&customVMBin, "vm", "", "Path to custom VM binary")
cmd.Flags().BoolVar(&useEVM, "evm", false, "Use Lux EVM")
cmd.Flags().BoolVar(&useParsVM, "pars", false, "Use Pars VM (post-quantum messaging)")
cmd.Flags().BoolVar(&useCustomVM, "custom-vm", false, "Use custom VM")
cmd.Flags().StringVar(&vmVersion, "vm-version", "", "VM version to use")
cmd.Flags().BoolVar(&useLatestVM, "latest", false, "Use latest VM version")
cmd.Flags().BoolVar(&enablePreconfirm, "enable-preconfirm", false, "Enable pre-confirmations")
// Genesis configuration flags
cmd.Flags().Uint64Var(&evmChainID, "evm-chain-id", 0, "EVM chain ID (default: 200200)")
cmd.Flags().StringVar(&tokenName, "token-name", "", "Native token name (default: TOKEN)")
cmd.Flags().StringVar(&tokenSymbol, "token-symbol", "", "Native token symbol (default: TKN)")
cmd.Flags().StringVar(&airdropAddress, "airdrop-address", "", "Address to airdrop tokens to")
cmd.Flags().StringVar(&airdropAmount, "airdrop-amount", "", "Amount to airdrop in wei")
return cmd
}
func createChain(cmd *cobra.Command, args []string) error {
chainName := args[0]
// Validate chain name
if err := validateChainName(chainName); err != nil {
return err
}
// Check if configuration already exists
if app.ChainConfigExists(chainName) && !forceCreate {
return fmt.Errorf("chain %s already exists. Use --force to overwrite", chainName)
}
// Determine VM type
var vmType models.VMType
switch {
case useEVM:
vmType = models.EVM
case useParsVM:
vmType = models.ParsVM
case useCustomVM:
vmType = models.CustomVM
default:
// Default to EVM for all chain types
vmType = models.EVM
}
// Handle genesis
var chainGenesis []byte
var err error
if genesisFile != "" {
chainGenesis, err = os.ReadFile(genesisFile) //nolint:gosec // G304: User-specified genesis file
if err != nil {
return fmt.Errorf("failed to read genesis file: %w", err)
}
ux.Logger.PrintToUser("Importing genesis")
} else {
// Generate default genesis based on VM type
switch vmType {
case models.ParsVM:
chainGenesis, err = generateParsGenesis(chainName)
default:
chainGenesis, err = generateDefaultGenesis(chainName, chainType)
}
if err != nil {
return fmt.Errorf("failed to generate genesis: %w", err)
}
}
// Validate genesis
if vmType == models.EVM {
var genesis core.Genesis
if err := json.Unmarshal(chainGenesis, &genesis); err != nil {
return fmt.Errorf("invalid genesis format: %w", err)
}
}
// Resolve chain ID - prompt if not provided and interactive
resolvedChainID := evmChainID
defaultChainID := uint64(200200)
if vmType == models.ParsVM {
defaultChainID = vm.ParsDefaultChainID // 7070 for Pars
}
if resolvedChainID == 0 {
if !prompts.IsInteractive() {
resolvedChainID = defaultChainID
} else {
chainIDStr, err := app.Prompt.CaptureString(fmt.Sprintf("Enter chain ID (default: %d)", defaultChainID))
if err != nil {
return err
}
if chainIDStr == "" {
resolvedChainID = defaultChainID
} else {
parsed, err := strconv.ParseUint(chainIDStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid chain ID: %w", err)
}
resolvedChainID = parsed
}
}
}
// Resolve token name - prompt if not provided and interactive
resolvedTokenName := tokenName
defaultTokenName := "TOKEN"
if vmType == models.ParsVM {
defaultTokenName = "PARS"
}
if resolvedTokenName == "" {
if !prompts.IsInteractive() {
resolvedTokenName = defaultTokenName
} else {
name, err := app.Prompt.CaptureString(fmt.Sprintf("Enter token name (default: %s)", defaultTokenName))
if err != nil {
return err
}
if name == "" {
resolvedTokenName = defaultTokenName
} else {
resolvedTokenName = name
}
}
}
// Resolve token symbol - prompt if not provided and interactive
resolvedTokenSymbol := tokenSymbol
if resolvedTokenSymbol == "" {
if !prompts.IsInteractive() {
resolvedTokenSymbol = "TKN" // default
} else {
symbol, err := app.Prompt.CaptureString("Enter token symbol (default: TKN)")
if err != nil {
return err
}
if symbol == "" {
resolvedTokenSymbol = "TKN"
} else {
resolvedTokenSymbol = symbol
}
}
}
// Create sidecar configuration
sc := models.Sidecar{
Name: chainName,
VM: vmType,
Chain: chainName, // Network name (not chain)
TokenName: resolvedTokenName,
TokenSymbol: resolvedTokenSymbol,
EVMChainID: fmt.Sprintf("%d", resolvedChainID),
Version: "1.4.0",
BasedRollup: chainType == "l2",
Sovereign: chainType == "l1",
SequencerType: sequencerType,
PreconfirmEnabled: enablePreconfirm,
ChainLayer: getChainLayer(chainType),
}
// Set L1 block time based on sequencer
switch sequencerType {
case "lux":
sc.L1BlockTime = 100 // 100ms
case "ethereum":
sc.L1BlockTime = 12000 // 12s
default:
sc.L1BlockTime = 2000 // 2s default
}
// Handle custom VM
if useCustomVM && customVMBin != "" {
if err := vm.CopyCustomVM(app, chainName, customVMBin); err != nil {
return fmt.Errorf("failed to copy custom VM binary: %w", err)
}
ux.Logger.PrintToUser("Copied custom VM binary")
}
// Get VM version and RPC version if using EVM
if vmType == models.EVM {
if vmVersion != "" {
sc.VMVersion = vmVersion
} else {
sc.VMVersion = constants.DefaultEVMVersion // Default EVM version
}
// Set correct RPC version for Lux EVM
// This must match the running node's EVM RPC version
sc.RPCVersion = constants.DefaultEVMRPCVersion
}
// Create chain directory
chainDir := filepath.Join(app.GetChainsDir(), chainName)
if err := os.MkdirAll(chainDir, constants.DefaultPerms755); err != nil {
return fmt.Errorf("failed to create chain directory: %w", err)
}
// Write genesis
genesisPath := filepath.Join(chainDir, constants.GenesisFileName)
if err := os.WriteFile(genesisPath, chainGenesis, constants.WriteReadReadPerms); err != nil {
return fmt.Errorf("failed to write genesis: %w", err)
}
// Write sidecar
if err := app.CreateSidecar(&sc); err != nil {
return fmt.Errorf("failed to create sidecar: %w", err)
}
// Success message
ux.Logger.PrintToUser("Creating %s chain %s", chainType, chainName)
ux.Logger.PrintToUser("Chain Configuration:")
ux.Logger.PrintToUser(" Type: %s", strings.ToUpper(chainType))
ux.Logger.PrintToUser(" Chain ID: %s", sc.ChainID)
ux.Logger.PrintToUser(" Token: %s (%s)", sc.TokenName, sc.TokenSymbol)
if chainType == "l2" {
ux.Logger.PrintToUser(" Sequencer: %s", sequencerType)
ux.Logger.PrintToUser(" Block Time: %dms", sc.L1BlockTime)
}
ux.Logger.PrintToUser("Successfully created chain configuration")
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser("Next steps:")
ux.Logger.PrintToUser(" 1. Start network: lux network start --devnet")
ux.Logger.PrintToUser(" 2. Deploy chain: lux chain deploy %s --devnet", chainName)
return nil
}
func validateChainName(name string) error {
if name == "" {
return errors.New("chain name cannot be empty")
}
if len(name) > 32 {
return errors.New("chain name must be 32 characters or less")
}
// Check for reserved names
reserved := []string{"c", "p", "x", "primary", "platform"}
for _, r := range reserved {
if strings.EqualFold(name, r) {
return fmt.Errorf("%s is a reserved chain name", name)
}
}
return nil
}
// getChainLayer returns the chain layer (1=L1, 2=L2, 3=L3)
func getChainLayer(chainType string) int {
switch chainType {
case "l1":
return 1
case "l2":
return 2
case "l3":
return 3
default:
return 2 // Default to L2
}
}
// genesisParams contains parameters for genesis generation
type genesisParams struct {
chainID uint64
airdropAddress string
airdropAmount string // hex-encoded balance
}
// getGenesisParams resolves genesis parameters from flags or defaults
func getGenesisParams() genesisParams {
params := genesisParams{
chainID: 200200, // Default chain ID
airdropAddress: "9011E888251AB053B7bD1cdB598Db4f9DEd94714", // Default test account
airdropAmount: "0x193e5939a08ce9dbd480000000", // ~500M tokens
}
// Override with flags if provided
if evmChainID != 0 {
params.chainID = evmChainID
}
if airdropAddress != "" {
// Strip 0x prefix if present for consistency
addr := airdropAddress
if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") {
addr = addr[2:]
}
params.airdropAddress = addr
}
if airdropAmount != "" {
// If provided as decimal, convert to hex
if !strings.HasPrefix(airdropAmount, "0x") {
// Parse as decimal and convert to hex
if n, ok := new(big.Int).SetString(airdropAmount, 10); ok {
params.airdropAmount = "0x" + n.Text(16)
} else {
// Already hex or invalid, use as-is
params.airdropAmount = airdropAmount
}
} else {
params.airdropAmount = airdropAmount
}
}
return params
}
func generateDefaultGenesis(_, _ string) ([]byte, error) {
params := getGenesisParams()
// Default genesis for EVM-compatible chains
genesis := map[string]interface{}{
"config": map[string]interface{}{
"chainId": params.chainID,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"evmTimestamp": 0,
"feeConfig": map[string]interface{}{
"gasLimit": 8000000,
"targetBlockRate": 2,
"minBaseFee": 25000000000,
"targetGas": 15000000,
"baseFeeChangeDenominator": 36,
"minBlockGasCost": 0,
"maxBlockGasCost": 1000000,
"blockGasCostStep": 200000,
},
"allowFeeRecipients": true,
},
"alloc": map[string]interface{}{
params.airdropAddress: map[string]interface{}{
"balance": params.airdropAmount,
},
},
"nonce": "0x0",
"timestamp": "0x6727e9c3",
"extraData": "0x",
"gasLimit": "0x7a1200",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
}
return json.MarshalIndent(genesis, "", " ")
}
// generateParsGenesis generates a default genesis for Pars VM
func generateParsGenesis(chainName string) ([]byte, error) {
params := getGenesisParams()
if params.chainID == 200200 {
params.chainID = vm.ParsDefaultChainID // Use Pars default
}
genesis := map[string]interface{}{
"chainId": params.chainID,
"network": map[string]interface{}{
"rpcAddr": "127.0.0.1:9650",
"p2pAddr": "0.0.0.0:9651",
"chainId": params.chainID,
"networkId": params.chainID,
},
"evm": map[string]interface{}{
"enabled": true,
"precompiles": map[string]interface{}{
"mldsa": "0x0601",
"mlkem": "0x0603",
"bls": "0x0B00",
"corona": "0x0700",
"fhe": "0x0800",
},
},
"pars": map[string]interface{}{
"enabled": true,
"storage": map[string]interface{}{
"maxSize": 10737418240, // 10GB
"retentionDays": 30,
},
"onion": map[string]interface{}{
"hopCount": 3,
},
"session": map[string]interface{}{
"idPrefix": "07", // Post-quantum prefix
},
},
"warp": map[string]interface{}{
"enabled": true,
"luxEndpoint": "https://api.lux.network",
},
"crypto": map[string]interface{}{
"gpuEnabled": true,
"signatureScheme": "ML-DSA-65",
"kemScheme": "ML-KEM-768",
"thresholdScheme": "Corona",
},
"consensus": map[string]interface{}{
"engine": "quasar",
"blockTimeMs": 2000,
},
}
return json.MarshalIndent(genesis, "", " ")
}