├── main.go ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── ci.yml ├── .gitignore ├── LICENSE ├── common └── common.go ├── wallet ├── bip44_test.go ├── store_test.go ├── bip32_test.go ├── bip32.go ├── wallet_test.go ├── bip44.go ├── wallet.go └── store.go ├── cmd ├── root.go ├── genesis.go └── wallet.go ├── go.mod ├── README.md ├── Makefile ├── go.sum └── .golangci.yml /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/spacemeshos/smcli/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | bin/ 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | deps/ 21 | 22 | # Go workspace file 23 | go.work 24 | 25 | .idea 26 | 27 | # Default build artifact 28 | smcli -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Spacemesh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "time" 7 | ) 8 | 9 | const ( 10 | // MaxAccountsPerWallet is the maximum number of accounts that a single wallet file may contain. 11 | // It's relatively arbitrary but we need some limit. 12 | MaxAccountsPerWallet = 128 13 | ) 14 | 15 | func NowTimeString() string { 16 | return time.Now().UTC().Format("2006-01-02T15-04-05.000") + "Z" 17 | } 18 | 19 | // .spacemesh 20 | // ├── bin 21 | // │ └── [ Linux | macOS | Windows ] 22 | // │ ├── go-spacemesh 23 | // │ └── config.json 24 | // ├── logs 25 | // │ └── go-spacemesh.log 26 | // ├── config.yaml 27 | // └── state.json 28 | 29 | func DotDirectory() string { 30 | home, _ := os.UserHomeDir() 31 | return filepath.Join(home + "/.spacemesh") 32 | } 33 | 34 | func ConfigFileName() string { 35 | return "config" 36 | } 37 | 38 | func ConfigFileType() string { 39 | return "yaml" 40 | } 41 | 42 | func StateFile() string { 43 | return filepath.Join(DotDirectory(), "state.json") 44 | } 45 | 46 | func WalletFile() string { 47 | return filepath.Join(DotDirectory(), "wallet_"+NowTimeString()+".json") 48 | } 49 | -------------------------------------------------------------------------------- /wallet/bip44_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestHDPathToString(t *testing.T) { 10 | s := HDPathToString(HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, 0, 0, 0}) 11 | require.Equal(t, "m/44'/540'/0/0/0", s) 12 | } 13 | 14 | func TestStringToHDPath(t *testing.T) { 15 | testVectors := []struct { 16 | path string 17 | expected HDPath 18 | }{ 19 | { 20 | "m/44'/540'", 21 | HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540}, 22 | }, 23 | { 24 | "m/44'/540'/0", 25 | HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, 0}, 26 | }, 27 | { 28 | "m/44'/540'/0'/0'/0'", 29 | HDPath{ 30 | BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, BIP32HardenedKeyStart, 31 | BIP32HardenedKeyStart, BIP32HardenedKeyStart, 32 | }, 33 | }, 34 | { 35 | "m/44'/540'/0'/0/0", 36 | HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, BIP32HardenedKeyStart, 0, 0}, 37 | }, 38 | { 39 | "m/44'/540'/0/0'/0", 40 | HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, 0, BIP32HardenedKeyStart, 0}, 41 | }, 42 | { 43 | "m/44'/540'/2'/0/0", 44 | HDPath{BIP32HardenedKeyStart | 44, BIP32HardenedKeyStart | 540, BIP32HardenedKeyStart | 2, 0, 0}, 45 | }, 46 | } 47 | 48 | for _, tv := range testVectors { 49 | t.Run(tv.path, func(t *testing.T) { 50 | p, err := StringToHDPath(tv.path) 51 | require.NoError(t, err) 52 | require.Equal(t, tv.expected, p) 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | strategy: 10 | matrix: 11 | # build and publish in parallel 12 | include: 13 | - image: macos-latest 14 | name: macos-amd64 15 | - image: ubuntu-22.04 16 | name: linux-amd64 17 | - image: windows-latest 18 | name: windows-amd64 19 | binaryextension: .exe 20 | - image: [self-hosted, macos, arm64] 21 | name: macos-arm64 22 | - image: ubuntu-latest-arm-8-cores 23 | name: linux-arm64 24 | env: 25 | BINARY_NAME: smcli${{ matrix.binaryextension }} 26 | ARTIFACT_NAME: smcli-${{ github.event.release.tag_name }}-${{ matrix.name }}.tar.gz 27 | runs-on: ${{ matrix.image }} 28 | name: Release ${{ matrix.name }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | - name: Set up go 33 | uses: actions/setup-go@v5 34 | with: 35 | check-latest: true 36 | go-version-file: "go.mod" 37 | - name: Install required packages 38 | # only run on GH-hosted runner; self-hosted runner already has these 39 | if: ${{ matrix.name == 'linux-amd64' || matrix.name == 'linux-arm64' }} 40 | run: sudo apt-get install -y libudev-dev 41 | - name: Build 42 | run: make build 43 | - name: Prepare files 44 | shell: bash 45 | run: | 46 | mkdir artifacts 47 | mv LICENSE README.md ${{ env.BINARY_NAME }} artifacts 48 | cd artifacts 49 | tar -czf ${{ env.ARTIFACT_NAME }} * 50 | mv ${{ env.ARTIFACT_NAME }} .. 51 | - name: Release 52 | uses: softprops/action-gh-release@v2 53 | with: 54 | files: ${{ env.ARTIFACT_NAME }} 55 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the main branch 5 | on: 6 | pull_request: 7 | push: 8 | branches: [develop] 9 | 10 | jobs: 11 | quicktests: 12 | runs-on: ubuntu-22.04 13 | timeout-minutes: 2 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | - name: set up go 18 | uses: actions/setup-go@v5 19 | with: 20 | check-latest: true 21 | go-version-file: "go.mod" 22 | - name: fmt, tidy 23 | run: | 24 | make install 25 | make test-fmt 26 | make test-tidy 27 | 28 | lint: 29 | runs-on: ubuntu-22.04 30 | timeout-minutes: 2 31 | steps: 32 | - name: checkout 33 | uses: actions/checkout@v4 34 | - name: set up go 35 | uses: actions/setup-go@v5 36 | with: 37 | check-latest: true 38 | go-version-file: "go.mod" 39 | - name: lint 40 | run: | 41 | make install 42 | make lint 43 | 44 | build: 45 | runs-on: ubuntu-22.04 46 | timeout-minutes: 2 47 | steps: 48 | - name: checkout 49 | uses: actions/checkout@v4 50 | - name: install udev 51 | run: sudo apt-get install -y libudev-dev 52 | - name: set up go 53 | uses: actions/setup-go@v5 54 | with: 55 | check-latest: true 56 | go-version-file: "go.mod" 57 | - name: build 58 | run: make build 59 | 60 | test: 61 | runs-on: ubuntu-22.04 62 | timeout-minutes: 2 63 | steps: 64 | - name: checkout 65 | uses: actions/checkout@v4 66 | - name: set up go 67 | uses: actions/setup-go@v5 68 | with: 69 | check-latest: true 70 | go-version-file: "go.mod" 71 | - name: go test 72 | run: make test 73 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | 10 | "github.com/spacemeshos/smcli/common" 11 | ) 12 | 13 | var cfgFile string 14 | 15 | // rootCmd represents the base command when called without any subcommands. 16 | var rootCmd = &cobra.Command{ 17 | Use: "smcli", 18 | Short: "The official CLI for Spacemesh.", 19 | Long: `smcli is your terminal's connection to the Spacemesh network. 20 | 21 | This tool provides an ergonomic set of tools for managing a wallet.`, 22 | // Uncomment the following line if your bare application 23 | // has an action associated with it: 24 | // Run: func(cmd *cobra.Command, args []string) { 25 | // fmt.Println("Hello world!") 26 | // }, 27 | } 28 | 29 | // Execute adds all child commands to the root command and sets flags appropriately. 30 | // This is called by main.main(). It only needs to happen once to the rootCmd. 31 | func Execute() { 32 | err := rootCmd.Execute() 33 | if err != nil { 34 | os.Exit(1) 35 | } 36 | } 37 | 38 | func init() { 39 | cobra.OnInitialize(initConfig) 40 | 41 | // Here you will define your flags and configuration settings. 42 | // Cobra supports persistent flags, which, if defined here, 43 | // will be common for your application. 44 | 45 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.smcli.yaml)") 46 | 47 | // Cobra also supports local flags, which will only run 48 | // when this action is called directly. 49 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 50 | } 51 | 52 | // initConfig reads in config file and ENV variables if set. 53 | func initConfig() { 54 | if cfgFile != "" { 55 | // Use config file from the flag. 56 | viper.SetConfigFile(cfgFile) 57 | } else { 58 | 59 | // Search config in home directory with name ".smcli" (without extension). 60 | viper.AddConfigPath(common.DotDirectory()) 61 | viper.SetConfigType(common.ConfigFileType()) 62 | viper.SetConfigName(common.ConfigFileName()) 63 | } 64 | 65 | viper.AutomaticEnv() // read in environment variables that match 66 | // If a config file is found, read it in. 67 | if err := viper.ReadInConfig(); err == nil { 68 | fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /wallet/store_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestStoreAndRetrieveWalletToFromFile(t *testing.T) { 15 | saltSlice, _ := hex.DecodeString("0102030405060708090a0b0c0d0e0f10") 16 | password, _ := hex.DecodeString("70617373776f7264") 17 | var salt, salt2 [Pbkdf2SaltBytesLen]byte 18 | copy(salt[:], saltSlice) 19 | 20 | wKey := NewKey( 21 | WithSalt(salt), 22 | WithPbkdf2Password(password), 23 | ) 24 | 25 | w, err := NewMultiWalletRandomMnemonic(1) 26 | require.NoError(t, err) 27 | 28 | file, err := os.CreateTemp("./", "test_wallet.*.json") 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | defer os.Remove(file.Name()) 33 | 34 | fmt.Println(file.Name()) 35 | err = wKey.Export(file, w) 36 | require.NoError(t, err) 37 | 38 | file.Seek(0, io.SeekStart) 39 | w2, err := wKey.Open(file, false) 40 | require.NoError(t, err) 41 | require.Equal(t, w.Secrets.Accounts, w2.Secrets.Accounts) 42 | require.Equal(t, w.Secrets.Mnemonic, w2.Secrets.Mnemonic) 43 | 44 | // trying to open with a different wallet key, same pw and nonce, should work 45 | wKey = NewKey( 46 | WithSalt(salt), 47 | WithPbkdf2Password(password), 48 | ) 49 | file.Seek(0, io.SeekStart) 50 | w2, err = wKey.Open(file, false) 51 | require.NoError(t, err) 52 | require.Equal(t, w.Secrets.Accounts, w2.Secrets.Accounts) 53 | require.Equal(t, w.Secrets.Mnemonic, w2.Secrets.Mnemonic) 54 | 55 | // trying to open the same file with a different key or nonce should fail 56 | password2 := password[:] 57 | password2[0]++ 58 | 59 | // right salt, wrong password 60 | wKey = NewKey( 61 | WithSalt(salt), 62 | WithPbkdf2Password(password2), 63 | ) 64 | file.Seek(0, io.SeekStart) 65 | _, err = wKey.Open(file, false) 66 | require.Error(t, err) 67 | 68 | // right password, wrong salt 69 | copy(salt2[:], saltSlice) 70 | salt2[0]++ 71 | wKey = NewKey( 72 | WithSalt(salt2), 73 | WithPbkdf2Password(password), 74 | ) 75 | file.Seek(0, io.SeekStart) 76 | _, err = wKey.Open(file, false) 77 | require.Error(t, err) 78 | 79 | // both wrong 80 | wKey = NewKey( 81 | WithSalt(salt2), 82 | WithPbkdf2Password(password2), 83 | ) 84 | file.Seek(0, io.SeekStart) 85 | _, err = wKey.Open(file, false) 86 | require.Error(t, err) 87 | } 88 | -------------------------------------------------------------------------------- /wallet/bip32_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/spacemeshos/smkeys/bip32" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | var goodSeed = []byte("abandon abandon abandon abandon abandon abandon abandon abandon ") 13 | 14 | func TestNonHardenedPath(t *testing.T) { 15 | path1Str := "m/44'/540'/0'/0'/0'" 16 | path1Hd, err := StringToHDPath(path1Str) 17 | require.NoError(t, err) 18 | require.True(t, IsPathCompletelyHardened(path1Hd)) 19 | 20 | path2Str := "m/44'/540'/0'/0'/0" 21 | path2Hd, err := StringToHDPath(path2Str) 22 | require.NoError(t, err) 23 | require.False(t, IsPathCompletelyHardened(path2Hd)) 24 | } 25 | 26 | // Test that path string produces expected path and vice-versa. 27 | func TestPath(t *testing.T) { 28 | path1Str := "m/44'/540'/0'/0'/0'" 29 | path1Hd, err := StringToHDPath(path1Str) 30 | require.NoError(t, err) 31 | path := DefaultPath() 32 | path2Hd := path.Extend(BIP44HardenedAccountIndex(0)) 33 | path2Str := HDPathToString(path2Hd) 34 | require.Equal(t, path1Str, path2Str) 35 | require.Equal(t, path1Hd, path2Hd) 36 | } 37 | 38 | // Test deriving a child keypair. 39 | func TestChildKeyPair(t *testing.T) { 40 | defaultPath := DefaultPath() 41 | path := defaultPath.Extend(BIP44HardenedAccountIndex(0)) 42 | 43 | // generate first keypair 44 | masterKeyPair, err := NewMasterKeyPair(goodSeed) 45 | require.NoError(t, err) 46 | childKeyPair1, err := masterKeyPair.NewChildKeyPair(goodSeed, 0) 47 | require.Equal(t, path, childKeyPair1.Path) 48 | require.Len(t, childKeyPair1.Private, ed25519.PrivateKeySize) 49 | require.Len(t, childKeyPair1.Public, ed25519.PublicKeySize) 50 | require.NoError(t, err) 51 | require.NotEmpty(t, childKeyPair1) 52 | 53 | // test signing 54 | msg := []byte("child test") 55 | sig := ed25519.Sign(ed25519.PrivateKey(childKeyPair1.Private), msg) 56 | valid := ed25519.Verify(ed25519.PublicKey(childKeyPair1.Public), msg, sig) 57 | require.True(t, valid) 58 | 59 | // generate second keypair and check lengths 60 | childKeyPair2, err := bip32.Derive(HDPathToString(path), goodSeed) 61 | require.NoError(t, err) 62 | require.Len(t, childKeyPair2, ed25519.PrivateKeySize) 63 | privkey2 := PrivateKey(childKeyPair2[:]) 64 | require.Len(t, privkey2, ed25519.PrivateKeySize) 65 | edpubkey2 := ed25519.PrivateKey(privkey2).Public().(ed25519.PublicKey) 66 | require.Len(t, edpubkey2, ed25519.PublicKeySize) 67 | pubkey2 := PublicKey(edpubkey2) 68 | require.Len(t, pubkey2, ed25519.PublicKeySize) 69 | 70 | // make sure they agree 71 | require.Equal(t, "feae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd", hex.EncodeToString(pubkey2)) 72 | require.Equal(t, hex.EncodeToString(childKeyPair1.Public), hex.EncodeToString(pubkey2)) 73 | //nolint:lll 74 | require.Equal(t, "05fe9affa5562ca833faf3803ce5f6f7615d3c37c4a27903492027f6853e486dfeae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd", hex.EncodeToString(privkey2)) 75 | require.Equal(t, hex.EncodeToString(childKeyPair1.Private), hex.EncodeToString(privkey2)) 76 | } 77 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spacemeshos/smcli 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/btcsuite/btcutil v1.0.2 7 | github.com/jedib0t/go-pretty/v6 v6.6.7 8 | github.com/spacemeshos/economics v0.1.4 9 | github.com/spacemeshos/go-spacemesh v1.8.3 10 | github.com/spacemeshos/smkeys v1.0.4 11 | github.com/stretchr/testify v1.10.0 12 | ) 13 | 14 | require ( 15 | github.com/anacrolix/chansync v0.3.0 // indirect 16 | github.com/anacrolix/missinggo v1.2.1 // indirect 17 | github.com/anacrolix/missinggo/perf v1.0.0 // indirect 18 | github.com/anacrolix/sync v0.3.0 // indirect 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/c0mm4nd/go-ripemd v0.0.0-20200326052756-bd1759ad7d10 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/cosmos/btcutil v1.0.5 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/fsnotify/fsnotify v1.8.0 // indirect 25 | github.com/go-llsqlite/crawshaw v0.5.5 // indirect 26 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 27 | github.com/google/go-cmp v0.7.0 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/hashicorp/go-secure-stdlib/password v0.1.4 30 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 31 | github.com/huandu/xstrings v1.2.0 // indirect 32 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 33 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 34 | github.com/mattn/go-runewidth v0.0.16 // indirect 35 | github.com/minio/sha256-simd v1.0.1 // indirect 36 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 37 | github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect 38 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 39 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 40 | github.com/prometheus/client_golang v1.22.0 // indirect 41 | github.com/prometheus/client_model v0.6.2 // indirect 42 | github.com/prometheus/common v0.63.0 // indirect 43 | github.com/prometheus/procfs v0.15.1 // indirect 44 | github.com/rivo/uniseg v0.4.7 // indirect 45 | github.com/sagikazarmark/locafero v0.7.0 // indirect 46 | github.com/sourcegraph/conc v0.3.0 // indirect 47 | github.com/spacemeshos/go-scale v1.3.0 // indirect 48 | github.com/spacemeshos/merkle-tree v0.2.6 // indirect 49 | github.com/spacemeshos/poet v0.10.12 // indirect 50 | github.com/spacemeshos/post v0.13.1 // indirect 51 | github.com/spacemeshos/sha256-simd v0.1.0 // indirect 52 | github.com/spf13/afero v1.14.0 // indirect 53 | github.com/spf13/cast v1.7.1 // indirect 54 | github.com/spf13/cobra v1.9.1 55 | github.com/spf13/pflag v1.0.6 // indirect 56 | github.com/spf13/viper v1.20.1 57 | github.com/subosito/gotenv v1.6.0 // indirect 58 | github.com/tyler-smith/go-bip39 v1.1.0 59 | github.com/xdg-go/pbkdf2 v1.0.0 60 | github.com/zeebo/blake3 v0.2.4 // indirect 61 | go.uber.org/mock v0.5.1 // indirect 62 | go.uber.org/multierr v1.11.0 // indirect 63 | go.uber.org/zap v1.27.0 // indirect 64 | golang.org/x/crypto v0.36.0 // indirect 65 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect 66 | golang.org/x/sync v0.13.0 // indirect 67 | golang.org/x/sys v0.31.0 // indirect 68 | golang.org/x/term v0.30.0 // indirect 69 | golang.org/x/text v0.23.0 // indirect 70 | google.golang.org/protobuf v1.36.6 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /cmd/genesis.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/spacemeshos/economics/constants" 10 | "github.com/spacemeshos/go-spacemesh/common/types" 11 | "github.com/spacemeshos/go-spacemesh/genvm/core" 12 | "github.com/spacemeshos/go-spacemesh/genvm/templates/multisig" 13 | "github.com/spacemeshos/go-spacemesh/genvm/templates/vault" 14 | "github.com/spacemeshos/go-spacemesh/genvm/templates/vesting" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | // genesisCmd represents the wallet command. 19 | var genesisCmd = &cobra.Command{ 20 | Use: "genesis", 21 | Short: "Genesis-related utilities", 22 | } 23 | 24 | // createCmd represents the create command. 25 | var verifyCmd = &cobra.Command{ 26 | Use: "verify", 27 | Short: "Verify a genesis ledger account", 28 | Args: cobra.MaximumNArgs(0), 29 | Run: func(cmd *cobra.Command, args []string) { 30 | var err error 31 | 32 | // first, collect the keys 33 | var keys []core.PublicKey 34 | fmt.Print("First, let's collect your public keys. ") 35 | fmt.Print("Keys must be entered in hex format: 64 characters, without 0x prefix.\n") 36 | fmt.Print("Enter pub keys one at a time; press enter again when done: ") 37 | for { 38 | var keyStr string 39 | _, err := fmt.Scanln(&keyStr) 40 | if err != nil { 41 | break 42 | } 43 | keyBytes, err := hex.DecodeString(keyStr) 44 | if err != nil || len(keyBytes) != ed25519.PublicKeySize { 45 | log.Fatalln("Error: key is unreadable") 46 | } 47 | key := [ed25519.PublicKeySize]byte{} 48 | copy(key[:], keyBytes) 49 | keys = append(keys, key) 50 | fmt.Printf("[enter next key or just press enter to end] > ") 51 | } 52 | if len(keys) == 0 { 53 | log.Fatalln("Error: must enter at least one key") 54 | } 55 | 56 | // next collect multisig params 57 | m := uint8(1) 58 | if len(keys) > 1 { 59 | fmt.Printf("Enter number of required signatures (between 1 and %d): ", len(keys)) 60 | _, err := fmt.Scanln(&m) 61 | cobra.CheckErr(err) 62 | } 63 | 64 | // finally, collect amount 65 | var amount uint64 66 | fmt.Printf("Enter vault balance (denominated in SMH): ") 67 | _, err = fmt.Scanln(&amount) 68 | cobra.CheckErr(err) 69 | amount *= constants.OneSmesh 70 | 71 | // calculate keys 72 | vestingArgs := &multisig.SpawnArguments{ 73 | Required: m, 74 | PublicKeys: keys, 75 | } 76 | if int(vestingArgs.Required) > len(vestingArgs.PublicKeys) { 77 | log.Fatalf("requires more signatures (%d) than public keys (%d) in the wallet\n", 78 | vestingArgs.Required, 79 | len(vestingArgs.PublicKeys), 80 | ) 81 | } 82 | vestingAddress := core.ComputePrincipal(vesting.TemplateAddress, vestingArgs) 83 | vaultArgs := &vault.SpawnArguments{ 84 | Owner: vestingAddress, 85 | TotalAmount: amount, 86 | InitialUnlockAmount: amount / 4, 87 | VestingStart: types.LayerID(constants.VestStart), 88 | VestingEnd: types.LayerID(constants.VestEnd), 89 | } 90 | vaultAddress := core.ComputePrincipal(vault.TemplateAddress, vaultArgs) 91 | 92 | // output addresses 93 | fmt.Printf("Vesting address: %s\nVault address: %s\n", 94 | vestingAddress.String(), 95 | vaultAddress.String()) 96 | }, 97 | } 98 | 99 | func init() { 100 | rootCmd.AddCommand(genesisCmd) 101 | genesisCmd.AddCommand(verifyCmd) 102 | } 103 | -------------------------------------------------------------------------------- /wallet/bip32.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | 10 | smbip32 "github.com/spacemeshos/smkeys/bip32" 11 | ledger "github.com/spacemeshos/smkeys/remote-wallet" 12 | 13 | "github.com/spacemeshos/smcli/common" 14 | ) 15 | 16 | // Function names inspired by https://github.com/tyler-smith/go-bip32/blob/master/bip32.go 17 | // We assume all keys are hardened. 18 | 19 | type PublicKey ed25519.PublicKey 20 | 21 | type keyType int 22 | 23 | const ( 24 | typeSoftware keyType = iota 25 | typeLedger 26 | ) 27 | 28 | func (k *PublicKey) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(hex.EncodeToString(*k)) 30 | } 31 | 32 | func (k *PublicKey) UnmarshalJSON(data []byte) (err error) { 33 | var hexString string 34 | if err = json.Unmarshal(data, &hexString); err != nil { 35 | return 36 | } 37 | *k, err = hex.DecodeString(hexString) 38 | return 39 | } 40 | 41 | type PrivateKey ed25519.PrivateKey 42 | 43 | func (k *PrivateKey) MarshalJSON() ([]byte, error) { 44 | return json.Marshal(hex.EncodeToString(*k)) 45 | } 46 | 47 | func (k *PrivateKey) UnmarshalJSON(data []byte) (err error) { 48 | var hexString string 49 | if err = json.Unmarshal(data, &hexString); err != nil { 50 | return 51 | } 52 | *k, err = hex.DecodeString(hexString) 53 | return 54 | } 55 | 56 | type EDKeyPair struct { 57 | DisplayName string `json:"displayName"` 58 | Created string `json:"created"` 59 | Path HDPath `json:"path"` 60 | Public PublicKey `json:"publicKey"` 61 | Private PrivateKey `json:"secretKey"` 62 | KeyType keyType `json:"keyType"` 63 | } 64 | 65 | func NewMasterKeyPair(seed []byte) (*EDKeyPair, error) { 66 | path := DefaultPath() 67 | key, err := smbip32.Derive(HDPathToString(path), seed) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return &EDKeyPair{ 73 | DisplayName: "Master Key", 74 | Created: common.NowTimeString(), 75 | Private: key[:], 76 | Public: PublicKey(ed25519.PrivateKey(key[:]).Public().(ed25519.PublicKey)), 77 | Path: path, 78 | }, nil 79 | } 80 | 81 | func (kp *EDKeyPair) NewChildKeyPair(seed []byte, childIdx int) (*EDKeyPair, error) { 82 | path := kp.Path.Extend(BIP44HardenedAccountIndex(uint32(childIdx))) 83 | switch kp.KeyType { 84 | case typeLedger: 85 | return pubkeyFromLedger(path, false) 86 | case typeSoftware: 87 | key, err := smbip32.Derive(HDPathToString(path), seed) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return &EDKeyPair{ 92 | DisplayName: fmt.Sprintf("Child Key %d", childIdx), 93 | Created: common.NowTimeString(), 94 | Private: key[:], 95 | Public: PublicKey(ed25519.PrivateKey(key).Public().(ed25519.PublicKey)), 96 | Path: path, 97 | }, nil 98 | default: 99 | return nil, errors.New("unknown key type") 100 | } 101 | } 102 | 103 | func NewMasterKeyPairFromLedger() (*EDKeyPair, error) { 104 | return pubkeyFromLedger(DefaultPath(), true) 105 | } 106 | 107 | func pubkeyFromLedger(path HDPath, master bool) (*EDKeyPair, error) { 108 | // TODO: support multiple ledger devices (https://github.com/spacemeshos/smcli/issues/46) 109 | // don't bother confirming the master key; we only want the user to have to confirm a single key, 110 | // the one they really care about, which is the first child key. 111 | key, err := ledger.ReadPubkeyFromLedger("", HDPathToString(path), !master) 112 | if err != nil { 113 | return nil, fmt.Errorf("error reading pubkey from ledger: %w", err) 114 | } 115 | 116 | name := "Ledger Master Key" 117 | if !master { 118 | name = "Ledger Child Key" 119 | } 120 | 121 | return &EDKeyPair{ 122 | DisplayName: name, 123 | Created: common.NowTimeString(), 124 | // note: we do not set a Private key here (it lives on the device) 125 | Public: key[:], 126 | Path: path, 127 | KeyType: typeLedger, 128 | }, nil 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > Please note that the format of the wallet used by smcli is NOT compatible with the web wallet. It is, however, compatible with the Smapp wallet format. 3 | > Additionally, please note that the mentioned Ledger integration in smcli was a Proof of Concept that later evolved into an official Spacemesh Ledger app. 4 | > We plan to support all the recent features in smcli at a later phase. 5 | 6 | 7 | # smcli: Spacemesh Command-line Interface Tool 8 | 9 | smcli is a simple command line tool that you can use to manage wallet files (in the future it may be expanded with 10 | additional functionality). 11 | 12 | It currently supports the following features. Note that this documentation is not intended to be as complete as the 13 | built-in help documentation in the application itself, which fully documents all commands, flags, and features. 14 | Run `smcli -h` to see this documentation. 15 | 16 | ## Wallet 17 | 18 | smcli allows you to read encrypted wallet files (including those created using Smapp and other compatible tools), and 19 | generate new wallet files. 20 | 21 | ### Reading 22 | 23 | To read an encrypted wallet file, run: 24 | 25 | ```console 26 | smcli wallet read 27 | ``` 28 | 29 | You'll be prompted to enter the (optional) password used to encrypt the wallet file. If you enter the correct password, 30 | you'll see the contents of the wallet printed, including the accounts it contains. Include the flags `--full` to see 31 | full keys, and `--private` to see private keys and mnemonic in addition to public keys. 32 | 33 | Note that you can read both wallet files created using `smcli` as well as those created using 34 | [Smapp](https://github.com/spacemeshos/smapp/) or any other tool that supports standard Spacemesh wallet format. 35 | 36 | ### Generation 37 | 38 | To generate a new wallet, run: 39 | 40 | ```console 41 | smcli wallet create 42 | ``` 43 | 44 | The command will prompt you to enter a 45 | [BIP39-compatible mnemonic](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki), or alternatively generate 46 | a new, random mnemonic for you. It will then prompt you to enter a password to encrypt the wallet file (optional but 47 | highly recommended) and will then generate an encrypted wallet file with one or more new keypairs. 48 | 49 | Note that these keypairs (public and private key) are _not_ the same as Spacemesh wallet addresses. The public key can 50 | be converted directly and deterministically into your wallet address; in other words, there is a one-to-one mapping 51 | between public keys and wallet addresses. Conversion and outputting of public keys as wallet addresses 52 | [will be available shortly](https://github.com/spacemeshos/smcli/issues/38). 53 | 54 | #### Hardware wallet support 55 | 56 | `smcli` supports key generation using Ledger hardware devices including Nano S, Nano S+, and Nano X. To generate a 57 | wallet file using a hardware wallet, first sideload the 58 | [Spacemesh Ledger app](https://github.com/spacemeshos/app-spacemesh) onto your device (follow the instructions in that 59 | README). Make sure the device is connected and unlocked and the Spacemesh app is open, then run: 60 | 61 | ```console 62 | smcli wallet create --ledger 63 | ``` 64 | 65 | Note that the created wallet file will not contain any private keys or mnemonic (as these obviously remain on the 66 | Ledger device). If you subsequently use `smcli wallet read` to read the file, these will not be printed. We still 67 | recommend encrypting the wallet file with a secure password for privacy purposes. 68 | 69 | **NOTE: We strongly recommend only creating a new wallet on a hardware wallet or on a secure, airgapped computer. You 70 | are responsible for safely storing your mnemonic and wallet files. Your mnemonic is the ONLY way to restore access to 71 | your wallet and accounts if you misplace the wallet file, so it's essential that you back it up securely and reliably. 72 | There is absolutely nothing that we can do to help you recover your wallet if you misplace the file or mnemonic.** 73 | 74 | ## Genesis 75 | 76 | smcli includes commands to verify the information contained in the genesis ledger. 77 | 78 | ### Verify 79 | 80 | To verify the vesting (owner) address and vault address for a particular genesis vesting vault, run: 81 | 82 | ```console 83 | smcli genesis verify 84 | ``` 85 | 86 | This command will prompt you to enter one or more public keys, along with the multisig params (minimum required signers) 87 | and vaulted amount. It will subsequently output the vesting and vault addresses associated with the vault. 88 | 89 | ## Building 90 | 91 | Building the app is fairly straightforward. The only prerequisites are Golang with CGO support, `libudev` on Linux 92 | (`sudo apt-get install libudev-dev` on Debian/Ubuntu) and two libraries that will be statically linked into the binary. 93 | All of the details are handled in `Makefile` and should work on Linux (AMD64 and ARM64), macOS (Intel and Apple 94 | Silicon), and Windows. Simply run `make build`, which should download the correct libraries for your OS and platform. 95 | See the [release CI workflow](https://github.com/spacemeshos/smcli/blob/develop/.github/workflows/release.yml) for more 96 | details, and feel free to open an issue if you encounter any trouble. 97 | -------------------------------------------------------------------------------- /wallet/wallet_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/spacemeshos/go-spacemesh/common/types" 10 | "github.com/stretchr/testify/require" 11 | "github.com/tyler-smith/go-bip39" 12 | ) 13 | 14 | const Bip44Prefix = "m/44'/540'" 15 | 16 | func TestRandomAndMnemonic(t *testing.T) { 17 | n := 3 18 | 19 | // generate a wallet with a random mnemonic 20 | w1, err := NewMultiWalletRandomMnemonic(n) 21 | require.NoError(t, err) 22 | require.Len(t, w1.Secrets.Accounts, n) 23 | 24 | // now use that mnemonic to generate a new wallet 25 | w2, err := NewMultiWalletFromMnemonic(w1.Mnemonic(), n) 26 | require.NoError(t, err) 27 | require.Len(t, w2.Secrets.Accounts, n) 28 | 29 | // make sure all the keys match 30 | for i := 0; i < n; i++ { 31 | require.Equal(t, w1.Secrets.Accounts[i].Private, w2.Secrets.Accounts[i].Private) 32 | require.Equal(t, w1.Secrets.Accounts[i].Public, w2.Secrets.Accounts[i].Public) 33 | } 34 | } 35 | 36 | func TestAccountFromSeed(t *testing.T) { 37 | master, err := NewMasterKeyPair(goodSeed) 38 | require.NoError(t, err) 39 | accts, err := accountsFromMaster(master, goodSeed, 1) 40 | require.NoError(t, err) 41 | require.Len(t, accts, 1) 42 | keypair := accts[0] 43 | 44 | expPubKey := "feae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" 45 | //nolint:lll 46 | expPrivKey := "05fe9affa5562ca833faf3803ce5f6f7615d3c37c4a27903492027f6853e486dfeae6977b42bf3441d04314d09c72c5d6f2d1cb4bf94834680785b819f8738dd" 47 | 48 | actualPubKey := hex.EncodeToString(keypair.Public) 49 | actualPrivKey := hex.EncodeToString(keypair.Private) 50 | require.Equal(t, expPubKey, actualPubKey) 51 | require.Equal(t, expPrivKey, actualPrivKey) 52 | 53 | msg := []byte("hello world") 54 | // Sanity check that the keypair works with the ed25519 library 55 | sig := ed25519.Sign(ed25519.PrivateKey(keypair.Private), msg) 56 | require.True(t, ed25519.Verify(ed25519.PublicKey(keypair.Public), msg, sig)) 57 | 58 | // create another account from the same seed 59 | accts2, err := accountsFromMaster(master, goodSeed, 1) 60 | require.NoError(t, err) 61 | require.Len(t, accts2, 1) 62 | require.Equal(t, keypair.Public, accts2[0].Public) 63 | require.Equal(t, keypair.Private, accts2[0].Private) 64 | } 65 | 66 | func TestWalletFromNewMnemonic(t *testing.T) { 67 | entropy, _ := bip39.NewEntropy(256) 68 | mnemonic, _ := bip39.NewMnemonic(entropy) 69 | w, err := NewMultiWalletFromMnemonic(mnemonic, 1) 70 | 71 | require.NoError(t, err) 72 | require.NotNil(t, w) 73 | require.Equal(t, mnemonic, w.Mnemonic()) 74 | } 75 | 76 | func TestWalletFromGivenMnemonic(t *testing.T) { 77 | mnemonic := "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding" 78 | w, err := NewMultiWalletFromMnemonic(mnemonic, 1) 79 | require.NoError(t, err) 80 | expPubKey := "de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" 81 | //nolint:lll 82 | expPrivKey := "cd85df73aa3bc31de2f0b69bb1421df7eb0cdca7cb170a457869ab337749dae1de30fc9b812248583da6259433626fcdd2cb5ce589b00047b81e127950b9bca6" 83 | 84 | actualPubKey := hex.EncodeToString(w.Secrets.Accounts[0].Public) 85 | actualPrivKey := hex.EncodeToString(w.Secrets.Accounts[0].Private) 86 | require.Equal(t, expPubKey, actualPubKey) 87 | require.Equal(t, expPrivKey, actualPrivKey) 88 | 89 | msg := []byte("hello world") 90 | 91 | // Sanity check that the keypair works with the standard ed25519 library 92 | sig := ed25519.Sign(ed25519.PrivateKey(w.Secrets.Accounts[0].Private), msg) 93 | require.True(t, ed25519.Verify(ed25519.PublicKey(w.Secrets.Accounts[0].Public), msg, sig)) 94 | 95 | // Test conversion to a Spacemesh wallet address 96 | expAddress := "sm1qqqqqqz9rf583slhn38g6q6a562ctltv9fv5w8q2gdz9k" 97 | address := PubkeyToAddress(w.Secrets.Accounts[0].Public, types.NetworkHRP()) 98 | require.Equal(t, expAddress, address) 99 | 100 | expAddressTestnet := "stest1qqqqqqz9rf583slhn38g6q6a562ctltv9fv5w8qha56t0" 101 | addressTestnet := PubkeyToAddress(w.Secrets.Accounts[0].Public, "stest") 102 | require.Equal(t, expAddressTestnet, addressTestnet) 103 | } 104 | 105 | func TestKeysInWalletMaintainExpectedPath(t *testing.T) { 106 | n := 100 107 | w, err := NewMultiWalletRandomMnemonic(n) 108 | require.NoError(t, err) 109 | 110 | for i := 0; i < n; i++ { 111 | expectedPath := fmt.Sprintf("%s/0'/0'/%d'", Bip44Prefix, i) 112 | path := w.Secrets.Accounts[i].Path 113 | require.Equal(t, expectedPath, HDPathToString(path)) 114 | } 115 | } 116 | 117 | func TestMnemonicWhitespace(t *testing.T) { 118 | mnemonics := []string{ 119 | "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding", 120 | "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding", 121 | "film theme cheese broken kingdom destroy inch ready wear\ninspire shove pudding", 122 | "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding\t", 123 | " film theme cheese broken kingdom destroy inch ready wear inspire shove pudding", 124 | "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding ", 125 | "film theme cheese broken kingdom destroy inch ready wear inspire shove pudding", 126 | } 127 | for _, m := range mnemonics { 128 | _, err := NewMultiWalletFromMnemonic(m, 1) 129 | require.Equal(t, errWhitespace, err, "expected whitespace error in mnemonic") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Based on https://gist.github.com/trosendal/d4646812a43920bfe94e 2 | 3 | DEPTAG := 1.0.3 4 | DEPLIBNAME := spacemesh-sdk 5 | DEPLOC := https://github.com/spacemeshos/$(DEPLIBNAME)/releases/download 6 | UNZIP_DEST := deps 7 | REAL_DEST := $(CURDIR)/$(UNZIP_DEST) 8 | DOWNLOAD_DEST := $(UNZIP_DEST)/$(DEPLIBNAME).tar.gz 9 | 10 | LINKLIBS := -L$(REAL_DEST) 11 | RPATH := -Wl,-rpath,@loader_path -Wl,-rpath,$(REAL_DEST) 12 | CGO_LDFLAGS := $(LINKLIBS) $(RPATH) 13 | STATICLDFLAGS := -L$(UNZIP_DEST) -led25519_bip32 -lspacemesh_remote_wallet 14 | EXTRACT = tar -xzf 15 | 16 | GOLANGCI_LINT_VERSION := v1.64.6 17 | GOTESTSUM_VERSION := v1.12.0 18 | 19 | # Detect operating system 20 | ifeq ($(OS),Windows_NT) 21 | SYSTEM := windows 22 | else 23 | UNAME_S := $(shell uname -s) 24 | ifeq ($(UNAME_S),Linux) 25 | SYSTEM := linux 26 | else ifeq ($(UNAME_S),Darwin) 27 | SYSTEM := darwin 28 | else 29 | $(error Unknown operating system: $(UNAME_S)) 30 | endif 31 | endif 32 | 33 | # Default values. Can be overridden on command line, e.g., inside CLI for cross-compilation. 34 | # Note: this Makefile structure theoretically supports cross-compilation using GOOS and GOARCH. 35 | # In practice, however, depending on the host and target OS/architecture, you'll likely run into 36 | # errors in both the compiler and the linker when trying to compile cross-platform. 37 | GOOS ?= $(SYSTEM) 38 | GOARCH ?= unknown 39 | 40 | # Detect processor architecture 41 | ifeq ($(GOARCH),unknown) 42 | UNAME_M := $(shell uname -m) 43 | ifeq ($(UNAME_M),x86_64) 44 | GOARCH := amd64 45 | else ifneq ($(filter %86,$(UNAME_M)),) 46 | $(error Unsupported processor architecture: $(UNAME_M)) 47 | else ifneq ($(filter arm%,$(UNAME_M)),) 48 | GOARCH := arm64 49 | else ifneq ($(filter aarch64%,$(UNAME_M)),) 50 | GOARCH := arm64 51 | else 52 | $(error Unknown processor architecture: $(UNAME_M)) 53 | endif 54 | endif 55 | 56 | ifeq ($(GOOS),linux) 57 | MACHINE = linux 58 | 59 | # Linux specific settings 60 | # We statically link our own libraries and dynamically link other required libraries 61 | LDFLAGS = -ldflags '-linkmode external -extldflags "-Wl,-Bstatic $(STATICLDFLAGS) -Wl,-Bdynamic -ludev -lm"' 62 | else ifeq ($(GOOS),darwin) 63 | MACHINE = macos 64 | 65 | # macOS specific settings 66 | # statically link our libs, dynamic build using default toolchain 67 | CGO_LDFLAGS = $(LINKLIBS) $(REAL_DEST)/libed25519_bip32.a $(REAL_DEST)/libspacemesh_remote_wallet.a -framework CoreFoundation -framework IOKit -framework AppKit $(RPATH) 68 | LDFLAGS = 69 | else ifeq ($(GOOS),windows) 70 | # static build using default toolchain 71 | # add a few extra required libs 72 | LDFLAGS = -ldflags '-linkmode external -extldflags "-static $(STATICLDFLAGS) -lws2_32 -luserenv -lbcrypt"' 73 | else 74 | $(error Unknown operating system: $(GOOS)) 75 | endif 76 | 77 | ifeq ($(SYSTEM),windows) 78 | # Windows settings 79 | PLATFORM = windows-amd64 80 | else 81 | # Linux and macOS settings 82 | ifeq ($(GOARCH),amd64) 83 | PLATFORM = $(MACHINE)-amd64 84 | else ifeq ($(GOARCH),arm64) 85 | PLATFORM = $(MACHINE)-arm64 86 | else 87 | $(error Unknown processor architecture: $(GOARCH)) 88 | endif 89 | endif 90 | FN = $(DEPLIBNAME)_$(PLATFORM).tar.gz 91 | 92 | $(UNZIP_DEST): $(DOWNLOAD_DEST) 93 | cd $(UNZIP_DEST) && $(EXTRACT) ../$(DOWNLOAD_DEST) 94 | 95 | $(DOWNLOAD_DEST): 96 | mkdir -p $(UNZIP_DEST) 97 | curl -sSfL $(DEPLOC)/v$(DEPTAG)/$(FN) -o $(DOWNLOAD_DEST) 98 | 99 | .PHONY: install 100 | install: 101 | go mod download 102 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s $(GOLANGCI_LINT_VERSION) 103 | go install gotest.tools/gotestsum@$(GOTESTSUM_VERSION) 104 | 105 | .PHONY: tidy 106 | tidy: 107 | go mod tidy 108 | 109 | .PHONY: build 110 | build: $(UNZIP_DEST) 111 | CGO_CFLAGS="-I$(REAL_DEST)" \ 112 | CGO_LDFLAGS="$(CGO_LDFLAGS)" \ 113 | GOOS=$(GOOS) \ 114 | GOARCH=$(GOARCH) \ 115 | CGO_ENABLED=1 \ 116 | go build $(LDFLAGS) 117 | 118 | .PHONY: test 119 | test: $(UNZIP_DEST) 120 | CGO_CFLAGS="-I$(REAL_DEST)" \ 121 | CGO_LDFLAGS="$(CGO_LDFLAGS)" \ 122 | LD_LIBRARY_PATH=$(REAL_DEST) \ 123 | go test -v -count 1 -ldflags "-extldflags \"$(STATICLDFLAGS)\"" ./... 124 | 125 | .PHONY: test-tidy 126 | test-tidy: 127 | # Working directory must be clean, or this test would be destructive 128 | git diff --quiet || (echo "\033[0;31mWorking directory not clean!\033[0m" && git --no-pager diff && exit 1) 129 | # We expect `go mod tidy` not to change anything, the test should fail otherwise 130 | make tidy 131 | git diff --exit-code || (git --no-pager diff && git checkout . && exit 1) 132 | 133 | .PHONY: test-fmt 134 | test-fmt: 135 | git diff --quiet || (echo "\033[0;31mWorking directory not clean!\033[0m" && git --no-pager diff && exit 1) 136 | # We expect `go fmt` not to change anything, the test should fail otherwise 137 | go fmt ./... 138 | git diff --exit-code || (git --no-pager diff && git checkout . && exit 1) 139 | 140 | .PHONY: lint 141 | lint: $(UNZIP_DEST) 142 | CGO_CFLAGS="-I$(REAL_DEST)" \ 143 | CGO_LDFLAGS="$(CGO_LDFLAGS)" \ 144 | LD_LIBRARY_PATH=$(REAL_DEST) \ 145 | ./bin/golangci-lint run --config .golangci.yml 146 | 147 | # Auto-fixes golangci-lint issues where possible. 148 | .PHONY: lint-fix 149 | lint-fix: $(UNZIP_DEST) 150 | CGO_CFLAGS="-I$(REAL_DEST)" \ 151 | CGO_LDFLAGS="$(CGO_LDFLAGS)" \ 152 | LD_LIBRARY_PATH=$(REAL_DEST) \ 153 | ./bin/golangci-lint run --config .golangci.yml --fix 154 | 155 | clean: 156 | rm -rf $(UNZIP_DEST) 157 | -------------------------------------------------------------------------------- /wallet/bip44.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | //lint:file-ignore SA4016 ignore ineffective bitwise operations to aid readability 12 | 13 | // BIP32HardenedKeyStart: keys with index >= this must be hardened as per BIP32. 14 | // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys 15 | const BIP32HardenedKeyStart uint32 = 0x80000000 16 | 17 | const ( 18 | HDPurposeSegment = 0 19 | HDCoinTypeSegment = 1 20 | HDAccountSegment = 2 21 | HDChainSegment = 3 22 | HDIndexSegment = 4 23 | ) 24 | 25 | type HDPath []uint32 26 | 27 | func (p *HDPath) MarshalJSON() ([]byte, error) { 28 | return json.Marshal(p.String()) 29 | } 30 | 31 | func (p *HDPath) UnmarshalJSON(data []byte) (err error) { 32 | var s string 33 | if err = json.Unmarshal(data, &s); err != nil { 34 | return 35 | } 36 | *p, err = StringToHDPath(s) 37 | return 38 | } 39 | 40 | func (p *HDPath) String() string { 41 | return HDPathToString(*p) 42 | } 43 | 44 | func (p *HDPath) Purpose() uint32 { 45 | return (*p)[HDPurposeSegment] 46 | } 47 | 48 | func (p *HDPath) CoinType() uint32 { 49 | return (*p)[HDCoinTypeSegment] 50 | } 51 | 52 | func (p *HDPath) Account() uint32 { 53 | return (*p)[HDAccountSegment] 54 | } 55 | 56 | func (p *HDPath) Chain() uint32 { 57 | return (*p)[HDChainSegment] 58 | } 59 | 60 | func (p *HDPath) Index() uint32 { 61 | return (*p)[HDIndexSegment] 62 | } 63 | 64 | func (p *HDPath) Extend(idx uint32) HDPath { 65 | return append(*p, idx) 66 | } 67 | 68 | // Root of the path is m/purpose' (m/44') 69 | // https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#purpose 70 | // We use this to indicate that we're using the BIP44 hierarchy. 71 | func BIP44Purpose() uint32 { 72 | return 0x8000002C 73 | } 74 | 75 | // After the purpose comes the coin type (m/44'/540') 76 | // https://github.com/satoshilabs/slips/blob/master/slip-0044.md?plain=1#L571 77 | func BIP44SpacemeshCoinType() uint32 { 78 | return 0x8000021c 79 | } 80 | 81 | // After the coin type comes the account (m/44'/540'/account') 82 | // For now we only support account 0'. 83 | func BIP44Account() uint32 { 84 | //nolint:staticcheck // ignore ineffective bitwise operations to aid readability 85 | return BIP32HardenedKeyStart | 0 86 | } 87 | 88 | // After the account comes the change level, BUT as of now, we don't support 89 | // un-hardened derivation so we'll be deviating from the spec here. We'll be 90 | // continuing with hardened derivation. 91 | // We call it the "hardened chain" level and not the "change" level because 92 | // these chains don't support "change" functionality in the way that Bitcoin does. 93 | // Spacemesh isn't a UTXO-based system, so there's no concept of "change". 94 | // We're keeping the name "chain" because it's a more general term that can be 95 | // used to describe a sequence of addresses that are related to each other even 96 | // after the account level. 97 | // (m/44'/540'/account'/chain') 98 | // For now we only support "chain" 0. We may want to use a different chain for testnet. 99 | func BIP44HardenedChain() uint32 { 100 | //nolint:staticcheck // ignore ineffective bitwise operations to aid readability 101 | return BIP32HardenedKeyStart | 0 102 | } 103 | 104 | // After the Hardened Chain level comes the address indices, as of now, we don't 105 | // support un-hardened derivation so we'll continue our deviation from the spec 106 | // here. All addresses will be hardened. 107 | // (m/44'/540'/account'/chain'/address_index'). 108 | func BIP44HardenedAccountIndex(hai uint32) uint32 { 109 | return BIP32HardenedKeyStart | hai 110 | } 111 | 112 | func DefaultPath() HDPath { 113 | return HDPath{ 114 | BIP44Purpose(), 115 | BIP44SpacemeshCoinType(), 116 | BIP44Account(), 117 | BIP44HardenedChain(), 118 | } 119 | } 120 | 121 | func IsPathCompletelyHardened(path HDPath) bool { 122 | for _, p := range path { 123 | if p < BIP32HardenedKeyStart { 124 | return false 125 | } 126 | } 127 | return true 128 | } 129 | 130 | // HDPathToString converts a BIP44 HD path to a string of the form 131 | // "m/44'/540'/account'/chain'/address_index'". 132 | func HDPathToString(path HDPath) string { 133 | var sb strings.Builder 134 | sb.WriteString("m") 135 | for _, p := range path { 136 | sb.WriteString("/") 137 | if p >= BIP32HardenedKeyStart { 138 | sb.WriteString(strconv.FormatUint(uint64(p-BIP32HardenedKeyStart), 10)) 139 | sb.WriteString("'") 140 | } else { 141 | sb.WriteString(strconv.FormatUint(uint64(p), 10)) 142 | } 143 | } 144 | return sb.String() 145 | } 146 | 147 | func parseUint(s string) uint { 148 | var u uint 149 | fmt.Sscanf(s, "%d", &u) 150 | return u 151 | } 152 | 153 | // StringToHDPath converts a BIP44 HD path string of the form 154 | // (m/44'/540'/account'/chain'/address_index') to its uint32 slice representation. 155 | func StringToHDPath(s string) (HDPath, error) { 156 | // regex of the form m/44'/540'/account'/chain'/address_index' 157 | rWholePath := regexp.MustCompile(`^m(/\d+'?)+$`) 158 | if !rWholePath.Match([]byte(s)) { 159 | return nil, fmt.Errorf("invalid HD path string: %s", s) 160 | } 161 | rCrumbs := regexp.MustCompile(`/(\d+)('?)`) 162 | crumbs := rCrumbs.FindAllStringSubmatch(s, -1) 163 | path := make(HDPath, len(crumbs)) 164 | for i, crumb := range crumbs { 165 | path[i] = uint32(parseUint(crumb[1])) 166 | if crumb[2] == "'" { 167 | path[i] |= BIP32HardenedKeyStart 168 | } 169 | } 170 | return path, nil 171 | } 172 | -------------------------------------------------------------------------------- /wallet/wallet.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ed25519" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/spacemeshos/go-spacemesh/common/types" 12 | "github.com/spacemeshos/go-spacemesh/genvm/core" 13 | walletTemplate "github.com/spacemeshos/go-spacemesh/genvm/templates/wallet" 14 | "github.com/tyler-smith/go-bip39" 15 | 16 | "github.com/spacemeshos/smcli/common" 17 | ) 18 | 19 | var errWhitespace = errors.New("whitespace violation in mnemonic phrase") 20 | 21 | // Wallet is the basic data structure. 22 | type Wallet struct { 23 | // keystore string 24 | // password string 25 | // unlocked bool 26 | Meta walletMetadata `json:"meta"` 27 | Secrets walletSecrets `json:"crypto"` 28 | } 29 | 30 | // EncryptedWalletFile is the encrypted representation of the wallet on the filesystem. 31 | type EncryptedWalletFile struct { 32 | Meta walletMetadata `json:"meta"` 33 | Secrets walletSecretsEncrypted `json:"crypto"` 34 | } 35 | 36 | type walletMetadata struct { 37 | DisplayName string `json:"displayName"` 38 | Created string `json:"created"` 39 | GenesisID string `json:"genesisID"` 40 | // NetID int `json:"netId"` 41 | 42 | // is this needed? 43 | // Type WalletType 44 | // RemoteAPI string 45 | } 46 | 47 | type hexEncodedCiphertext []byte 48 | 49 | func (c *hexEncodedCiphertext) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(hex.EncodeToString(*c)) 51 | } 52 | 53 | func (c *hexEncodedCiphertext) UnmarshalJSON(data []byte) (err error) { 54 | var hexString string 55 | if err = json.Unmarshal(data, &hexString); err != nil { 56 | return 57 | } 58 | *c, err = hex.DecodeString(hexString) 59 | return 60 | } 61 | 62 | type walletSecretsEncrypted struct { 63 | Cipher string `json:"cipher"` 64 | CipherText hexEncodedCiphertext `json:"cipherText"` 65 | CipherParams struct { 66 | IV hexEncodedCiphertext `json:"iv"` 67 | } `json:"cipherParams"` 68 | KDF string `json:"kdf"` 69 | KDFParams struct { 70 | DKLen int `json:"dklen"` 71 | Hash string `json:"hash"` 72 | Salt hexEncodedCiphertext `json:"salt"` 73 | Iterations int `json:"iterations"` 74 | } `json:"kdfparams"` 75 | } 76 | 77 | type walletSecrets struct { 78 | Mnemonic string `json:"mnemonic"` 79 | MasterKeypair *EDKeyPair 80 | Accounts []*EDKeyPair `json:"accounts"` 81 | } 82 | 83 | func NewMultiWalletRandomMnemonic(n int) (*Wallet, error) { 84 | // generate a new, random mnemonic 85 | e, err := bip39.NewEntropy(ed25519.SeedSize * 8) 86 | if err != nil { 87 | return nil, err 88 | } 89 | m, err := bip39.NewMnemonic(e) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | return NewMultiWalletFromMnemonic(m, n) 95 | } 96 | 97 | func NewMultiWalletFromMnemonic(m string, n int) (*Wallet, error) { 98 | if n < 0 || n > common.MaxAccountsPerWallet { 99 | return nil, errors.New("invalid number of accounts") 100 | } 101 | 102 | // bip39 lib doesn't properly validate whitespace so we have to do that manually. 103 | if expected := strings.Join(strings.Fields(m), " "); m != expected { 104 | return nil, errWhitespace 105 | } 106 | 107 | // this checks the number of words and the checksum. 108 | if !bip39.IsMnemonicValid(m) { 109 | return nil, errors.New("invalid mnemonic") 110 | } 111 | 112 | // TODO: add option for user to provide passphrase 113 | // https://github.com/spacemeshos/smcli/issues/18 114 | 115 | seed := bip39.NewSeed(m, "") 116 | masterKeyPair, err := NewMasterKeyPair(seed) 117 | if err != nil { 118 | return nil, err 119 | } 120 | accounts, err := accountsFromMaster(masterKeyPair, seed, n) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return walletFromMnemonicAndAccounts(m, masterKeyPair, accounts) 125 | } 126 | 127 | func NewMultiWalletFromLedger(n int) (*Wallet, error) { 128 | if n < 0 || n > common.MaxAccountsPerWallet { 129 | return nil, errors.New("invalid number of accounts") 130 | } 131 | masterKeyPair, err := NewMasterKeyPairFromLedger() 132 | if err != nil { 133 | fmt.Println("Error: ", err) 134 | fmt.Println("Are you sure the ledger is connected, unlocked, and the Spacemesh app is open?") 135 | return nil, err 136 | } 137 | // seed is not used in case of ledger 138 | accounts, err := accountsFromMaster(masterKeyPair, []byte{}, n) 139 | if err != nil { 140 | return nil, err 141 | } 142 | return walletFromMnemonicAndAccounts("(none)", masterKeyPair, accounts) 143 | } 144 | 145 | func walletFromMnemonicAndAccounts(m string, masterKp *EDKeyPair, kp []*EDKeyPair) (*Wallet, error) { 146 | w := &Wallet{ 147 | Meta: walletMetadata{ 148 | DisplayName: "Main Wallet", 149 | Created: common.NowTimeString(), 150 | // TODO: set correctly 151 | GenesisID: "", 152 | }, 153 | Secrets: walletSecrets{ 154 | Mnemonic: m, 155 | MasterKeypair: masterKp, 156 | Accounts: kp, 157 | }, 158 | } 159 | return w, nil 160 | } 161 | 162 | // accountsFromMaster generates one or more accounts from a master keypair and seed. Accounts use sequential HD paths. 163 | // The master keypair does not contain the seed that was used to generate it, so it needs to be passed in explicitly. 164 | func accountsFromMaster(masterKeypair *EDKeyPair, masterSeed []byte, n int) (accounts []*EDKeyPair, err error) { 165 | accounts = make([]*EDKeyPair, 0, n) 166 | for i := 0; i < n; i++ { 167 | acct, err := masterKeypair.NewChildKeyPair(masterSeed, i) 168 | if err != nil { 169 | return nil, err 170 | } 171 | accounts = append(accounts, acct) 172 | } 173 | return 174 | } 175 | 176 | func (w *Wallet) Mnemonic() string { 177 | return w.Secrets.Mnemonic 178 | } 179 | 180 | func PubkeyToAddress(pubkey []byte, hrp string) string { 181 | types.SetNetworkHRP(hrp) 182 | key := [ed25519.PublicKeySize]byte{} 183 | copy(key[:], pubkey) 184 | walletArgs := &walletTemplate.SpawnArguments{PublicKey: key} 185 | walletAddress := core.ComputePrincipal(walletTemplate.TemplateAddress, walletArgs) 186 | return walletAddress.String() 187 | } 188 | -------------------------------------------------------------------------------- /wallet/store.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/hmac" 9 | "crypto/rand" 10 | "crypto/sha512" 11 | "encoding/json" 12 | "errors" 13 | "io" 14 | "log" 15 | 16 | "github.com/spf13/cobra" 17 | "github.com/xdg-go/pbkdf2" 18 | ) 19 | 20 | const EncKeyLen = 32 21 | 22 | // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 23 | const ( 24 | Pbkdf2Iterations = 210000 25 | Pbkdf2Dklen = 256 26 | Pbkdf2SaltBytesLen = 16 27 | ) 28 | 29 | var Pbkdf2HashFunc = sha512.New 30 | 31 | type ( 32 | WalletKeyOpt func(*WalletKey) 33 | WalletKey struct { 34 | key []byte 35 | pw []byte 36 | salt []byte 37 | iterations int 38 | } 39 | ) 40 | 41 | func NewKey(opts ...WalletKeyOpt) WalletKey { 42 | w := &WalletKey{} 43 | for _, opt := range opts { 44 | opt(w) 45 | } 46 | if w.key == nil && w.pw == nil { 47 | log.Fatalf("Some form of key generation method must be provided. Try WithXXXPassword.") 48 | } 49 | 50 | return *w 51 | } 52 | 53 | func WithRandomSalt() WalletKeyOpt { 54 | return func(k *WalletKey) { 55 | if k.salt != nil { 56 | log.Fatalf("Can only set salt once.") 57 | } 58 | k.salt = make([]byte, Pbkdf2SaltBytesLen) 59 | _, err := rand.Read(k.salt) 60 | cobra.CheckErr(err) 61 | } 62 | } 63 | 64 | func WithSalt(salt [Pbkdf2SaltBytesLen]byte) WalletKeyOpt { 65 | return func(k *WalletKey) { 66 | if k.salt != nil { 67 | log.Fatalf("Can only set salt once.") 68 | } 69 | k.salt = salt[:] 70 | 71 | // if password is set, set the key as well 72 | if k.pw != nil { 73 | WithPbkdf2Password(k.pw)(k) 74 | } 75 | } 76 | } 77 | 78 | func WithIterations(iterations int) WalletKeyOpt { 79 | return func(k *WalletKey) { 80 | k.iterations = iterations 81 | if k.key != nil { 82 | // regenerate 83 | k.key = nil 84 | WithPbkdf2Password(k.pw)(k) 85 | } 86 | } 87 | } 88 | 89 | // WithPasswordOnly is used for reading a stored file. The stored wallet file contains 90 | // a salt, so it does not need to be set before reading the file. 91 | func WithPasswordOnly(password []byte) WalletKeyOpt { 92 | return func(k *WalletKey) { 93 | if k.salt != nil { 94 | log.Fatalf("Salt must not be set.") 95 | } 96 | if k.key != nil { 97 | log.Fatalf("Can only generate key once.") 98 | } 99 | if k.pw != nil { 100 | log.Fatalf("Password can only be set once.") 101 | } 102 | k.pw = password 103 | } 104 | } 105 | 106 | func WithPbkdf2Password(password []byte) WalletKeyOpt { 107 | return func(k *WalletKey) { 108 | if k.salt == nil { 109 | log.Fatalf("Salt must be set.") 110 | } 111 | if k.key != nil { 112 | log.Fatalf("Can only generate key once.") 113 | } 114 | iterations := k.iterations 115 | if iterations == 0 { 116 | iterations = Pbkdf2Iterations 117 | } 118 | k.key = pbkdf2.Key( 119 | password, 120 | k.salt, 121 | iterations, 122 | EncKeyLen, 123 | Pbkdf2HashFunc, 124 | ) 125 | k.pw = password 126 | } 127 | } 128 | 129 | // https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html#71-encryption-types-to-use 130 | func (k *WalletKey) encrypt(plaintext []byte) (ciphertext, nonce []byte, err error) { 131 | block, err := aes.NewCipher(k.key) 132 | if err != nil { 133 | return 134 | } 135 | 136 | // Using default options for AES-GCM as recommended by the godoc. 137 | // For reference, NonceSize is 12 bytes, and TagSize is 16 bytes: 138 | // https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/crypto/cipher/gcm.go;l=153-158 139 | aesgcm, err := cipher.NewGCM(block) 140 | if err != nil { 141 | return 142 | } 143 | hash := hmac.New(sha512.New, k.key) 144 | nonce = hash.Sum(plaintext)[:aesgcm.NonceSize()] 145 | 146 | ciphertext = aesgcm.Seal(nil, nonce, plaintext, nil) 147 | return 148 | } 149 | 150 | func (k *WalletKey) decrypt(ciphertext, nonce []byte) (plaintext []byte, err error) { 151 | block, err := aes.NewCipher(k.key) 152 | if err != nil { 153 | return 154 | } 155 | aesgcm, err := cipher.NewGCM(block) 156 | if err != nil { 157 | return 158 | } 159 | 160 | plaintext, err = aesgcm.Open(nil, nonce, ciphertext, nil) 161 | return 162 | } 163 | 164 | func (k *WalletKey) Open(file io.Reader, debugMode bool) (*Wallet, error) { 165 | ew := &EncryptedWalletFile{} 166 | if err := json.NewDecoder(file).Decode(ew); err != nil { 167 | return nil, err 168 | } 169 | 170 | // set the salt, and warn if it's different 171 | if k.salt == nil { 172 | var salt [Pbkdf2SaltBytesLen]byte 173 | copy(salt[:], ew.Secrets.KDFParams.Salt) 174 | if !bytes.Equal(salt[:], ew.Secrets.KDFParams.Salt) { 175 | return nil, errors.New("error reading encrypted wallet file salt, check salt length") 176 | } 177 | WithSalt(salt)(k) 178 | } else if !bytes.Equal(ew.Secrets.KDFParams.Salt, k.salt) { 179 | log.Printf("wallet key salt does not match wallet file salt") 180 | } 181 | WithIterations(ew.Secrets.KDFParams.Iterations)(k) 182 | if ew.Secrets.KDFParams.Iterations < Pbkdf2Iterations { 183 | log.Println("Warning: wallet file iterations count lower than recommended") 184 | } 185 | 186 | nonce := ew.Secrets.CipherParams.IV 187 | encWallet := ew.Secrets.CipherText 188 | 189 | // TODO: before decrypting, check that other meta params match 190 | plaintext, err := k.decrypt(encWallet, nonce) 191 | if err != nil { 192 | return nil, err 193 | } 194 | if debugMode { 195 | log.Println("Decrypted JSON data:", string(plaintext)) 196 | } 197 | secrets := &walletSecrets{} 198 | if err := json.Unmarshal(plaintext, secrets); err != nil { 199 | return nil, err 200 | } 201 | 202 | // we have everything we need, construct and return the wallet. 203 | w := &Wallet{ 204 | Meta: ew.Meta, 205 | Secrets: *secrets, 206 | } 207 | return w, nil 208 | } 209 | 210 | func (k *WalletKey) Export(file io.Writer, w *Wallet) error { 211 | // encrypt the secrets 212 | plaintext, err := json.Marshal(w.Secrets) 213 | if err != nil { 214 | return err 215 | } 216 | ciphertext, nonce, err := k.encrypt(plaintext) 217 | if err != nil { 218 | return err 219 | } 220 | ew := &EncryptedWalletFile{ 221 | Meta: w.Meta, 222 | Secrets: walletSecretsEncrypted{ 223 | Cipher: "AES-GCM", 224 | CipherText: ciphertext, 225 | CipherParams: struct { 226 | IV hexEncodedCiphertext `json:"iv"` 227 | }{ 228 | IV: nonce, 229 | }, 230 | KDF: "PBKDF2", 231 | KDFParams: struct { 232 | DKLen int `json:"dklen"` 233 | Hash string `json:"hash"` 234 | Salt hexEncodedCiphertext `json:"salt"` 235 | Iterations int `json:"iterations"` 236 | }{ 237 | DKLen: Pbkdf2Dklen, 238 | Hash: crypto.SHA256.String(), 239 | Salt: k.salt, 240 | Iterations: Pbkdf2Iterations, 241 | }, 242 | }, 243 | } 244 | return json.NewEncoder(file).Encode(ew) 245 | } 246 | -------------------------------------------------------------------------------- /cmd/wallet.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/btcsuite/btcutil/base58" 13 | "github.com/hashicorp/go-secure-stdlib/password" 14 | "github.com/jedib0t/go-pretty/v6/table" 15 | "github.com/spacemeshos/go-spacemesh/common/types" 16 | "github.com/spf13/cobra" 17 | 18 | "github.com/spacemeshos/smcli/common" 19 | "github.com/spacemeshos/smcli/wallet" 20 | ) 21 | 22 | var ( 23 | // debug indicates that the program is in debug mode. 24 | debug bool 25 | 26 | // printPrivate indicates that private keys should be printed. 27 | printPrivate bool 28 | 29 | // printFull indicates that full keys should be printed (not abbreviated). 30 | printFull bool 31 | 32 | // printBase58 indicates that keys should be printed in base58 format. 33 | printBase58 bool 34 | 35 | // printParent indicates that the parent key should be printed. 36 | printParent bool 37 | 38 | // useLedger indicates that the Ledger device should be used. 39 | useLedger bool 40 | 41 | // hrp is the human-readable network identifier used in Spacemesh network addresses. 42 | hrp string 43 | ) 44 | 45 | // walletCmd represents the wallet command. 46 | var walletCmd = &cobra.Command{ 47 | Use: "wallet", 48 | Short: "Wallet management", 49 | } 50 | 51 | // createCmd represents the create command. 52 | var createCmd = &cobra.Command{ 53 | Use: "create [--ledger] [numaccounts]", 54 | Short: "Generate a new wallet file from a BIP-39-compatible mnemonic or Ledger device", 55 | Long: `Create a new wallet file containing one or more accounts using a BIP-39-compatible mnemonic 56 | or a Ledger hardware wallet. If using a mnemonic you can choose to use an existing mnemonic or generate 57 | a new, random mnemonic. 58 | 59 | Add --ledger to instead read the public key from a Ledger device. If using a Ledger device please make 60 | sure the device is connected, unlocked, and the Spacemesh app is open.`, 61 | Args: cobra.MaximumNArgs(1), 62 | Run: func(cmd *cobra.Command, args []string) { 63 | // get the number of accounts to create 64 | n := 1 65 | if len(args) > 0 { 66 | tmpN, err := strconv.ParseInt(args[0], 10, 16) 67 | cobra.CheckErr(err) 68 | n = int(tmpN) 69 | } 70 | 71 | var w *wallet.Wallet 72 | var err error 73 | 74 | // Short-circuit and check for a ledger device 75 | if useLedger { 76 | w, err = wallet.NewMultiWalletFromLedger(n) 77 | cobra.CheckErr(err) 78 | fmt.Println("Note that, when using a hardware wallet, the wallet file I'm about to produce won't " + 79 | "contain any private keys or mnemonics, but you may still choose to encrypt it to protect privacy.") 80 | } else { 81 | // get or generate the mnemonic 82 | fmt.Print("Enter a BIP-39-compatible mnemonic (or leave blank to generate a new one): ") 83 | text, err := password.Read(os.Stdin) 84 | fmt.Println() 85 | cobra.CheckErr(err) 86 | fmt.Print("Note: This application does not yet support BIP-39-compatible optional passwords. ") 87 | fmt.Println("Support will be added soon.") 88 | 89 | // It's critical that we trim whitespace, including CRLF. Otherwise it will get included in the mnemonic. 90 | text = strings.TrimSpace(text) 91 | 92 | if text == "" { 93 | w, err = wallet.NewMultiWalletRandomMnemonic(n) 94 | cobra.CheckErr(err) 95 | fmt.Print("\nThis is your mnemonic (seed phrase). Write it down and store it safely.") 96 | fmt.Print("It is the ONLY way to restore your wallet.\n") 97 | fmt.Print("Neither Spacemesh nor anyone else can help you restore your wallet without this mnemonic.\n") 98 | fmt.Print("\n***********************************\n") 99 | fmt.Print("SAVE THIS MNEMONIC IN A SAFE PLACE!") 100 | fmt.Print("\n***********************************\n") 101 | fmt.Println() 102 | fmt.Println(w.Mnemonic()) 103 | fmt.Println("\nPress enter when you have securely saved your mnemonic.") 104 | _, _ = fmt.Scanln() 105 | } else { 106 | // try to use as a mnemonic 107 | w, err = wallet.NewMultiWalletFromMnemonic(text, n) 108 | cobra.CheckErr(err) 109 | } 110 | } 111 | 112 | fmt.Print("Enter a secure password used to encrypt the wallet file (optional but strongly recommended): ") 113 | password, err := password.Read(os.Stdin) 114 | fmt.Println() 115 | cobra.CheckErr(err) 116 | wk := wallet.NewKey(wallet.WithRandomSalt(), wallet.WithPbkdf2Password([]byte(password))) 117 | err = os.MkdirAll(common.DotDirectory(), 0o700) 118 | cobra.CheckErr(err) 119 | 120 | // Make sure we're not overwriting an existing wallet (this should not happen) 121 | walletFn := common.WalletFile() 122 | _, err = os.Stat(walletFn) 123 | switch { 124 | case errors.Is(err, os.ErrNotExist): 125 | // all fine 126 | case err == nil: 127 | log.Fatalln("Wallet file already exists") 128 | default: 129 | log.Fatalf("Error opening %s: %v\n", walletFn, err) 130 | } 131 | 132 | // Now open for writing 133 | f2, err := os.OpenFile(walletFn, os.O_WRONLY|os.O_CREATE, 0o600) 134 | cobra.CheckErr(err) 135 | defer f2.Close() 136 | cobra.CheckErr(wk.Export(f2, w)) 137 | 138 | fmt.Printf("Wallet saved to %s. BACK UP THIS FILE NOW!\n", walletFn) 139 | }, 140 | } 141 | 142 | // readCmd reads an existing wallet file. 143 | var readCmd = &cobra.Command{ 144 | Use: "read [wallet file] [--full/-f] [--private/-p] [--base58]", 145 | Short: "Reads an existing wallet file", 146 | Long: `This command can be used to verify whether an existing wallet file can be 147 | successfully read and decrypted, whether the password to open the file is correct, etc. 148 | It prints the accounts from the wallet file. By default it does not print private keys. 149 | Add --private to print private keys. Add --full to print full keys. Add --base58 to print 150 | keys in base58 format rather than hexadecimal. Add --parent to print parent key (and not 151 | only child keys).`, 152 | Args: cobra.ExactArgs(1), 153 | Run: func(cmd *cobra.Command, args []string) { 154 | walletFn := args[0] 155 | 156 | // make sure the file exists 157 | f, err := os.Open(walletFn) 158 | cobra.CheckErr(err) 159 | defer f.Close() 160 | 161 | // get the password 162 | fmt.Print("Enter wallet password: ") 163 | password, err := password.Read(os.Stdin) 164 | fmt.Println() 165 | cobra.CheckErr(err) 166 | 167 | // attempt to read it 168 | wk := wallet.NewKey(wallet.WithPasswordOnly([]byte(password))) 169 | w, err := wk.Open(f, debug) 170 | cobra.CheckErr(err) 171 | 172 | widthEnforcer := func(col string, maxLen int) string { 173 | if len(col) <= maxLen { 174 | return col 175 | } 176 | if maxLen <= 7 { 177 | return col[:maxLen] 178 | } 179 | return fmt.Sprintf("%s..%s", col[:maxLen-7], col[len(col)-5:]) 180 | } 181 | 182 | t := table.NewWriter() 183 | t.SetOutputMirror(os.Stdout) 184 | t.SetTitle("Wallet Contents") 185 | caption := "" 186 | if printPrivate { 187 | caption = fmt.Sprintf("Mnemonic: %s", w.Mnemonic()) 188 | } 189 | if !printFull { 190 | if printPrivate { 191 | caption += "\n" 192 | } 193 | caption += "To print full keys, use the --full flag." 194 | } 195 | t.SetCaption(caption) 196 | maxWidth := 20 197 | if printFull { 198 | // full key is 64 bytes which is 128 chars in hex, need to print at least this much 199 | maxWidth = 150 200 | } 201 | if printPrivate { 202 | t.AppendHeader(table.Row{ 203 | "address", 204 | "pubkey", 205 | "privkey", 206 | "path", 207 | "name", 208 | "created", 209 | }) 210 | t.SetColumnConfigs([]table.ColumnConfig{ 211 | {Number: 2, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, 212 | {Number: 3, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, 213 | }) 214 | } else { 215 | t.AppendHeader(table.Row{ 216 | "address", 217 | "pubkey", 218 | "path", 219 | "name", 220 | "created", 221 | }) 222 | t.SetColumnConfigs([]table.ColumnConfig{ 223 | {Number: 2, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, 224 | }) 225 | } 226 | 227 | // set the encoder 228 | encoder := hex.EncodeToString 229 | if printBase58 { 230 | encoder = base58.Encode 231 | } 232 | 233 | privKeyEncoder := func(privKey []byte) string { 234 | if len(privKey) == 0 { 235 | return "(none)" 236 | } 237 | return encoder(privKey) 238 | } 239 | 240 | // print the master account 241 | if printParent { 242 | master := w.Secrets.MasterKeypair 243 | if master != nil { 244 | if printPrivate { 245 | t.AppendRow(table.Row{ 246 | "N/A", 247 | encoder(master.Public), 248 | privKeyEncoder(master.Private), 249 | master.Path.String(), 250 | master.DisplayName, 251 | master.Created, 252 | }) 253 | } else { 254 | t.AppendRow(table.Row{ 255 | "N/A", 256 | encoder(master.Public), 257 | master.Path.String(), 258 | master.DisplayName, 259 | master.Created, 260 | }) 261 | } 262 | } 263 | } 264 | 265 | // print child accounts 266 | for _, a := range w.Secrets.Accounts { 267 | if printPrivate { 268 | t.AppendRow(table.Row{ 269 | wallet.PubkeyToAddress(a.Public, hrp), 270 | encoder(a.Public), 271 | privKeyEncoder(a.Private), 272 | a.Path.String(), 273 | a.DisplayName, 274 | a.Created, 275 | }) 276 | } else { 277 | t.AppendRow(table.Row{ 278 | wallet.PubkeyToAddress(a.Public, hrp), 279 | encoder(a.Public), 280 | a.Path.String(), 281 | a.DisplayName, 282 | a.Created, 283 | }) 284 | } 285 | } 286 | t.Render() 287 | }, 288 | } 289 | 290 | func init() { 291 | rootCmd.AddCommand(walletCmd) 292 | walletCmd.AddCommand(createCmd) 293 | walletCmd.AddCommand(readCmd) 294 | readCmd.Flags().BoolVarP(&printPrivate, "private", "p", false, "Print private keys") 295 | readCmd.Flags().BoolVarP(&printFull, "full", "f", false, "Print full keys (no abbreviation)") 296 | readCmd.Flags().BoolVar(&printBase58, "base58", false, "Print keys in base58 (rather than hex)") 297 | readCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent key (not only child keys)") 298 | readCmd.Flags().StringVar(&hrp, "hrp", types.NetworkHRP(), "Set human-readable address prefix") 299 | readCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") 300 | createCmd.Flags().BoolVarP(&useLedger, "ledger", "l", false, "Create a wallet using a Ledger device") 301 | } 302 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= 2 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 3 | github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= 4 | github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= 5 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 6 | github.com/anacrolix/envpprof v1.0.0 h1:AwZ+mBP4rQ5f7JSsrsN3h7M2xDW/xSE66IPVOqlnuUc= 7 | github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 8 | github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= 9 | github.com/anacrolix/missinggo v1.2.1 h1:0IE3TqX5y5D0IxeMwTyIgqdDew4QrzcXaaEnJQyjHvw= 10 | github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= 11 | github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= 12 | github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= 13 | github.com/anacrolix/sync v0.3.0 h1:ZPjTrkqQWEfnYVGTQHh5qNjokWaXnjsyXTJSMsKY0TA= 14 | github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= 15 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= 16 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 17 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 18 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 19 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0= 20 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 21 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 22 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 23 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 24 | github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= 25 | github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= 26 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 27 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 28 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 29 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 30 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 31 | github.com/c0mm4nd/go-ripemd v0.0.0-20200326052756-bd1759ad7d10 h1:wJ2csnFApV9G1jgh5KmYdxVOQMi+fihIggVTjcbM7ts= 32 | github.com/c0mm4nd/go-ripemd v0.0.0-20200326052756-bd1759ad7d10/go.mod h1:mYPR+a1fzjnHY3VFH5KL3PkEjMlVfGXP7c8rbWlkLJg= 33 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 34 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 35 | github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= 36 | github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= 37 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 38 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 41 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 43 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 44 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 45 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 46 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 47 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 48 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 49 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 50 | github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 51 | github.com/go-llsqlite/crawshaw v0.5.5 h1:sXnRkiV26MBv++lbPbzp+ZzFcTqzVMxftO8yHyFvwUA= 52 | github.com/go-llsqlite/crawshaw v0.5.5/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE= 53 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 54 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 55 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 57 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 58 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 59 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 60 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 61 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 62 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 63 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 64 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 65 | github.com/hashicorp/go-secure-stdlib/password v0.1.4 h1:h8gOB6qDRlOMvoMHOqtf4oTdB+goC/hWX0+TXA+VltI= 66 | github.com/hashicorp/go-secure-stdlib/password v0.1.4/go.mod h1:DeLx56RZZdmwX8Q94fVQUetkXv6zVBfDtGAPJDh69AU= 67 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 68 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 69 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 70 | github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= 71 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= 72 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 73 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 74 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 75 | github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo= 76 | github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= 77 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 78 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 79 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 80 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 81 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 82 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 83 | github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 84 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 85 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 86 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 87 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 88 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 89 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 90 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 91 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 92 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 93 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 94 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 95 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 96 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= 97 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 98 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 99 | github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= 100 | github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= 101 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 102 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 103 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 104 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 105 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 106 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 107 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 108 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 109 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 110 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 111 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 112 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 113 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 114 | github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= 115 | github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= 116 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 117 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 118 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 119 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 120 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 121 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 122 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 123 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 124 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= 125 | github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= 126 | github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= 127 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 128 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 129 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 130 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 131 | github.com/spacemeshos/economics v0.1.4 h1:twlawrcQhYNqPgyDv08+24EL/OgUKz3d7q+PvJIAND0= 132 | github.com/spacemeshos/economics v0.1.4/go.mod h1:6HKWKiKdxjVQcGa2z/wA0LR4M/DzKib856bP16yqNmQ= 133 | github.com/spacemeshos/go-scale v1.3.0 h1:J3rbksTxInnM86T8GnMGqD3iZOQJja7YIrfjMsQ8tUk= 134 | github.com/spacemeshos/go-scale v1.3.0/go.mod h1:cYK6xEozIptppvOybr3naxr9hrSVvXG/LInC/DrJofA= 135 | github.com/spacemeshos/go-spacemesh v1.8.3 h1:WDgcytrO8vZFeU5SMLxsI67RtjbJGsc3n3DulSk/MIs= 136 | github.com/spacemeshos/go-spacemesh v1.8.3/go.mod h1:I4cWnzc9yFsr9pps6Z0OguCNXcezhRV1DbUT7Vuj258= 137 | github.com/spacemeshos/merkle-tree v0.2.6 h1:PJ4LBx0vBbYVIHwApyjLy/yqUGEK35ggGTo05oiPhwg= 138 | github.com/spacemeshos/merkle-tree v0.2.6/go.mod h1:lxMuC/C2qhN6wdH6iSXW0HM8FS6fnKnyLWjCAKsCtr8= 139 | github.com/spacemeshos/poet v0.10.12 h1:xlk0L+MyDpqGzpsFgLKVeNMmQtzZ3k53/yoHs8vUfZo= 140 | github.com/spacemeshos/poet v0.10.12/go.mod h1:M32hQhE0QBT80CMEb8P3aXz0lQVGK6V8EJqnl2f9cYE= 141 | github.com/spacemeshos/post v0.13.1 h1:EeM/mYtS1ON28HOso1Gj6s1zoj2Dx6/0JDFoM5KJgAQ= 142 | github.com/spacemeshos/post v0.13.1/go.mod h1:3utSIJO9M7lhA5xm3BQ2hszVNjiLgiseq0Ty4vpS0BU= 143 | github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= 144 | github.com/spacemeshos/sha256-simd v0.1.0/go.mod h1:O8CClVIilId7RtuCMV2+YzMj6qjVn75JsxOxaE8vcfM= 145 | github.com/spacemeshos/smkeys v1.0.4 h1:M4A2tO2WSbtaVgmLWMgRNh+gmFPDjtVMkSMBO02GMQo= 146 | github.com/spacemeshos/smkeys v1.0.4/go.mod h1:gj9yv0Zek5D9p6zWmVV/2d0WdhPwyKXDMQm2MpmxIow= 147 | github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= 148 | github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 149 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 150 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 151 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 152 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 153 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 154 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 155 | github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 156 | github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 157 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 158 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 159 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 160 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 161 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 162 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 163 | github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= 164 | github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= 165 | github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 166 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 167 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 168 | github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= 169 | github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 170 | github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= 171 | github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= 172 | github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= 173 | github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= 174 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 175 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 176 | go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= 177 | go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 178 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 179 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 180 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 181 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 182 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 183 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 184 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 185 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 186 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 187 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 188 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= 189 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= 190 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 191 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 192 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 194 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 195 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 196 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 197 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 199 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 200 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 201 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 202 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 203 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 204 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 205 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 206 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 207 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 208 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 209 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 210 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 211 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 212 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 213 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 214 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 215 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values (in comments). 3 | # 4 | # This file is not a configuration example, 5 | # it contains the exhaustive configuration with explanations of the options. 6 | 7 | linters: 8 | # Disable all linters. 9 | # Default: false 10 | disable-all: true 11 | # Enable specific linter 12 | # https://golangci-lint.run/usage/linters/#enabled-by-default 13 | enable: 14 | # - asasalint 15 | # - asciicheck 16 | # - bidichk 17 | # - bodyclose 18 | - canonicalheader 19 | # - containedctx 20 | # - contextcheck 21 | - copyloopvar 22 | # - cyclop 23 | # - decorder 24 | - depguard 25 | # - dogsled 26 | # - dupl 27 | # - dupword 28 | # - durationcheck 29 | # - err113 30 | # - errcheck 31 | # - errchkjson 32 | # - errname 33 | # - errorlint 34 | # - exhaustive 35 | # - exhaustruct 36 | # - exptostd 37 | - fatcontext 38 | # - forbidigo 39 | # - forcetypeassert 40 | # - funlen 41 | - gci 42 | # - ginkgolinter 43 | # - gocheckcompilerdirectives 44 | # - gochecknoglobals 45 | # - gochecknoinits 46 | - gochecksumtype 47 | # - gocognit 48 | # - goconst 49 | # - gocritic 50 | # - gocyclo 51 | - godot 52 | # - godox 53 | - gofmt 54 | - gofumpt 55 | # - goheader 56 | # - goimports 57 | # - gomoddirectives 58 | # - gomodguard 59 | # - goprintffuncname 60 | # - gosec 61 | - gosimple 62 | # - gosmopolitan 63 | - govet 64 | # - grouper 65 | # - iface 66 | - importas 67 | # - inamedparam 68 | - ineffassign 69 | # - interfacebloat 70 | # - intrange 71 | # - ireturn 72 | - lll 73 | # - loggercheck 74 | # - maintidx 75 | # - makezero 76 | # - mirror 77 | - misspell 78 | # - mnd 79 | # - musttag 80 | - nakedret 81 | - nestif 82 | # - nilerr 83 | - nilnesserr 84 | # - nilnil 85 | # - nlreturn 86 | # - noctx 87 | # - nolintlint 88 | # - nonamedreturns 89 | # - nosprintfhostport 90 | # - paralleltest 91 | - perfsprint 92 | # - prealloc 93 | # - predeclared 94 | # - promlinter 95 | # - protogetter 96 | # - reassign 97 | # - recvcheck 98 | - revive 99 | # - rowserrcheck 100 | # - sloglint 101 | - spancheck 102 | # - sqlclosecheck 103 | - staticcheck 104 | # - stylecheck 105 | # - tagalign 106 | # - tagliatelle 107 | # - testableexamples 108 | - testifylint 109 | # - testpackage 110 | # - thelper 111 | # - tparallel 112 | # - unconvert 113 | # - unparam 114 | - unused 115 | - usestdlibvars 116 | - usetesting 117 | # - varnamelen 118 | # - wastedassign 119 | # - whitespace 120 | # - wrapcheck 121 | # - wsl 122 | # - zerologlint 123 | 124 | # Enable all available linters. 125 | # Default: false 126 | # enable-all: true 127 | # Disable specific linter 128 | # https://golangci-lint.run/usage/linters/#disabled-by-default 129 | # disable: 130 | # - asasalint 131 | # - asciicheck 132 | # - bidichk 133 | # - bodyclose 134 | # - canonicalheader 135 | # - containedctx 136 | # - contextcheck 137 | # - copyloopvar 138 | # - cyclop 139 | # - decorder 140 | # - depguard 141 | # - dogsled 142 | # - dupl 143 | # - dupword 144 | # - durationcheck 145 | # - err113 146 | # - errcheck 147 | # - errchkjson 148 | # - errname 149 | # - errorlint 150 | # - exhaustive 151 | # - exhaustruct 152 | # - exptostd 153 | # - fatcontext 154 | # - forbidigo 155 | # - forcetypeassert 156 | # - funlen 157 | # - gci 158 | # - ginkgolinter 159 | # - gocheckcompilerdirectives 160 | # - gochecknoglobals 161 | # - gochecknoinits 162 | # - gochecksumtype 163 | # - gocognit 164 | # - goconst 165 | # - gocritic 166 | # - gocyclo 167 | # - godot 168 | # - godox 169 | # - gofmt 170 | # - gofumpt 171 | # - goheader 172 | # - goimports 173 | # - gomoddirectives 174 | # - gomodguard 175 | # - goprintffuncname 176 | # - gosec 177 | # - gosimple 178 | # - gosmopolitan 179 | # - govet 180 | # - grouper 181 | # - iface 182 | # - importas 183 | # - inamedparam 184 | # - ineffassign 185 | # - interfacebloat 186 | # - intrange 187 | # - ireturn 188 | # - lll 189 | # - loggercheck 190 | # - maintidx 191 | # - makezero 192 | # - mirror 193 | # - misspell 194 | # - mnd 195 | # - musttag 196 | # - nakedret 197 | # - nestif 198 | # - nilerr 199 | # - nilnesserr 200 | # - nilnil 201 | # - nlreturn 202 | # - noctx 203 | # - nolintlint 204 | # - nonamedreturns 205 | # - nosprintfhostport 206 | # - paralleltest 207 | # - perfsprint 208 | # - prealloc 209 | # - predeclared 210 | # - promlinter 211 | # - protogetter 212 | # - reassign 213 | # - recvcheck 214 | # - revive 215 | # - rowserrcheck 216 | # - sloglint 217 | # - spancheck 218 | # - sqlclosecheck 219 | # - staticcheck 220 | # - stylecheck 221 | # - tagalign 222 | # - tagliatelle 223 | # - testableexamples 224 | # - testifylint 225 | # - testpackage 226 | # - thelper 227 | # - tparallel 228 | # - unconvert 229 | # - unparam 230 | # - unused 231 | # - usestdlibvars 232 | # - usetesting 233 | # - varnamelen 234 | # - wastedassign 235 | # - whitespace 236 | # - wrapcheck 237 | # - wsl 238 | # - zerologlint 239 | # - deadcode # Deprecated 240 | # - execinquery # Deprecated 241 | # - exhaustivestruct # Deprecated 242 | # - exportloopref # Deprecated 243 | # - golint # Deprecated 244 | # - gomnd # Deprecated 245 | # - ifshort # Deprecated 246 | # - interfacer # Deprecated 247 | # - maligned # Deprecated 248 | # - nosnakecase # Deprecated 249 | # - scopelint # Deprecated 250 | # - structcheck # Deprecated 251 | # - tenv # Deprecated 252 | # - varcheck # Deprecated 253 | 254 | # Enable presets. 255 | # https://golangci-lint.run/usage/linters 256 | # Default: [] 257 | # presets: 258 | # - bugs 259 | # - comment 260 | # - complexity 261 | # - error 262 | # - format 263 | # - import 264 | # - metalinter 265 | # - module 266 | # - performance 267 | # - sql 268 | # - style 269 | # - test 270 | # - unused 271 | 272 | # Enable only fast linters from enabled linters set (first run won't be fast) 273 | # Default: false 274 | # fast: true 275 | 276 | 277 | # All available settings of specific linters. 278 | linters-settings: 279 | copyloopvar: 280 | # Check all assigning the loop variable to another variable. 281 | # Default: false 282 | check-alias: true 283 | 284 | depguard: 285 | # Rules to apply. 286 | # 287 | # Variables: 288 | # - File Variables 289 | # Use an exclamation mark `!` to negate a variable. 290 | # Example: `!$test` matches any file that is not a go test file. 291 | # 292 | # `$all` - matches all go files 293 | # `$test` - matches all go test files 294 | # 295 | # - Package Variables 296 | # 297 | # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) 298 | # 299 | # Default (applies if no custom rules are defined): Only allow $gostd in all files. 300 | rules: 301 | # Name of a rule. 302 | main: 303 | # Defines package matching behavior. Available modes: 304 | # - `original`: allowed if it doesn't match the deny list and either matches the allow list or the allow list is empty. 305 | # - `strict`: allowed only if it matches the allow list and either doesn't match the deny list or the allow rule is more specific (longer) than the deny rule. 306 | # - `lax`: allowed if it doesn't match the deny list or the allow rule is more specific (longer) than the deny rule. 307 | # Default: "original" 308 | # list-mode: lax 309 | # List of file globs that will match this list of settings to compare against. 310 | # Default: $all 311 | # files: 312 | # - "!**/*_a _file.go" 313 | # List of allowed packages. 314 | # Entries can be a variable (starting with $), a string prefix, or an exact match (if ending with $). 315 | # Default: [] 316 | # allow: 317 | # - $gostd 318 | # - github.com/OpenPeeDeeP 319 | # List of packages that are not allowed. 320 | # Entries can be a variable (starting with $), a string prefix, or an exact match (if ending with $). 321 | # Default: [] 322 | deny: 323 | # - pkg: "math/rand$" 324 | # desc: use math/rand/v2 325 | - pkg: "io/ioutil" 326 | desc: Use os instead 327 | - pkg: "github.com/pkg/errors" 328 | desc: Should be replaced by standard lib errors package 329 | - pkg: "golang.org/x/xerrors" 330 | desc: Should be replaced by standard lib errors package 331 | - pkg: "golang.org/x/net/context" 332 | desc: Should be replaced by standard lib context package 333 | - pkg: "golang.org/x/crypto/ed25519" 334 | desc: Should be replaced by standard lib ed25519 package 335 | 336 | fatcontext: 337 | # Check for potential fat contexts in struct pointers. 338 | # May generate false positives. 339 | # Default: false 340 | check-struct-pointers: true 341 | 342 | gci: 343 | # Section configuration to compare against. 344 | # Section names are case-insensitive and may contain parameters in (). 345 | # The default order of sections is `standard > default > custom > blank > dot > alias > localmodule`, 346 | # If `custom-order` is `true`, it follows the order of `sections` option. 347 | # Default: ["standard", "default"] 348 | sections: 349 | - standard # Standard section: captures all standard packages. 350 | - default # Default section: contains all imports that could not be matched to another section type. 351 | - prefix(github.com/spacemeshos/smcli) # Custom section: groups all imports with the specified Prefix. 352 | # - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. 353 | # - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. 354 | # - alias # Alias section: contains all alias imports. This section is not present unless explicitly enabled. 355 | # - localmodule # Local module section: contains all local packages. This section is not present unless explicitly enabled. 356 | 357 | # Checks that no inline Comments are present. 358 | # Default: false 359 | # no-inline-comments: true 360 | 361 | # Checks that no prefix Comments(comment lines above an import) are present. 362 | # Default: false 363 | no-prefix-comments: true 364 | 365 | # Skip generated files. 366 | # Default: true 367 | # skip-generated: false 368 | 369 | # Enable custom order of sections. 370 | # If `true`, make the section order the same as the order of `sections`. 371 | # Default: false 372 | # custom-order: true 373 | 374 | # Drops lexical ordering for custom sections. 375 | # Default: false 376 | # no-lex-order: true 377 | 378 | gochecksumtype: 379 | # Presence of `default` case in switch statements satisfies exhaustiveness, if all members are not listed. 380 | # Default: true 381 | default-signifies-exhaustive: false 382 | # Include shared interfaces in the exhaustiviness check. 383 | # Default: false 384 | include-shared-interfaces: true 385 | 386 | godot: 387 | # Comments to be checked: `declarations`, `toplevel`, or `all`. 388 | # Default: declarations 389 | # scope: toplevel 390 | # List of regexps for excluding particular comment lines from check. 391 | # Default: [] 392 | exclude: 393 | # Exclude todo and fixme comments. 394 | - "^fixme:" 395 | - "^todo:" 396 | # Check that each sentence ends with a period. 397 | # Default: true 398 | # period: false 399 | # Check that each sentence starts with a capital letter. 400 | # Default: false 401 | # capital: true 402 | 403 | gofmt: 404 | # Simplify code: gofmt with `-s` option. 405 | # Default: true 406 | # simplify: false 407 | # Apply the rewrite rules to the source before reformatting. 408 | # https://pkg.go.dev/cmd/gofmt 409 | # Default: [] 410 | rewrite-rules: 411 | - pattern: 'interface{}' 412 | replacement: 'any' 413 | - pattern: 'a[b:len(a)]' 414 | replacement: 'a[b:]' 415 | 416 | gofumpt: 417 | # Module path which contains the source code being formatted. 418 | # Default: "" 419 | # module-path: github.com/org/project 420 | 421 | # Choose whether to use the extra rules. 422 | # Default: false 423 | extra-rules: true 424 | 425 | gosimple: 426 | # Sxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks 427 | # Default: ["*"] 428 | checks: [ "all" ] 429 | 430 | govet: 431 | # Disable all analyzers. 432 | # Default: false 433 | # disable-all: true 434 | # Enable analyzers by name. 435 | # (in addition to default: 436 | # appends, asmdecl, assign, atomic, bools, buildtag, cgocall, composites, copylocks, defers, directive, errorsas, 437 | # framepointer, httpresponse, ifaceassert, loopclosure, lostcancel, nilfunc, printf, shift, sigchanyzer, slog, 438 | # stdmethods, stringintconv, structtag, testinggoroutine, tests, timeformat, unmarshal, unreachable, unsafeptr, 439 | # unusedresult 440 | # ). 441 | # Run `GL_DEBUG=govet golangci-lint run --enable=govet` to see default, all available analyzers, and enabled analyzers. 442 | # Default: [] 443 | # enable: 444 | # # Check for missing values after append. 445 | # - appends 446 | # # Report mismatches between assembly files and Go declarations. 447 | # - asmdecl 448 | # # Check for useless assignments. 449 | # - assign 450 | # # Check for common mistakes using the sync/atomic package. 451 | # - atomic 452 | # # Check for non-64-bits-aligned arguments to sync/atomic functions. 453 | # - atomicalign 454 | # # Check for common mistakes involving boolean operators. 455 | # - bools 456 | # # Check //go:build and // +build directives. 457 | # - buildtag 458 | # # Detect some violations of the cgo pointer passing rules. 459 | # - cgocall 460 | # # Check for unkeyed composite literals. 461 | # - composites 462 | # # Check for locks erroneously passed by value. 463 | # - copylocks 464 | # # Check for calls of reflect.DeepEqual on error values. 465 | # - deepequalerrors 466 | # # Report common mistakes in defer statements. 467 | # - defers 468 | # # Check Go toolchain directives such as //go:debug. 469 | # - directive 470 | # # Report passing non-pointer or non-error values to errors.As. 471 | # - errorsas 472 | # # Find structs that would use less memory if their fields were sorted. 473 | # - fieldalignment 474 | # # Find calls to a particular function. 475 | # - findcall 476 | # # Report assembly that clobbers the frame pointer before saving it. 477 | # - framepointer 478 | # # Check for mistakes using HTTP responses. 479 | # - httpresponse 480 | # # Detect impossible interface-to-interface type assertions. 481 | # - ifaceassert 482 | # # Check references to loop variables from within nested functions. 483 | # - loopclosure 484 | # # Check cancel func returned by context.WithCancel is called. 485 | # - lostcancel 486 | # # Check for useless comparisons between functions and nil. 487 | # - nilfunc 488 | # # Check for redundant or impossible nil comparisons. 489 | # - nilness 490 | # # Check consistency of Printf format strings and arguments. 491 | # - printf 492 | # # Check for comparing reflect.Value values with == or reflect.DeepEqual. 493 | # - reflectvaluecompare 494 | # # Check for possible unintended shadowing of variables. 495 | # - shadow 496 | # # Check for shifts that equal or exceed the width of the integer. 497 | # - shift 498 | # # Check for unbuffered channel of os.Signal. 499 | # - sigchanyzer 500 | # # Check for invalid structured logging calls. 501 | # - slog 502 | # # Check the argument type of sort.Slice. 503 | # - sortslice 504 | # # Check signature of methods of well-known interfaces. 505 | # - stdmethods 506 | # # Report uses of too-new standard library symbols. 507 | # - stdversion 508 | # # Check for string(int) conversions. 509 | # - stringintconv 510 | # # Check that struct field tags conform to reflect.StructTag.Get. 511 | # - structtag 512 | # # Report calls to (*testing.T).Fatal from goroutines started by a test. 513 | # - testinggoroutine 514 | # # Check for common mistaken usages of tests and examples. 515 | # - tests 516 | # # Check for calls of (time.Time).Format or time.Parse with 2006-02-01. 517 | # - timeformat 518 | # # Report passing non-pointer or non-interface values to unmarshal. 519 | # - unmarshal 520 | # # Check for unreachable code. 521 | # - unreachable 522 | # # Check for invalid conversions of uintptr to unsafe.Pointer. 523 | # - unsafeptr 524 | # # Check for unused results of calls to some functions. 525 | # - unusedresult 526 | # # Checks for unused writes. 527 | # - unusedwrite 528 | # # Check for misuses of sync.WaitGroup. 529 | # - waitgroup 530 | 531 | # Enable all analyzers. 532 | # Default: false 533 | enable-all: true 534 | # Disable analyzers by name. 535 | # (in addition to default 536 | # atomicalign, deepequalerrors, fieldalignment, findcall, nilness, reflectvaluecompare, shadow, sortslice, 537 | # timeformat, unusedwrite 538 | # ). 539 | # Run `GL_DEBUG=govet golangci-lint run --enable=govet` to see default, all available analyzers, and enabled analyzers. 540 | # Default: [] 541 | disable: 542 | # - appends 543 | # - asmdecl 544 | # - assign 545 | # - atomic 546 | # - atomicalign 547 | # - bools 548 | # - buildtag 549 | # - cgocall 550 | # - composites 551 | # - copylocks 552 | # - deepequalerrors 553 | # - defers 554 | # - directive 555 | # - errorsas 556 | - fieldalignment 557 | # - findcall 558 | # - framepointer 559 | # - httpresponse 560 | # - ifaceassert 561 | # - loopclosure 562 | # - lostcancel 563 | # - nilfunc 564 | # - nilness 565 | # - printf 566 | # - reflectvaluecompare 567 | - shadow 568 | # - shift 569 | # - sigchanyzer 570 | # - slog 571 | # - sortslice 572 | # - stdmethods 573 | # - stdversion 574 | # - stringintconv 575 | # - structtag 576 | # - testinggoroutine 577 | # - tests 578 | # - timeformat 579 | # - unmarshal 580 | # - unreachable 581 | # - unsafeptr 582 | # - unusedresult 583 | # - unusedwrite 584 | # - waitgroup 585 | 586 | # Settings per analyzer. 587 | # settings: 588 | # Analyzer name, run `go tool vet help` to see all analyzers. 589 | # printf: 590 | # # Comma-separated list of print function names to check (in addition to default, see `go tool vet help printf`). 591 | # # Default: [] 592 | # funcs: 593 | # - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 594 | # - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 595 | # - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 596 | # - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 597 | # shadow: 598 | # # Whether to be strict about shadowing; can be noisy. 599 | # # Default: false 600 | # strict: true 601 | # unusedresult: 602 | # # Comma-separated list of functions whose results must be used 603 | # # (in addition to default: 604 | # # context.WithCancel, context.WithDeadline, context.WithTimeout, context.WithValue, errors.New, fmt.Errorf, 605 | # # fmt.Sprint, fmt.Sprintf, sort.Reverse 606 | # # ). 607 | # # Default: [] 608 | # funcs: 609 | # - pkg.MyFunc 610 | # # Comma-separated list of names of methods of type func() string whose results must be used 611 | # # (in addition to default Error,String) 612 | # # Default: [] 613 | # stringmethods: 614 | # - MyMethod 615 | 616 | importas: 617 | # Do not allow unaliased imports of aliased packages. 618 | # Default: false 619 | no-unaliased: true 620 | # Do not allow non-required aliases. 621 | # Default: false 622 | # no-extra-aliases: true 623 | # List of aliases 624 | # Default: [] 625 | alias: 626 | - pkg: "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" 627 | alias: chaos 628 | - pkg: "github.com/hashicorp/golang-lru/v2" 629 | alias: lru 630 | - pkg: "github.com/grpc-ecosystem/go-grpc-middleware" 631 | alias: grpcmw 632 | - pkg: "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" 633 | alias: grpczap 634 | - pkg: "github.com/grpc-ecosystem/go-grpc-middleware/tags" 635 | alias: grpctags 636 | - pkg: "github.com/libp2p/go-libp2p-pubsub" 637 | alias: pubsub 638 | - pkg: "github.com/libp2p/go-libp2p-pubsub/pb" 639 | alias: pubsubpb 640 | - pkg: "github.com/libp2p/go-libp2p/p2p/net/mock" 641 | alias: mocknet 642 | - pkg: "github.com/libp2p/go-libp2p-testing/netutil" 643 | alias: p2putil 644 | - pkg: "github.com/multiformats/go-multiaddr" 645 | alias: ma 646 | - pkg: "github.com/multiformats/go-multiaddr/net" 647 | alias: manet 648 | - pkg: "github.com/spacemeshos/api/release/go/spacemesh/v1" 649 | alias: pb 650 | - pkg: "github.com/spacemeshos/go-spacemesh/genvm" 651 | alias: vm 652 | - pkg: "github.com/spacemeshos/go-spacemesh/p2p/metrics" 653 | alias: p2pmetrics 654 | - pkg: "github.com/spacemeshos/go-spacemesh/sql/metrics" 655 | alias: dbmetrics 656 | - pkg: "github.com/spacemeshos/go-spacemesh/txs/types" 657 | alias: txtypes 658 | - pkg: "google.golang.org/genproto/googleapis/rpc/status" 659 | alias: rpcstatus 660 | - pkg: "k8s.io/apimachinery/pkg/apis/meta/v1" 661 | alias: apimetav1 662 | - pkg: "k8s.io/api/apps/v1" 663 | alias: apiappsv1 664 | - pkg: "k8s.io/api/core/v1" 665 | alias: apiv1 666 | - pkg: "k8s.io/client-go/applyconfigurations/apps/v1" 667 | alias: appsv1 668 | - pkg: "k8s.io/client-go/applyconfigurations/core/v1" 669 | alias: corev1 670 | - pkg: "k8s.io/client-go/applyconfigurations/meta/v1" 671 | alias: metav1 672 | 673 | lll: 674 | # Max line length, lines longer will be reported. 675 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option. 676 | # Default: 120. 677 | line-length: 120 678 | # Tab width in spaces. 679 | # Default: 1 680 | tab-width: 4 681 | 682 | misspell: 683 | # Correct spellings using locale preferences for US or UK. 684 | # Setting locale to US will correct the British spelling of 'colour' to 'color'. 685 | # Default is to use a neutral variety of English. 686 | locale: US 687 | # Typos to ignore. 688 | # Should be in lower case. 689 | # Default: [] 690 | # ignore-words: 691 | # - someword 692 | # Extra word corrections. 693 | # `typo` and `correction` should only contain letters. 694 | # The words are case-insensitive. 695 | # Default: [] 696 | extra-words: 697 | - typo: "iff" 698 | correction: "if" 699 | - typo: "cancelation" 700 | correction: "cancellation" 701 | # Mode of the analysis: 702 | # - default: checks all the file content. 703 | # - restricted: checks only comments. 704 | # Default: "" 705 | mode: restricted 706 | 707 | nakedret: 708 | # Make an issue if func has more lines of code than this setting, and it has naked returns. 709 | # Default: 30 710 | max-func-lines: 30 711 | 712 | nestif: 713 | # Minimal complexity of if statements to report. 714 | # Default: 5 715 | min-complexity: 15 716 | 717 | perfsprint: 718 | # Enable/disable optimization of integer formatting. 719 | # Default: true 720 | # integer-format: false 721 | # Optimizes even if it requires an int or uint type cast. 722 | # Default: true 723 | # int-conversion: false 724 | # Enable/disable optimization of error formatting. 725 | # Default: true 726 | # error-format: false 727 | # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. 728 | # Default: false 729 | # err-error: true 730 | # Optimizes `fmt.Errorf`. 731 | # Default: true 732 | # errorf: false 733 | # Enable/disable optimization of string formatting. 734 | # Default: true 735 | # string-format: false 736 | # Optimizes `fmt.Sprintf` with only one argument. 737 | # Default: true 738 | # sprintf1: false 739 | # Optimizes into strings concatenation. 740 | # Default: true 741 | strconcat: false 742 | # Enable/disable optimization of bool formatting. 743 | # Default: true 744 | # bool-format: false 745 | # Enable/disable optimization of hex formatting. 746 | # Default: true 747 | # hex-format: false 748 | 749 | revive: 750 | # Maximum number of open files at the same time. 751 | # See https://github.com/mgechev/revive#command-line-flags 752 | # Defaults to unlimited. 753 | max-open-files: 2048 754 | 755 | # When set to false, ignores files with "GENERATED" header, similar to golint. 756 | # See https://github.com/mgechev/revive#configuration for details. 757 | # Default: false 758 | ignore-generated-header: true 759 | 760 | # Sets the default severity. 761 | # See https://github.com/mgechev/revive#configuration 762 | # Default: warning 763 | # severity: error 764 | 765 | # Enable all available rules. 766 | # Default: false 767 | # enable-all-rules: true 768 | 769 | # Sets the default failure confidence. 770 | # This means that linting errors with less than 0.8 confidence will be ignored. 771 | # Default: 0.8 772 | # confidence: 0.1 773 | # Run `GL_DEBUG=revive golangci-lint run --enable-only=revive` to see default, all available rules, and enabled rules. 774 | # rules: 775 | 776 | spancheck: 777 | # Checks to enable. 778 | # Options include: 779 | # - `end`: check that `span.End()` is called 780 | # - `record-error`: check that `span.RecordError(err)` is called when an error is returned 781 | # - `set-status`: check that `span.SetStatus(codes.Error, msg)` is called when an error is returned 782 | # Default: ["end"] 783 | checks: 784 | - end 785 | - record-error 786 | - set-status 787 | # A list of regexes for function signatures that silence `record-error` and `set-status` reports 788 | # if found in the call path to a returned error. 789 | # https://github.com/jjti/go-spancheck#ignore-check-signatures 790 | # Default: [] 791 | ignore-check-signatures: 792 | - "telemetry.RecordError" 793 | # A list of regexes for additional function signatures that create spans. 794 | # This is useful if you have a utility method to create spans. 795 | # Each entry should be of the form `:`, where `telemetry-type` can be `opentelemetry` or `opencensus`. 796 | # https://github.com/jjti/go-spancheck#extra-start-span-signatures 797 | # Default: [] 798 | # extra-start-span-signatures: 799 | # - "github.com/user/repo/telemetry/trace.Start:opentelemetry" 800 | staticcheck: 801 | # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks 802 | # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] 803 | # Default: ["*"] 804 | checks: [ "all" ] 805 | 806 | testifylint: 807 | # Enable all checkers (https://github.com/Antonboom/testifylint#checkers). 808 | # Default: false 809 | enable-all: true 810 | # Disable checkers by name 811 | # (in addition to default 812 | # suite-thelper 813 | # ). 814 | # disable: 815 | # - blank-import 816 | # - bool-compare 817 | # - compares 818 | # - contains 819 | # - empty 820 | # - encoded-compare 821 | # - error-is-as 822 | # - error-nil 823 | # - expected-actual 824 | # - float-compare 825 | # - formatter 826 | # - go-require 827 | # - len 828 | # - negative-positive 829 | # - nil-compare 830 | # - regexp 831 | # - require-error 832 | # - suite-broken-parallel 833 | # - suite-dont-use-pkg 834 | # - suite-extra-assert-call 835 | # - suite-subtest-run 836 | # - suite-thelper 837 | # - useless-assert 838 | 839 | # Disable all checkers (https://github.com/Antonboom/testifylint#checkers). 840 | # Default: false 841 | # disable-all: true 842 | # Enable checkers by name 843 | # (in addition to default 844 | # blank-import, bool-compare, compares, contains, empty, encoded-compare, error-is-as, error-nil, expected-actual, 845 | # go-require, float-compare, formatter, len, negative-positive, nil-compare, regexp, require-error, 846 | # suite-broken-parallel, suite-dont-use-pkg, suite-extra-assert-call, suite-subtest-run, useless-assert 847 | # ). 848 | # enable: 849 | # - blank-import 850 | # - bool-compare 851 | # - compares 852 | # - contains 853 | # - empty 854 | # - encoded-compare 855 | # - error-is-as 856 | # - error-nil 857 | # - expected-actual 858 | # - float-compare 859 | # - formatter 860 | # - go-require 861 | # - len 862 | # - negative-positive 863 | # - nil-compare 864 | # - regexp 865 | # - require-error 866 | # - suite-broken-parallel 867 | # - suite-dont-use-pkg 868 | # - suite-extra-assert-call 869 | # - suite-subtest-run 870 | # - suite-thelper 871 | # - useless-assert 872 | 873 | usestdlibvars: 874 | # Suggest the use of http.MethodXX. 875 | # Default: true 876 | # http-method: false 877 | # Suggest the use of http.StatusXX. 878 | # Default: true 879 | # http-status-code: false 880 | # Suggest the use of time.Weekday.String(). 881 | # Default: true 882 | time-weekday: true 883 | # Suggest the use of time.Month.String(). 884 | # Default: false 885 | time-month: true 886 | # Suggest the use of time.Layout. 887 | # Default: false 888 | time-layout: true 889 | # Suggest the use of crypto.Hash.String(). 890 | # Default: false 891 | crypto-hash: true 892 | # Suggest the use of rpc.DefaultXXPath. 893 | # Default: false 894 | default-rpc-path: true 895 | # Suggest the use of sql.LevelXX.String(). 896 | # Default: false 897 | sql-isolation-level: true 898 | # Suggest the use of tls.SignatureScheme.String(). 899 | # Default: false 900 | tls-signature-scheme: true 901 | # Suggest the use of constant.Kind.String(). 902 | # Default: false 903 | constant-kind: true 904 | 905 | usetesting: 906 | # Enable/disable `os.CreateTemp("", ...)` detections. 907 | # Default: true 908 | # os-create-temp: false 909 | 910 | # Enable/disable `os.MkdirTemp()` detections. 911 | # Default: true 912 | # os-mkdir-temp: false 913 | 914 | # Enable/disable `os.Setenv()` detections. 915 | # Default: true 916 | # os-setenv: false 917 | 918 | # Enable/disable `os.TempDir()` detections. 919 | # Default: false 920 | os-temp-dir: true 921 | 922 | # Enable/disable `os.Chdir()` detections. 923 | # Disabled if Go < 1.24. 924 | # Default: true 925 | # os-chdir: false 926 | 927 | # Enable/disable `context.Background()` detections. 928 | # Disabled if Go < 1.24. 929 | # Default: true 930 | # context-background: false 931 | 932 | # Enable/disable `context.TODO()` detections. 933 | # Disabled if Go < 1.24. 934 | # Default: true 935 | # context-todo: false 936 | 937 | unused: 938 | # Mark all struct fields that have been written to as used. 939 | # Default: true 940 | field-writes-are-uses: false 941 | # Treat IncDec statement (e.g. `i++` or `i--`) as both read and write operation instead of just write. 942 | # Default: false 943 | # post-statements-are-reads: true 944 | # Mark all exported fields as used. 945 | # default: true 946 | exported-fields-are-used: false 947 | # Mark all function parameters as used. 948 | # default: true 949 | # parameters-are-used: false 950 | # Mark all local variables as used. 951 | # default: true 952 | local-variables-are-used: false 953 | # Mark all identifiers inside generated files as used. 954 | # Default: true 955 | # generated-is-used: false 956 | 957 | issues: 958 | # List of regexps of issue texts to exclude. 959 | # 960 | # But independently of this option we use default exclude patterns, 961 | # it can be disabled by `exclude-use-default: false`. 962 | # To list all excluded by default patterns execute `golangci-lint run --help` 963 | # 964 | # Default: https://golangci-lint.run/usage/false-positives/#default-exclusions 965 | # exclude: 966 | # - abcdef 967 | 968 | # Excluding configuration per-path, per-linter, per-text and per-source 969 | # exclude-rules: 970 | # # Exclude some linters from running on tests files. 971 | # - path: _test\.go 972 | # linters: 973 | # - gocyclo 974 | # - errcheck 975 | # - dupl 976 | # - gosec 977 | 978 | # # Run some linter only for test files by excluding its issues for everything else. 979 | # - path-except: _test\.go 980 | # linters: 981 | # - forbidigo 982 | 983 | # # Exclude known linters from partially hard-vendored code, 984 | # # which is impossible to exclude via `nolint` comments. 985 | # # `/` will be replaced by current OS file path separator to properly work on Windows. 986 | # - path: internal/hmac/ 987 | # text: "weak cryptographic primitive" 988 | # linters: 989 | # - gosec 990 | 991 | # # Exclude some `staticcheck` messages. 992 | # - linters: 993 | # - staticcheck 994 | # text: "SA9003:" 995 | 996 | # # Exclude `lll` issues for long lines with `go:generate`. 997 | # - linters: 998 | # - lll 999 | # source: "^//go:generate " 1000 | 1001 | # Independently of option `exclude` we use default exclude patterns, 1002 | # it can be disabled by this option. 1003 | # To list all excluded by default patterns execute `golangci-lint run --help`. 1004 | # Default: true 1005 | exclude-use-default: false 1006 | 1007 | # If set to true, `exclude` and `exclude-rules` regular expressions become case-sensitive. 1008 | # Default: false 1009 | exclude-case-sensitive: false 1010 | 1011 | # Which dirs to exclude: issues from them won't be reported. 1012 | # Can use regexp here: `generated.*`, regexp is applied on full path, 1013 | # including the path prefix if one is set. 1014 | # Default dirs are skipped independently of this option's value (see exclude-dirs-use-default). 1015 | # "/" will be replaced by current OS file path separator to properly work on Windows. 1016 | # Default: [] 1017 | # exclude-dirs: 1018 | # - src/external_libs 1019 | # - autogenerated_by_my_lib 1020 | 1021 | # Enables exclude of directories: 1022 | # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 1023 | # Default: true 1024 | exclude-dirs-use-default: false 1025 | 1026 | # Which files to exclude: they will be analyzed, but issues from them won't be reported. 1027 | # There is no need to include all autogenerated files, 1028 | # we confidently recognize autogenerated files. 1029 | # If it's not, please let us know. 1030 | # "/" will be replaced by current OS file path separator to properly work on Windows. 1031 | # Default: [] 1032 | # exclude-files: 1033 | # - ".*\\.my\\.go$" 1034 | # - lib/bad.go 1035 | 1036 | # Mode of the generated files analysis. 1037 | # 1038 | # - `strict`: sources are excluded by following strictly the Go generated file convention. 1039 | # Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` 1040 | # This line must appear before the first non-comment, non-blank text in the file. 1041 | # https://go.dev/s/generatedcode 1042 | # - `lax`: sources are excluded if they contain lines `autogenerated file`, `code generated`, `do not edit`, etc. 1043 | # - `disable`: disable the generated files exclusion. 1044 | # 1045 | # Default: lax 1046 | # exclude-generated: strict 1047 | 1048 | # The list of ids of default excludes to include or disable. 1049 | # https://golangci-lint.run/usage/false-positives/#default-exclusions 1050 | # Default: [] 1051 | # include: 1052 | # - EXC0001 1053 | # - EXC0002 1054 | # - EXC0003 1055 | # - EXC0004 1056 | # - EXC0005 1057 | # - EXC0006 1058 | # - EXC0007 1059 | # - EXC0008 1060 | # - EXC0009 1061 | # - EXC0010 1062 | # - EXC0011 1063 | # - EXC0012 1064 | # - EXC0013 1065 | # - EXC0014 1066 | # - EXC0015 1067 | 1068 | # Maximum issues count per one linter. 1069 | # Set to 0 to disable. 1070 | # Default: 50 1071 | max-issues-per-linter: 0 1072 | 1073 | # Maximum count of issues with the same text. 1074 | # Set to 0 to disable. 1075 | # Default: 3 1076 | max-same-issues: 0 1077 | 1078 | # Make issues output unique by line. 1079 | # Default: true 1080 | # uniq-by-line: false 1081 | 1082 | # Show only new issues: if there are unstaged changes or untracked files, 1083 | # only those changes are analyzed, else only changes in HEAD~ are analyzed. 1084 | # It's a super-useful option for integration of golangci-lint into existing large codebase. 1085 | # It's not practical to fix all existing issues at the moment of integration: 1086 | # much better don't allow issues in new code. 1087 | # 1088 | # Default: false 1089 | # new: true 1090 | 1091 | # Show only new issues created after the best common ancestor (merge-base against HEAD). 1092 | # Default: "" 1093 | # new-from-merge-base: main 1094 | 1095 | # Show only new issues created after git revision `REV`. 1096 | # Default: "" 1097 | # new-from-rev: HEAD 1098 | 1099 | # Show only new issues created in git patch with set file path. 1100 | # Default: "" 1101 | # new-from-patch: path/to/patch/file 1102 | 1103 | # Show issues in any part of update files (requires new-from-rev or new-from-patch). 1104 | # Default: false 1105 | whole-files: true 1106 | 1107 | # Fix found issues (if it's supported by the linter). 1108 | # Default: false 1109 | # fix: true 1110 | 1111 | 1112 | # output configuration options 1113 | output: 1114 | # The formats used to render issues. 1115 | # Formats: 1116 | # - `colored-line-number` 1117 | # - `line-number` 1118 | # - `json` 1119 | # - `colored-tab` 1120 | # - `tab` 1121 | # - `html` 1122 | # - `checkstyle` 1123 | # - `code-climate` 1124 | # - `junit-xml` 1125 | # - `junit-xml-extended` 1126 | # - `github-actions` 1127 | # - `teamcity` 1128 | # - `sarif` 1129 | # Output path can be either `stdout`, `stderr` or path to the file to write to. 1130 | # 1131 | # For the CLI flag (`--out-format`), multiple formats can be specified by separating them by comma. 1132 | # The output can be specified for each of them by separating format name and path by colon symbol. 1133 | # Example: "--out-format=checkstyle:report.xml,json:stdout,colored-line-number" 1134 | # The CLI flag (`--out-format`) override the configuration file. 1135 | # 1136 | # Default: 1137 | # formats: 1138 | # - format: colored-line-number 1139 | # path: stdout 1140 | formats: 1141 | # - format: json 1142 | # path: stderr 1143 | # - format: checkstyle 1144 | # path: report.xml 1145 | - format: colored-line-number 1146 | 1147 | # Print lines of code with issue. 1148 | # Default: true 1149 | # print-issued-lines: false 1150 | 1151 | # Print linter name in the end of issue text. 1152 | # Default: true 1153 | # print-linter-name: false 1154 | 1155 | # Add a prefix to the output file references. 1156 | # Default: "" 1157 | # path-prefix: "" 1158 | 1159 | # Sort results by the order defined in `sort-order`. 1160 | # Default: false 1161 | sort-results: true 1162 | 1163 | # Order to use when sorting results. 1164 | # Require `sort-results` to `true`. 1165 | # Possible values: `file`, `linter`, and `severity`. 1166 | # 1167 | # If the severity values are inside the following list, they are ordered in this order: 1168 | # 1. error 1169 | # 2. warning 1170 | # 3. high 1171 | # 4. medium 1172 | # 5. low 1173 | # Either they are sorted alphabetically. 1174 | # 1175 | # Default: ["file"] 1176 | sort-order: 1177 | - linter 1178 | - severity 1179 | - file # filepath, line, and column. 1180 | 1181 | # Show statistics per linter. 1182 | # Default: false 1183 | # show-stats: true 1184 | 1185 | # Options for analysis running. 1186 | run: 1187 | # Timeout for analysis, e.g. 30s, 5m. 1188 | # If the value is lower or equal to 0, the timeout is disabled. 1189 | # Default: 1m 1190 | timeout: 5m 1191 | 1192 | # The mode used to evaluate relative paths. 1193 | # It's used by exclusions, Go plugins, and some linters. 1194 | # The value can be: 1195 | # - `gomod`: the paths will be relative to the directory of the `go.mod` file. 1196 | # - `gitroot`: the paths will be relative to the git root (the parent directory of `.git`). 1197 | # - `cfg`: the paths will be relative to the configuration file. 1198 | # - `wd` (NOT recommended): the paths will be relative to the place where golangci-lint is run. 1199 | # Default: wd 1200 | relative-path-mode: gomod 1201 | 1202 | # Exit code when at least one issue was found. 1203 | # Default: 1 1204 | # issues-exit-code: 2 1205 | 1206 | # Include test files or not. 1207 | # Default: true 1208 | # tests: false 1209 | 1210 | # List of build tags, all linters use it. 1211 | # Default: [] 1212 | # build-tags: 1213 | # - mytag 1214 | 1215 | # If set, we pass it to "go list -mod={option}". From "go help modules": 1216 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 1217 | # automatic updating of go.mod described above. Instead, it fails when any changes 1218 | # to go.mod are needed. This setting is most useful to check that go.mod does 1219 | # not need updates, such as in a continuous integration and testing system. 1220 | # If invoked with -mod=vendor, the go command assumes that the vendor 1221 | # directory holds the correct copies of dependencies and ignores 1222 | # the dependency descriptions in go.mod. 1223 | # 1224 | # Allowed values: readonly|vendor|mod 1225 | # Default: "" 1226 | modules-download-mode: readonly 1227 | 1228 | # Allow multiple parallel golangci-lint instances running. 1229 | # If false, golangci-lint acquires file lock on start. 1230 | # Default: false 1231 | # allow-parallel-runners: true 1232 | 1233 | # Allow multiple golangci-lint instances running, but serialize them around a lock. 1234 | # If false, golangci-lint exits with an error if it fails to acquire file lock on start. 1235 | # Default: false 1236 | # allow-serial-runners: true 1237 | 1238 | # Define the Go version limit. 1239 | # Mainly related to generics support since go1.18. 1240 | # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17 1241 | # go: '1.19' 1242 | 1243 | # Number of operating system threads (`GOMAXPROCS`) that can execute golangci-lint simultaneously. 1244 | # If it is explicitly set to 0 (i.e. not the default) then golangci-lint will automatically set the value to match Linux container CPU quota. 1245 | # Default: the number of logical CPUs in the machine 1246 | concurrency: 4 1247 | 1248 | severity: 1249 | # Set the default severity for issues. 1250 | # 1251 | # If severity rules are defined and the issues do not match or no severity is provided to the rule 1252 | # this will be the default severity applied. 1253 | # Severities should match the supported severity names of the selected out format. 1254 | # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity 1255 | # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel 1256 | # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message 1257 | # - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance 1258 | # 1259 | # `@linter` can be used as severity value to keep the severity from linters (e.g. revive, gosec, ...) 1260 | # 1261 | # Default: "" 1262 | default-severity: error 1263 | 1264 | # If set to true `severity-rules` regular expressions become case-sensitive. 1265 | # Default: false 1266 | case-sensitive: true 1267 | 1268 | # When a list of severity rules are provided, severity information will be added to lint issues. 1269 | # Severity rules have the same filtering capability as exclude rules 1270 | # except you are allowed to specify one matcher per severity rule. 1271 | # 1272 | # `@linter` can be used as severity value to keep the severity from linters (e.g. revive, gosec, ...) 1273 | # 1274 | # Only affects out formats that support setting severity information. 1275 | # 1276 | # Default: [] 1277 | # rules: 1278 | # - linters: 1279 | # - dupl 1280 | # severity: info 1281 | --------------------------------------------------------------------------------