-
Notifications
You must be signed in to change notification settings - Fork 385
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
gnoland secrets
command suite (#1593)
## Description This PR introduces the secret management command suite as a the `gnoland` subcommand -- `gnoland secrets`. It is part of a series of PRs I plan to do on improving the chain initialization flow, with subsequent PRs focusing on the `config` file and its manipulation. Secrets being managed: - Validator private key (consensus) - Node p2p key (networking) - Validator last signed state (consensus optimization) Available commands: - `secrets init` - Initializes the Gno node secrets locally, including the validator key, validator state and node key - `secrets verify` - Verifies the Gno node secrets locally, including the validator key, validator state and node key - `secrets get` - Shows the Gno node secrets locally, including the validator key, validator state and node key ```mermaid --- title: secrets command suite --- flowchart LR subgraph init A[init] --> B[--data-dir] B -.-> C1[ValidatorPrivateKey] B -.-> C2[ValidatorState] B -.-> C3[NodeKey] end subgraph verify D[verify] --> E[--data-dir] E -.-> E1[ValidatorPrivateKey] E -.-> E2[ValidatorState] E -.-> E3[NodeKey] end subgraph get G[get] --> H[--data-dir] H -.-> H1[ValidatorPrivateKey] H -.-> H2[ValidatorState] H -.-> H3[NodeKey] end ``` <details><summary>Contributors' checklist...</summary> - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details> --------- Co-authored-by: Guilhem Fanton <[email protected]>
- Loading branch information
1 parent
c474f1c
commit 831bb6f
Showing
11 changed files
with
1,984 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
|
||
"github.com/gnolang/gno/tm2/pkg/commands" | ||
) | ||
|
||
var ( | ||
errInvalidDataDir = errors.New("invalid data directory provided") | ||
errInvalidSecretsKey = errors.New("invalid number of secret key arguments") | ||
) | ||
|
||
const ( | ||
defaultSecretsDir = "./secrets" | ||
defaultValidatorKeyName = "priv_validator_key.json" | ||
defaultNodeKeyName = "node_key.json" | ||
defaultValidatorStateName = "priv_validator_state.json" | ||
) | ||
|
||
const ( | ||
nodeKeyKey = "NodeKey" | ||
validatorPrivateKeyKey = "ValidatorPrivateKey" | ||
validatorStateKey = "ValidatorState" | ||
) | ||
|
||
// newSecretsCmd creates the secrets root command | ||
func newSecretsCmd(io commands.IO) *commands.Command { | ||
cmd := commands.NewCommand( | ||
commands.Metadata{ | ||
Name: "secrets", | ||
ShortUsage: "secrets <subcommand> [flags] [<arg>...]", | ||
ShortHelp: "gno secrets manipulation suite", | ||
LongHelp: "gno secrets manipulation suite, for managing the validator key, p2p key and validator state", | ||
}, | ||
commands.NewEmptyConfig(), | ||
commands.HelpExec, | ||
) | ||
|
||
cmd.AddSubCommands( | ||
newSecretsInitCmd(io), | ||
newSecretsVerifyCmd(io), | ||
newSecretsGetCmd(io), | ||
) | ||
|
||
return cmd | ||
} | ||
|
||
// commonAllCfg is the common | ||
// configuration for secrets commands | ||
// that require a bundled secrets dir | ||
type commonAllCfg struct { | ||
dataDir string | ||
} | ||
|
||
func (c *commonAllCfg) RegisterFlags(fs *flag.FlagSet) { | ||
fs.StringVar( | ||
&c.dataDir, | ||
"data-dir", | ||
defaultSecretsDir, | ||
"the secrets output directory", | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/gnolang/gno/tm2/pkg/amino" | ||
"github.com/gnolang/gno/tm2/pkg/bft/privval" | ||
"github.com/gnolang/gno/tm2/pkg/crypto" | ||
"github.com/gnolang/gno/tm2/pkg/p2p" | ||
) | ||
|
||
var ( | ||
errInvalidPrivateKey = errors.New("invalid validator private key") | ||
errPublicKeyMismatch = errors.New("public key does not match private key derivation") | ||
errAddressMismatch = errors.New("address does not match public key") | ||
|
||
errInvalidSignStateStep = errors.New("invalid sign state step value") | ||
errInvalidSignStateHeight = errors.New("invalid sign state height value") | ||
errInvalidSignStateRound = errors.New("invalid sign state round value") | ||
|
||
errSignatureMismatch = errors.New("signature does not match signature bytes") | ||
errSignatureValuesMissing = errors.New("missing signature value") | ||
|
||
errInvalidNodeKey = errors.New("invalid node p2p key") | ||
) | ||
|
||
// saveSecretData saves the given data as Amino JSON to the path | ||
func saveSecretData(data any, path string) error { | ||
// Get Amino JSON | ||
marshalledData, err := amino.MarshalJSONIndent(data, "", "\t") | ||
if err != nil { | ||
return fmt.Errorf("unable to marshal data into JSON, %w", err) | ||
} | ||
|
||
// Save the data to disk | ||
if err := os.WriteFile(path, marshalledData, 0o644); err != nil { | ||
return fmt.Errorf("unable to save data to path, %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// isValidDirectory verifies the directory at the given path exists | ||
func isValidDirectory(dirPath string) bool { | ||
fileInfo, err := os.Stat(dirPath) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
// Check if the path is indeed a directory | ||
return fileInfo.IsDir() | ||
} | ||
|
||
type secretData interface { | ||
privval.FilePVKey | privval.FilePVLastSignState | p2p.NodeKey | ||
} | ||
|
||
// readSecretData reads the secret data from the given path | ||
func readSecretData[T secretData]( | ||
path string, | ||
) (*T, error) { | ||
dataRaw, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read data, %w", err) | ||
} | ||
|
||
var data T | ||
if err := amino.UnmarshalJSON(dataRaw, &data); err != nil { | ||
return nil, fmt.Errorf("unable to unmarshal data, %w", err) | ||
} | ||
|
||
return &data, nil | ||
} | ||
|
||
// validateValidatorKey validates the validator's private key | ||
func validateValidatorKey(key *privval.FilePVKey) error { | ||
// Make sure the private key is set | ||
if key.PrivKey == nil { | ||
return errInvalidPrivateKey | ||
} | ||
|
||
// Make sure the public key is derived | ||
// from the private one | ||
if !key.PrivKey.PubKey().Equals(key.PubKey) { | ||
return errPublicKeyMismatch | ||
} | ||
|
||
// Make sure the address is derived | ||
// from the public key | ||
if key.PubKey.Address().Compare(key.Address) != 0 { | ||
return errAddressMismatch | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// validateValidatorState validates the validator's last sign state | ||
func validateValidatorState(state *privval.FilePVLastSignState) error { | ||
// Make sure the sign step is valid | ||
if state.Step < 0 { | ||
return errInvalidSignStateStep | ||
} | ||
|
||
// Make sure the height is valid | ||
if state.Height < 0 { | ||
return errInvalidSignStateHeight | ||
} | ||
|
||
// Make sure the round is valid | ||
if state.Round < 0 { | ||
return errInvalidSignStateRound | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// validateValidatorStateSignature validates the signature section | ||
// of the last sign validator state | ||
func validateValidatorStateSignature( | ||
state *privval.FilePVLastSignState, | ||
key crypto.PubKey, | ||
) error { | ||
// Make sure the signature and signature bytes are valid | ||
signBytesPresent := state.SignBytes != nil | ||
signaturePresent := state.Signature != nil | ||
|
||
if signBytesPresent && !signaturePresent || | ||
!signBytesPresent && signaturePresent { | ||
return errSignatureValuesMissing | ||
} | ||
|
||
if !signaturePresent { | ||
// No need to verify further | ||
return nil | ||
} | ||
|
||
// Make sure the signature bytes match the signature | ||
if !key.VerifyBytes(state.SignBytes, state.Signature) { | ||
return errSignatureMismatch | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// validateNodeKey validates the node's p2p key | ||
func validateNodeKey(key *p2p.NodeKey) error { | ||
if key.PrivKey == nil { | ||
return errInvalidNodeKey | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// verifySecretsKey verifies the secrets key value from the passed in arguments | ||
func verifySecretsKey(args []string) error { | ||
// Check if any key is set | ||
if len(args) == 0 { | ||
return nil | ||
} | ||
|
||
// Check if more than 1 key is set | ||
if len(args) > 1 { | ||
return errInvalidSecretsKey | ||
} | ||
|
||
// Verify the set key | ||
key := args[0] | ||
|
||
if key != nodeKeyKey && | ||
key != validatorPrivateKeyKey && | ||
key != validatorStateKey { | ||
return fmt.Errorf( | ||
"invalid secrets key value [%s, %s, %s]", | ||
validatorPrivateKeyKey, | ||
validatorStateKey, | ||
nodeKeyKey, | ||
) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getAvailableSecretsKeys formats and returns the available secret keys (constants) | ||
func getAvailableSecretsKeys() string { | ||
return fmt.Sprintf( | ||
"[%s, %s, %s]", | ||
validatorPrivateKeyKey, | ||
nodeKeyKey, | ||
validatorStateKey, | ||
) | ||
} |
Oops, something went wrong.