Remove cbk and mnemonic commands from sutil
sutil holds only the utilities wanted on every machine; it is not a command-line front end for the packages in pkg/. cbk and bip39wordlist remain libraries with no CLI surface. The README states the boundary.
This commit is contained in:
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
`sneak.berlin/go/x` is a WTFPL-licensed Go module and CLI tool by
|
`sneak.berlin/go/x` is a WTFPL-licensed Go module and CLI tool by
|
||||||
[@sneak](https://sneak.berlin) that serves as an umbrella publishing space for
|
[@sneak](https://sneak.berlin) that serves as an umbrella publishing space for
|
||||||
small reusable packages, plus `sutil`, a command-line utility that exposes them
|
small reusable packages, plus `sutil`, a command-line utility holding a few
|
||||||
from the shell.
|
small tools worth having on every machine.
|
||||||
|
|
||||||
Importable packages live in `pkg/`. The CLI is `cmd/sutil/`, and the code that
|
Importable packages live in `pkg/`. The CLI is `cmd/sutil/`, and the code that
|
||||||
exists only to support it lives in `internal/`.
|
exists only to support it lives in `internal/`.
|
||||||
@@ -39,15 +39,6 @@ sutil generate random --count 1073741824 "my seed" > testdata.bin
|
|||||||
sutil generate password
|
sutil generate password
|
||||||
# marble7-sprout2-velvet9
|
# marble7-sprout2-velvet9
|
||||||
|
|
||||||
# A new 24-word BIP39 mnemonic phrase (or --words 12).
|
|
||||||
sutil generate mnemonic
|
|
||||||
|
|
||||||
# A crypto_box keypair derived from a mnemonic. The mnemonic is read from
|
|
||||||
# stdin, never from the command line, so it stays out of shell history.
|
|
||||||
sutil generate mnemonic > mnemonic.txt
|
|
||||||
sutil cbk derive < mnemonic.txt
|
|
||||||
sutil cbk derive --public < mnemonic.txt
|
|
||||||
|
|
||||||
# Build metadata, including which config file (if any) was loaded.
|
# Build metadata, including which config file (if any) was loaded.
|
||||||
sutil version
|
sutil version
|
||||||
sutil info
|
sutil info
|
||||||
@@ -228,12 +219,14 @@ sutil
|
|||||||
├── info print version, Go version, module, VCS metadata
|
├── info print version, Go version, module, VCS metadata
|
||||||
├── generate (gen)
|
├── generate (gen)
|
||||||
│ ├── random [seedstr] stream pseudorandom bytes (--count N to stop)
|
│ ├── random [seedstr] stream pseudorandom bytes (--count N to stop)
|
||||||
│ ├── password (pw) print a memorable password
|
│ └── password (pw) print a memorable password
|
||||||
│ └── mnemonic print a new BIP39 mnemonic (--words N)
|
|
||||||
└── cbk
|
|
||||||
└── derive keypair from a mnemonic on stdin (--public)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`sutil` is not a command-line front end for the packages in `pkg/`. It holds
|
||||||
|
only the handful of utilities the author wants available everywhere. `pkg/cbk`
|
||||||
|
and `pkg/bip39wordlist` are libraries with no CLI surface, and a command is
|
||||||
|
added because a tool is wanted, not because a package exists.
|
||||||
|
|
||||||
### Build, test, and CI
|
### Build, test, and CI
|
||||||
|
|
||||||
The `Makefile` is the single entry point: `make test` (30-second timeout, quiet
|
The `Makefile` is the single entry point: `make test` (30-second timeout, quiet
|
||||||
@@ -275,10 +268,6 @@ make docker # the full CI build
|
|||||||
`exhaustruct`, and replaces the deprecated `gomodguard` with
|
`exhaustruct`, and replaces the deprecated `gomodguard` with
|
||||||
`gomodguard_v2`. With `default: all`, newer images enable `exhaustruct_v5`
|
`gomodguard_v2`. With `default: all`, newer images enable `exhaustruct_v5`
|
||||||
and fail every partial struct literal.
|
and fail every partial struct literal.
|
||||||
- [ ] Add `sutil cbk seal` and `sutil cbk open` so messages can be sealed and
|
|
||||||
opened from the shell, not just from Go.
|
|
||||||
- [ ] Let `sutil cbk derive` take the BIP39 passphrase from an environment
|
|
||||||
variable; the library supports it, the CLI does not expose it yet.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
// Command sutil is sneak's shell utility: small tools backed by the
|
// Command sutil is sneak's shell utility: a few small tools worth having on
|
||||||
// packages in sneak.berlin/go/x. Run `sutil --help` for the command list.
|
// every machine. Run `sutil --help` for the command list.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"sneak.berlin/go/x/pkg/cbk"
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxMnemonicInputBytes bounds how much of stdin `cbk derive` will read; a
|
|
||||||
// 24-word mnemonic is well under 300 bytes.
|
|
||||||
const maxMnemonicInputBytes = 64 << 10
|
|
||||||
|
|
||||||
// newCBKCommand returns `sutil cbk`, the parent of the crypto_box
|
|
||||||
// keypair commands.
|
|
||||||
func newCBKCommand() *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "cbk",
|
|
||||||
Short: "crypto_box keypairs derived from BIP39 mnemonics",
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.AddCommand(newCBKDeriveCommand())
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCBKDeriveCommand returns `sutil cbk derive`.
|
|
||||||
func newCBKDeriveCommand() *cobra.Command {
|
|
||||||
var publicOnly bool
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "derive",
|
|
||||||
Short: "Derive a crypto_box keypair from a mnemonic read on stdin",
|
|
||||||
Long: `Reads a BIP39 mnemonic from stdin, in any letter case and with any
|
|
||||||
whitespace between words, and prints the crypto_box keypair derived from it
|
|
||||||
as hex. The same mnemonic always yields the same keypair.
|
|
||||||
|
|
||||||
The mnemonic is read from stdin rather than taken as an argument so that it
|
|
||||||
does not end up in shell history or process listings:
|
|
||||||
|
|
||||||
sutil generate mnemonic > mnemonic.txt
|
|
||||||
sutil cbk derive < mnemonic.txt`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
input := io.LimitReader(cmd.InOrStdin(), maxMnemonicInputBytes)
|
|
||||||
|
|
||||||
mnemonic, err := io.ReadAll(input)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading mnemonic from stdin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keypair, err := cbk.New(cbk.Params{Mnemonic: string(mnemonic)})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeKeypair(cmd.OutOrStdout(), keypair, publicOnly)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().BoolVar(&publicOnly, "public", false, "print only the public key")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeKeypair prints the keypair as labeled hex lines, or just the bare
|
|
||||||
// public key when publicOnly is set.
|
|
||||||
func writeKeypair(w io.Writer, keypair cbk.CryptoBoxKeypair, publicOnly bool) error {
|
|
||||||
if publicOnly {
|
|
||||||
_, err := fmt.Fprintln(w, keypair.PublicKeyHex())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprintf(
|
|
||||||
w, "public: %s\nprivate: %s\n", keypair.PublicKeyHex(), keypair.PrivateKeyHex(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
+2
-4
@@ -37,9 +37,8 @@ func New(p Params) *cobra.Command {
|
|||||||
root := &cobra.Command{
|
root := &cobra.Command{
|
||||||
Use: "sutil",
|
Use: "sutil",
|
||||||
Short: "sneak's shell utility",
|
Short: "sneak's shell utility",
|
||||||
Long: `sutil is a small collection of shell utilities built on the packages in
|
Long: `sutil is a small collection of shell utilities worth having on every
|
||||||
sneak.berlin/go/x: fast pseudorandom byte streams, memorable passwords,
|
machine: a fast pseudorandom byte stream and a memorable password generator.
|
||||||
BIP39 mnemonics, and crypto_box keypairs derived from them.
|
|
||||||
|
|
||||||
Before any command runs, sutil loads the optional configuration file named
|
Before any command runs, sutil loads the optional configuration file named
|
||||||
by --config, or else ~/.sneak.yaml (or .json, .toml, ...) if it exists, and
|
by --config, or else ~/.sneak.yaml (or .json, .toml, ...) if it exists, and
|
||||||
@@ -58,7 +57,6 @@ makes environment variables available alongside it.`,
|
|||||||
newVersionCommand(p),
|
newVersionCommand(p),
|
||||||
newInfoCommand(a),
|
newInfoCommand(a),
|
||||||
newGenerateCommand(),
|
newGenerateCommand(),
|
||||||
newCBKCommand(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|||||||
+24
-79
@@ -13,29 +13,14 @@ import (
|
|||||||
|
|
||||||
"sneak.berlin/go/x/internal/cli"
|
"sneak.berlin/go/x/internal/cli"
|
||||||
"sneak.berlin/go/x/internal/pwgen"
|
"sneak.berlin/go/x/internal/pwgen"
|
||||||
"sneak.berlin/go/x/pkg/bip39wordlist"
|
|
||||||
"sneak.berlin/go/x/pkg/cbk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const testVersion = "v1.2.3-test"
|
||||||
testVersion = "v1.2.3-test"
|
|
||||||
|
|
||||||
// vectorRepeats is how many times "abandon" appears in the standard
|
|
||||||
// BIP39 test vector for all-zero 128-bit entropy, which ends with
|
|
||||||
// "about".
|
|
||||||
vectorRepeats = bip39wordlist.MinWordCount - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// vectorMnemonic returns the standard BIP39 test vector for all-zero
|
|
||||||
// 128-bit entropy.
|
|
||||||
func vectorMnemonic() string {
|
|
||||||
return strings.Repeat("abandon ", vectorRepeats) + "about"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVersionCommand(t *testing.T) {
|
func TestVersionCommand(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
output, err := execute(t, "", "version")
|
output, err := execute(t, "version")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, testVersion+"\n", output)
|
assert.Equal(t, testVersion+"\n", output)
|
||||||
}
|
}
|
||||||
@@ -43,7 +28,7 @@ func TestVersionCommand(t *testing.T) {
|
|||||||
func TestVersionFlag(t *testing.T) {
|
func TestVersionFlag(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
output, err := execute(t, "", "--version")
|
output, err := execute(t, "--version")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, output, testVersion)
|
assert.Contains(t, output, testVersion)
|
||||||
}
|
}
|
||||||
@@ -51,7 +36,7 @@ func TestVersionFlag(t *testing.T) {
|
|||||||
func TestInfoCommand(t *testing.T) {
|
func TestInfoCommand(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
output, err := execute(t, "", "info")
|
output, err := execute(t, "info")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, output, "version: "+testVersion+"\n")
|
assert.Contains(t, output, "version: "+testVersion+"\n")
|
||||||
assert.Contains(t, output, "go: go")
|
assert.Contains(t, output, "go: go")
|
||||||
@@ -68,7 +53,7 @@ func TestConfigFileDiscoveredInConfigDir(t *testing.T) {
|
|||||||
|
|
||||||
params := cli.Params{Version: testVersion, ConfigDir: configDir}
|
params := cli.Params{Version: testVersion, ConfigDir: configDir}
|
||||||
|
|
||||||
output, err := executeWithParams(t, params, "", "info")
|
output, err := executeWithParams(t, params, "info")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, output, "Using config file: "+path+"\n")
|
assert.Contains(t, output, "Using config file: "+path+"\n")
|
||||||
assert.Contains(t, output, "config: "+path+"\n")
|
assert.Contains(t, output, "config: "+path+"\n")
|
||||||
@@ -80,7 +65,7 @@ func TestConfigFlagLoadsNamedFile(t *testing.T) {
|
|||||||
path := filepath.Join(t.TempDir(), "elsewhere.yaml")
|
path := filepath.Join(t.TempDir(), "elsewhere.yaml")
|
||||||
require.NoError(t, os.WriteFile(path, []byte("example: 1\n"), 0o600))
|
require.NoError(t, os.WriteFile(path, []byte("example: 1\n"), 0o600))
|
||||||
|
|
||||||
output, err := execute(t, "", "--config", path, "info")
|
output, err := execute(t, "--config", path, "info")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, output, "Using config file: "+path+"\n")
|
assert.Contains(t, output, "Using config file: "+path+"\n")
|
||||||
assert.Contains(t, output, "config: "+path+"\n")
|
assert.Contains(t, output, "config: "+path+"\n")
|
||||||
@@ -91,7 +76,7 @@ func TestConfigFlagRejectsMissingFile(t *testing.T) {
|
|||||||
|
|
||||||
path := filepath.Join(t.TempDir(), "missing.yaml")
|
path := filepath.Join(t.TempDir(), "missing.yaml")
|
||||||
|
|
||||||
_, err := execute(t, "", "--config", path, "version")
|
_, err := execute(t, "--config", path, "version")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,22 +89,22 @@ func TestConfigRejectsMalformedFile(t *testing.T) {
|
|||||||
|
|
||||||
params := cli.Params{Version: testVersion, ConfigDir: configDir}
|
params := cli.Params{Version: testVersion, ConfigDir: configDir}
|
||||||
|
|
||||||
_, err := executeWithParams(t, params, "", "version")
|
_, err := executeWithParams(t, params, "version")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateRandomSeededIsDeterministic(t *testing.T) {
|
func TestGenerateRandomSeededIsDeterministic(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
first, err := execute(t, "", "generate", "random", "--count", "4096", "seed")
|
first, err := execute(t, "generate", "random", "--count", "4096", "seed")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, first, 4096)
|
assert.Len(t, first, 4096)
|
||||||
|
|
||||||
second, err := execute(t, "", "generate", "random", "--count", "4096", "seed")
|
second, err := execute(t, "generate", "random", "--count", "4096", "seed")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, first, second)
|
assert.Equal(t, first, second)
|
||||||
|
|
||||||
other, err := execute(t, "", "gen", "random", "-c", "4096", "other seed")
|
other, err := execute(t, "gen", "random", "-c", "4096", "other seed")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEqual(t, first, other)
|
assert.NotEqual(t, first, other)
|
||||||
}
|
}
|
||||||
@@ -127,11 +112,11 @@ func TestGenerateRandomSeededIsDeterministic(t *testing.T) {
|
|||||||
func TestGenerateRandomUnseeded(t *testing.T) {
|
func TestGenerateRandomUnseeded(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
first, err := execute(t, "", "generate", "random", "--count", "1024")
|
first, err := execute(t, "generate", "random", "--count", "1024")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, first, 1024)
|
assert.Len(t, first, 1024)
|
||||||
|
|
||||||
second, err := execute(t, "", "generate", "random", "--count", "1024")
|
second, err := execute(t, "generate", "random", "--count", "1024")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEqual(t, first, second)
|
assert.NotEqual(t, first, second)
|
||||||
}
|
}
|
||||||
@@ -139,28 +124,10 @@ func TestGenerateRandomUnseeded(t *testing.T) {
|
|||||||
func TestGenerateRandomRejectsExtraArguments(t *testing.T) {
|
func TestGenerateRandomRejectsExtraArguments(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := execute(t, "", "generate", "random", "one", "two")
|
_, err := execute(t, "generate", "random", "one", "two")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateMnemonic(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
output, err := execute(t, "", "generate", "mnemonic")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
mnemonic := strings.TrimSuffix(output, "\n")
|
|
||||||
require.NoError(t, bip39wordlist.ValidateWordlist(mnemonic))
|
|
||||||
assert.Len(t, strings.Fields(mnemonic), bip39wordlist.MaxWordCount)
|
|
||||||
|
|
||||||
output, err = execute(t, "", "generate", "mnemonic", "--words", "12")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, strings.Fields(output), bip39wordlist.MinWordCount)
|
|
||||||
|
|
||||||
_, err = execute(t, "", "generate", "mnemonic", "--words", "13")
|
|
||||||
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGeneratePassword(t *testing.T) {
|
func TestGeneratePassword(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -171,7 +138,7 @@ func TestGeneratePassword(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
output, err := execute(t, "", "generate", "password")
|
output, err := execute(t, "generate", "password")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
password := strings.TrimSuffix(output, "\n")
|
password := strings.TrimSuffix(output, "\n")
|
||||||
@@ -181,48 +148,27 @@ func TestGeneratePassword(t *testing.T) {
|
|||||||
assert.True(t, strings.ContainsAny(password, pwgen.Delimiters), password)
|
assert.True(t, strings.ContainsAny(password, pwgen.Delimiters), password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCBKDerive(t *testing.T) {
|
func TestUnknownCommandFails(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
expected, err := cbk.FromMnemonic(vectorMnemonic())
|
_, err := execute(t, "cbk")
|
||||||
require.NoError(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
stdin := " " + strings.ToUpper(vectorMnemonic()) + "\n"
|
|
||||||
|
|
||||||
output, err := execute(t, stdin, "cbk", "derive")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t,
|
|
||||||
"public: "+expected.PublicKeyHex()+"\n"+
|
|
||||||
"private: "+expected.PrivateKeyHex()+"\n",
|
|
||||||
output,
|
|
||||||
)
|
|
||||||
|
|
||||||
output, err = execute(t, vectorMnemonic(), "cbk", "derive", "--public")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, expected.PublicKeyHex()+"\n", output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCBKDeriveRejectsInvalidMnemonic(t *testing.T) {
|
// execute runs sutil with the given arguments, looking for config in an
|
||||||
t.Parallel()
|
// empty temporary directory, and returns everything it wrote to stdout and
|
||||||
|
// stderr.
|
||||||
_, err := execute(t, "not a mnemonic\n", "cbk", "derive")
|
func execute(t *testing.T, args ...string) (string, error) {
|
||||||
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute runs sutil with the given stdin and arguments, looking for config
|
|
||||||
// in an empty temporary directory, and returns everything it wrote to
|
|
||||||
// stdout and stderr.
|
|
||||||
func execute(t *testing.T, stdin string, args ...string) (string, error) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
params := cli.Params{Version: testVersion, ConfigDir: t.TempDir()}
|
params := cli.Params{Version: testVersion, ConfigDir: t.TempDir()}
|
||||||
|
|
||||||
return executeWithParams(t, params, stdin, args...)
|
return executeWithParams(t, params, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeWithParams is execute with explicit Params.
|
// executeWithParams is execute with explicit Params.
|
||||||
func executeWithParams(
|
func executeWithParams(
|
||||||
t *testing.T, params cli.Params, stdin string, args ...string,
|
t *testing.T, params cli.Params, args ...string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -232,7 +178,6 @@ func executeWithParams(
|
|||||||
|
|
||||||
root.SetOut(&output)
|
root.SetOut(&output)
|
||||||
root.SetErr(&output)
|
root.SetErr(&output)
|
||||||
root.SetIn(strings.NewReader(stdin))
|
|
||||||
root.SetArgs(args)
|
root.SetArgs(args)
|
||||||
|
|
||||||
err := root.Execute()
|
err := root.Execute()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"sneak.berlin/go/x/internal/pwgen"
|
"sneak.berlin/go/x/internal/pwgen"
|
||||||
"sneak.berlin/go/x/internal/randstream"
|
"sneak.berlin/go/x/internal/randstream"
|
||||||
"sneak.berlin/go/x/pkg/bip39wordlist"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// streamChunkSize is the write size used by `generate random`. Large
|
// streamChunkSize is the write size used by `generate random`. Large
|
||||||
@@ -21,13 +20,12 @@ func newGenerateCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "generate",
|
Use: "generate",
|
||||||
Aliases: []string{"gen"},
|
Aliases: []string{"gen"},
|
||||||
Short: "Generate random bytes, passwords, and mnemonics",
|
Short: "Generate random bytes and passwords",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newGenerateRandomCommand(),
|
newGenerateRandomCommand(),
|
||||||
newGeneratePasswordCommand(),
|
newGeneratePasswordCommand(),
|
||||||
newGenerateMnemonicCommand(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@@ -107,36 +105,6 @@ no usable word list.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newGenerateMnemonicCommand returns `sutil generate mnemonic`.
|
|
||||||
func newGenerateMnemonicCommand() *cobra.Command {
|
|
||||||
var wordCount int
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "mnemonic",
|
|
||||||
Short: "Print a new random BIP39 mnemonic phrase",
|
|
||||||
Long: `Prints a new BIP39 mnemonic phrase generated from crypto/rand. The
|
|
||||||
default of 24 words encodes 256 bits of entropy; 12 words encodes 128.`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
mnemonic, err := bip39wordlist.GenerateMnemonic(wordCount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), mnemonic)
|
|
||||||
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().IntVarP(
|
|
||||||
&wordCount, "words", "w", bip39wordlist.MaxWordCount,
|
|
||||||
"number of words: 12, 15, 18, 21, or 24",
|
|
||||||
)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// streamBytes copies limit bytes (or everything, when limit is zero) from
|
// streamBytes copies limit bytes (or everything, when limit is zero) from
|
||||||
// src to dst in streamChunkSize chunks.
|
// src to dst in streamChunkSize chunks.
|
||||||
func streamBytes(dst io.Writer, src io.Reader, limit int64) error {
|
func streamBytes(dst io.Writer, src io.Reader, limit int64) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user