diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac66420..4468b7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,52 @@ on: branches: [main] jobs: + quicktests: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: set up go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.go-version }} + - name: install musl + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: musl-tools # provides musl-gcc + version: 1.0 + - name: fmt, tidy + run: | + make install + make test-fmt + make test-tidy + - name: staticcheck + run: make staticcheck + + lint: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: set up go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.go-version }} + - name: install musl + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: musl-tools # provides musl-gcc + version: 1.0 + - name: setup env + run: make install + - name: lint + run: make lint-github-action + build: runs-on: ubuntu-latest + timeout-minutes: 2 steps: - name: checkout uses: actions/checkout@v3 @@ -27,5 +71,21 @@ jobs: version: 1.0 - name: build run: make build + + test: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: set up go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.go-version }} + - name: install musl + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: musl-tools # provides musl-gcc + version: 1.0 - name: go test run: make test diff --git a/.gitignore b/.gitignore index e3f4f32..3383004 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins +bin/ *.exe *.exe~ *.dll diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..23d3bdc --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,421 @@ +# Options for analysis running. +run: + # The default concurrency value is the number of available CPU. + # concurrency: 4 + + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 5m + + # Exit code when at least one issue was found. + # Default: 1 + issues-exit-code: 1 + + # Include test files or not. + # Default: true + tests: true + + # List of build tags, all linters use it. + # Default: []. + # build-tags: + # - mytag + + # Which dirs to skip: issues from them won't be reported. + # Can use regexp here: `generated.*`, regexp is applied on full path. + # Default value is empty list, + # but default dirs are skipped independently of this option's value (see skip-dirs-use-default). + # "/" will be replaced by current OS file path separator to properly work on Windows. + # skip-dirs: + # - src/external_libs + # - autogenerated_by_my_lib + # Enables skipping of directories: + # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # Default: true + skip-dirs-use-default: false + + # Which files to skip: they will be analyzed, but issues from them won't be reported. + # Default value is empty list, + # but there is no need to include all autogenerated files, + # we confidently recognize autogenerated files. + # If it's not please let us know. + # "/" will be replaced by current OS file path separator to properly work on Windows. + skip-files: + - "^mock_*\\.go$" + + # If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + # + # Allowed values: readonly|vendor|mod + # By default, it isn't set. + modules-download-mode: readonly + + # Allow multiple parallel golangci-lint instances running. + # If false (default) - golangci-lint acquires file lock on start. + allow-parallel-runners: false + + # Define the Go version limit. + # Mainly related to generics support since go1.18. + # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18 + # go: '1.19' + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" + format: colored-line-number + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + +# All available settings of specific linters. +linters-settings: + staticcheck: + # SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks + # Default: ["*"] + checks: ["all"] + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`. + # Such cases aren't reported by default. + # Default: false + check-blank: true + + govet: + # Report about shadowed variables. + # Default: false + check-shadowing: false + # Disable all analyzers. + # Default: false + disable-all: false + # Enable analyzers by name (in addition to default). + # Run `go tool vet help` to see all analyzers. + # Default: [] + # enable: + # Enable all analyzers. + # Default: false + enable-all: false + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + # disable: + + revive: + # Sets the default failure confidence. + # This means that linting errors with less than 0.8 confidence will be ignored. + # Default: 0.8 + confidence: 0.8 + + gofmt: + # Simplify code: gofmt with `-s` option. + # Default: true + simplify: true + + # Apply the rewrite rules to the source before reformatting. + # https://pkg.go.dev/cmd/gofmt + # Default: [] + rewrite-rules: + - pattern: "interface{}" + replacement: "any" + - pattern: "a[b:len(a)]" + replacement: "a[b:]" + + gocyclo: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 10 + + goconst: + # Minimal length of string constant. + # Default: 3 + min-len: 3 + + # Minimum occurrences of constant string count to trigger issue. + # Default: 3 + min-occurrences: 3 + + depguard: + # Kind of list is passed in. + # Allowed values: allowlist|denylist + # Default: denylist + list-type: denylist + + # Check the list against standard lib. + # Default: false + include-go-root: true + + # A list of packages for the list type specified. + # Can accept both string prefixes and string glob patterns. + # Default: [] + packages: + - "io/ioutil" + - "github.com/pkg/errors" + - "golang.org/x/xerrors" + - "golang.org/x/net/context" + - "golang.org/x/crypto/ed25519" + + misspell: + # Correct spellings using locale preferences for US or UK. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + # Default is to use a neutral variety of English. + locale: US + + lll: + # Max line length, lines longer will be reported. + # '\t' is counted as 1 character by default, and can be changed with the tab-width option. + # Default: 120. + line-length: 120 + # Tab width in spaces. + # Default: 1 + tab-width: 4 + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 30 + + prealloc: + # IMPORTANT: we don't recommend using this linter before doing performance profiling. + # For most programs usage of prealloc will be a premature optimization. + + # Report pre-allocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. + # Default: true + simple: true + # Report pre-allocation suggestions on range loops. + # Default: true + range-loops: true + # Report pre-allocation suggestions on for loops. + # Default: false + for-loops: true + + gci: + # Section configuration to compare against. + # Section names are case-insensitive and may contain parameters in (). + # The default order of sections is `standard > default > custom > blank > dot`, + # If `custom-order` is `true`, it follows the order of `sections` option. + # Default: ["standard", "default"] + sections: + - standard + - default + - prefix(github.com/spacemeshos/smcli) + + importas: + # Do not allow unaliased imports of aliased packages. + # Default: false + no-unaliased: false + # Do not allow non-required aliases. + # Default: false + no-extra-aliases: false + # List of aliases + # Default: [] + alias: + - pkg: "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + alias: chaos + - pkg: "github.com/hashicorp/golang-lru" + alias: lru + - pkg: "github.com/grpc-ecosystem/go-grpc-middleware" + alias: grpcmw + - pkg: "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" + alias: grpczap + - pkg: "github.com/grpc-ecosystem/go-grpc-middleware/tags" + alias: grpctags + - pkg: "github.com/libp2p/go-libp2p-pubsub" + alias: pubsub + - pkg: "github.com/libp2p/go-libp2p-pubsub/pb" + alias: pb + - pkg: "github.com/libp2p/go-libp2p/p2p/net/mock" + alias: mocknet + - pkg: "github.com/libp2p/go-libp2p-testing/netutil" + alias: p2putil + - pkg: "github.com/multiformats/go-multiaddr" + alias: ma + - pkg: "github.com/multiformats/go-multiaddr/net" + alias: manet + - pkg: "github.com/spacemeshos/api/release/go/spacemesh/v1" + alias: pb + - pkg: "github.com/spacemeshos/go-spacemesh/genvm" + alias: vm + - pkg: "github.com/spacemeshos/go-spacemesh/p2p/metrics" + alias: p2pmetrics + - pkg: "github.com/spacemeshos/go-spacemesh/sql/proposals" + alias: dbproposals + - pkg: "github.com/spacemeshos/go-spacemesh/sql/metrics" + alias: dbmetrics + - pkg: "github.com/spacemeshos/go-spacemesh/txs/types" + alias: txtypes + - pkg: "google.golang.org/genproto/googleapis/rpc/status" + alias: rpcstatus + - pkg: "k8s.io/apimachinery/pkg/apis/meta/v1" + alias: apimetav1 + - pkg: "k8s.io/api/apps/v1" + alias: apiappsv1 + - pkg: "k8s.io/api/core/v1" + alias: apiv1 + - pkg: "k8s.io/client-go/applyconfigurations/apps/v1" + alias: appsv1 + - pkg: "k8s.io/client-go/applyconfigurations/core/v1" + alias: corev1 + - pkg: "k8s.io/client-go/applyconfigurations/meta/v1" + alias: metav1 + + godot: + # Comments to be checked: `declarations`, `toplevel`, or `all`. + # Default: declarations + scope: declarations + # List of regexps for excluding particular comment lines from check. + # Default: [] + exclude: + # Exclude todo and fixme comments. + - "^FIXME:" + - "^TODO:" + # Check that each sentence ends with a period. + # Default: true + period: true + # Check that each sentence starts with a capital letter. + # Default: false + capital: false + + gofumpt: + # Choose whether to use the extra rules. + # Default: false + extra-rules: false + + gosec: + # To select a subset of rules to run. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] - means include all rules + # includes: + # To specify a set of rules to explicitly exclude. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] + # excludes: + # Exclude generated files + # Default: false + exclude-generated: false + # Filter out the issues with a lower severity than the given value. + # Valid options are: low, medium, high. + # Default: low + severity: medium + # Filter out the issues with a lower confidence than the given value. + # Valid options are: low, medium, high. + # Default: low + confidence: medium + # Concurrency value. + # Default: the number of logical CPUs usable by the current process. + concurrency: 12 + + whitespace: + # Enforces newlines (or comments) after every multi-line if statement. + # Default: false + multi-if: true + # Enforces newlines (or comments) after every multi-line function signature. + # Default: false + multi-func: true + + wrapcheck: + # An array of strings that specify substrings of signatures to ignore. + # If this set, it will override the default set of ignored signatures. + # See https://github.com/tomarrell/wrapcheck#configuration for more information. + # Default: [".Errorf(", "errors.New(", "errors.Unwrap(", ".Wrap(", ".Wrapf(", ".WithMessage(", ".WithMessagef(", ".WithStack("] + ignoreSigs: + - .Errorf( + - errors.New( + - .WithMessage( + - .WithMessagef( + - .WithStack( + +linters: + # Disable all linters. + # Default: false + disable-all: true + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default + enable: + - gci + - importas + - govet + - godot + - gofmt + - gofumpt + - revive + - misspell + - staticcheck + - unused + - ineffassign + - typecheck + - nakedret + - depguard + - goconst + # - whitespace + # - wrapcheck + # - errcheck + # - gosec + # - nakedret + # - gocyclo + # - lll + # - prealloc + + # Enable all available linters. + # Default: false + enable-all: false + + # Disable specific linter + # https://golangci-lint.run/usage/linters/#disabled-by-default + # disable: + + # Run only fast linters from enabled linters set (first run won't be fast) + # Default: false + fast: false + +issues: + # List of regexps of issue texts to exclude. + # + # But independently of this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. + # To list all excluded by default patterns execute `golangci-lint run --help` + # + # Default: https://golangci-lint.run/usage/false-positives/#default-exclusions + # exclude: + + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - linters: + - staticcheck + text: SA1019 + + # Independently of option `exclude` we use default exclude patterns, + # it can be disabled by this option. + # To list all excluded by default patterns execute `golangci-lint run --help`. + # Default: true. + exclude-use-default: false + + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 0 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing large codebase. + # It's not practical to fix all existing issues at the moment of integration: + # much better don't allow issues in new code. + # + # Default: false. + new: false diff --git a/Makefile b/Makefile index ae93c9f..0e0680d 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,17 @@ $(DOWNLOAD_DEST): $(MKDIR) $(UNZIP_DEST) curl -sSfL $(DEPLOC)/v$(DEPTAG)/$(FN) -o $(DOWNLOAD_DEST) +.PHONY: install +install: + go mod download + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.52.0 + go install gotest.tools/gotestsum@v1.9.0 + go install honnef.co/go/tools/cmd/staticcheck@v0.3.3 + +.PHONY: tidy +tidy: + go mod tidy + .PHONY: build build: $(UNZIP_DEST) $(CPREFIX) GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=1 go build -ldflags '$(LDFLAGS)' @@ -116,6 +127,38 @@ build: $(UNZIP_DEST) test: $(UNZIP_DEST) LD_LIBRARY_PATH=$(REAL_DEST) go test -v -ldflags "-extldflags \"-L$(REAL_DEST) -led25519_bip32\"" ./... +.PHONY: test-tidy +test-tidy: + # Working directory must be clean, or this test would be destructive + git diff --quiet || (echo "\033[0;31mWorking directory not clean!\033[0m" && git --no-pager diff && exit 1) + # We expect `go mod tidy` not to change anything, the test should fail otherwise + make tidy + git diff --exit-code || (git --no-pager diff && git checkout . && exit 1) + +.PHONY: test-fmt +test-fmt: + git diff --quiet || (echo "\033[0;31mWorking directory not clean!\033[0m" && git --no-pager diff && exit 1) + # We expect `go fmt` not to change anything, the test should fail otherwise + go fmt ./... + git diff --exit-code || (git --no-pager diff && git checkout . && exit 1) + +.PHONY: lint +lint: + ./bin/golangci-lint run --config .golangci.yml + +# Auto-fixes golangci-lint issues where possible. +.PHONY: lint-fix +lint-fix: + ./bin/golangci-lint run --config .golangci.yml --fix + +.PHONY: lint-github-action +lint-github-action: + ./bin/golangci-lint run --config .golangci.yml --out-format=github-actions + +.PHONY: staticcheck +staticcheck: + staticcheck ./... + clean: $(RM) $(DOWNLOAD_DEST) $(RMDIR) $(UNZIP_DEST) diff --git a/cmd/root.go b/cmd/root.go index 177fb3e..7192ce8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,20 +1,18 @@ -/* -Copyright © 2022 NAME HERE -*/ package cmd import ( "fmt" "os" - "github.com/spacemeshos/smcli/common" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/spacemeshos/smcli/common" ) var cfgFile string -// rootCmd represents the base command when called without any subcommands +// rootCmd represents the base command when called without any subcommands. var rootCmd = &cobra.Command{ Use: "sm", Short: "The official CLI for Spacemesh.", diff --git a/cmd/wallet.go b/cmd/wallet.go index 3ba4321..db5aa58 100644 --- a/cmd/wallet.go +++ b/cmd/wallet.go @@ -1,39 +1,38 @@ -/* -Copyright © 2022 NAME HERE -*/ package cmd import ( "encoding/hex" "errors" "fmt" + "log" + "os" + "strconv" + "strings" + "github.com/btcsuite/btcutil/base58" "github.com/hashicorp/go-secure-stdlib/password" "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "github.com/spacemeshos/smcli/common" "github.com/spacemeshos/smcli/wallet" - "github.com/spf13/cobra" - "log" - "os" - "strconv" - "strings" ) var ( - // debug indicates that the program is in debug mode - debug, + // debug indicates that the program is in debug mode. + debug bool - // printPrivate indicates that private keys should be printed - printPrivate, + // printPrivate indicates that private keys should be printed. + printPrivate bool - // printFull indicates that full keys should be printed (not abbreviated) - printFull, + // printFull indicates that full keys should be printed (not abbreviated). + printFull bool - // printBase58 indicates that keys should be printed in base58 format + // printBase58 indicates that keys should be printed in base58 format. printBase58 bool ) -// walletCmd represents the wallet command +// walletCmd represents the wallet command. var walletCmd = &cobra.Command{ Use: "wallet", Short: "A brief description of your command", @@ -45,7 +44,7 @@ This application is a tool to generate the needed files to quickly create a Cobra application.`, } -// createCmd represents the create command +// createCmd represents the create command. var createCmd = &cobra.Command{ Use: "create [numaccounts]", Short: "Generate a new wallet file from a BIP-39-compatible mnemonic", @@ -93,7 +92,7 @@ You can choose to use an existing mnemonic or generate a new, random mnemonic.`, fmt.Println() cobra.CheckErr(err) wk := wallet.NewKey(wallet.WithRandomSalt(), wallet.WithPbkdf2Password([]byte(password))) - err = os.MkdirAll(common.DotDirectory(), 0700) + err = os.MkdirAll(common.DotDirectory(), 0o700) cobra.CheckErr(err) // Make sure we're not overwriting an existing wallet (this should not happen) @@ -109,7 +108,7 @@ You can choose to use an existing mnemonic or generate a new, random mnemonic.`, } // Now open for writing - f2, err := os.OpenFile(walletFn, os.O_WRONLY|os.O_CREATE, 0600) + f2, err := os.OpenFile(walletFn, os.O_WRONLY|os.O_CREATE, 0o600) cobra.CheckErr(err) defer f2.Close() cobra.CheckErr(wk.Export(f2, w)) @@ -118,7 +117,7 @@ You can choose to use an existing mnemonic or generate a new, random mnemonic.`, }, } -// readCmd reads an existing wallet file +// readCmd reads an existing wallet file. var readCmd = &cobra.Command{ Use: "read [wallet file] [--full/-f] [--private/-p] [--base58]", Short: "Reads an existing wallet file", @@ -126,15 +125,15 @@ var readCmd = &cobra.Command{ successfully read and decrypted, whether the password to open the file is correct, etc. It prints the accounts from the wallet file. By default it does not print private keys. Add --private to print private keys. Add --full to print full keys. Add --base58 to print -keys in base58 format rather than hexidecimal.`, +keys in base58 format rather than hexadecimal.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { walletFn := args[0] // make sure the file exists f, err := os.Open(walletFn) - defer f.Close() cobra.CheckErr(err) + defer f.Close() // get the password fmt.Print("Enter wallet password: ") diff --git a/common/common.go b/common/common.go index 0c03111..c84eec5 100644 --- a/common/common.go +++ b/common/common.go @@ -30,15 +30,19 @@ func DotDirectory() string { home, _ := os.UserHomeDir() return filepath.Join(home + "/.spacemesh") } + func ConfigFileName() string { return "config" } + func ConfigFileType() string { return "yaml" } + func StateFile() string { return filepath.Join(DotDirectory(), "state.json") } + func WalletFile() string { return filepath.Join(DotDirectory(), "wallet_"+NowTimeString()+".json") } diff --git a/go.mod b/go.mod index 797f4c8..b435aaa 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/xdg-go/pbkdf2 v1.0.0 golang.org/x/crypto v0.8.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 225a563..3f09e27 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/main.go b/main.go index 40b7b3b..7bbd8d9 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,3 @@ -/* -Copyright © 2022 NAME HERE -*/ package main import "github.com/spacemeshos/smcli/cmd" diff --git a/wallet/bip32.go b/wallet/bip32.go index bbdf21c..262262e 100644 --- a/wallet/bip32.go +++ b/wallet/bip32.go @@ -5,8 +5,10 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/spacemeshos/smcli/common" + smbip32 "github.com/spacemeshos/smkeys/bip32" + + "github.com/spacemeshos/smcli/common" ) // Function names inspired by https://github.com/tyler-smith/go-bip32/blob/master/bip32.go diff --git a/wallet/bip32_test.go b/wallet/bip32_test.go index e87ed43..077eb88 100644 --- a/wallet/bip32_test.go +++ b/wallet/bip32_test.go @@ -3,9 +3,9 @@ package wallet import ( "crypto/ed25519" "encoding/hex" - "github.com/spacemeshos/smkeys/bip32" "testing" + "github.com/spacemeshos/smkeys/bip32" "github.com/stretchr/testify/require" ) @@ -23,7 +23,7 @@ func TestNonHardenedPath(t *testing.T) { require.False(t, IsPathCompletelyHardened(path2Hd)) } -// Test that path string produces expected path and vice-versa +// Test that path string produces expected path and vice-versa. func TestPath(t *testing.T) { path1Str := "m/44'/540'/0'/0'/0'" path1Hd, err := StringToHDPath(path1Str) @@ -35,7 +35,7 @@ func TestPath(t *testing.T) { require.Equal(t, path1Hd, path2Hd) } -// Test deriving a child keypair +// Test deriving a child keypair. func TestChildKeyPair(t *testing.T) { defaultPath := DefaultPath() path := defaultPath.Extend(BIP44HardenedAccountIndex(0)) diff --git a/wallet/bip44.go b/wallet/bip44.go index 3c44df0..04249fb 100644 --- a/wallet/bip44.go +++ b/wallet/bip44.go @@ -6,6 +6,8 @@ import ( "regexp" ) +//lint:file-ignore SA4016 ignore ineffective bitwise operations to aid readability + // BIP32HardenedKeyStart: keys with index >= this must be hardened as per BIP32. // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys const BIP32HardenedKeyStart uint32 = 0x80000000 @@ -40,15 +42,19 @@ func (p *HDPath) String() string { func (p *HDPath) Purpose() uint32 { return (*p)[HDPurposeSegment] } + func (p *HDPath) CoinType() uint32 { return (*p)[HDCoinTypeSegment] } + func (p *HDPath) Account() uint32 { return (*p)[HDAccountSegment] } + func (p *HDPath) Chain() uint32 { return (*p)[HDChainSegment] } + func (p *HDPath) Index() uint32 { return (*p)[HDIndexSegment] } @@ -59,7 +65,7 @@ func (p *HDPath) Extend(idx uint32) HDPath { // Root of the path is m/purpose' (m/44') // https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#purpose -// We use this to indicate that we're using the BIP44 hierarchy +// We use this to indicate that we're using the BIP44 hierarchy. func BIP44Purpose() uint32 { return 0x8000002C } @@ -71,8 +77,9 @@ func BIP44SpacemeshCoinType() uint32 { } // After the coin type comes the account (m/44'/540'/account') -// For now we only support account 0' +// For now we only support account 0'. func BIP44Account() uint32 { + //nolint:staticcheck // ignore ineffective bitwise operations to aid readability return BIP32HardenedKeyStart | 0 } @@ -88,13 +95,14 @@ func BIP44Account() uint32 { // (m/44'/540'/account'/chain') // For now we only support "chain" 0. We may want to use a different chain for testnet. func BIP44HardenedChain() uint32 { + //nolint:staticcheck // ignore ineffective bitwise operations to aid readability return BIP32HardenedKeyStart | 0 } // After the Hardened Chain level comes the address indices, as of now, we don't // support un-hardened derivation so we'll continue our deviation from the spec // here. All addresses will be hardened. -// (m/44'/540'/account'/chain'/address_index') +// (m/44'/540'/account'/chain'/address_index'). func BIP44HardenedAccountIndex(hai uint32) uint32 { return BIP32HardenedKeyStart | hai } @@ -118,7 +126,7 @@ func IsPathCompletelyHardened(path HDPath) bool { } // HDPathToString converts a BIP44 HD path to a string of the form -// "m/44'/540'/account'/chain'/address_index'" +// "m/44'/540'/account'/chain'/address_index'". func HDPathToString(path HDPath) string { s := "m" for _, p := range path { @@ -138,7 +146,7 @@ func parseUint(s string) uint { } // StringToHDPath converts a BIP44 HD path string of the form -// (m/44'/540'/account'/chain'/address_index') to its uint32 slice representation +// (m/44'/540'/account'/chain'/address_index') to its uint32 slice representation. func StringToHDPath(s string) (HDPath, error) { // regex of the form m/44'/540'/account'/chain'/address_index' rWholePath := regexp.MustCompile(`^m(/\d+'?)+$`) diff --git a/wallet/bip44_test.go b/wallet/bip44_test.go index b1fc2fc..e398d7d 100644 --- a/wallet/bip44_test.go +++ b/wallet/bip44_test.go @@ -1,8 +1,9 @@ package wallet import ( - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" ) func TestHDPathToString(t *testing.T) { diff --git a/wallet/store.go b/wallet/store.go index d21b5a5..4213e10 100644 --- a/wallet/store.go +++ b/wallet/store.go @@ -19,19 +19,23 @@ import ( const EncKeyLen = 32 // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 -const Pbkdf2Iterations = 210000 -const Pbkdf2Dklen = 256 -const Pbkdf2SaltBytesLen = 16 +const ( + Pbkdf2Iterations = 210000 + Pbkdf2Dklen = 256 + Pbkdf2SaltBytesLen = 16 +) var Pbkdf2HashFunc = sha512.New -type WalletKeyOpt func(*WalletKey) -type WalletKey struct { - key []byte - pw []byte - salt []byte - iterations int -} +type ( + WalletKeyOpt func(*WalletKey) + WalletKey struct { + key []byte + pw []byte + salt []byte + iterations int + } +) func NewKey(opts ...WalletKeyOpt) WalletKey { w := &WalletKey{} @@ -156,10 +160,10 @@ func (k *WalletKey) decrypt(ciphertext []byte, nonce []byte) (plaintext []byte, return } -func (k *WalletKey) Open(file io.Reader, debugMode bool) (w *Wallet, err error) { +func (k *WalletKey) Open(file io.Reader, debugMode bool) (*Wallet, error) { ew := &EncryptedWalletFile{} - if err = json.NewDecoder(file).Decode(ew); err != nil { - return + if err := json.NewDecoder(file).Decode(ew); err != nil { + return nil, err } // set the salt, and warn if it's different @@ -184,22 +188,22 @@ func (k *WalletKey) Open(file io.Reader, debugMode bool) (w *Wallet, err error) // TODO: before decrypting, check that other meta params match plaintext, err := k.decrypt(encWallet, nonce) if err != nil { - return + return nil, err } if debugMode { log.Println("Decrypted JSON data:", string(plaintext)) } secrets := &walletSecrets{} - if err = json.Unmarshal(plaintext, secrets); err != nil { - return + if err := json.Unmarshal(plaintext, secrets); err != nil { + return nil, err } // we have everything we need, construct and return the wallet. - w = &Wallet{ + w := &Wallet{ Meta: ew.Meta, Secrets: *secrets, } - return + return w, nil } func (k *WalletKey) Export(file io.Writer, w *Wallet) (err error) { diff --git a/wallet/wallet.go b/wallet/wallet.go index cb29d53..ed7fd61 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -5,20 +5,22 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/spacemeshos/smcli/common" + "github.com/tyler-smith/go-bip39" + + "github.com/spacemeshos/smcli/common" ) // Wallet is the basic data structure. type Wallet struct { - //keystore string - //password string - //unlocked bool + // keystore string + // password string + // unlocked bool Meta walletMetadata `json:"meta"` Secrets walletSecrets `json:"crypto"` } -// EncryptedWalletFile is the encrypted representation of the wallet on the filesystem +// EncryptedWalletFile is the encrypted representation of the wallet on the filesystem. type EncryptedWalletFile struct { Meta walletMetadata `json:"meta"` Secrets walletSecretsEncrypted `json:"crypto"` @@ -28,11 +30,11 @@ type walletMetadata struct { DisplayName string `json:"displayName"` Created string `json:"created"` GenesisID string `json:"genesisID"` - //NetID int `json:"netId"` + // NetID int `json:"netId"` // is this needed? - //Type WalletType - //RemoteAPI string + // Type WalletType + // RemoteAPI string } type hexEncodedCiphertext []byte diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index c15e8b1..7119466 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -2,13 +2,12 @@ package wallet import ( "crypto/ed25519" - "fmt" - "github.com/tyler-smith/go-bip39" - "encoding/hex" + "fmt" "testing" "github.com/stretchr/testify/require" + "github.com/tyler-smith/go-bip39" ) const Bip44Prefix = "m/44'/540'" @@ -41,10 +40,8 @@ func TestAccountFromSeed(t *testing.T) { require.Len(t, accts, 1) keypair := accts[0] - expPubKey := - "feae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" - expPrivKey := - "05fe9affa5562ca833faf3803ce5f6f7615d3c37c4a27903492027f6853e486dfeae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" + expPubKey := "feae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" + expPrivKey := "05fe9affa5562ca833faf3803ce5f6f7615d3c37c4a27903492027f6853e486dfeae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" actualPubKey := hex.EncodeToString(keypair.Public) actualPrivKey := hex.EncodeToString(keypair.Private) @@ -78,10 +75,8 @@ func TestWalletFromGivenMnemonic(t *testing.T) { mnemonic := "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding" w, err := NewMultiWalletFromMnemonic(mnemonic, 1) require.NoError(t, err) - expPubKey := - "de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" - expPrivKey := - "cd85df73aa3bc31de2f0b69bb1421df7eb0cdca7cb170a457869ab337749dae1de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" + expPubKey := "de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" + expPrivKey := "cd85df73aa3bc31de2f0b69bb1421df7eb0cdca7cb170a457869ab337749dae1de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" actualPubKey := hex.EncodeToString(w.Secrets.Accounts[0].Public) actualPrivKey := hex.EncodeToString(w.Secrets.Accounts[0].Private)