diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff7beaee..8c19b7ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,111 +29,112 @@ jobs: - name: Create release ๐Ÿ“œ uses: goreleaser/goreleaser-action@v3.0.0 with: - args: release + version: '~> v2' + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - build-macos: - name: Build MacOS binary - runs-on: macos-10.15 - steps: - - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 - - - name: Unshallow - run: git fetch --prune --unshallow --tags --force - - - name: Set variables - run: | - echo "VERSION=$(git describe --always | sed 's/^v//')" >> $GITHUB_ENV - - name: Setup Go ๐Ÿงฐ - uses: actions/setup-go@v3 - with: - go-version: 1.23 - - - name: Build the binary ๐Ÿญ - run: | - LEDGER_ENABLED=true make build - mv build/bitsongd "build/bitsongd-$VERSION-darwin-amd64" - - name: Upload the artifacts ๐Ÿ“ค - uses: actions/upload-artifact@v4 - with: - name: "darwin-amd64" - path: "build/*darwin*amd64*" - - build-linux: - name: Build Linux binaries - runs-on: ubuntu-18.04 - strategy: - matrix: - go-arch: ["amd64", "arm64"] - steps: - - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 - - - name: Fetch tags - run: git fetch --prune --unshallow --tags --force - - - name: Set variables - run: | - echo "VERSION=$(git describe --always | sed 's/^v//')" >> $GITHUB_ENV - - name: Setup Go ๐Ÿงฐ - uses: actions/setup-go@v3 - with: - go-version: 1.23 - - - name: Compute diff ๐Ÿ“œ - uses: technote-space/get-diff-action@v6.1.0 - id: git_diff - with: - PATTERNS: | - **/**.go - go.mod - go.sum - - name: Build ๐Ÿ”จ - run: | - GOARCH=${{ matrix.go-arch }} LEDGER_ENABLED=true make build - mv build/bitsongd "build/bitsongd-$VERSION-linux-${{ matrix.go-arch }}" - - name: Upload the linux/amd64 artifact ๐Ÿ“ค - uses: actions/upload-artifact@v4 - with: - name: "linux-amd64" - path: "build/*linux*amd64*" - - - name: Upload the linux/arm64 artifact ๐Ÿ“ค - uses: actions/upload-artifact@v4 - with: - name: "linux-arm64" - path: "build/*linux*arm64*" - - build-windows: - name: Build Windows binary - runs-on: windows-latest - steps: - - name: Setting up dependencies - run: | - choco install make - - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 - - - name: Fetch tags - run: git fetch --prune --unshallow --tags --force - - - name: Set variables - run: | - Add-Content -Path $env:GITHUB_ENV -Value "VERSION=$(git describe --always | sed 's/^v//')" - - name: Setup Go ๐Ÿงฐ - uses: actions/setup-go@v3 - with: - go-version: 1.23 - - - name: Build the binary ๐Ÿญ - run: | - make LEDGER_ENABLED=true build - echo "build/bitsongd-$env:VERSION-$env:COMMIT-windows-amd64.exe" - mv build/bitsongd.exe "build/bitsongd-$env:VERSION-windows-amd64.exe" - - name: Upload the artifacts ๐Ÿ“ค - uses: actions/upload-artifact@v4 - with: - name: "windows-amd64" - path: "build/*windows*amd64*" + # build-macos: + # name: Build MacOS binary + # runs-on: macos-10.15 + # steps: + # - name: Checkout ๐Ÿ›Ž๏ธ + # uses: actions/checkout@v3 + + # - name: Unshallow + # run: git fetch --prune --unshallow --tags --force + + # - name: Set variables + # run: | + # echo "VERSION=$(git describe --always | sed 's/^v//')" >> $GITHUB_ENV + # - name: Setup Go ๐Ÿงฐ + # uses: actions/setup-go@v3 + # with: + # go-version: 1.23 + + # - name: Build the binary ๐Ÿญ + # run: | + # LEDGER_ENABLED=true make build + # mv build/bitsongd "build/bitsongd-$VERSION-darwin-amd64" + # - name: Upload the artifacts ๐Ÿ“ค + # uses: actions/upload-artifact@v4 + # with: + # name: "darwin-amd64" + # path: "build/*darwin*amd64*" + + # build-linux: + # name: Build Linux binaries + # runs-on: ubuntu-18.04 + # strategy: + # matrix: + # go-arch: ["amd64", "arm64"] + # steps: + # - name: Checkout ๐Ÿ›Ž๏ธ + # uses: actions/checkout@v3 + + # - name: Fetch tags + # run: git fetch --prune --unshallow --tags --force + + # - name: Set variables + # run: | + # echo "VERSION=$(git describe --always | sed 's/^v//')" >> $GITHUB_ENV + # - name: Setup Go ๐Ÿงฐ + # uses: actions/setup-go@v3 + # with: + # go-version: 1.23 + + # - name: Compute diff ๐Ÿ“œ + # uses: technote-space/get-diff-action@v6.1.0 + # id: git_diff + # with: + # PATTERNS: | + # **/**.go + # go.mod + # go.sum + # - name: Build ๐Ÿ”จ + # run: | + # GOARCH=${{ matrix.go-arch }} LEDGER_ENABLED=true make build + # mv build/bitsongd "build/bitsongd-$VERSION-linux-${{ matrix.go-arch }}" + # - name: Upload the linux/amd64 artifact ๐Ÿ“ค + # uses: actions/upload-artifact@v4 + # with: + # name: "linux-amd64" + # path: "build/*linux*amd64*" + + # - name: Upload the linux/arm64 artifact ๐Ÿ“ค + # uses: actions/upload-artifact@v4 + # with: + # name: "linux-arm64" + # path: "build/*linux*arm64*" + + # build-windows: + # name: Build Windows binary + # runs-on: windows-latest + # steps: + # - name: Setting up dependencies + # run: | + # choco install make + # - name: Checkout ๐Ÿ›Ž๏ธ + # uses: actions/checkout@v3 + + # - name: Fetch tags + # run: git fetch --prune --unshallow --tags --force + + # - name: Set variables + # run: | + # Add-Content -Path $env:GITHUB_ENV -Value "VERSION=$(git describe --always | sed 's/^v//')" + # - name: Setup Go ๐Ÿงฐ + # uses: actions/setup-go@v3 + # with: + # go-version: 1.23 + + # - name: Build the binary ๐Ÿญ + # run: | + # make LEDGER_ENABLED=true build + # echo "build/bitsongd-$env:VERSION-$env:COMMIT-windows-amd64.exe" + # mv build/bitsongd.exe "build/bitsongd-$env:VERSION-windows-amd64.exe" + # - name: Upload the artifacts ๐Ÿ“ค + # uses: actions/upload-artifact@v4 + # with: + # name: "windows-amd64" + # path: "build/*windows*amd64*" diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 00000000..e37b718f --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,19 @@ +name: Check for typos + +on: + merge_group: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + check-typos: + name: "Spell-check repository source" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Run spell-check + uses: crate-ci/typos@master \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index bd03ab6f..b215a0d3 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,27 +1,242 @@ ---- +version: 2 + project_name: go-bitsong -release: - github: - owner: bitsongofficial - name: go-bitsong +env: + - CGO_ENABLED=1 builds: - - skip: true + - id: bitsongd-linux-amd64 + main: ./cmd/bitsongd + binary: bitsongd + hooks: + pre: + - wget https://github.com/CosmWasm/wasmvm/releases/download/{{ .Env.COSMWASM_VERSION }}/libwasmvm_muslc.x86_64.a -O /usr/lib/libwasmvm_muslc.x86_64.a + goos: + - linux + goarch: + - amd64 + env: + - CC=x86_64-linux-gnu-gcc + flags: + - -mod=readonly + - -trimpath + ldflags: + - -X github.com/cosmos/cosmos-sdk/version.Name=osmosis + - -X github.com/cosmos/cosmos-sdk/version.AppName=bitsongd + - -X github.com/cosmos/cosmos-sdk/version.Version={{ .Version }} + - -X github.com/cosmos/cosmos-sdk/version.Commit={{ .Commit }} + - -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,muslc,osusergo + - -w -s + - -linkmode=external + - -extldflags '-L/usr/lib -lwasmvm_muslc.x86_64 -Wl,-z,muldefs -static -lm' + tags: + - netgo + - ledger + - muslc + - osusergo + + - id: bitsongd-linux-arm64 + main: ./cmd/bitsongd + binary: bitsongd + hooks: + pre: + - wget https://github.com/CosmWasm/wasmvm/releases/download/{{ .Env.COSMWASM_VERSION }}/libwasmvm_muslc.aarch64.a -O /usr/lib/libwasmvm_muslc.aarch64.a + goos: + - linux + goarch: + - arm64 + env: + - CC=aarch64-linux-gnu-gcc + flags: + - -mod=readonly + - -trimpath + ldflags: + - -X github.com/cosmos/cosmos-sdk/version.Name=bitsong + - -X github.com/cosmos/cosmos-sdk/version.AppName=bitsongd + - -X github.com/cosmos/cosmos-sdk/version.Version={{ .Version }} + - -X github.com/cosmos/cosmos-sdk/version.Commit={{ .Commit }} + - -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,muslc,osusergo + - -w -s + - -linkmode=external + - -extldflags '-L/usr/lib -lwasmvm_muslc.aarch64 -Wl,-z,muldefs -static -lm' + tags: + - netgo + - ledger + - muslc + - osusergo + + - id: bitsongd-darwin-amd64 + main: ./cmd/bitsongd/main.go + binary: bitsongd + hooks: + pre: + - wget https://github.com/CosmWasm/wasmvm/releases/download/{{ .Env.COSMWASM_VERSION }}/libwasmvmstatic_darwin.a -O /lib/libwasmvmstatic_darwin.a + env: + - CC=o64-clang + - CGO_CFLAGS=-mmacosx-version-min=10.12 + - CGO_LDFLAGS=-L/lib -mmacosx-version-min=10.12 + goos: + - darwin + goarch: + - amd64 + flags: + - -mod=readonly + - -trimpath + ldflags: + - -X github.com/cosmos/cosmos-sdk/version.Name=osmosis + - -X github.com/cosmos/cosmos-sdk/version.AppName=bitsongd + - -X github.com/cosmos/cosmos-sdk/version.Version={{ .Version }} + - -X github.com/cosmos/cosmos-sdk/version.Commit={{ .Commit }} + - -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,static_wasm + - -w -s + - -linkmode=external + tags: + - netgo + - ledger + - static_wasm + + - id: bitsongd-darwin-arm64 + main: ./cmd/bitsongd/main.go + binary: bitsongd + hooks: + pre: + - wget https://github.com/CosmWasm/wasmvm/releases/download/{{ .Env.COSMWASM_VERSION }}/libwasmvmstatic_darwin.a -O /lib/libwasmvmstatic_darwin.a + env: + - CC=oa64-clang + - CGO_LDFLAGS=-L/lib + goos: + - darwin + goarch: + - arm64 + flags: + - -mod=readonly + - -trimpath + ldflags: + - -X github.com/cosmos/cosmos-sdk/version.Name=bitsong + - -X github.com/cosmos/cosmos-sdk/version.AppName=bitsongd + - -X github.com/cosmos/cosmos-sdk/version.Version={{ .Version }} + - -X github.com/cosmos/cosmos-sdk/version.Commit={{ .Commit }} + - -X github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger,static_wasm + - -w -s + - -linkmode=external + tags: + - netgo + - ledger + - static_wasm + +universal_binaries: + - id: bitsongd-darwin-universal + ids: + - bitsongd-darwin-amd64 + - bitsongd-darwin-arm64 + replace: false archives: - - format: tar.gz - wrap_in_directory: true - format_overrides: - - goos: windows - format: zip - name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + - id: zipped + builds: + - bitsongd-darwin-universal + - bitsongd-linux-amd64 + - bitsongd-linux-arm64 + - bitsongd-darwin-amd64 + - bitsongd-darwin-arm64 + name_template: "{{.ProjectName}}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" + format: tar.gz + files: + - none* + - id: binaries + builds: + - bitsongd-darwin-universal + - bitsongd-linux-amd64 + - bitsongd-linux-arm64 + - bitsongd-darwin-amd64 + - bitsongd-darwin-arm64 + name_template: "{{.ProjectName}}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" + format: binary files: - - LICENSE - - README.md + - none* -snapshot: - name_template: SNAPSHOT-{{ .Commit }} +checksum: + name_template: "sha256sum.txt" + algorithm: sha256 +# Docs: https://goreleaser.com/customization/changelog/ changelog: - skip: true + disable: true + +blobs: + - provider: s3 + bucket: bitsong + directory: "binaries/v{{.Version}}" + endpoint: '{{ .Env.S3_ENDPOINT }}' + region: '{{ .Env.S3_REGION }}' + acl: public-read + ids: + - zipped + - binaries + +# Docs: https://goreleaser.com/customization/release/ +release: + github: + owner: bitsongofficial + name: go-bitsong + replace_existing_draft: true + header: | + < DESCRIPTION OF RELEASE > + + ## Changelog + + See the full changelog [here](https://github.com/bitsongofficial/go-bitsong/blob/v{{ .Version }}/CHANGELOG.md) + + ## โšก๏ธ Binaries + + Binaries for Linux (amd64 and arm64) are available below. + + #### ๐Ÿ”จ Build from source + + If you prefer to build from source, you can use the following commands: + + ````bash + git clone https://github.com/bitsongofficial/go-bitsong + cd go-bitsong && git checkout v{{ .Version }} + make install + ```` + + ## ๐Ÿณ Run with Docker + + As an alternative to installing and running bitsongd on your system, you may run bitsongd in a Docker container. + The following Docker images are available in our registry: + + | Image Name | Base | Description | + |----------------------------------------------|--------------------------------------|-----------------------------------| + | `bitsongofficial/go-bitsong:{{ .Version }}` | `distroless/static-debian11` | Default image based on Distroless | + | `bitsongofficial/go-bitsong:{{ .Version }}-distroless` | `distroless/static-debian11` | Distroless image (same as above) | + | `bitsongofficial/go-bitsong:{{ .Version }}-nonroot` | `distroless/static-debian11:nonroot` | Distroless non-root image | + | `bitsongofficial/go-bitsong:{{ .Version }}-alpine` | `alpine` | Alpine image | + + Example run: + + ```bash + docker run bitsongofficial/go-bitsong:{{ .Version }} version + # v{{ .Version }} + ```` + + All the images support `arm64` and `amd64` architectures. + + name_template: "Go-Bitsong v{{.Version}} ๐Ÿงช" + mode: replace + draft: true + +# Docs: https://goreleaser.com/customization/announce/ +# We could automatically announce the release in +# - discord +# - slack +# - twitter +# - webhooks +# - telegram +# - reddit +# +# announce: + # discord: + # enabled: true + # message_template: 'New {{.Tag}} is out!' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8274b757..93ee6305 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,8 +45,8 @@ COPY --from=go-builder /code/build/bitsongd /usr/bin/bitsongd ENV HOME=/bitsongd WORKDIR $HOME -# rest server, tendermint p2p, tendermint rpc -EXPOSE 1317 26656 26657 +# rest server, tendermint p2p, tendermint rpc, grpc +EXPOSE 1317 26656 26657 8080 9091 CMD ["/usr/bin/bitsongd"] diff --git a/Makefile b/Makefile index f8e8a8d5..9d4b2f0b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ #!/usr/bin/make -f -include contrib/devtools/Makefile +include scripts/devtools/Makefile include scripts/makefiles/build.mk +include scripts/makefiles/release.mk include scripts/makefiles/docker.mk include scripts/makefiles/e2e.mk include scripts/makefiles/format.mk @@ -24,6 +25,7 @@ help: @echo " make hl Show available docker commands (via Strangelove's Heighliner Tooling)" @echo " make install Install Bitsong node binary" @echo " make localnet Show available localnet commands" + @echo " make release Show available release commands" @echo " make proto Show available protobuf commands" @echo " make test Show available testing commands" @echo "Run 'make [subcommand]' to see the available commands for each subcommand." @@ -152,4 +154,70 @@ distclean: clean .PHONY: all build-linux install install-debug \ go-mod-cache draw-deps clean build \ build-docker-bitsongdnode localnet-start localnet-stop test-docker test-docker-push \ - test test-all test-cover \ No newline at end of file + test test-all test-cover + + +############################################################################### +### Release ### +############################################################################### +GORELEASER_IMAGE := ghcr.io/goreleaser/goreleaser-cross:v$(GO_MAJOR_MINOR) +COSMWASM_VERSION := $(shell go list -m github.com/CosmWasm/wasmvm/v2 | sed 's/.* //') + +ifdef GITHUB_TOKEN +ifdef S3_ENDPOINT +ifdef S3_REGION +ifdef AWS_ACCESS_KEY_ID +ifdef AWS_SECRET_ACCESS_KEY + +release: + docker run \ + --rm \ + -e GITHUB_TOKEN=$(GITHUB_TOKEN) \ + -e COSMWASM_VERSION=$(COSMWASM_VERSION) \ + -e S3_ENDPOINT=$(S3_ENDPOINT) \ + -e S3_REGION=$(S3_REGION) \ + -e AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \ + -e AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/osmosisd \ + -w /go/src/osmosisd \ + $(GORELEASER_IMAGE) \ + release \ + --clean + +else +release: + @echo "Error: GITHUB_TOKEN is not defined. Please define it before running 'make release'." +endif +else +release: + @echo "Error: S3_ENDPOINT is not defined. Please define it before running 'make release'." +endif +else +release: + @echo "Error: S3_REGION is not defined. Please define it before running 'make release'." +endif +else +release: + @echo "Error: AWS_ACCESS_KEY_ID is not defined. Please define it before running 'make release'." +endif +else +release: + @echo "Error: AWS_SECRET_ACCESS_KEY is not defined. Please define it before running 'make release'." +endif + +release-test: + docker run \ + --rm \ + -e COSMWASM_VERSION=$(COSMWASM_VERSION) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/osmosisd \ + -w /go/src/osmosisd \ + $(GORELEASER_IMAGE) \ + release \ + --snapshot --clean + +.PHONY: all build-linux install format lint \ + go-mod-cache draw-deps clean build build-contract-tests-hooks \ + test test-all test-build test-cover test-unit test-race benchmark \ + release release-dry-run release-snapshot update-deps \ No newline at end of file diff --git a/app/ante_handler.go b/app/ante_handler.go index b93b92a9..400e711f 100644 --- a/app/ante_handler.go +++ b/app/ante_handler.go @@ -140,11 +140,11 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // authenticatorVerificationDecorator is the new authenticator flow that's embedded into the circuit breaker ante authenticatorVerificationDecorator := sdk.ChainAnteDecorators( smartaccountante.NewEmitPubKeyDecoratorEvents(options.AccountKeeper), - ante.NewValidateSigCountDecorator(options.AccountKeeper), // we can probably remove this as multisigs are not supported here + ante.NewValidateSigCountDecorator(options.AccountKeeper), // Both the signature verification, fee deduction, and gas consumption functionality // is embedded in the authenticator decorator smartaccountante.NewAuthenticatorDecorator(options.Cdc, options.SmartAccount, options.AccountKeeper, options.SignModeHandler, deductFeeDecorator), - ante.NewIncrementSequenceDecorator(options.AccountKeeper), + smartaccountante.NewIncrementSequenceDecorator(options.AccountKeeper), ) anteDecorators := []sdk.AnteDecorator{ diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 68ae25c2..6ad982e4 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -254,6 +254,7 @@ func NewAppKeepers( authenticator.NewAnyOf(appKeepers.AuthenticatorManager), authenticator.NewPartitionedAnyOf(appKeepers.AuthenticatorManager), authenticator.NewPartitionedAllOf(appKeepers.AuthenticatorManager), + authenticator.NewBls12381(appKeepers.AuthenticatorManager, appKeepers.keys[smartaccounttypes.ModuleName]), }) govModuleAddr := appKeepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName) diff --git a/btsgutils/cache/nonblocking/list.go b/btsgutils/cache/nonblocking/list.go new file mode 100644 index 00000000..c3654d2c --- /dev/null +++ b/btsgutils/cache/nonblocking/list.go @@ -0,0 +1,123 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE_list file. +package nonblocking + +// entry is an LRU entry +type entry[K comparable, V any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *entry[K, V] + + // The list to which this element belongs. + list *lruList[K, V] + + // The LRU key of this element. + key K + + // The value stored with this element. + value V +} + +// lruList represents a doubly linked list. +// The zero value for lruList is an empty list ready to use. +type lruList[K comparable, V any] struct { + root entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// init initializes or clears list l. +func (l *lruList[K, V]) init() *lruList[K, V] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// newList returns an initialized list. +func newList[K comparable, V any]() *lruList[K, V] { return new(lruList[K, V]).init() } + +// length returns the number of elements of list l. +// The complexity is O(1). +func (l *lruList[K, V]) length() int { return l.len } + +// back returns the last element of list l or nil if the list is empty. +func (l *lruList[K, V]) back() *entry[K, V] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *lruList[K, V]) lazyInit() { + if l.root.next == nil { + l.init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *lruList[K, V]) insert(e, at *entry[K, V]) *entry[K, V] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *lruList[K, V]) insertValue(k K, v V, at *entry[K, V]) *entry[K, V] { + return l.insert(&entry[K, V]{value: v, key: k}, at) +} + +// remove removes e from its list, decrements l.len +func (l *lruList[K, V]) remove(e *entry[K, V]) V { + // If already removed, do nothing. + if e.prev == nil && e.next == nil { + return e.value + } + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- + + return e.value +} + +// move moves e to next to at. +func (*lruList[K, V]) move(e, at *entry[K, V]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// pushFront inserts a new element e with value v at the front of list l and returns e. +func (l *lruList[K, V]) pushFront(k K, v V) *entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, &l.root) +} + +// moveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *lruList[K, V]) moveToFront(e *entry[K, V]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} diff --git a/btsgutils/cache/nonblocking/lru.go b/btsgutils/cache/nonblocking/lru.go new file mode 100644 index 00000000..e37f1022 --- /dev/null +++ b/btsgutils/cache/nonblocking/lru.go @@ -0,0 +1,146 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package nonblocking + +import ( + "errors" + "sync" +) + +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback[K comparable, V any] func(key K, value V) + +// LRU implements a non-thread safe fixed size LRU cache +type LRU[K comparable, V any] struct { + itemsLock sync.RWMutex + evictListLock sync.RWMutex + size int + evictList *lruList[K, V] + items map[K]*entry[K, V] + onEvict EvictCallback[K, V] + getChan chan *entry[K, V] +} + +// NewLRU constructs an LRU of the given size +func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) { + if size <= 0 { + return nil, errors.New("must provide a positive size") + } + // Initialize the channel buffer size as being 10% of the cache size. + chanSize := size / 10 + + c := &LRU[K, V]{ + size: size, + evictList: newList[K, V](), + items: make(map[K]*entry[K, V]), + onEvict: onEvict, + getChan: make(chan *entry[K, V], chanSize), + } + // Spin off separate go-routine to handle evict list + // operations. + go c.handleGetRequests() + return c, nil +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *LRU[K, V]) Add(key K, value V) (evicted bool) { + // Check for existing item + c.itemsLock.RLock() + if ent, ok := c.items[key]; ok { + c.itemsLock.RUnlock() + + c.evictListLock.Lock() + c.evictList.moveToFront(ent) + c.evictListLock.Unlock() + ent.value = value + return false + } + c.itemsLock.RUnlock() + + // Add new item + c.evictListLock.Lock() + ent := c.evictList.pushFront(key, value) + c.evictListLock.Unlock() + + c.itemsLock.Lock() + c.items[key] = ent + c.itemsLock.Unlock() + + c.evictListLock.RLock() + evict := c.evictList.length() > c.size + c.evictListLock.RUnlock() + + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU[K, V]) Get(key K) (value V, ok bool) { + c.itemsLock.RLock() + if ent, ok := c.items[key]; ok { + c.itemsLock.RUnlock() + + // Make this get function non-blocking for multiple readers. + c.getChan <- ent + return ent.value, true + } + c.itemsLock.RUnlock() + return +} + +// Len returns the number of items in the cache. +func (c *LRU[K, V]) Len() int { + c.evictListLock.RLock() + defer c.evictListLock.RUnlock() + return c.evictList.length() +} + +// Resize changes the cache size. +func (c *LRU[K, V]) Resize(size int) (evicted int) { + diff := c.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.removeOldest() + } + c.size = size + return diff +} + +// removeOldest removes the oldest item from the cache. +func (c *LRU[K, V]) removeOldest() { + c.evictListLock.RLock() + if ent := c.evictList.back(); ent != nil { + c.evictListLock.RUnlock() + c.removeElement(ent) + return + } + c.evictListLock.RUnlock() +} + +// removeElement is used to remove a given list element from the cache +func (c *LRU[K, V]) removeElement(e *entry[K, V]) { + c.evictListLock.Lock() + c.evictList.remove(e) + c.evictListLock.Unlock() + + c.itemsLock.Lock() + delete(c.items, e.key) + c.itemsLock.Unlock() + if c.onEvict != nil { + c.onEvict(e.key, e.value) + } +} + +func (c *LRU[K, V]) handleGetRequests() { + for { + entry := <-c.getChan + c.evictListLock.Lock() + c.evictList.moveToFront(entry) + c.evictListLock.Unlock() + } +} diff --git a/crypto/bls/bls.go b/crypto/bls/bls.go new file mode 100644 index 00000000..2e528af1 --- /dev/null +++ b/crypto/bls/bls.go @@ -0,0 +1,78 @@ +// Package bls implements a go-wrapper around a library implementing the +// BLS12-381 curve and signature scheme. This package exposes a public API for +// verifying and aggregating BLS signatures used by Ethereum. +package bls + +import ( + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" +) + +func init() {} + +// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice. +func SecretKeyFromBytes(privKey []byte) (SecretKey, error) { + return blst.SecretKeyFromBytes(privKey) +} + +// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice. +func PublicKeyFromBytes(pubKey []byte) (PublicKey, error) { + return blst.PublicKeyFromBytes(pubKey) +} + +// SignatureFromBytesNoValidation creates a BLS signature from a LittleEndian byte slice. +// It does not check validity of the signature, use only when the byte slice has +// already been verified +func SignatureFromBytesNoValidation(sig []byte) (Signature, error) { + return blst.SignatureFromBytesNoValidation(sig) +} + +// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice. +func SignatureFromBytes(sig []byte) (Signature, error) { + return blst.SignatureFromBytes(sig) +} + +// MultipleSignaturesFromBytes creates a slice of BLS signatures from a LittleEndian 2d-byte slice. +func MultipleSignaturesFromBytes(sigs [][]byte) ([]Signature, error) { + return blst.MultipleSignaturesFromBytes(sigs) +} + +// AggregatePublicKeys aggregates the provided raw public keys into a single key. +func AggregatePublicKeys(pubs [][]byte) (PublicKey, error) { + return blst.AggregatePublicKeys(pubs) +} + +// AggregateMultiplePubkeys aggregates the provided decompressed keys into a single key. +func AggregateMultiplePubkeys(pubs []PublicKey) PublicKey { + return blst.AggregateMultiplePubkeys(pubs) +} + +// AggregateSignatures converts a list of signatures into a single, aggregated sig. +func AggregateSignatures(sigs []common.Signature) common.Signature { + return blst.AggregateSignatures(sigs) +} + +// AggregateCompressedSignatures converts a list of compressed signatures into a single, aggregated sig. +func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) { + return blst.AggregateCompressedSignatures(multiSigs) +} + +// VerifySignature verifies a single signature. For performance reason, always use VerifyMultipleSignatures if possible. +func VerifySignature(sig []byte, msg [32]byte, pubKey common.PublicKey) (bool, error) { + return blst.VerifySignature(sig, msg, pubKey) +} + +// VerifyMultipleSignatures verifies multiple signatures for distinct messages securely. +func VerifyMultipleSignatures(height uint64, sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) { + return blst.VerifyMultipleSignatures(height, sigs, msgs, pubKeys) +} + +// NewAggregateSignature creates a blank aggregate signature. +func NewAggregateSignature() common.Signature { + return blst.NewAggregateSignature() +} + +// RandKey creates a new private key using a random input. +// func RandKey() (common.SecretKey, error) { +// return blst.RandKey() +// } diff --git a/crypto/bls/blst/aliases.go b/crypto/bls/blst/aliases.go new file mode 100644 index 00000000..8f3028a7 --- /dev/null +++ b/crypto/bls/blst/aliases.go @@ -0,0 +1,11 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import blst "github.com/supranational/blst/bindings/go" + +// Internal types for blst. +type blstPublicKey = blst.P1Affine +type blstSignature = blst.P2Affine +type blstAggregateSignature = blst.P2Aggregate +type blstAggregatePublicKey = blst.P1Aggregate diff --git a/crypto/bls/blst/init.go b/crypto/bls/blst/init.go new file mode 100644 index 00000000..7223466a --- /dev/null +++ b/crypto/bls/blst/init.go @@ -0,0 +1,20 @@ +package blst + +import ( + "fmt" + + "github.com/bitsongofficial/go-bitsong/btsgutils/cache/nonblocking" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + blst "github.com/supranational/blst/bindings/go" +) + +func init() { + // Limit blst operations to a single core + blst.SetMaxProcs(1) + onEvict := func(_ [48]byte, _ common.PublicKey) {} + keysCache, err := nonblocking.NewLRU(maxKeys, onEvict) + if err != nil { + panic(fmt.Sprintf("Could not initiate public keys cache: %v", err)) + } + pubkeyCache = keysCache +} diff --git a/crypto/bls/blst/public_key.go b/crypto/bls/blst/public_key.go new file mode 100644 index 00000000..4b6edb83 --- /dev/null +++ b/crypto/bls/blst/public_key.go @@ -0,0 +1,175 @@ +package blst + +import ( + "fmt" + + "github.com/bitsongofficial/go-bitsong/btsgutils/cache/nonblocking" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/pkg/errors" + blst "github.com/supranational/blst/bindings/go" +) + +// max keys in cache +var maxKeys = 200_000 +var pubkeyCache *nonblocking.LRU[[48]byte, common.PublicKey] + +// PublicKey used in the BLS signature scheme. +type PublicKey struct { + p *blstPublicKey +} + +// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice. +func PublicKeyFromBytes(pubKey []byte) (common.PublicKey, error) { + return publicKeyFromBytes(pubKey, true) +} + +func publicKeyFromBytes(pubKey []byte, cacheCopy bool) (common.PublicKey, error) { + if len(pubKey) != 48 { + return nil, fmt.Errorf("public key must be %d bytes", 48) + } + newKey := (*[48]byte)(pubKey) + if cv, ok := pubkeyCache.Get(*newKey); ok { + if cacheCopy { + return cv.Copy(), nil + } + return cv, nil + } + // Subgroup check NOT done when decompressing pubkey. + p := new(blstPublicKey).Uncompress(pubKey) + if p == nil { + return nil, errors.New("could not unmarshal bytes into public key") + } + // Subgroup and infinity check + if !p.KeyValidate() { + // NOTE: the error is not quite accurate since it includes group check + return nil, errors.New("common.ErrInfinitePubKey") + } + pubKeyObj := &PublicKey{p: p} + copiedKey := pubKeyObj.Copy() + cacheKey := *newKey + pubkeyCache.Add(cacheKey, copiedKey) + return pubKeyObj, nil +} + +// AggregatePublicKeys aggregates the provided raw public keys into a single key. +func AggregatePublicKeys(pubs [][]byte) (common.PublicKey, error) { + if len(pubs) == 0 { + return nil, errors.New("nil or empty public keys") + } + agg := new(blstAggregatePublicKey) + mulP1 := make([]*blstPublicKey, 0, len(pubs)) + for _, pubkey := range pubs { + pubKeyObj, err := publicKeyFromBytes(pubkey, false) + if err != nil { + return nil, err + } + mulP1 = append(mulP1, pubKeyObj.(*PublicKey).p) + } + // No group check needed here since it is done in PublicKeyFromBytes + // Note the checks could be moved from PublicKeyFromBytes into Aggregate + // and take advantage of multi-threading. + agg.Aggregate(mulP1, false) + return &PublicKey{p: agg.ToAffine()}, nil +} + +// Marshal a public key into a LittleEndian byte slice. +func (p *PublicKey) Marshal() []byte { + return p.p.Compress() +} + +// Copy the public key to a new pointer reference. +func (p *PublicKey) Copy() common.PublicKey { + np := *p.p + return &PublicKey{p: &np} +} + +// IsInfinite checks if the public key is infinite. +func (p *PublicKey) IsInfinite() bool { + zeroKey := new(blstPublicKey) + return p.p.Equals(zeroKey) +} + +// Equals checks if the provided public key is equal to +// the current one. +func (p *PublicKey) Equals(p2 common.PublicKey) bool { + return p.p.Equals(p2.(*PublicKey).p) +} + +// Aggregate two public keys. +func (p *PublicKey) Aggregate(p2 common.PublicKey) common.PublicKey { + agg := new(blstAggregatePublicKey) + // No group check here since it is checked at decompression time + agg.Add(p.p, false) + agg.Add(p2.(*PublicKey).p, false) + p.p = agg.ToAffine() + + return p +} + +// AggregateMultiplePubkeys aggregates the provided decompressed keys into a single key. +func AggregateMultiplePubkeys(pubkeys []common.PublicKey) common.PublicKey { + mulP1 := make([]*blstPublicKey, 0, len(pubkeys)) + for _, pubkey := range pubkeys { + mulP1 = append(mulP1, pubkey.(*PublicKey).p) + } + agg := new(blstAggregatePublicKey) + // No group check needed here since it is done in PublicKeyFromBytes + // Note the checks could be moved from PublicKeyFromBytes into Aggregate + // and take advantage of multi-threading. + agg.Aggregate(mulP1, false) + return &PublicKey{p: agg.ToAffine()} +} + +// Retrieves the pubkey compatible with the new cosmos bls library, for a given private key defined in this modules internal packages. +// - Common friction point with working with bls & cosmos account interface +func GetCosmosBlsPubkey(privKey common.SecretKey) (cryptotypes.PubKey, error) { + // Example using tendermint's bls12381 library + blsPrivKey, _ := SecretKeyFromBytes(privKey.Marshal()) + if len(blsPrivKey.Marshal()) != 32 { + return nil, errors.New("invalid BLS private key size") + } + // Derive public key + blsPubKey := blsPrivKey.PublicKey() + + // Subgroup check NOT done when decompressing pubkey. + p := new(blst.P1Affine).Uncompress(blsPubKey.Marshal()) + if p == nil { + return nil, errors.New("could not Uncompress") + } + + // p.Print("test") + // fmt.Printf("p.InG1(): %v\n", p.InG1()) + + // Ensure the public key is in the correct format for Cosmos SDK + pk, err := bls12381.NewPublicKeyFromBytes(p.Serialize()) + if err != nil { + return nil, err + } + // fmt.Printf("len(pk.Key): %v\n", len(pk.Key)) + // fmt.Printf("len(pk.Bytes()): %v\n", len(pk.Bytes())) + // fmt.Printf("pk.String(): %v\n", pk.String()) + + if err != nil { + return nil, errors.New("could not marshall") + } + + return pk, nil +} + +// Retrieves the pubkey compatible with the new cosmos bls library, for a given private key defined in this modules internal packages. +// - Common friction point with working with bls & cosmos account interface +func GetCosmosBlsPubkeyFromPubkey(blsPubKey common.PublicKey) (cryptotypes.PubKey, error) { + // Subgroup check NOT done when decompressing pubkey. + p := new(blst.P1Affine).Uncompress(blsPubKey.Marshal()) + if p == nil { + return nil, errors.New("could not unmarshal bytes into public key") + } + // Ensure the public key is in the correct format for Cosmos SDK + pk, err := bls12381.NewPublicKeyFromBytes(p.Serialize()) + if err != nil { + return nil, err + } + return pk, nil +} diff --git a/crypto/bls/blst/public_key_test.go b/crypto/bls/blst/public_key_test.go new file mode 100644 index 00000000..df57cfff --- /dev/null +++ b/crypto/bls/blst/public_key_test.go @@ -0,0 +1,252 @@ +package blst_test + +import ( + "bytes" + "errors" + "fmt" + "sync" + "testing" + + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381" + "github.com/stretchr/testify/require" +) + +func TestPubKey_MarshalJSON(t *testing.T) { + // gen privkey using blst bindings + privKey, err := bls12381.GenPrivKey() + require.NoError(t, err) + defer privKey.Zeroize() + // assert public key interface works + pubKey, _ := privKey.PubKey().(*bls12381.PubKey) + + // marshal to base64 encoded json + jsonBytes, err := pubKey.MarshalJSON() + require.NoError(t, err) + // assert unmarshalling works + pubKey2 := new(bls12381.PubKey) + err = pubKey2.UnmarshalJSON(jsonBytes) + require.NoError(t, err) + + // fmt.Printf("len(pubKey2.Key.Serialize()): %v\n", len(pubKey2.Key)) + // assert we can transcode pubkeys between cosmos-sdk & our libary + pubkey3, err := blst.PublicKeyFromBytes(pubKey2.Key) + require.NoError(t, err) + // assert identical outcome + require.Equal(t, pubKey2.Key, pubkey3.Marshal()) + require.NoError(t, err) +} + +func TestPrivKey_MarshalJSON(t *testing.T) { + privKey, err := blst.RandKey() + require.NoError(t, err) + + privKey2, _ := bls12381.GenPrivKey() + require.Equal(t, len(privKey.Marshal()), len(privKey2.Bytes())) + + privKey3 := new(bls12381.PrivKey) + err = privKey3.UnmarshalAmino(privKey.Marshal()) + require.NoError(t, err) + + require.Equal(t, len(privKey.Marshal()), len(privKey3.Bytes())) + require.Equal(t, privKey.Marshal(), privKey3.Key) + + cosmosPubKey, err := blst.GetCosmosBlsPubkey(privKey) + require.NoError(t, err) + + require.Equal(t, privKey3.PubKey().Bytes(), cosmosPubKey.Bytes()) + + fmt.Printf("privKey: %v\n", privKey.Marshal()) + fmt.Printf("privKey2: %v\n", privKey2.Key) + fmt.Printf("privKey3: %v\n", privKey3.Key) + fmt.Printf("cosmosPubKey: %v\n", cosmosPubKey) + fmt.Printf("cosmosPubKey.Bytes(): %v\n", cosmosPubKey.Bytes()) +} + +func TestPublicKeyFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into public key"), + }, + { + name: "Good", + input: []byte{0xa9, 0x9a, 0x76, 0xed, 0x77, 0x96, 0xf7, 0xbe, 0x22, 0xd5, 0xb7, 0xe8, 0x5d, 0xee, 0xb7, 0xc5, 0x67, 0x7e, 0x88, 0xe5, 0x11, 0xe0, 0xb3, 0x37, 0x61, 0x8f, 0x8c, 0x4e, 0xb6, 0x13, 0x49, 0xb4, 0xbf, 0x2d, 0x15, 0x3f, 0x64, 0x9f, 0x7b, 0x53, 0x35, 0x9f, 0xe8, 0xb9, 0x4a, 0x38, 0xe4, 0x4c}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := blst.PublicKeyFromBytes(test.input) + if test.err != nil { + require.NotEqual(t, nil, err, "No error returned") + require.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + require.NoError(t, err) + require.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestPublicKey_Copy(t *testing.T) { + priv, err := blst.RandKey() + require.NoError(t, err) + pubkeyA := priv.PublicKey() + pubkeyBytes := pubkeyA.Marshal() + + pubkeyB := pubkeyA.Copy() + priv2, err := blst.RandKey() + require.NoError(t, err) + pubkeyB.Aggregate(priv2.PublicKey()) + + require.Equal(t, pubkeyA.Marshal(), pubkeyBytes, "Pubkey was mutated after copy") +} + +func TestPublicKey_Aggregate(t *testing.T) { + priv, err := blst.RandKey() + require.NoError(t, err) + pubkeyA := priv.PublicKey() + + pubkeyB := pubkeyA.Copy() + priv2, err := blst.RandKey() + require.NoError(t, err) + resKey := pubkeyB.Aggregate(priv2.PublicKey()) + + aggKey := blst.AggregateMultiplePubkeys([]common.PublicKey{priv.PublicKey(), priv2.PublicKey()}) + + require.Equal(t, resKey.Marshal(), aggKey.Marshal(), "Pubkey does not match up") +} + +func TestPublicKey_Aggregation_NoCorruption(t *testing.T) { + var pubkeys []common.PublicKey + for i := 0; i < 100; i++ { + priv, err := blst.RandKey() + require.NoError(t, err) + pubkey := priv.PublicKey() + pubkeys = append(pubkeys, pubkey) + } + + var compressedKeys [][]byte + // Fill up the cache + for _, pkey := range pubkeys { + _, err := blst.PublicKeyFromBytes(pkey.Marshal()) + require.NoError(t, err) + compressedKeys = append(compressedKeys, pkey.Marshal()) + } + + wg := new(sync.WaitGroup) + + // Aggregate different sets of keys. + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[:10]) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[:40]) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[20:60]) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[80:]) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[60:90]) + require.NoError(t, err) + wg.Done() + }() + + wg.Add(1) + go func() { + _, err := blst.AggregatePublicKeys(compressedKeys[40:99]) + require.NoError(t, err) + wg.Done() + }() + + wg.Wait() + + for _, pkey := range pubkeys { + cachedPubkey, err := blst.PublicKeyFromBytes(pkey.Marshal()) + require.NoError(t, err) + require.Equal(t, true, cachedPubkey.Equals(pkey)) + } +} + +func TestPublicKeysEmpty(t *testing.T) { + var pubs [][]byte + _, err := blst.AggregatePublicKeys(pubs) + require.ErrorContains(t, err, "nil or empty public keys") +} + +func BenchmarkPublicKeyFromBytes(b *testing.B) { + priv, err := blst.RandKey() + require.NoError(b, err) + pubkey := priv.PublicKey() + pubkeyBytes := pubkey.Marshal() + + b.Run("cache on", func(b *testing.B) { + blst.EnableCaches() + for i := 0; i < b.N; i++ { + _, err := blst.PublicKeyFromBytes(pubkeyBytes) + require.NoError(b, err) + } + }) + + b.Run("cache off", func(b *testing.B) { + blst.DisableCaches() + for i := 0; i < b.N; i++ { + _, err := blst.PublicKeyFromBytes(pubkeyBytes) + require.NoError(b, err) + } + }) + +} diff --git a/crypto/bls/blst/secret_key.go b/crypto/bls/blst/secret_key.go new file mode 100644 index 00000000..dcda6f22 --- /dev/null +++ b/crypto/bls/blst/secret_key.go @@ -0,0 +1,81 @@ +package blst + +import ( + "crypto/subtle" + "fmt" + + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/bitsongofficial/go-bitsong/crypto/rand" + blst "github.com/supranational/blst/bindings/go" +) + +// bls12SecretKey used in the BLS signature scheme. +type bls12SecretKey struct { + p *blst.SecretKey +} + +// RandKey creates a new private key using a random method provided as an io.Reader. +func RandKey() (common.SecretKey, error) { + // Generate 32 bytes of randomness + var ikm [32]byte + _, err := rand.NewGenerator().Read(ikm[:]) + if err != nil { + return nil, err + } + // Defensive check, that we have not generated a secret key, + secKey := &bls12SecretKey{blst.KeyGen(ikm[:])} + if IsZero(secKey.Marshal()) { + return nil, common.ErrZeroKey + } + return secKey, nil +} + +// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice. +func SecretKeyFromBytes(privKey []byte) (common.SecretKey, error) { + if len(privKey) != 32 { + return nil, fmt.Errorf("secret key must be %d bytes", 32) + } + secKey := new(blst.SecretKey).Deserialize(privKey) + if secKey == nil { + return nil, common.ErrSecretUnmarshal + } + wrappedKey := &bls12SecretKey{p: secKey} + if IsZero(privKey) { + return nil, common.ErrZeroKey + } + return wrappedKey, nil +} + +// IsZero checks if the secret key is a zero key. +func IsZero(sKey []byte) bool { + b := byte(0) + for _, s := range sKey { + b |= s + } + return subtle.ConstantTimeByteEq(b, 0) == 1 +} + +// PublicKey obtains the public key corresponding to the BLS secret key. +func (s *bls12SecretKey) PublicKey() common.PublicKey { + return &PublicKey{p: new(blstPublicKey).From(s.p)} +} + +// Sign a message using a secret key - in a beacon/validator client. +// +// In IETF draft BLS specification: +// Sign(SK, message) -> signature: a signing algorithm that generates +// +// a deterministic signature given a secret key SK and a message. + +// In Ethereum proof of stake specification: +// def Sign(SK: int, message: Bytes) -> BLSSignature +func (s *bls12SecretKey) Sign(msg []byte) common.Signature { + signature := new(blstSignature).Sign(s.p, msg, dst) + return &Signature{s: signature} +} + +// Marshal a secret key into a LittleEndian byte slice. +func (s *bls12SecretKey) Marshal() []byte { + keyBytes := s.p.Serialize() + return keyBytes +} diff --git a/crypto/bls/blst/secret_key_test.go b/crypto/bls/blst/secret_key_test.go new file mode 100644 index 00000000..47ca8475 --- /dev/null +++ b/crypto/bls/blst/secret_key_test.go @@ -0,0 +1,118 @@ +package blst_test + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "testing" + + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/stretchr/testify/require" +) + +func TestMarshalUnmarshal(t *testing.T) { + priv, err := blst.RandKey() + require.NoError(t, err) + b := priv.Marshal() + // b32 := bytesutil.ToBytes32(b) + pk, err := blst.SecretKeyFromBytes(b[:]) + require.NoError(t, err) + pk2, err := blst.SecretKeyFromBytes(b[:]) + require.NoError(t, err) + require.Equal(t, pk.Marshal(), pk2.Marshal(), "Keys not equal") +} + +func TestSecretKeyFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Bad", + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + err: common.ErrSecretUnmarshal, + }, + { + name: "Good", + input: []byte{0x25, 0x29, 0x5f, 0x0d, 0x1d, 0x59, 0x2a, 0x90, 0xb3, 0x33, 0xe2, 0x6e, 0x85, 0x14, 0x97, 0x08, 0x20, 0x8e, 0x9f, 0x8e, 0x8b, 0xc1, 0x8f, 0x6c, 0x77, 0xbd, 0x62, 0xf8, 0xad, 0x7a, 0x68, 0x66}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := blst.SecretKeyFromBytes(test.input) + if test.err != nil { + require.NotEqual(t, nil, err, "No error returned") + require.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + require.NoError(t, err) + require.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestSerialize(t *testing.T) { + rk, err := blst.RandKey() + require.NoError(t, err) + b := rk.Marshal() + + _, err = blst.SecretKeyFromBytes(b) + require.NoError(t, err) +} + +func TestZeroKey(t *testing.T) { + // Is Zero + var zKey [32]byte + require.Equal(t, true, blst.IsZero(zKey[:])) + + // Is Not Zero + _, err := rand.Read(zKey[:]) + require.NoError(t, err) + require.Equal(t, false, blst.IsZero(zKey[:])) +} + +func TestRandKeyUniqueness(t *testing.T) { + numKeys := 10 + keys := make([]common.SecretKey, numKeys) + + for i := 0; i < numKeys; i++ { + key, err := blst.RandKey() + if err != nil { + t.Errorf("RandKey() returned an error: %v", err) + } + keys[i] = key + fmt.Printf("key.Marshal(): %v\n", key.Marshal()) + } + + // Compare each key with every other key + for i := 0; i < numKeys; i++ { + for j := i + 1; j < numKeys; j++ { + if bytes.Equal(keys[i].Marshal(), keys[j].Marshal()) { + t.Errorf("RandKey() generated duplicate keys at indices %d and %d", i, j) + } + } + } +} diff --git a/crypto/bls/blst/signature.go b/crypto/bls/blst/signature.go new file mode 100644 index 00000000..61b81ec6 --- /dev/null +++ b/crypto/bls/blst/signature.go @@ -0,0 +1,289 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled + +package blst + +import ( + "bytes" + "fmt" + "math/rand" + "sync" + + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/pkg/errors" + + blst "github.com/supranational/blst/bindings/go" +) + +var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") + +const scalarBytes = 32 +const randBitsEntropy = 64 + +// Signature used in the BLS signature scheme. +type Signature struct { + s *blstSignature +} + +// signatureFromBytesNoValidation creates a BLS signature from a LittleEndian +// byte slice. It does not validate that the signature is in the BLS group +func signatureFromBytesNoValidation(sig []byte) (*blstSignature, error) { + if len(sig) != 96 { + return nil, fmt.Errorf("signature must be %d bytes", 96) + } + signature := new(blstSignature).Uncompress(sig) + if signature == nil { + return nil, errors.New("could not unmarshal bytes into signature") + } + return signature, nil +} + +// SignatureFromBytesNoValidation creates a BLS signature from a LittleEndian +// byte slice. It does not validate that the signature is in the BLS group +func SignatureFromBytesNoValidation(sig []byte) (common.Signature, error) { + signature, err := signatureFromBytesNoValidation(sig) + if err != nil { + return nil, errors.Wrap(err, "could not create signature from byte slice") + } + return &Signature{s: signature}, nil +} + +// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice. +func SignatureFromBytes(sig []byte) (common.Signature, error) { + signature, err := signatureFromBytesNoValidation(sig) + if err != nil { + return nil, errors.Wrap(err, "could not create signature from byte slice") + } + // Group check signature. Do not check for infinity since an aggregated signature + // could be infinite. + if !signature.SigValidate(false) { + return nil, errors.New("signature not in group") + } + return &Signature{s: signature}, nil +} + +// AggregateCompressedSignatures converts a list of compressed signatures into a single, aggregated sig. +func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) { + signature := new(blstAggregateSignature) + valid := signature.AggregateCompressed(multiSigs, true) + if !valid { + return nil, errors.New("provided signatures fail the group check and cannot be compressed") + } + return &Signature{s: signature.ToAffine()}, nil +} + +// MultipleSignaturesFromBytes creates a group of BLS signatures from a LittleEndian 2d-byte slice. +func MultipleSignaturesFromBytes(multiSigs [][]byte) ([]common.Signature, error) { + if len(multiSigs) == 0 { + return nil, errors.New("0 signatures provided to the method") + } + for _, s := range multiSigs { + if len(s) != 96 { + return nil, fmt.Errorf("signature must be %d bytes", 96) + } + } + multiSignatures := new(blstSignature).BatchUncompress(multiSigs) + if len(multiSignatures) == 0 { + return nil, errors.New("could not unmarshal bytes into signature") + } + if len(multiSignatures) != len(multiSigs) { + return nil, errors.Errorf("wanted %d decompressed signatures but got %d", len(multiSigs), len(multiSignatures)) + } + wrappedSigs := make([]common.Signature, len(multiSignatures)) + for i, signature := range multiSignatures { + // Group check signature. Do not check for infinity since an aggregated signature + // could be infinite. + if !signature.SigValidate(false) { + return nil, errors.New("signature not in group") + } + copiedSig := signature + wrappedSigs[i] = &Signature{s: copiedSig} + } + return wrappedSigs, nil +} + +// Verify a bls signature given a public key, a message. +// +// In IETF draft BLS specification: +// Verify(PK, message, signature) -> VALID or INVALID: a verification +// +// algorithm that outputs VALID if signature is a valid signature of +// message under public key PK, and INVALID otherwise. +// +// In the Ethereum proof of stake specification: +// def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool +func (s *Signature) Verify(pubKey common.PublicKey, msg []byte) bool { + // Signature and PKs are assumed to have been validated upon decompression! + return s.s.Verify(false, pubKey.(*PublicKey).p, false, msg, dst) +} + +// AggregateVerify verifies each public key against its respective message. This is vulnerable to +// rogue public-key attack. Each user must provide a proof-of-knowledge of the public key. +// +// Note: The msgs must be distinct. For maximum performance, this method does not ensure distinct +// messages. +// +// In IETF draft BLS specification: +// AggregateVerify((PK_1, message_1), ..., (PK_n, message_n), +// +// signature) -> VALID or INVALID: an aggregate verification +// algorithm that outputs VALID if signature is a valid aggregated +// signature for a collection of public keys and messages, and +// outputs INVALID otherwise. +// +// In the Ethereum proof of stake specification: +// def AggregateVerify(pairs: Sequence[PK: BLSPubkey, message: Bytes], signature: BLSSignature) -> bool +// +// Deprecated: Use FastAggregateVerify or use this method in spectests only. +func (s *Signature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte) bool { + size := len(pubKeys) + if size == 0 { + return false + } + if size != len(msgs) { + return false + } + msgSlices := make([][]byte, len(msgs)) + rawKeys := make([]*blstPublicKey, len(msgs)) + for i := 0; i < size; i++ { + msgSlices[i] = msgs[i][:] + rawKeys[i] = pubKeys[i].(*PublicKey).p + } + // Signature and PKs are assumed to have been validated upon decompression! + return s.s.AggregateVerify(false, rawKeys, false, msgSlices, dst) +} + +// FastAggregateVerify verifies all the provided public keys with their aggregated signature. +// +// In IETF draft BLS specification: +// FastAggregateVerify(PK_1, ..., PK_n, message, signature) -> VALID +// +// or INVALID: a verification algorithm for the aggregate of multiple +// signatures on the same message. This function is faster than +// AggregateVerify. +// +// In the Ethereum proof of stake specification: +// def FastAggregateVerify(PKs: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool +func (s *Signature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { + if len(pubKeys) == 0 { + return false + } + rawKeys := make([]*blstPublicKey, len(pubKeys)) + for i := 0; i < len(pubKeys); i++ { + rawKeys[i] = pubKeys[i].(*PublicKey).p + } + return s.s.FastAggregateVerify(true, rawKeys, msg[:], dst) +} + +// Eth2FastAggregateVerify implements a wrapper on top of bls's FastAggregateVerify. It accepts G2_POINT_AT_INFINITY signature +// when pubkeys empty. +// +// Spec code: +// def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: +// +// """ +// Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. +// """ +// if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: +// return True +// return bls.FastAggregateVerify(pubkeys, message, signature) +func (s *Signature) Eth2FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { + if len(pubKeys) == 0 && bytes.Equal(s.Marshal(), common.InfiniteSignature[:]) { + return true + } + return s.FastAggregateVerify(pubKeys, msg) +} + +// NewAggregateSignature creates a blank aggregate signature. +func NewAggregateSignature() common.Signature { + sig := blst.HashToG2([]byte{'m', 'o', 'c', 'k'}, dst).ToAffine() + return &Signature{s: sig} +} + +// AggregateSignatures converts a list of signatures into a single, aggregated sig. +func AggregateSignatures(sigs []common.Signature) common.Signature { + if len(sigs) == 0 { + return nil + } + + rawSigs := make([]*blstSignature, len(sigs)) + for i := 0; i < len(sigs); i++ { + rawSigs[i] = sigs[i].(*Signature).s + } + + // Signature and PKs are assumed to have been validated upon decompression! + signature := new(blstAggregateSignature) + signature.Aggregate(rawSigs, false) + return &Signature{s: signature.ToAffine()} +} + +// VerifySignature verifies a single signature using public key and message. +func VerifySignature(sig []byte, msg [32]byte, pubKey common.PublicKey) (bool, error) { + rSig, err := SignatureFromBytes(sig) + if err != nil { + return false, err + } + return rSig.Verify(pubKey, msg[:]), nil +} + +// VerifyMultipleSignatures verifies a non-singular set of signatures and its respective pubkeys and messages. +// This method provides a safe way to verify multiple signatures at once. We pick a number randomly from 1 to max +// uint64 and then multiply the signature by it. We continue doing this for all signatures and its respective pubkeys. +// S* = S_1 * r_1 + S_2 * r_2 + ... + S_n * r_n +// P'_{i,j} = P_{i,j} * r_i +// e(S*, G) = \prod_{i=1}^n \prod_{j=1}^{m_i} e(P'_{i,j}, M_{i,j}) +// Using this we can verify multiple signatures safely. +func VerifyMultipleSignatures(height uint64, sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) { + if len(sigs) == 0 || len(pubKeys) == 0 { + return false, nil + } + rawSigs := new(blstSignature).BatchUncompress(sigs) + + length := len(sigs) + if length != len(pubKeys) || length != len(msgs) { + return false, errors.Errorf("provided signatures, pubkeys and messages have differing lengths. S: %d, P: %d,M %d", + length, len(pubKeys), len(msgs)) + } + mulP1Aff := make([]*blstPublicKey, length) + rawMsgs := make([]blst.Message, length) + + for i := 0; i < length; i++ { + mulP1Aff[i] = pubKeys[i].(*PublicKey).p + rawMsgs[i] = msgs[i][:] + } + + randGen := rand.New(rand.NewSource(int64(height))) + randLock := new(sync.Mutex) + + randFunc := func(scalar *blst.Scalar) { + var rbytes [scalarBytes]byte + randLock.Lock() + randGen.Read(rbytes[:]) // #nosec G104 -- Error will always be nil in `read` in math/rand + randLock.Unlock() + // Protect against the generator returning 0. Since the scalar value is + // derived from a big endian byte slice, we take the last byte. + rbytes[len(rbytes)-1] |= 0x01 + scalar.FromBEndian(rbytes[:]) + } + dummySig := new(blstSignature) + + // Validate signatures since we uncompress them here. Public keys should already be validated. + return dummySig.MultipleAggregateVerify(rawSigs, true, mulP1Aff, false, rawMsgs, dst, randFunc, randBitsEntropy), nil +} + +// Marshal a signature into a LittleEndian byte slice. +func (s *Signature) Marshal() []byte { + return s.s.Compress() +} + +// Copy returns a full deep copy of a signature. +func (s *Signature) Copy() common.Signature { + sign := *s.s + return &Signature{s: &sign} +} + +// VerifyCompressed verifies that the compressed signature and pubkey +// are valid from the message provided. +func VerifyCompressed(signature, pub, msg []byte) bool { + // Validate signature and PKs since we will uncompress them here + return new(blstSignature).VerifyCompressed(signature, true, pub, true, msg, dst) +} diff --git a/crypto/bls/blst/test_helper_test.go b/crypto/bls/blst/test_helper_test.go new file mode 100644 index 00000000..dd42f998 --- /dev/null +++ b/crypto/bls/blst/test_helper_test.go @@ -0,0 +1,13 @@ +package blst + +// Note: These functions are for tests to access private globals, such as pubkeyCache. + +// DisableCaches sets the cache sizes to 0. +func DisableCaches() { + pubkeyCache.Resize(0) +} + +// EnableCaches sets the cache sizes to the default values. +func EnableCaches() { + pubkeyCache.Resize(maxKeys) +} diff --git a/crypto/bls/common/constants.go b/crypto/bls/common/constants.go new file mode 100644 index 00000000..163e93e5 --- /dev/null +++ b/crypto/bls/common/constants.go @@ -0,0 +1,10 @@ +package common + +// ZeroSecretKey represents a zero secret key. +var ZeroSecretKey = [32]byte{} + +// InfinitePublicKey represents an infinite public key (G1 Point at Infinity). +var InfinitePublicKey = [48]byte{0xC0} + +// InfiniteSignature represents an infinite signature (G2 Point at Infinity). +var InfiniteSignature = [96]byte{0xC0} diff --git a/crypto/bls/common/errors.go b/crypto/bls/common/errors.go new file mode 100644 index 00000000..13cc48ea --- /dev/null +++ b/crypto/bls/common/errors.go @@ -0,0 +1,13 @@ +package common + +import "errors" + +// ErrZeroKey describes an error due to a zero secret key. +var ErrZeroKey = errors.New("received secret key is zero") + +// ErrSecretUnmarshal describes an error which happens during unmarshalling +// a secret key. +var ErrSecretUnmarshal = errors.New("could not unmarshal bytes into secret key") + +// ErrInfinitePubKey describes an error due to an infinite public key. +var ErrInfinitePubKey = errors.New("received an infinite public key") diff --git a/crypto/bls/common/interface.go b/crypto/bls/common/interface.go new file mode 100644 index 00000000..87317272 --- /dev/null +++ b/crypto/bls/common/interface.go @@ -0,0 +1,33 @@ +// Package common provides the BLS interfaces that are implemented by the various BLS wrappers. +// +// This package should not be used by downstream consumers. These interfaces are re-exporter by +// github.com/prysmaticlabs/prysm/crypto/bls. This package exists to prevent an import circular +// dependency. +package common + +// SecretKey represents a BLS secret or private key. +type SecretKey interface { + PublicKey() PublicKey + Sign(msg []byte) Signature + Marshal() []byte +} + +// PublicKey represents a BLS public key. +type PublicKey interface { + Marshal() []byte + Copy() PublicKey + Aggregate(p2 PublicKey) PublicKey + IsInfinite() bool + Equals(p2 PublicKey) bool +} + +// Signature represents a BLS signature. +type Signature interface { + Verify(pubKey PublicKey, msg []byte) bool + // Deprecated: Use FastAggregateVerify or use this method in spectests only. + AggregateVerify(pubKeys []PublicKey, msgs [][32]byte) bool + FastAggregateVerify(pubKeys []PublicKey, msg [32]byte) bool + Eth2FastAggregateVerify(pubKeys []PublicKey, msg [32]byte) bool + Marshal() []byte + Copy() Signature +} diff --git a/crypto/bls/interface.go b/crypto/bls/interface.go new file mode 100644 index 00000000..88f87543 --- /dev/null +++ b/crypto/bls/interface.go @@ -0,0 +1,12 @@ +package bls + +import "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + +// PublicKey represents a BLS public key. +type PublicKey = common.PublicKey + +// SecretKey represents a BLS secret or private key. +type SecretKey = common.SecretKey + +// Signature represents a BLS signature. +type Signature = common.Signature diff --git a/crypto/rand/rand.go b/crypto/rand/rand.go new file mode 100644 index 00000000..d8b07529 --- /dev/null +++ b/crypto/rand/rand.go @@ -0,0 +1,86 @@ +/* +Package rand defines methods of obtaining random number generators. + +One is expected to use randomness from this package only, without introducing any other packages. +This limits the scope of code that needs to be hardened. + +There are two modes, one for deterministic and another non-deterministic randomness: +1. If deterministic pseudo-random generator is enough, use: + + import "github.com/bitsongofficial/go-bitsong/crypto/rand" + randGen := rand.NewDeterministicGenerator() + randGen.Intn(32) // or any other func defined in math.rand API + + In this mode, only seed is generated using cryptographically secure source (crypto/rand). So, + once seed is obtained, and generator is seeded, the next generations are deterministic, thus fast. + However given that we only seed this 63 bits from crypto/rand and use math/rand to generate the outputs, + this method is not cryptographically secure. This is directly stated in the math/rand package, + https://github.com/golang/go/blob/release-branch.go1.17/src/math/rand/rand.go#L15. For any security + sensitive work this particular generator is NOT to be used. + +2. For cryptographically secure non-deterministic mode (CSPRNG), use: + + import "github.com/bitsongofficial/go-bitsong/crypto/rand" + randGen := rand.NewGenerator() + randGen.Intn(32) // or any other func defined in math.rand API + + Again, any of the functions from `math/rand` can be used, however, they all use custom source + of randomness (crypto/rand), on every step. This makes randomness non-deterministic. However, + you take a performance hit -- as it is an order of magnitude slower. +*/ +package rand + +import ( + "crypto/rand" + "encoding/binary" + mrand "math/rand" + "sync" +) + +type source struct{} + +var lock sync.RWMutex +var _ mrand.Source64 = (*source)(nil) // #nosec G404 -- This ensures we meet the interface + +// Seed does nothing when crypto/rand is used as source. +func (_ *source) Seed(_ int64) {} + +// Int63 returns uniformly-distributed random (as in CSPRNG) int64 value within [0, 1<<63) range. +// Panics if random generator reader cannot return data. +func (s *source) Int63() int64 { + return int64(s.Uint64() & ^uint64(1<<63)) +} + +// Uint64 returns uniformly-distributed random (as in CSPRNG) uint64 value within [0, 1<<64) range. +// Panics if random generator reader cannot return data. +func (_ *source) Uint64() (val uint64) { + lock.RLock() + defer lock.RUnlock() + if err := binary.Read(rand.Reader, binary.BigEndian, &val); err != nil { + panic(err) // lint:nopanic -- Panic risk is communicated in the godoc commentary. + } + return +} + +// Rand is alias for underlying random generator. +type Rand = mrand.Rand // #nosec G404 + +// NewGenerator returns a new generator that uses random values from crypto/rand as a source +// (cryptographically secure random number generator). +// Panics if crypto/rand input cannot be read. +// Use it for everything where crypto secure non-deterministic randomness is required. Performance +// takes a hit, so use sparingly. +func NewGenerator() *Rand { + return mrand.New(&source{}) // #nosec G404 -- excluded +} + +// NewDeterministicGenerator returns a random generator which is only seeded with crypto/rand, +// but is deterministic otherwise (given seed, produces given results, deterministically). +// Panics if crypto/rand input cannot be read. +// Use this method for performance, where deterministic pseudo-random behaviour is enough. +// Otherwise, rely on NewGenerator(). This method is not cryptographically secure as outputs +// can be potentially predicted even without knowledge of the underlying seed. +func NewDeterministicGenerator() *Rand { + randGen := NewGenerator() + return mrand.New(mrand.NewSource(randGen.Int63())) // #nosec G404 -- excluded +} diff --git a/crypto/sha256/helpers.go b/crypto/sha256/helpers.go new file mode 100644 index 00000000..345b806a --- /dev/null +++ b/crypto/sha256/helpers.go @@ -0,0 +1 @@ +package helpers diff --git a/go.mod b/go.mod index c8c41106..e416f00c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/bitsongofficial/go-bitsong -go 1.23.2 +go 1.23.5 require ( cosmossdk.io/api v0.9.2 @@ -15,7 +15,7 @@ require ( cosmossdk.io/x/feegrant v0.1.1 cosmossdk.io/x/tx v0.14.0 cosmossdk.io/x/upgrade v0.1.4 - github.com/CosmWasm/wasmd v0.53.2 + github.com/CosmWasm/wasmd v0.53.3 github.com/CosmWasm/wasmvm/v2 v2.1.5 github.com/cometbft/cometbft v0.38.17 github.com/cosmos/cosmos-db v1.1.1 @@ -42,6 +42,7 @@ require ( github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 + github.com/supranational/blst v0.3.14 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 golang.org/x/sync v0.13.0 google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 @@ -89,17 +90,17 @@ require ( github.com/cockroachdb/pebble v1.1.5 // indirect github.com/cockroachdb/redact v1.1.6 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/cometbft/cometbft-db v1.0.4 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/iavl v1.2.2 // indirect + github.com/cosmos/iavl v1.2.4 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/desertbit/timer v1.0.1 // indirect - github.com/dgraph-io/badger/v4 v4.5.1 // indirect + github.com/dgraph-io/badger/v4 v4.6.0 // indirect github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -120,8 +121,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -171,6 +171,7 @@ require ( github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect @@ -198,7 +199,6 @@ require ( github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.0 // indirect go.etcd.io/bbolt v1.4.0 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect @@ -243,13 +243,10 @@ replace ( github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 ) -// v022 debug: +// feat/bls12 debug: +// run: git clone -b feat/bls12 https://github.com/permissionlessweb/cosmos-sdk +replace github.com/cosmos/cosmos-sdk => github.com/permissionlessweb/cosmos-sdk v0.0.70 -// run: git clone -b v0.38.17.logs https://github.com/permissionlessweb/cometbft -// run: git clone -b v0.50.11.bitsong https://github.com/permissionlessweb/cosmos-sdk -// -// uncomment: -// replace github.com/cometbft/cometbft => ./cometbft -// replace github.com/cosmos/cosmos-sdk => ./cosmos-sdk +// replace github.com/cosmos/cosmos-sdk => ../../COSMOS/mine exclude github.com/gogo/protobuf v1.3.3 diff --git a/go.sum b/go.sum index 7ca84846..4194d445 100644 --- a/go.sum +++ b/go.sum @@ -657,8 +657,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CosmWasm/wasmd v0.53.2 h1:c3MQjeQq+r+Zj2rPeW+c9kJmTevuNnZOIkiiCP/3bg4= -github.com/CosmWasm/wasmd v0.53.2/go.mod h1:gP10E56tuToU5rsZR7vZLBL5ssW2mie6KN/WrQLG7/I= +github.com/CosmWasm/wasmd v0.53.3 h1:kZkkSM2hf0Le7iJPLLNm0QTi2j+wiuLMMn7SyOqBiYw= +github.com/CosmWasm/wasmd v0.53.3/go.mod h1:gP10E56tuToU5rsZR7vZLBL5ssW2mie6KN/WrQLG7/I= github.com/CosmWasm/wasmvm/v2 v2.1.5 h1:cYI1Ook1IAA5DFA0IVvDQQboorNHZPAZ7emBBHFmXSQ= github.com/CosmWasm/wasmvm/v2 v2.1.5/go.mod h1:bMhLQL4Yp9CzJi9A83aR7VO9wockOsSlZbT4ztOl6bg= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -806,8 +806,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= -github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= -github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/cometbft/cometbft-db v1.0.4 h1:cezb8yx/ZWcF124wqUtAFjAuDksS1y1yXedvtprUFxs= +github.com/cometbft/cometbft-db v1.0.4/go.mod h1:M+BtHAGU2XLrpUxo3Nn1nOCcnVCiLM9yx5OuT0u5SCA= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -820,8 +820,6 @@ github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNC github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw= github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= -github.com/cosmos/cosmos-sdk v0.53.0 h1:ZsB2tnBVudumV059oPuElcr0K1lLOutaI6WJ+osNTbI= -github.com/cosmos/cosmos-sdk v0.53.0/go.mod h1:UPcRyFwOUy2PfSFBWxBceO/HTjZOuBVqY583WyazIGs= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= @@ -829,8 +827,8 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= -github.com/cosmos/iavl v1.2.2 h1:qHhKW3I70w+04g5KdsdVSHRbFLgt3yY3qTMd4Xa4rC8= -github.com/cosmos/iavl v1.2.2/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/iavl v1.2.4 h1:IHUrG8dkyueKEY72y92jajrizbkZKPZbMmG14QzsEkw= +github.com/cosmos/iavl v1.2.4/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.1.1 h1:+EGYrTsQ2hu8pBwCWAgqc0g/zSklvBFehda9URLfvOU= github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.1.1/go.mod h1:8sbOclBgOCgBPesufd3ZlLRHvJ3dOeN9+dXhn3KbKOc= github.com/cosmos/ibc-apps/modules/async-icq/v8 v8.0.0 h1:nKP2+Rzlz2iyvTosY5mvP+aEBPe06oaDl3G7xLGBpNI= @@ -868,8 +866,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjY github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= -github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= -github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= +github.com/dgraph-io/badger/v4 v4.6.0 h1:acOwfOOZ4p1dPRnYzvkVm7rUk2Y21TgPVepCy5dJdFQ= +github.com/dgraph-io/badger/v4 v4.6.0/go.mod h1:KSJ5VTuZNC3Sd+YhvVjk2nYua9UZnnTr/SkXvdtiPgI= github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= @@ -1010,8 +1008,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -1045,8 +1041,9 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -1375,8 +1372,8 @@ github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdM github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -1398,6 +1395,8 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/permissionlessweb/cosmos-sdk v0.0.70 h1:ldrKasYqbFa89Nvndf3Ic6ZwOM3YdRrgEqayb1Darok= +github.com/permissionlessweb/cosmos-sdk v0.0.70/go.mod h1:oqkFb+Bje0LJ7IIlpKc5V5bespZYUPQzTkHnbqUurkU= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA= github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= @@ -1548,6 +1547,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= @@ -1594,7 +1595,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= diff --git a/proto/bitsong/fantoken/v1beta1/tx.proto b/proto/bitsong/fantoken/v1beta1/tx.proto index dfc9079a..081e0d94 100644 --- a/proto/bitsong/fantoken/v1beta1/tx.proto +++ b/proto/bitsong/fantoken/v1beta1/tx.proto @@ -31,6 +31,7 @@ service Msg { // MsgIssue defines a message for issuing a new fan token message MsgIssue { + option (cosmos.msg.v1.signer) = "minter"; // symbol which corresponds to the symbol of the fan token. It is a string and // cannot change for the whole life of the fan token @@ -67,6 +68,7 @@ message MsgIssueResponse { // MsgDisableMint defines a message for disable the mint function message MsgDisableMint { + option (cosmos.msg.v1.signer) = "minter"; string denom = 1; string minter = 2; } @@ -78,6 +80,7 @@ message MsgDisableMintResponse { // MsgMint defines a message for minting a new fan token message MsgMint { + option (cosmos.msg.v1.signer) = "minter"; string recipient = 1; // coin mean the amount + denom, eg: 10000ftFADJID34MCDM @@ -97,6 +100,7 @@ message MsgMintResponse { // MsgBurn defines a message for burning some fan tokens message MsgBurn { + option (cosmos.msg.v1.signer) = "sender"; // coin mean the amount + denom, eg: 10000ftFADJID34MCDM cosmos.base.v1beta1.Coin coin = 1 [ (gogoproto.moretags) = "yaml:\"coin\"", (gogoproto.nullable) = false ]; @@ -115,7 +119,7 @@ message MsgBurnResponse { // MsgSetMinter defines a message for changing the fan token minter address message MsgSetMinter { - + option (cosmos.msg.v1.signer) = "old_minter"; // denom the fan token denom string denom = 1; @@ -140,7 +144,7 @@ message MsgSetMinterResponse { // MsgSetAuthority defines a message for changing the fan token minter address message MsgSetAuthority { - + option (cosmos.msg.v1.signer) = "old_authority"; // denom the fan token denom string denom = 1; @@ -164,6 +168,7 @@ message MsgSetAuthorityResponse { } message MsgSetUri { + option (cosmos.msg.v1.signer) = "authority"; string authority = 1; string denom = 2; string uri = 3 [ (gogoproto.customname) = "URI" ]; diff --git a/proto/bitsong/smartaccount/v1beta1/tx.proto b/proto/bitsong/smartaccount/v1beta1/tx.proto index 9df18bde..89c10b08 100644 --- a/proto/bitsong/smartaccount/v1beta1/tx.proto +++ b/proto/bitsong/smartaccount/v1beta1/tx.proto @@ -3,6 +3,7 @@ package bitsong.smartaccount.v1beta1; import "cosmos/msg/v1/msg.proto"; import "amino/amino.proto"; +import "gogoproto/gogo.proto"; option go_package = "github.com/bitsongofficial/go-bitsong/x/smart-account/types"; @@ -57,10 +58,29 @@ message MsgSetActiveState { message MsgSetActiveStateResponse {} + +// AgAuthData is a Serialized array of signing.SignatureV2. +// We Marshal & Unmarshal via `UnmarshalSignatureJSON` & `MarshalSignatureJSON` +message AgAuthData { + bytes data = 1; +} + // TxExtension allows for additional authenticator-specific data in // transactions. message TxExtension { // selected_authenticators holds the authenticator_id for the chosen // authenticator per message. repeated uint64 selected_authenticators = 1; + // optional, used to provide aggregate key signature data to module for authentication. + AgAuthData agg_auth = 2; } + +// BlsConfig +message BlsConfig { + // list of pubkeys that are points in aggregate key set + repeated bytes pubkeys = 1; + // minimum threshold of points in order for tx to be valid + uint64 threshold = 2; +} + + \ No newline at end of file diff --git a/proto/buf.lock b/proto/buf.lock index 3632aba9..207c8a84 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -9,8 +9,8 @@ deps: - remote: buf.build owner: cosmos repository: cosmos-sdk - commit: 34ac2e8322d44db08830e553ad21b93c - digest: shake256:b0dd0de5ef770147002e290a9e9f8de8cc8727cbf932628892fbb9027d5ebec5c2a63c0e340e4ef7af72369ecdf51221dcc385ce2137afaa72532228f6999d74 + commit: 650cd9ad7f7a468e8e19975269958658 + digest: shake256:c2c1e67ed8efa7f5c6af7a2fc02a6c257dc78fe86911cbf4d3dd379710bf475565ffe2ae4f65221888373d515caa31be68b720897cccec6e0c6a1a91ff0b5227 - remote: buf.build owner: cosmos repository: gogo-proto @@ -24,5 +24,5 @@ deps: - remote: buf.build owner: protocolbuffers repository: wellknowntypes - commit: 7727a3b7399540398e5736b9916f0faa - digest: shake256:74c046f009811965d227acd11bb2c6259bf387759d4230b95405e402a2ed4212d6f4babab690407d07bc81095c3917c4ae9144195ddfb081525c9aec58e51173 + commit: 3ddd61d1f53d485abd3d3a2b47a62b8e + digest: shake256:9e6799d56700d0470c3723a2fd027e8b4a41a07085a0c90c58e05f6c0038fac9b7a0170acd7692707a849983b1b8189aa33e7b73f91d68157f7136823115546b diff --git a/proto/cosmos/crypto/bls12381/keys.proto b/proto/cosmos/crypto/bls12381/keys.proto new file mode 100644 index 00000000..726d1968 --- /dev/null +++ b/proto/cosmos/crypto/bls12381/keys.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; +package cosmos.crypto.bls12381; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/bls12381"; + +// PubKey is an bls12381 public key for aggregated +// It's needed for Any serialization and SDK compatibility. +// It must not be used in a non Tendermint key context because it doesn't implement +// ADR-28. Nevertheless, you will like to use bls12381 in app user level +// then you must create a new proto message and follow ADR-28 for Address construction. +message PubKey { + option (amino.name) = "tendermint/PubKeyBls12381"; + // The Amino encoding is simply the inner bytes field, and not the Amino + // encoding of the whole PubKey struct. + // + // Example (JSON): + // s := PubKey{Key: []byte{0x01}} + // out := AminoJSONEncoder(s) + // + // Then we have: + // out == `"MQ=="` + // out != `{"key":"MQ=="}` + option (amino.message_encoding) = "key_field"; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.compare) = true; + + bytes key = 1; +} + +// PrivKey defines a bls12381 private key. +// NOTE: bls12381 keys must not be used in SDK apps except in a tendermint validator context. +message PrivKey { + option (amino.name) = "tendermint/PrivKeyBls12381"; + option (amino.message_encoding) = "key_field"; + + bytes key = 1; +} diff --git a/contrib/devtools/Makefile b/scripts/devtools/Makefile similarity index 100% rename from contrib/devtools/Makefile rename to scripts/devtools/Makefile diff --git a/contrib/devtools/install-golangci-lint.sh b/scripts/devtools/install-golangci-lint.sh similarity index 100% rename from contrib/devtools/install-golangci-lint.sh rename to scripts/devtools/install-golangci-lint.sh diff --git a/contrib/docker/run_bitsongd.sh b/scripts/docker/run_bitsongd.sh similarity index 100% rename from contrib/docker/run_bitsongd.sh rename to scripts/docker/run_bitsongd.sh diff --git a/contrib/docker/setup_and_run.sh b/scripts/docker/setup_and_run.sh similarity index 100% rename from contrib/docker/setup_and_run.sh rename to scripts/docker/setup_and_run.sh diff --git a/contrib/docker/setup_bitsongd.sh b/scripts/docker/setup_bitsongd.sh similarity index 97% rename from contrib/docker/setup_bitsongd.sh rename to scripts/docker/setup_bitsongd.sh index a6fbe17f..44910624 100644 --- a/contrib/docker/setup_bitsongd.sh +++ b/scripts/docker/setup_bitsongd.sh @@ -19,7 +19,7 @@ if [ -f "$GENESIS_FILE" ]; then else echo "$GENESIS_FILE does not exist. Generating..." - bitsongdd init --chain-id "$CHAIN_ID" "$MONIKER" + bitsongd init --chain-id "$CHAIN_ID" "$MONIKER" bitsongd add-ica-config # staking/governance token is hardcoded in config, change this sed -i "s/\"stake\"/\"$STAKE\"/" "$GENESIS_FILE" @@ -31,6 +31,7 @@ else sed -i 's/keyring-backend = "os"/keyring-backend = "test"/' "$HOME"/.bitsongd/config/client.toml fi +## todo: configure toml & config APP_TOML_CONFIG="$HOME"/.bitsongd/config/app.toml APP_TOML_CONFIG_NEW="$HOME"/.bitsongd/config/app_new.toml CONFIG_TOML_CONFIG="$HOME"/.bitsongd/config/config.toml diff --git a/scripts/makefiles/build.mk b/scripts/makefiles/build.mk index eec1594f..acc914a4 100644 --- a/scripts/makefiles/build.mk +++ b/scripts/makefiles/build.mk @@ -20,11 +20,15 @@ build-help: build: build-check-version go.sum ifeq ($(OS),Windows_NT) - go build -mod=readonly $(BUILD_FLAGS) -o build/bitsongd.exe ./cmd/bitsongd + $(error bitsongd server not supported. Use "make build-windows-client" for client) + exit 1 else go build $(BUILD_FLAGS) -o build/bitsongd ./cmd/bitsongd endif +build-windows-client: go.sum + GOOS=windows GOARCH=amd64 go build -mod=readonly $(BUILD_FLAGS) -o build/bitsongd.exe ./cmd/bitsongd + install: build-check-version go.sum go install -mod=readonly $(BUILD_FLAGS) ./cmd/bitsongd diff --git a/scripts/makefiles/release.mk b/scripts/makefiles/release.mk new file mode 100644 index 00000000..0c704b7b --- /dev/null +++ b/scripts/makefiles/release.mk @@ -0,0 +1,40 @@ +############################################################################### +### Release ### +############################################################################### +release-help: + @echo "release subcommands" + @echo "" + @echo "Usage:" + @echo " make release-[command]" + @echo "" + @echo "Available Commands:" + @echo " dry-run Perform a dry run release" + @echo " snapshot Create a snapshot release" + +GORELEASER_IMAGE := ghcr.io/goreleaser/goreleaser-cross:v$(GO_VERSION) +COSMWASM_VERSION := $(shell go list -m github.com/CosmWasm/wasmvm/v2 | sed 's/.* //') + +release-dry-run: + docker run \ + --rm \ + -e COSMWASM_VERSION=$(COSMWASM_VERSION) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/bitsongd \ + -w /go/src/bitsongd \ + $(GORELEASER_IMAGE) \ + release \ + --clean \ + --skip=publish + +release-snapshot: + docker run \ + --rm \ + -e COSMWASM_VERSION=$(COSMWASM_VERSION) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/bitsongd \ + -w /go/src/bitsongd \ + $(GORELEASER_IMAGE) \ + release \ + --clean \ + --snapshot \ + --skip=publish,validate \ No newline at end of file diff --git a/tests/ict/basic_start_test.go b/tests/ict/basic_start_test.go index d3309771..8f5aa877 100644 --- a/tests/ict/basic_start_test.go +++ b/tests/ict/basic_start_test.go @@ -8,7 +8,10 @@ import ( "testing" "time" + "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" + + fantokentypes "github.com/bitsongofficial/go-bitsong/x/fantoken/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -93,12 +96,86 @@ func TestBasicBtsgStart(t *testing.T) { t.Run("slashing", func(t *testing.T) { testSlashing(ctx, t, bitsong) }) + t.Run("fantoken", func(t *testing.T) { + testFanToken(ctx, t, bitsong, users) + }) + t.Run("smart-account", func(t *testing.T) { + testSmartAccount(ctx, t, bitsong, users) + }) t.Cleanup(func() { _ = ic.Close() }) } +// testUpgrade test the queries for upgrade information. Actual upgrades take place in other test. +// func testUpgrade(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { +// v, err := chain.UpgradeQueryAllModuleVersions(ctx) +// require.NoError(t, err) +// require.NotEmpty(t, v) + +// // UpgradeQueryModuleVersion +// authority, err := chain.UpgradeQueryAuthority(ctx) +// require.NoError(t, err) +// require.NotEmpty(t, authority) + +// plan, err := chain.UpgradeQueryPlan(ctx) +// require.NoError(t, err) +// require.Nil(t, plan) + +// _, err = chain.UpgradeQueryAppliedPlan(ctx, "") +// require.NoError(t, err) +// } + +// func testGov(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { +// node := chain.GetNode() + +// govModule := "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" +// coin := sdk.NewCoin(chain.Config().Denom, sdkmath.NewInt(1)) + +// bankMsg := &banktypes.MsgSend{ +// FromAddress: govModule, +// ToAddress: users[1].FormattedAddress(), +// Amount: sdk.NewCoins(coin), +// } + +// // submit governance proposal +// title := "Test Proposal" +// prop, err := chain.BuildProposal([]cosmos.ProtoMessage{bankMsg}, title, title+" Summary", "none", "500"+chain.Config().Denom, govModule, false) +// require.NoError(t, err) + +// _, err = node.GovSubmitProposal(ctx, users[0].KeyName(), prop) +// require.NoError(t, err) + +// proposal, err := chain.GovQueryProposalV1(ctx, 1) +// require.NoError(t, err) +// require.EqualValues(t, proposal.Title, title) + +// // vote on the proposal +// err = node.VoteOnProposal(ctx, users[0].KeyName(), 1, "yes") +// require.NoError(t, err) + +// v, err := chain.GovQueryVote(ctx, 1, users[0].FormattedAddress()) +// require.NoError(t, err) +// require.EqualValues(t, v.Options[0].Option, govv1.VoteOption_VOTE_OPTION_YES) + +// // pass vote with all validators +// err = chain.VoteOnProposalAllValidators(ctx, 1, "yes") +// require.NoError(t, err) + +// // GovQueryProposalsV1 +// proposals, err := chain.GovQueryProposalsV1(ctx, govv1.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD) +// require.NoError(t, err) +// require.Len(t, proposals, 1) + +// require.NoError(t, testutil.WaitForBlocks(ctx, 10, chain)) + +// // Proposal fails due to gov not having any funds +// proposals, err = chain.GovQueryProposalsV1(ctx, govv1.ProposalStatus_PROPOSAL_STATUS_FAILED) +// require.NoError(t, err) +// require.Len(t, proposals, 1) +// } + func testAuthz(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { granter := users[0].FormattedAddress() grantee := users[1].FormattedAddress() @@ -157,6 +234,115 @@ func testAuthz(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, use require.EqualValues(t, balanceBefore.SubRaw(int64(sendAmt)), balanceAfter) } +func testFanToken(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + fantokenName := "strang" + fantokenSymbol := "symbol" + fantokenUri := "ipfs://" + fantokenMaxSupply := "21" + user0 := users[0].FormattedAddress() + user1 := users[1].FormattedAddress() + user2 := users[2].FormattedAddress() + + node := chain.GetNode() + protocolPoolAcc := authtypes.NewModuleAddress("protocolpool") + + maxSupply, _ := sdkmath.NewIntFromString(fantokenMaxSupply) + // issue fantoken + denom := fantokentypes.NewFanToken(fantokenName, fantokenSymbol, fantokenUri, maxSupply, users[0].Address(), users[0].Address(), node.CliContext().Height).Denom + err := FantokenIssue(ctx, node, user0, fantokenName, fantokenSymbol, fantokenMaxSupply, fantokenUri, user0, user0) + require.NoError(t, err) + + // query fantoken just issued + fantoken, err := FantokenQueryFantoken(ctx, node, denom) + require.NoError(t, err) + require.Equal(t, fantokenName, fantoken.MetaData.Name) + require.Equal(t, fantokenSymbol, fantoken.MetaData.Symbol) + require.Equal(t, fantokenUri, fantoken.MetaData.URI) + require.Equal(t, maxSupply, fantoken.MaxSupply) + + // assert non-owner cannot mint owner's fantoken + err = FantokenMint(ctx, node, user1, "21"+denom, user1) + require.Equal(t, errors.Wrapf(fantokentypes.ErrInvalidMinter, "the address %s is not the minter of the fantoken %s", user1, denom), err) + + // owner mints fantoken + err = FantokenMint(ctx, node, user0, "1"+denom, user1) + require.NoError(t, err) + + // ensure token was minted + balance, err := chain.GetBalance(ctx, user1, denom) + require.NoError(t, err) + require.Equal(t, sdkmath.NewInt(1), balance) + + // cannot mint to module account + moduleAddr := authtypes.NewModuleAddress("fantoken") + err = FantokenMint(ctx, node, user0, "1"+denom, moduleAddr.String()) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid recipient address") + + // ensure fee goes to community pool as expected after mint + require.NoError(t, err) + + communityPoolBefore, err := chain.BankQueryBalance(ctx, protocolPoolAcc.String(), denom) + require.NoError(t, err) + err = FantokenMint(ctx, node, user0, "1"+denom, user1) + require.NoError(t, err) + communityPoolAfter, err := chain.BankQueryBalance(ctx, protocolPoolAcc.String(), denom) + require.NoError(t, err) + require.True(t, communityPoolAfter.GT(communityPoolBefore)) + + // ensure we do not mint more than max supply + err = FantokenMint(ctx, node, user0, "20"+denom, user1) + require.Error(t, err) + require.Contains(t, err.Error(), "exceeds max supply") + + // ensure token is minted to correct destination + err = FantokenMint(ctx, node, user0, "1"+denom, user2) + require.NoError(t, err) + + balance, err = chain.BankQueryBalance(ctx, user2, denom) + require.NoError(t, err) + require.Equal(t, sdkmath.NewInt(1), balance) + + // ensure only current authority can set new authority + err = FantokenSetAuthority(ctx, node, user1, denom, user2) + require.Error(t, err) + require.Contains(t, err.Error(), "not the authority") + err = FantokenSetAuthority(ctx, node, user0, denom, user2) + require.NoError(t, err) + fantoken, err = FantokenQueryFantoken(ctx, node, denom) + require.NoError(t, err) + require.Equal(t, user2, fantoken.GetAuthority().String()) + + // ensure only current authority can set a new minter + err = FantokenSetMinter(ctx, node, user1, denom, user1) + require.Error(t, err) + require.Contains(t, err.Error(), "not the authority") + err = FantokenSetMinter(ctx, node, user2, denom, user1) + require.NoError(t, err) + fantoken, err = FantokenQueryFantoken(ctx, node, denom) + require.NoError(t, err) + require.Equal(t, user1, fantoken.Minter) + + // ensure uri is set properly + fantoken, err = FantokenQueryFantoken(ctx, node, denom) + require.NoError(t, err) + require.Equal(t, fantokenUri, fantoken.GetMetaData().URI) +} + +func testSmartAccount(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + user0 := users[0].FormattedAddress() + node := chain.GetNode() + t.Log("Signature verification") + // Only test that we are able to register and unregister any authenticators using the default sk of the account + SmartAccountAddAuthenticator(ctx, node, "SignatureVerification", user0, string(users[1].Address())) + SmartAccountRemoveAuthenticator(ctx, node, user0, 1) + t.Log("AllOf") + t.Log("AnyOf") + t.Log("CosmwasmAuthentication") + t.Log("Bls12381") + +} + func testBank(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { user0 := users[0].FormattedAddress() user1 := users[1].FormattedAddress() @@ -585,7 +771,73 @@ func testAuth(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { require.EqualValues(t, govAddr, accInfo.Address) } -// DistributionFundCommunityPool funds the community pool with the specified amount of coins. +// FantokenIssue issues a new fantoken. +func FantokenIssue(ctx context.Context, node *cosmos.ChainNode, keyName, name, symbol, maxSupply, uri, minter, authority string) error { + _, err := node.ExecTx(ctx, + keyName, "fantoken", "issue", + "--name", name, + "--minter", name, + "--symbol", symbol, + "--max-supply", maxSupply, + "--uri", uri, + "--minter", minter, + "--authority", authority, + // "--fees", fees, + // "--chain-id", node.CliContext().ChainID, + ) + return err +} + +// FantokenMint issues a new fantoken. +func FantokenMint(ctx context.Context, node *cosmos.ChainNode, keyName, amountWithDenom, recipient string) error { + _, err := node.ExecTx(ctx, + keyName, "fantoken", "mint", amountWithDenom, + "--recipient", recipient, + ) + return err +} + +// FantokenSetAuthority sets an new authority for a fantoken. +func FantokenSetMinter(ctx context.Context, node *cosmos.ChainNode, keyName, denom, newAuthority string) error { + _, err := node.ExecTx(ctx, + keyName, "fantoken", "set-minter", denom, + "--new-minter", newAuthority, + ) + return err +} + +// FantokenSetAuthority sets an new authority for a fantoken. +func FantokenSetAuthority(ctx context.Context, node *cosmos.ChainNode, keyName, denom, newAuthority string) error { + _, err := node.ExecTx(ctx, + keyName, "fantoken", "set-authority", denom, + "--new-authority", newAuthority, + ) + return err +} + +// FantokenMint issues a new fantoken. +func FantokenDisableMint(ctx context.Context, node *cosmos.ChainNode, keyName, fantoken string) error { + _, err := node.ExecTx(ctx, + keyName, "fantoken", "disable-mint", fantoken, + ) + return err +} + +// FantokenQueryParams returns the params for the fantoken module +func FantokenQueryParams(ctx context.Context, node *cosmos.ChainNode) (*fantokentypes.Params, error) { + res, err := fantokentypes.NewQueryClient(node.GrpcConn).Params(ctx, &fantokentypes.QueryParamsRequest{}) + return &res.Params, err +} + +// FantokenQueryParams returns a fantoken given the denom +func FantokenQueryFantoken(ctx context.Context, node *cosmos.ChainNode, denom string) (*fantokentypes.FanToken, error) { + res, err := fantokentypes.NewQueryClient(node.GrpcConn).FanToken(ctx, &fantokentypes.QueryFanTokenRequest{ + Denom: denom, + }) + return res.Fantoken, err +} + +// ProtocolPoolFundCommunityPool funds the community pool with the specified amount of coins. func ProtocolPoolFundCommunityPool(ctx context.Context, node *cosmos.ChainNode, keyName, amount string) error { _, err := node.ExecTx(ctx, keyName, "protocolpool", "fund-community-pool", amount, @@ -598,3 +850,19 @@ func ProtocolPoolQueryCommunityPool(ctx context.Context, node *cosmos.ChainNode) res, err := pooltypes.NewQueryClient(node.GrpcConn).CommunityPool(ctx, &pooltypes.QueryCommunityPoolRequest{}) return &res.Pool, err } + +// ProtocolPoolFundCommunityPool funds the community pool with the specified amount of coins. +func SmartAccountAddAuthenticator(ctx context.Context, node *cosmos.ChainNode, authType, keyName, data string) error { + _, err := node.ExecTx(ctx, + keyName, "smartaccount", "add-authenticator", authType, data, + ) + return err +} + +// ProtocolPoolFundCommunityPool funds the community pool with the specified amount of coins. +func SmartAccountRemoveAuthenticator(ctx context.Context, node *cosmos.ChainNode, keyName string, authType uint64) error { + _, err := node.ExecTx(ctx, + keyName, "smartaccount", "remove-authenticator", string(authType), + ) + return err +} diff --git a/tests/ict/module_smart_account_test.go b/tests/ict/module_smart_account_test.go new file mode 100644 index 00000000..e69de29b diff --git a/tests/localbitsong/README.md b/tests/localbitsong/README.md index f5feea59..068dcf25 100644 --- a/tests/localbitsong/README.md +++ b/tests/localbitsong/README.md @@ -105,6 +105,6 @@ bitsongd keys add bitsong1regz7kj3ylg2dn9rl8vwrhclkgz528mf0tfsck --keyring-backe ## FAQ -Q: How do I enable pprof server in LocalBitSong? + diff --git a/x/fantoken/types/tx.pb.go b/x/fantoken/types/tx.pb.go index 4025f479..f5164580 100644 --- a/x/fantoken/types/tx.pb.go +++ b/x/fantoken/types/tx.pb.go @@ -628,61 +628,63 @@ func init() { func init() { proto.RegisterFile("bitsong/fantoken/v1beta1/tx.proto", fileDescriptor_d1955b4a1569b3cf) } var fileDescriptor_d1955b4a1569b3cf = []byte{ - // 852 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xcd, 0x6e, 0xdb, 0x46, - 0x10, 0x16, 0x2d, 0xcb, 0x29, 0xc7, 0x4a, 0x9b, 0xb0, 0x4a, 0xc2, 0x10, 0x06, 0xd5, 0x6c, 0x81, - 0x20, 0x4e, 0x51, 0xb2, 0x72, 0x83, 0x1e, 0x02, 0xa4, 0x40, 0xd5, 0x5e, 0x7c, 0xd0, 0xa1, 0x74, - 0x8c, 0x02, 0xbe, 0x18, 0x94, 0xb4, 0xa6, 0x17, 0x21, 0x77, 0x05, 0xee, 0x2a, 0x96, 0x6e, 0x05, - 0xfa, 0x02, 0x39, 0xf6, 0x05, 0x7a, 0x2a, 0x0a, 0xf4, 0x31, 0x7c, 0xcc, 0xb1, 0xf0, 0x41, 0x68, - 0xe5, 0x43, 0xef, 0x7a, 0x82, 0x82, 0xbb, 0xd4, 0x92, 0x32, 0x54, 0x49, 0x40, 0x7f, 0x72, 0xe2, - 0x72, 0xe6, 0xfb, 0x66, 0xbe, 0xe1, 0x0e, 0x67, 0x17, 0x1e, 0x75, 0x89, 0xe0, 0x8c, 0x46, 0xfe, - 0x59, 0x48, 0x05, 0x7b, 0x85, 0xa9, 0xff, 0xba, 0xd5, 0xc5, 0x22, 0x6c, 0xf9, 0x62, 0xe4, 0x0d, - 0x52, 0x26, 0x98, 0x65, 0xe7, 0x10, 0x6f, 0x0e, 0xf1, 0x72, 0x88, 0xe3, 0xf6, 0x18, 0x4f, 0x18, - 0xf7, 0xbb, 0x21, 0xc7, 0x9a, 0xd7, 0x63, 0x84, 0x2a, 0xa6, 0xd3, 0x88, 0x58, 0xc4, 0xe4, 0xd2, - 0xcf, 0x56, 0xb9, 0x75, 0x2f, 0x62, 0x2c, 0x8a, 0xb1, 0x1f, 0x0e, 0x88, 0x1f, 0x52, 0xca, 0x44, - 0x28, 0x08, 0xa3, 0x3c, 0xf7, 0x3e, 0xc8, 0x63, 0x26, 0x3c, 0xf2, 0x5f, 0xb7, 0xb2, 0x87, 0x72, - 0xa0, 0x2b, 0x03, 0xde, 0xeb, 0xf0, 0xe8, 0x90, 0xf3, 0x21, 0xb6, 0xee, 0xc3, 0x0e, 0x1f, 0x27, - 0x5d, 0x16, 0xdb, 0xc6, 0x47, 0xc6, 0x13, 0x33, 0xc8, 0xdf, 0x2c, 0x0b, 0xb6, 0x69, 0x98, 0x60, - 0x7b, 0x4b, 0x5a, 0xe5, 0xda, 0xfa, 0x16, 0x20, 0x09, 0x47, 0xa7, 0x7c, 0x38, 0x18, 0xc4, 0x63, - 0xbb, 0x9a, 0x79, 0xda, 0x07, 0x97, 0x93, 0x66, 0xe5, 0x6a, 0xd2, 0xbc, 0xa7, 0xb2, 0xf1, 0xfe, - 0x2b, 0x8f, 0x30, 0x3f, 0x09, 0xc5, 0xb9, 0x77, 0x48, 0xc5, 0x6c, 0xd2, 0xbc, 0x3b, 0x0e, 0x93, - 0xf8, 0x39, 0x2a, 0x88, 0x28, 0x30, 0x93, 0x70, 0x74, 0x24, 0xd7, 0xd6, 0x1e, 0x98, 0xe1, 0x50, - 0x9c, 0xb3, 0x94, 0x88, 0xb1, 0xbd, 0x2d, 0x73, 0x15, 0x86, 0x4c, 0x5c, 0x42, 0xa8, 0xc0, 0xa9, - 0x5d, 0x53, 0xe2, 0xd4, 0x9b, 0xf5, 0x10, 0xaa, 0xc3, 0x94, 0xd8, 0x3b, 0x52, 0xc1, 0xad, 0xe9, - 0xa4, 0x59, 0x3d, 0x0e, 0x0e, 0x83, 0xcc, 0x86, 0x9e, 0xc3, 0x9d, 0x79, 0x6d, 0x01, 0xe6, 0x03, - 0x46, 0x39, 0xb6, 0x1e, 0x43, 0xad, 0x8f, 0x29, 0x4b, 0x54, 0x89, 0xed, 0x3b, 0xb3, 0x49, 0xb3, - 0xae, 0x54, 0x49, 0x33, 0x0a, 0x94, 0x1b, 0x7d, 0x09, 0xef, 0x77, 0x78, 0xf4, 0x0d, 0xe1, 0x61, - 0x37, 0xc6, 0x1d, 0x42, 0x85, 0xd5, 0x58, 0x60, 0xe6, 0xb8, 0x92, 0xac, 0xad, 0xb2, 0x2c, 0xe4, - 0xc1, 0xfd, 0x45, 0xbe, 0x56, 0xb0, 0x34, 0x0e, 0xfa, 0xc1, 0x80, 0x5b, 0x1d, 0x1e, 0xc9, 0x4c, - 0x7b, 0x60, 0xa6, 0xb8, 0x47, 0x06, 0x04, 0x53, 0x91, 0xa3, 0x0a, 0x83, 0xd5, 0x86, 0xed, 0xac, - 0x1b, 0x64, 0xbe, 0xdd, 0x83, 0x87, 0x9e, 0xfa, 0xd8, 0x5e, 0xd6, 0x2e, 0xf3, 0x1e, 0xf2, 0xbe, - 0x66, 0x84, 0xb6, 0x3f, 0xcc, 0xb6, 0x63, 0x36, 0x69, 0xee, 0xaa, 0xfa, 0x32, 0x12, 0x0a, 0x24, - 0xb7, 0xa4, 0xba, 0xba, 0xa0, 0x9a, 0xc3, 0x07, 0xb9, 0x08, 0x2d, 0xf7, 0x3f, 0x17, 0x83, 0xb0, - 0xac, 0xbc, 0x3d, 0x4c, 0xa9, 0x0e, 0x67, 0xfc, 0xb3, 0xda, 0x38, 0xa6, 0xfd, 0x62, 0x47, 0xd4, - 0x1b, 0x4a, 0x64, 0x6d, 0x59, 0x1a, 0x5d, 0x5b, 0x01, 0x35, 0xca, 0xd0, 0x7f, 0xa5, 0xaa, 0x37, - 0x06, 0xd4, 0x3b, 0x3c, 0x3a, 0xc2, 0xa2, 0xa3, 0x1a, 0x75, 0x79, 0xff, 0x3c, 0x03, 0x60, 0x71, - 0xff, 0xb4, 0xdc, 0x43, 0xed, 0x7b, 0xc5, 0xaf, 0x52, 0xf8, 0x50, 0x60, 0xb2, 0xb8, 0x9f, 0xc7, - 0x7a, 0x06, 0x40, 0xf1, 0xc5, 0x69, 0x79, 0x0f, 0xcb, 0xac, 0xc2, 0x87, 0x02, 0x93, 0xe2, 0x0b, - 0xc5, 0x42, 0x3f, 0x1a, 0xd0, 0x28, 0x4b, 0x5a, 0xdd, 0x92, 0xff, 0xab, 0xb4, 0x9f, 0x0c, 0xb9, - 0x3b, 0x47, 0x58, 0x7c, 0xa5, 0xff, 0xf8, 0xe5, 0xaa, 0x5e, 0xc0, 0xed, 0x2c, 0x73, 0x31, 0x29, - 0x94, 0x30, 0x7b, 0x36, 0x69, 0x36, 0x0a, 0x61, 0xda, 0x8d, 0x82, 0x3a, 0x8b, 0xfb, 0x45, 0xd0, - 0x17, 0x70, 0x3b, 0x93, 0x50, 0xd0, 0xab, 0x37, 0xe9, 0x0b, 0x6e, 0x14, 0xd4, 0x29, 0xbe, 0xd0, - 0x74, 0xf4, 0x8b, 0x01, 0x0f, 0x6e, 0xe8, 0x5c, 0xf3, 0x15, 0xdf, 0xad, 0xde, 0x13, 0x30, 0x95, - 0xdc, 0xe3, 0x94, 0x2c, 0x0e, 0x58, 0xe3, 0xe6, 0x80, 0xd5, 0xf2, 0xb7, 0xca, 0xf2, 0xf3, 0xf1, - 0x5a, 0x5d, 0x32, 0x5e, 0xf7, 0xe1, 0xae, 0x8e, 0xbd, 0xfa, 0x23, 0x1c, 0xfc, 0x5c, 0x83, 0x6a, - 0x87, 0x47, 0xd6, 0x77, 0x50, 0x53, 0x47, 0x0d, 0xf2, 0xfe, 0xee, 0xfc, 0xf3, 0xe6, 0x23, 0xdb, - 0x79, 0xba, 0x1e, 0xa3, 0xd3, 0xbe, 0x84, 0x6d, 0x39, 0x3a, 0x1f, 0xad, 0xe4, 0x64, 0x10, 0x67, - 0x7f, 0x2d, 0xa4, 0x1c, 0x55, 0x8e, 0xa5, 0xd5, 0x51, 0x33, 0xc8, 0x9a, 0xa8, 0x0b, 0x53, 0x87, - 0xc0, 0x6e, 0xf9, 0x5c, 0x79, 0xb2, 0x92, 0x59, 0x42, 0x3a, 0x9f, 0x6d, 0x8a, 0xd4, 0xa9, 0x7a, - 0x60, 0x16, 0x03, 0xe8, 0xf1, 0x4a, 0xba, 0xc6, 0x39, 0xde, 0x66, 0x38, 0x9d, 0x24, 0x86, 0xfa, - 0xc2, 0x7f, 0xbb, 0xbf, 0x8e, 0xaf, 0xa1, 0x4e, 0x6b, 0x63, 0xa8, 0xce, 0x76, 0x02, 0x3b, 0x79, - 0x3b, 0x7f, 0xbc, 0x8e, 0x7c, 0x9c, 0x12, 0xe7, 0x93, 0x0d, 0x40, 0xf3, 0xd8, 0x4e, 0xed, 0xfb, - 0x3f, 0x7f, 0x7d, 0x6a, 0xb4, 0x5f, 0x5e, 0xfe, 0xe1, 0x56, 0x2e, 0xa7, 0xae, 0xf1, 0x76, 0xea, - 0x1a, 0xbf, 0x4f, 0x5d, 0xe3, 0xcd, 0xb5, 0x5b, 0x79, 0x7b, 0xed, 0x56, 0x7e, 0xbb, 0x76, 0x2b, - 0x27, 0x5f, 0x44, 0x44, 0x9c, 0x0f, 0xbb, 0x5e, 0x8f, 0x25, 0x7e, 0x1e, 0x9b, 0x9d, 0x9d, 0x91, - 0x1e, 0x09, 0x63, 0x3f, 0x62, 0x9f, 0xce, 0xaf, 0x7e, 0xa3, 0xe2, 0xf2, 0x27, 0xc6, 0x03, 0xcc, - 0xbb, 0x3b, 0xf2, 0xc6, 0xf5, 0xf9, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x83, 0x79, 0xa8, - 0x1d, 0x0a, 0x00, 0x00, + // 886 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0x3f, 0x6f, 0xdb, 0x46, + 0x14, 0x17, 0x2d, 0xcb, 0xa9, 0x9e, 0x95, 0x7f, 0xac, 0x93, 0x30, 0x84, 0x41, 0x35, 0x57, 0x20, + 0x88, 0x5d, 0x94, 0xac, 0xdd, 0xa0, 0x83, 0x80, 0x0c, 0x55, 0xbb, 0x18, 0x85, 0x86, 0xd2, 0x31, + 0x0a, 0x64, 0x31, 0x28, 0xe9, 0x4c, 0x5f, 0x43, 0xde, 0x09, 0xbc, 0x53, 0x6c, 0x6d, 0x45, 0x3f, + 0x41, 0xc6, 0xae, 0x9d, 0x8b, 0x02, 0x59, 0xfb, 0x0d, 0x3c, 0x66, 0x2c, 0x3a, 0x08, 0xad, 0x3d, + 0x78, 0xf7, 0x27, 0x28, 0xee, 0x8e, 0x3a, 0x92, 0x86, 0x23, 0x19, 0xe8, 0x9f, 0x4c, 0xba, 0xbb, + 0xf7, 0xfb, 0xbd, 0xf7, 0x7b, 0xef, 0x9e, 0xde, 0x11, 0x1e, 0xf5, 0x89, 0xe0, 0x8c, 0xc6, 0xc1, + 0x41, 0x44, 0x05, 0x7b, 0x89, 0x69, 0xf0, 0x6a, 0xab, 0x8f, 0x45, 0xb4, 0x15, 0x88, 0x63, 0x7f, + 0x94, 0x31, 0xc1, 0x6c, 0x27, 0x87, 0xf8, 0x33, 0x88, 0x9f, 0x43, 0x5c, 0x6f, 0xc0, 0x78, 0xca, + 0x78, 0xd0, 0x8f, 0x38, 0x36, 0xbc, 0x01, 0x23, 0x54, 0x33, 0xdd, 0xb5, 0x98, 0xc5, 0x4c, 0x2d, + 0x03, 0xb9, 0xca, 0x4f, 0xd7, 0x63, 0xc6, 0xe2, 0x04, 0x07, 0xd1, 0x88, 0x04, 0x11, 0xa5, 0x4c, + 0x44, 0x82, 0x30, 0xca, 0x73, 0xeb, 0x83, 0xdc, 0x67, 0xca, 0xe3, 0xe0, 0xd5, 0x96, 0xfc, 0xd1, + 0x06, 0x74, 0x6e, 0xc1, 0x07, 0x3d, 0x1e, 0xef, 0x70, 0x3e, 0xc6, 0xf6, 0x7d, 0x58, 0xe1, 0x93, + 0xb4, 0xcf, 0x12, 0xc7, 0xfa, 0xc8, 0x7a, 0xd2, 0x0c, 0xf3, 0x9d, 0x6d, 0xc3, 0x32, 0x8d, 0x52, + 0xec, 0x2c, 0xa9, 0x53, 0xb5, 0xb6, 0xbf, 0x05, 0x48, 0xa3, 0xe3, 0x7d, 0x3e, 0x1e, 0x8d, 0x92, + 0x89, 0x53, 0x97, 0x96, 0xee, 0xf6, 0xc9, 0xb4, 0x5d, 0xfb, 0x63, 0xda, 0xbe, 0xa7, 0xa3, 0xf1, + 0xe1, 0x4b, 0x9f, 0xb0, 0x20, 0x8d, 0xc4, 0xa1, 0xbf, 0x43, 0xc5, 0xc5, 0xb4, 0x7d, 0x77, 0x12, + 0xa5, 0x49, 0x07, 0x15, 0x44, 0x14, 0x36, 0xd3, 0xe8, 0x78, 0x57, 0xad, 0xed, 0x75, 0x68, 0x46, + 0x63, 0x71, 0xc8, 0x32, 0x22, 0x26, 0xce, 0xb2, 0x8a, 0x55, 0x1c, 0x48, 0x71, 0x29, 0xa1, 0x02, + 0x67, 0x4e, 0x43, 0x8b, 0xd3, 0x3b, 0xfb, 0x21, 0xd4, 0xc7, 0x19, 0x71, 0x56, 0x94, 0x82, 0x1b, + 0xa7, 0xd3, 0x76, 0x7d, 0x2f, 0xdc, 0x09, 0xe5, 0x59, 0x67, 0xf5, 0xc7, 0xf3, 0x37, 0x9b, 0x39, + 0x0e, 0x75, 0xe0, 0xce, 0x2c, 0xd1, 0x10, 0xf3, 0x11, 0xa3, 0x1c, 0xdb, 0x8f, 0xa1, 0x31, 0xc4, + 0x94, 0xa5, 0x3a, 0xdf, 0xee, 0x9d, 0x8b, 0x69, 0xbb, 0xa5, 0x25, 0xaa, 0x63, 0x14, 0x6a, 0x33, + 0xfa, 0x06, 0x6e, 0xf5, 0x78, 0xfc, 0x35, 0xe1, 0x51, 0x3f, 0xc1, 0x3d, 0x42, 0x85, 0xbd, 0x56, + 0x61, 0xe6, 0xb8, 0x92, 0xc6, 0xa5, 0xb2, 0xc6, 0xaa, 0x10, 0x1f, 0xee, 0x57, 0x9d, 0x19, 0x39, + 0x57, 0x3a, 0x45, 0xaf, 0x2d, 0xb8, 0xd1, 0xe3, 0xb1, 0x0a, 0xbb, 0x0e, 0xcd, 0x0c, 0x0f, 0xc8, + 0x88, 0x60, 0x2a, 0x72, 0x54, 0x71, 0x60, 0x77, 0x61, 0x59, 0xf6, 0x89, 0x0a, 0xbe, 0xba, 0xfd, + 0xd0, 0xd7, 0xd7, 0xe0, 0xcb, 0x46, 0x9a, 0x75, 0x97, 0xff, 0x15, 0x23, 0xb4, 0xfb, 0xa1, 0xbc, + 0xa8, 0x8b, 0x69, 0x7b, 0x55, 0x27, 0x2b, 0x49, 0x28, 0x54, 0xdc, 0x52, 0x0a, 0xf5, 0x77, 0xa7, + 0xc0, 0xe1, 0x76, 0xae, 0xc8, 0x68, 0xff, 0xcf, 0x95, 0xa1, 0x4c, 0x95, 0xa1, 0x3b, 0xce, 0xa8, + 0x71, 0x67, 0xfd, 0xb3, 0x44, 0x39, 0xa6, 0xc3, 0xe2, 0xae, 0xf4, 0x2e, 0x4f, 0x54, 0x6f, 0x50, + 0xaa, 0x12, 0x95, 0x31, 0x4d, 0xa2, 0x05, 0xcf, 0x2a, 0xf3, 0xfe, 0x95, 0x14, 0x7f, 0xb6, 0xa0, + 0xd5, 0xe3, 0xf1, 0x2e, 0x16, 0x3d, 0xdd, 0xdc, 0x57, 0xb7, 0xd9, 0x53, 0x00, 0x96, 0x0c, 0xf7, + 0xcb, 0xad, 0xd6, 0xbd, 0x57, 0xfc, 0xbd, 0x0a, 0x1b, 0x0a, 0x9b, 0x2c, 0x19, 0xe6, 0xbe, 0x9e, + 0x02, 0x50, 0x7c, 0xb4, 0x5f, 0xbe, 0xdd, 0x32, 0xab, 0xb0, 0xa1, 0xb0, 0x49, 0xf1, 0x91, 0x66, + 0x75, 0x6e, 0xcb, 0x72, 0x94, 0xc2, 0xa1, 0x9f, 0x2c, 0x58, 0x2b, 0x6b, 0x9c, 0xdf, 0xbd, 0xff, + 0xa7, 0x56, 0xf4, 0x9b, 0xa5, 0xae, 0x6b, 0x17, 0x8b, 0x2f, 0xcd, 0xd8, 0xb8, 0x5a, 0xd5, 0x33, + 0xb8, 0x29, 0x23, 0x17, 0xe3, 0x46, 0x0b, 0x73, 0x2e, 0xa6, 0xed, 0xb5, 0x42, 0x98, 0x31, 0xa3, + 0xb0, 0xc5, 0x92, 0x61, 0xe1, 0xf4, 0x19, 0xdc, 0x94, 0x12, 0x0a, 0x7a, 0xfd, 0x32, 0xbd, 0x62, + 0x46, 0x61, 0x8b, 0xe2, 0x23, 0x43, 0xef, 0xd8, 0xb2, 0xa6, 0x55, 0x01, 0xe8, 0x57, 0x0b, 0x1e, + 0x5c, 0xd2, 0xbe, 0xa0, 0xb2, 0xef, 0x35, 0x07, 0xf4, 0x3d, 0x34, 0xb5, 0xdc, 0xbd, 0x8c, 0x54, + 0x27, 0xb7, 0x75, 0x79, 0x72, 0x1b, 0xf9, 0x4b, 0x65, 0xf9, 0xf9, 0xdc, 0xae, 0x5f, 0x31, 0xb7, + 0x6f, 0xc9, 0xfa, 0x14, 0x0e, 0xd0, 0x06, 0xdc, 0x35, 0xb1, 0xe6, 0x17, 0x65, 0xfb, 0x97, 0x06, + 0xd4, 0x7b, 0x3c, 0xb6, 0xbf, 0x83, 0x86, 0x7e, 0xd3, 0x90, 0xff, 0xae, 0x87, 0xd6, 0x9f, 0x3d, + 0x07, 0xee, 0xe6, 0x62, 0x8c, 0x09, 0xfb, 0x1c, 0x96, 0xd5, 0x24, 0x7e, 0x34, 0x97, 0x23, 0x21, + 0xee, 0xc6, 0x42, 0x48, 0xd9, 0xab, 0x1a, 0x6c, 0xf3, 0xbd, 0x4a, 0xc8, 0x02, 0xaf, 0x95, 0x51, + 0x45, 0x60, 0xb5, 0xfc, 0x66, 0x3d, 0x99, 0xcb, 0x2c, 0x21, 0xdd, 0xcf, 0xae, 0x8b, 0x34, 0xa1, + 0x06, 0xd0, 0x2c, 0xa6, 0xd6, 0xe3, 0xb9, 0x74, 0x83, 0x73, 0xfd, 0xeb, 0xe1, 0x4c, 0x90, 0x04, + 0x5a, 0x95, 0xff, 0xf6, 0xc6, 0x22, 0xbe, 0x81, 0xba, 0x5b, 0xd7, 0x86, 0x9a, 0x68, 0x2f, 0x60, + 0x25, 0x6f, 0xef, 0x8f, 0x17, 0x91, 0xf7, 0x32, 0xe2, 0x7e, 0x72, 0x0d, 0xd0, 0xcc, 0xb7, 0xdb, + 0xf8, 0xe1, 0xfc, 0xcd, 0xa6, 0xd5, 0x7d, 0x7e, 0xf2, 0x97, 0x57, 0x3b, 0x39, 0xf5, 0xac, 0xb7, + 0xa7, 0x9e, 0xf5, 0xe7, 0xa9, 0x67, 0xbd, 0x3e, 0xf3, 0x6a, 0x6f, 0xcf, 0xbc, 0xda, 0xef, 0x67, + 0x5e, 0xed, 0xc5, 0x17, 0x31, 0x11, 0x87, 0xe3, 0xbe, 0x3f, 0x60, 0x69, 0x90, 0xfb, 0x66, 0x07, + 0x07, 0x64, 0x40, 0xa2, 0x24, 0x88, 0xd9, 0xa7, 0xb3, 0x6f, 0xcc, 0xe3, 0xe2, 0x2b, 0x53, 0x4c, + 0x46, 0x98, 0xf7, 0x57, 0xd4, 0xa7, 0xdd, 0xe7, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xa1, 0xd8, + 0x4d, 0x6a, 0x86, 0x0a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/smart-account/README.md b/x/smart-account/README.md index afb0f115..7374c768 100644 --- a/x/smart-account/README.md +++ b/x/smart-account/README.md @@ -1,5 +1,8 @@ # x/smart-account Module +## TODO: +- fix pubkey byte length (48) + ## General Explanation The `x/smart-account` module provides a robust and extensible framework for authenticating transactions. @@ -68,6 +71,14 @@ calling cosmwasm contracts to authenticate the messages. ### Authenticator configuration for accounts +| Authenticators implementations | Description | Custom Types Needed | +| --- | --- | --- | +| [`AllOf`](./authenticator/all_of.go#L17) | | +| [`AnyOf`](./authenticator/any_of.go#L17) | | +| [`CosmwasmAuthenticatorV1`](./authenticator/cosmwasm.go#L17) | | +| [`MessageFilter`](./authenticator/message_filter.go#L17) | | +| [`SignatureVerification`](./authenticator/signature_authenticator.go#L17) | | + Accounts have the flexibility to be linked with multiple authenticators, a setup maintained in the system's storage and managed by the module's Keeper. The keeper is responsible for adding and removing authenticators, as well as storing any user data that the authenticators may need. @@ -82,7 +93,6 @@ it needs to know which public key to use when verifying it. An account can confi `SignatureVerification` to be one of their authenticators and would need to provide the public key it wants to use for verification in the configuration data. - To make an authenticator work for a specific account, you just need to feed it the right information. For example, the `SignatureVerification` needs to know which public key to check when verifying a signature. So, if you're setting this up for your account, you have to configure it with the public key you want as part of the @@ -214,7 +224,6 @@ RemoveAuthenticator(account, authenticatorGlobalId) 2. **Identify Fee Payer**: The first signer of the transaction is considered the fee payer. 3. **Authenticate Each Message**: - - The associated account for every message is identified. - The system fetches the appropriate authenticators for that account. - The selected authenticator is retrieved from the transaction and used to determine which aithenticator to execute @@ -318,7 +327,7 @@ pub enum AuthenticatorSudoMsg { The last three messages corresponds to steps 3, 5 and 7 of the [transaction authentication process](#transaction-authentication-overview) and the first two messages are used to handle the addition and removal of the authenticator. -Request types are defined [here](https://docs.rs/osmosis-authenticators/latest/osmosis_authenticators). +Request types are defined [here](https://docs.rs/btsg-auth/latest/btsg_auth). ## Queries @@ -359,6 +368,8 @@ message TxExtension { // selected_authenticators holds the authenticator_id for the chosen // authenticator per message. repeated uint64 selected_authenticators = 1; + // Aggregated auth data if in use of aggregated keys authenticator + SmartAccountAuthData smart_account = 2; } ``` @@ -374,11 +385,8 @@ applications don't need to be aware of authenticators to get their txs processed To simplify the design of the authenticator module, a few restrictions have been set on the type of transactions that are accepted. -### Messages can only have one signer - -On cosmos SDK versions before 0.50 it was possible to have multiple signers for a message. This will no longer be -the case after v0.50, and we have introduced this restriction here as it makes it more clear which account a message -is associated with. +### Message Signing +Messages by default expect to have a single pubkey & signature provided in the array of ### The fee payer must be the first signer of the first message (or, feegrant exist for first sender) diff --git a/x/smart-account/ante/ante.go b/x/smart-account/ante/ante.go index 7ae487fd..c0003923 100644 --- a/x/smart-account/ante/ante.go +++ b/x/smart-account/ante/ante.go @@ -65,7 +65,6 @@ func (ad AuthenticatorDecorator) AnteHandle( if err != nil { return sdk.Context{}, err } - // Performing fee payer authentication with minimal gas allocation // serves as a spam-prevention strategy to prevent users from adding multiple // authenticators that may excessively consume computational resources. @@ -117,7 +116,7 @@ func (ad AuthenticatorDecorator) AnteHandle( feeGranter := feeTx.FeeGranter() fee := feeTx.GetFee() - selectedAuthenticators, err := ad.GetSelectedAuthenticators(tx, len(msgs)) + selectedAuthenticators, keysToAggregate, err := ad.GetSelectedAuthenticatorsAndAggSignData(tx, len(msgs)) if err != nil { return ctx, err } @@ -169,6 +168,7 @@ func (ad AuthenticatorDecorator) AnteHandle( msgIndex, simulate, authenticator.SequenceMatch, + keysToAggregate, ) if err != nil { return sdk.Context{}, @@ -300,29 +300,35 @@ func (ad AuthenticatorDecorator) ValidateAuthenticatorFeePayer(ctx sdk.Context, // If no selected authenticators are found in the extension, the function initializes the list with -1 values. // It returns an array of selected authenticators or an error if the number of selected authenticators does not match // the number of messages in the transaction. -func (ad AuthenticatorDecorator) GetSelectedAuthenticators( + +// Returns the aggregated keys & signatures for the provided +// +// if no selected authenticators are found in the extension, the function returns an empty array for both, as a defaultsecp256k1 supported signkey. +// This will depreceate with support from native cosmos-sdk of programmable account auth primitives (currenntly bech32, could hash G1 pubkey to derive 32 byte value, but still would need to access pubkey (assigned light client?)) +func (ad AuthenticatorDecorator) GetSelectedAuthenticatorsAndAggSignData( tx sdk.Tx, msgCount int, -) ([]uint64, error) { +) ([]uint64, *types.AgAuthData, error) { extTx, ok := tx.(authante.HasExtensionOptionsTx) if !ok { - return nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a HasExtensionOptionsTx to use Authenticators") + return nil, nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a HasExtensionOptionsTx to use Authenticators") } // Get the selected authenticator options from the transaction. txOptions := ad.smartAccountKeeper.GetAuthenticatorExtension(extTx.GetNonCriticalExtensionOptions()) if txOptions == nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, + return nil, nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "Cannot get AuthenticatorTxOptions from tx") } // Retrieve the selected authenticators from the extension. selectedAuthenticators := txOptions.GetSelectedAuthenticators() + keysToAggregate := txOptions.GetAggAuth() if len(selectedAuthenticators) != msgCount { // Return an error if the number of selected authenticators does not match the number of messages. - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, + return nil, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "Mismatch between the number of selected authenticators and messages, msg count %d, got %d selected authenticators", msgCount, len(selectedAuthenticators)) } - return selectedAuthenticators, nil + return selectedAuthenticators, keysToAggregate, nil } diff --git a/x/smart-account/ante/ante_test.go b/x/smart-account/ante/ante_test.go index 9990f8e1..fd8eaa32 100644 --- a/x/smart-account/ante/ante_test.go +++ b/x/smart-account/ante/ante_test.go @@ -178,6 +178,7 @@ func (s *AuthenticatorAnteSuite) TestSignatureVerificationWithAuthenticatorInSto s.Require().NoError(err) s.Require().Equal(id, uint64(2), "Adding authenticator returning incorrect id") + // MAIN LOGIC FOR FORMING MSG THAT HAS BLS SIGNATUREE tx, _ := GenTx(s.Ctx, s.EncodingConfig.TxConfig, []sdk.Msg{ testMsg1, testMsg2, diff --git a/x/smart-account/ante/sequence_decorator.go b/x/smart-account/ante/sequence_decorator.go new file mode 100644 index 00000000..0072cec1 --- /dev/null +++ b/x/smart-account/ante/sequence_decorator.go @@ -0,0 +1,58 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// IncrementSequenceDecorator handles incrementing sequences of all signers. +// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note, +// there is need to execute IncrementSequenceDecorator on RecheckTx since +// BaseApp.Commit() will set the check state based on the latest header. +// +// NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and +// sequential txs orginating from the same account cannot be handled correctly in +// a reliable way unless sequence numbers are managed and tracked manually by a +// client. It is recommended to instead use multiple messages in a tx. +type IncrementSequenceDecorator struct { + ak authante.AccountKeeper +} + +func NewIncrementSequenceDecorator(ak authante.AccountKeeper) IncrementSequenceDecorator { + return IncrementSequenceDecorator{ + ak: ak, + } +} + +func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + if utx, ok := tx.(sdk.TxWithUnordered); ok && utx.GetUnordered() { + if !isd.ak.UnorderedTransactionsEnabled() { + return ctx, errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are disabled") + } + return next(ctx, tx, simulate) + } + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + // increment sequence of all signers + signers, err := sigTx.GetSigners() + if err != nil { + return sdk.Context{}, err + } + + for _, signer := range signers { + acc := isd.ak.GetAccount(ctx, signer) + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + + isd.ak.SetAccount(ctx, acc) + } + + return next(ctx, tx, simulate) +} diff --git a/x/smart-account/authenticator/authentication_request.go b/x/smart-account/authenticator/authentication_request.go index c128280b..bbb0c9c2 100644 --- a/x/smart-account/authenticator/authentication_request.go +++ b/x/smart-account/authenticator/authentication_request.go @@ -1,19 +1,25 @@ package authenticator import ( + "crypto/sha256" + "encoding/json" "fmt" - txsigning "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" + sat "github.com/bitsongofficial/go-bitsong/x/smart-account/types" + + txsigning "cosmossdk.io/x/tx/signing" "github.com/cosmos/cosmos-sdk/types/tx/signing" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/codec" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -56,7 +62,7 @@ type ExplicitTxData struct { // A signer can only have one signature, so if it appears in multiple messages, the signatures must be // the same, and it will only be returned once by this function. This is to mimic the way the classic // sdk authentication works, and we will probably want to change this in the future -func GetSignerAndSignatures(tx sdk.Tx) (signers []sdk.AccAddress, signatures []signing.SignatureV2, err error) { +func GetSignerAndSignatures(cdc codec.Codec, tx sdk.Tx, aggSig *sat.AgAuthData) (signers []sdk.AccAddress, signatures []signing.SignatureV2, err error) { // Attempt to cast the provided transaction to an authsigning.Tx. sigTx, ok := tx.(authsigning.Tx) if !ok { @@ -76,10 +82,36 @@ func GetSignerAndSignatures(tx sdk.Tx) (signers []sdk.AccAddress, signatures []s return nil, nil, err } + if aggSig != nil { + aggregatedAuthData, err := UnmarshalSignatureJSON(cdc, aggSig.GetData()) + if err != nil { + return nil, nil, err + } + // static accAddress for account keys that registered agg authenticator. + signers = append(signers, sdk.AccAddress(signerBytes[0])) + + for _, signer := range aggregatedAuthData { + // fmt.Printf("singed.PubKey.Bytes(): %v\n", singed.PubKey.Bytes()) + // fmt.Printf("len(singed.PubKey.Bytes()): %v\n", len(singed.PubKey.Bytes())) + // fmt.Printf("len(singed.PubKey.Address().Bytes()): %v\n", len(singed.PubKey.Address().Bytes())) + // fmt.Printf("singed.PubKey.Address(): %v\n", singed.PubKey.Address()) + // fmt.Println(signer.PubKey.Address().Marshal()) + // add signer + signers = append(signers, sdk.AccAddress(signer.PubKey.Bytes())) + + // add signatures + signatures = append(signatures, signing.SignatureV2{ + PubKey: signer.PubKey, + Data: signer.Data, + Sequence: signer.Sequence, + }) + } + return signers, signatures, nil + } + for _, signer := range signerBytes { signers = append(signers, sdk.AccAddress(signer)) } - // check that signer length and signature length are the same if len(signatures) != len(signers) { return nil, nil, @@ -127,7 +159,7 @@ func extractExplicitTxData(tx sdk.Tx, signerData authsigning.SignerData) (Explic txMsgs := tx.GetMsgs() msgs := make([]LocalAny, len(txMsgs)) for i, txMsg := range txMsgs { - encodedMsg, err := types.NewAnyWithValue(txMsg) + encodedMsg, err := codectypes.NewAnyWithValue(txMsg) if err != nil { return ExplicitTxData{}, errorsmod.Wrap(err, "failed to encode msg") } @@ -153,14 +185,14 @@ func extractExplicitTxData(tx sdk.Tx, signerData authsigning.SignerData) (Explic // corresponding signer, which involves iterating over the signatures. To avoid iterating over the signatures twice, // we do replay protection here instead of in a separate replay protection function. // -// Only SingleSignatureData is supported. Multisigs can be implemented by using partitioned compound authenticators -func extractSignatures(txSigners []sdk.AccAddress, txSignatures []signing.SignatureV2, txData ExplicitTxData, account sdk.AccAddress, replayProtection ReplayProtection) (signatures [][]byte, msgSignature []byte, err error) { +// If this tx is making use of Aggregated Signatures,we optionally expect a single aggregated pk & sig, or else we return nothing. +func extractSignatures(cdc codec.Codec, txSigners []sdk.AccAddress, txSignatures []signing.SignatureV2, txData ExplicitTxData, account sdk.AccAddress, replayProtection ReplayProtection) (signatures [][]byte, msgSignature []byte, err error) { + for i, signature := range txSignatures { single, ok := signature.Data.(*signing.SingleSignatureData) if !ok { return nil, nil, errorsmod.Wrap(sdkerrors.ErrInvalidType, "failed to cast signature to SingleSignatureData") } - signatures = append(signatures, single.Signature) if txSigners[i].Equals(account) { @@ -168,7 +200,10 @@ func extractSignatures(txSigners []sdk.AccAddress, txSignatures []signing.Signat if err != nil { return nil, nil, err } - msgSignature = single.Signature + // set aggregate signature as single signature + if i == 0 { + msgSignature = single.Signature + } } } return signatures, msgSignature, nil @@ -189,28 +224,38 @@ func GenerateAuthenticationRequest( msgIndex int, simulate bool, replayProtection ReplayProtection, + agAuthData *sat.AgAuthData, ) (AuthenticationRequest, error) { - // Only supporting one signer per message. This will be enforced in sdk v0.50 + var simpleSignatureData = SimplifiedSignatureData{ + Signers: make([]sdk.AccAddress, 0), + Signatures: make([][]byte, 0), + } + // Only supporting one signer per message in default signer data signers, _, err := cdc.GetMsgV1Signers(msg) if err != nil { return AuthenticationRequest{}, err } + // fmt.Printf("signers: %v\n", signers) + + // either actual signer, or aggregated pubkey & address of account agg pubkeys control. signer := sdk.AccAddress(signers[0]) if !signer.Equals(account) { return AuthenticationRequest{}, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "invalid signer") } - // Get the signers and signatures from the transaction. A signer can only have one signature, so if it - // appears in multiple messages, the signatures must be the same, and it will only be returned once by - // this function. This is to mimic the way the classic sdk authentication works, and we will probably want - // to change this in the future - txSigners, txSignatures, err := GetSignerAndSignatures(tx) + // Get the signers and signatures from the transaction. + txSigners, txSignatures, err := GetSignerAndSignatures(cdc, tx, agAuthData) if err != nil { return AuthenticationRequest{}, errorsmod.Wrap(err, "failed to get signers and signatures") } + // fmt.Printf("txSigners: %v\n", txSigners) + // fmt.Printf("len(txSigners): %v\n", len(txSigners)) + // fmt.Printf("txSignatures: %v\n", txSignatures) + // fmt.Printf("len(txSignatures): %v\n", len(txSignatures)) // Get the signer data for the account. This is needed in the SignDoc signerData := getSignerData(ctx, ak, account) + // fmt.Printf("signerData: %v\n", signerData) // Get the concrete transaction data to be passed to the authenticators txData, err := extractExplicitTxData(tx, signerData) @@ -218,12 +263,19 @@ func GenerateAuthenticationRequest( return AuthenticationRequest{}, errorsmod.Wrap(err, "failed to get explicit tx data") } - // Get the signatures for the transaction and execute replay protection - signatures, msgSignature, err := extractSignatures(txSigners, txSignatures, txData, account, replayProtection) + // Get the signatures for the transaction and execute replay protection. + // If aggregate keys are in use, set agg key & sig as first value, followed by all key/sig pairs in extension + signatures, msgSignature, err := extractSignatures(cdc, txSigners, txSignatures, txData, account, replayProtection) if err != nil { return AuthenticationRequest{}, errorsmod.Wrap(err, "failed to get signatures") } + simpleSignatureData.Signatures = append(simpleSignatureData.Signatures, signatures...) + simpleSignatureData.Signers = append(simpleSignatureData.Signers, txSigners...) + + // fmt.Printf("len(simpleSignatureData.Signatures): %v\n", len(simpleSignatureData.Signatures)) + // fmt.Printf("len(simpleSignatureData.Signers): %v\n", len(simpleSignatureData.Signers)) + // Build the authentication request authRequest := AuthenticationRequest{ Account: account, @@ -237,10 +289,7 @@ func GenerateAuthenticationRequest( SignModeTxData: SignModeData{ Direct: []byte("signBytes"), }, - SignatureData: SimplifiedSignatureData{ - Signers: txSigners, - Signatures: signatures, - }, + SignatureData: simpleSignatureData, Simulate: simulate, AuthenticatorParams: nil, } @@ -260,6 +309,61 @@ func GenerateAuthenticationRequest( authRequest.SignModeTxData = SignModeData{ Direct: signBytes, } - return authRequest, nil } + +// Generates the SHA256SUM for an array of cosmos-sdk messages +func Sha256Msgs(msgs []LocalAny) [32]byte { + jsonBytes, _ := json.Marshal(msgs) + return sha256.Sum256(jsonBytes) +} + +func MarshalSignatureJSON(sigs []signing.SignatureV2) ([]byte, error) { + descs := make([]*signing.SignatureDescriptor, len(sigs)) + + for i, sig := range sigs { + descData := signing.SignatureDataToProto(sig.Data) + // assert public key interface works + pubKey, ok := sig.PubKey.(*bls12381.PubKey) + if !ok { + return nil, fmt.Errorf("failed to get bls12381.PubKey") + } + any, err := codectypes.NewAnyWithValue(pubKey) + if err != nil { + return nil, err + } + // fmt.Printf("any: %v\n", any) + descs[i] = &signing.SignatureDescriptor{ + PublicKey: any, + Data: descData, + Sequence: sig.Sequence, + } + } + + toJSON := &signing.SignatureDescriptors{Signatures: descs} + + return codec.ProtoMarshalJSON(toJSON, nil) +} + +func UnmarshalSignatureJSON(cdc codec.Codec, bz []byte) ([]signing.SignatureV2, error) { + var sigDescs signing.SignatureDescriptors + err := cdc.UnmarshalJSON(bz, &sigDescs) + if err != nil { + return nil, err + } + + sigs := make([]signing.SignatureV2, len(sigDescs.Signatures)) + for i, desc := range sigDescs.Signatures { + pubKey, _ := desc.PublicKey.GetCachedValue().(cryptotypes.PubKey) + + data := signing.SignatureDataFromProto(desc.Data) + + sigs[i] = signing.SignatureV2{ + PubKey: pubKey, + Data: data, + Sequence: desc.Sequence, + } + } + + return sigs, nil +} diff --git a/x/smart-account/authenticator/base_test.go b/x/smart-account/authenticator/base_test.go index 5e77d652..1389722b 100644 --- a/x/smart-account/authenticator/base_test.go +++ b/x/smart-account/authenticator/base_test.go @@ -17,6 +17,8 @@ import ( authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types" @@ -105,6 +107,48 @@ func (s *BaseAuthenticatorSuite) GenSimpleTx(msgs []sdk.Msg, signers []cryptotyp } +// Generates a Tx object to use with x/smart-account controlled by an set of keys compatible with aggregation. +func (s *BaseAuthenticatorSuite) GenSimpleTxBls12381(msgs []sdk.Msg, signers []common.SecretKey, originalPubkey cryptotypes.Address) (sdk.Tx, error) { + txconfig := app.MakeEncodingConfig().TxConfig + feeCoins := sdk.Coins{sdk.NewInt64Coin("bitsong", 2500)} + var accNums []uint64 + var accSeqs []uint64 + + for _, signer := range signers { + var account sdk.AccountI + acc, err := blst.GetCosmosBlsPubkey(signer) + if err != nil { + return nil, err + } + + account = NewBls12381Account(originalPubkey.String(), acc, 0, 0) + // fmt.Printf("account: %v\n", account) + // fmt.Printf("len(account.GetPubKey().Bytes()): %v\n", len(account.GetPubKey().Bytes())) + + accNums = append(accNums, account.GetAccountNumber()) + accSeqs = append(accSeqs, account.GetSequence()) + } + + tx, err := GenTxBls12381( + s.Ctx, + txconfig, + msgs, + feeCoins, + 300000, + "", + accNums, + accSeqs, + signers, + signers, + ) + + if err != nil { + return nil, err + } + return tx, nil + +} + func (s *BaseAuthenticatorSuite) GenSimpleTxWithSelectedAuthenticators(msgs []sdk.Msg, signers []cryptotypes.PrivKey, selectedAuthenticators []uint64) (sdk.Tx, error) { txconfig := app.MakeEncodingConfig().TxConfig feeCoins := sdk.Coins{sdk.NewInt64Coin(appparams.CoinUnit, 2500)} @@ -158,3 +202,20 @@ func (s *BaseAuthenticatorSuite) FundAcc(acc sdk.AccAddress, amounts sdk.Coins) err := testutil.FundAccount(s.Ctx, s.BitsongApp.BankKeeper, acc, amounts) s.Require().NoError(err) } + +// NewBls12381Account creates a new BaseAccount object, but within the specs expected for x/smart-account custom authenticator +// NOTE: Always set `Address`,`AccountNumber` & `Sequence`to the account address that is secured by aggregated keys. +func NewBls12381Account(address string, pubKey cryptotypes.PubKey, accountNumber, sequence uint64) *authtypes.BaseAccount { + acc := &authtypes.BaseAccount{ + Address: address, + AccountNumber: accountNumber, + Sequence: sequence, + } + + err := acc.SetPubKey(pubKey) + if err != nil { + panic(err) + } + + return acc +} diff --git a/x/smart-account/authenticator/bls12_381.go b/x/smart-account/authenticator/bls12_381.go new file mode 100644 index 00000000..0064bdac --- /dev/null +++ b/x/smart-account/authenticator/bls12_381.go @@ -0,0 +1,169 @@ +package authenticator + +import ( + "bytes" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + btsgblst "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/bitsongofficial/go-bitsong/x/smart-account/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// Bls12381 authenticates aggregate signatures from an set of public keys registered. +// It allows for complex pattern matching to support advanced authentication flows. +type Bls12381 struct { + am *AuthenticatorManager + cdc codec.Codec + storeKey storetypes.StoreKey +} + +var _ Authenticator = &Bls12381{} + +func NewBls12381(am *AuthenticatorManager, storeKey storetypes.StoreKey) Bls12381 { + return Bls12381{ + am: am, + storeKey: storeKey, + } +} + +func (bls Bls12381) Type() string { + return "Bls12381" +} + +func (bls Bls12381) StaticGas() uint64 { + return 0 +} + +func (bls Bls12381) Initialize(cfg []byte) (Authenticator, error) { + return bls, nil +} + +func (bls Bls12381) Authenticate(ctx sdk.Context, req AuthenticationRequest) error { + // Validate input + // fmt.Printf("req.AuthenticatorId: %v\n", req.AuthenticatorId) + // fmt.Printf("len(req.SignatureData.Signatures): %v\n", len(req.SignatureData.Signatures)) + // fmt.Printf("len(req.SignatureData.Signers): %v\n", len(req.SignatureData.Signers)) + // ensure threshold is met & keys provided are expected for this authenticator + var blsConfig types.BlsConfig + store := ctx.KVStore(bls.storeKey) + found, err := types.Get(store, types.KeyAccountBlsKeySet(req.Account, req.AuthenticatorId), &blsConfig) + if err != nil || !found { + return errorsmod.Wrap(err, "failed to get authenticator") + } + // ensure threshold has been met + if blsConfig.Threshold+1 < uint64(len(req.SignatureData.Signatures)) { + return errorsmod.Wrap(err, "aggregate signature threshold not satisfied") + + } + + msgDigestHash := Sha256Msgs(req.TxData.Msgs) + // fmt.Printf("msgDigestHash: %v\n", msgDigestHash) + + // Aggregate public keys + var g1 [][]byte + // first sig details is ALWAYS aggregated key, so we skip + for i, signer := range req.SignatureData.Signers[1:] { + validPoint := checkPubkeyExistence(&blsConfig, signer) + if !validPoint { + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "aggregate key is not valid point %d: %v", i, err) + } + // pk, _ := blst.PublicKeyFromBytes(signer) + // good, err := btsgblst.VerifySignature(req.SignatureData.Signatures[i+1], msgDigestHash, pk) + // fmt.Printf("good: %v\n", good) + // if err != nil || !good { + // return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "AHHHHHHHH: %v", err) + // } + // Aggregate public keys (add them in G1) + g1 = append(g1, signer) + } + + var g2 []common.Signature + for i, sigBytes := range req.SignatureData.Signatures[1:] { + if len(sigBytes) == 0 { + continue + } + + sig, err := btsgblst.SignatureFromBytes(sigBytes) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "failed to deserialize wavs operator signature at index %d: %v", i, err) + } + g2 = append(g2, sig) + } + + // Aggregate Signature + // aggregate signature is in default signature location + aggregatedSignature := btsgblst.AggregateSignatures(g2) + aggregatedPubkey, err := btsgblst.AggregatePublicKeys(g1) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "Aggregated Signature Failed: %v", err) + } + + // providedAggregateSignature, err := btsgblst.SignatureFromBytesNoValidation(req.Signature) + // if err != nil { + // return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "failed btsgblst.SignatureFromBytesNoValidatio: %v", err) + // } + // // fmt.Printf("aggregatedSignature: %v\n", aggregatedSignature.Marshal()) + // // fmt.Printf("providedAggregateSignature.Marshal(): %v\n", providedAggregateSignature.Marshal()) + // if providedAggregateSignature != aggregatedSignature { + // return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "Aggregated Signature Failed: %v", "computed aggregate signature does not match provided aggregate signature") + // } + + valid := aggregatedSignature.Verify(aggregatedPubkey, msgDigestHash[:]) + if !valid { + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "btsgblst.VerifySignature Failed: %v", "aggregate signature verification failed") + } + + return nil +} + +func (bls Bls12381) Track(ctx sdk.Context, request AuthenticationRequest) error { + return nil + // return subTrack(ctx, request, bls.SubAuthenticators) +} + +func (bls Bls12381) ConfirmExecution(ctx sdk.Context, request AuthenticationRequest) error { + return nil +} + +func (bls Bls12381) OnAuthenticatorAdded(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { + return onBls12381Added(ctx, bls.storeKey, account, config, authenticatorId, bls.am) +} + +func (bls Bls12381) OnAuthenticatorRemoved(ctx sdk.Context, account sdk.AccAddress, config []byte, authenticatorId string) error { + return onBls12381Removed(ctx, bls.storeKey, account, authenticatorId) +} + +func onBls12381Added(ctx sdk.Context, storekey storetypes.StoreKey, account sdk.AccAddress, data []byte, authenticatorId string, am *AuthenticatorManager) error { + var config types.BlsConfig + if err := config.Unmarshal(data); err != nil { + return errorsmod.Wrapf(err, "failed to unmarshal BlsConfig init data") + } + + if len(config.Pubkeys) < int(config.Threshold) { + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "must set threshold to atleast to the number of keys: %d", config.Threshold) + } + + key := types.KeyAccountBlsKeySet(account, authenticatorId) + types.MustSet(ctx.KVStore(storekey), key, &config) + return nil + +} +func onBls12381Removed(ctx sdk.Context, storekey storetypes.StoreKey, account sdk.AccAddress, authenticatorId string) error { + key := types.KeyAccountBlsKeySet(account, authenticatorId) + ctx.KVStore(storekey).Delete(key) + return nil +} + +func checkPubkeyExistence(blsConfig *types.BlsConfig, pubkey []byte) bool { + for _, pk := range blsConfig.Pubkeys { + if bytes.Equal(pk, pubkey) { + return true + } + } + return false +} diff --git a/x/smart-account/authenticator/bls12_381_test.go b/x/smart-account/authenticator/bls12_381_test.go new file mode 100644 index 00000000..00eb9f8a --- /dev/null +++ b/x/smart-account/authenticator/bls12_381_test.go @@ -0,0 +1,284 @@ +package authenticator_test + +import ( + "fmt" + "os" + "testing" + + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/suite" + + "github.com/bitsongofficial/go-bitsong/crypto/bls" + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + btsgblst "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" + "github.com/bitsongofficial/go-bitsong/x/smart-account/types" +) + +type Bls12381AuthenticatorTest struct { + BaseAuthenticatorSuite + Bls12381Auth authenticator.Bls12381 +} + +func TestBls12381Auth(t *testing.T) { + suite.Run(t, new(Bls12381AuthenticatorTest)) +} + +func (s *Bls12381AuthenticatorTest) SetupTest() { + s.SetupKeys() + am := authenticator.NewAuthenticatorManager() + + // Define authenticators + s.Bls12381Auth = authenticator.NewBls12381(am, s.BitsongApp.GetKVStoreKey()[types.ModuleName]) + am.RegisterAuthenticator(s.Bls12381Auth) +} + +func (s *Bls12381AuthenticatorTest) TearDownTest() { + os.RemoveAll(s.HomeDir) +} + +func (s *Bls12381AuthenticatorTest) TestBls12381() { + // Define test cases + type testCase struct { + name string + includeTxExt bool + includeAggPkSig bool + expectSuccessfulAdded bool // expect registering an auth to be valid + expectSuccessful bool // expect authentication to be valid + expectConfirm bool // expect confirm execution to be valid + numKeys uint64 // # off agg keys in set + threshold uint64 // signing threshold + signers uint64 // # of keys to include in msg + } + + testCases := []testCase{ + + { + name: "with txExt", + numKeys: 1, + threshold: 1, + signers: 1, + includeTxExt: true, + includeAggPkSig: true, + expectSuccessfulAdded: true, + expectSuccessful: true, + expectConfirm: true, + }, + { + name: "without txExt", + numKeys: 1, + threshold: 1, + signers: 1, + includeTxExt: false, + includeAggPkSig: true, + expectSuccessfulAdded: true, + expectSuccessful: false, + expectConfirm: true, + }, + { + name: "multiple keys, threshold met", + numKeys: 2, + threshold: 2, + signers: 2, + includeTxExt: true, + includeAggPkSig: true, + expectSuccessfulAdded: true, + expectSuccessful: true, + expectConfirm: true, + }, + { + name: "multiple keys, threshold not met", + numKeys: 1, + threshold: 2, + signers: 1, + includeTxExt: true, + includeAggPkSig: true, + expectSuccessfulAdded: false, + expectSuccessful: false, + expectConfirm: true, + }, + } + + for _, tc := range testCases { + s.T().Run(tc.name, func(t *testing.T) { + cdc := s.BitsongApp.AppCodec() + + secretKeys, blsConfig, err := GenerateBLSPrivateKeysReturnBlsConfig(int(tc.numKeys), tc.threshold, 369) + s.Require().NoError(err) + fmt.Printf("len(secretKeys): %v\n", len(secretKeys)) + + txSender := s.TestPrivKeys[0].PubKey().Address() + // fmt.Printf("txSender: %v\n", txSender) + // fmt.Printf("secretKeys[0].PublicKey().Marshal(): %v\n", secretKeys[0].PublicKey().Marshal()) + // fmt.Printf("len(secretKeys[0].PublicKey().Marshal()): %v\n", len(secretKeys[0].PublicKey().Marshal())) + initializedAuth, err := s.Bls12381Auth.Initialize([]byte{}) + s.Require().NoError(err) + + s.Require().NoError(err) + + // Generate authentication request + ak := s.BitsongApp.AccountKeeper + + // sample msg + msg := &bank.MsgSend{FromAddress: s.TestAccAddress[0].String(), ToAddress: "to", Amount: sdk.NewCoins(sdk.NewInt64Coin("foo", 1))} + + // digest msg into hash being signed + msgsToHash := []sdk.Msg{msg} + var anyMsgs []authenticator.LocalAny + for _, in := range msgsToHash { + anyMsg, _ := codectypes.NewAnyWithValue(in) + anyMsgs = append(anyMsgs, authenticator.LocalAny{ + TypeURL: anyMsg.TypeUrl, + Value: anyMsg.Value, + }) + } + + msgDigestHash := authenticator.Sha256Msgs(anyMsgs) + // fmt.Printf("msgDigestHash: %v\n", msgDigestHash) + signerKeys := secretKeys + if tc.signers < uint64(len(secretKeys)) { + signerKeys = secretKeys[:tc.signers] + } + // Sign the message with the keys + agAuthData, err := SignMessageWithTestBls12Keys(s.EncodingConfig.TxConfig, msgDigestHash[:], signerKeys) + + s.Require().NoError(err) + + // omit inclusion + var smartAccAuth *types.AgAuthData + if tc.includeTxExt { + smartAccAuth = agAuthData + } + // // fmt.Printf("smartAccAuth: %v\n", smartAccAuth) + // // todo: aggregate pubkey + // if tc.includeAggPkSig {} + + // sample tx + tx, err := s.GenSimpleTxBls12381(msgsToHash, secretKeys, txSender) + s.Require().NoError(err) + + // pass msgs based on test instance + request, err := authenticator.GenerateAuthenticationRequest( + s.Ctx, cdc, ak, + s.EncodingConfig.TxConfig.SignModeHandler(), + s.TestAccAddress[0], s.TestAccAddress[0], + nil, sdk.NewCoins(), + msg, tx, + 0, false, + authenticator.SequenceMatch, + smartAccAuth, + ) + s.Require().NoError(err) + + // sign, err := request.SignatureData.Signers[0].Marshal() + // fmt.Printf("request.SignatureData: %v\n", sign) + request.AuthenticatorId = "1" + + // fmt.Printf("request.Account.String(): %v\n", request.Account.String()) + // fmt.Printf("len(request.Account): %v\n", len(request.Account)) + // Attempt to authenticate using initialized authenticator + bzBlsConfig, err := blsConfig.Marshal() + s.Require().NoError(err) + // fmt.Printf("blsConfig: %v\n", blsConfig) + err = initializedAuth.OnAuthenticatorAdded(s.Ctx, request.Account, bzBlsConfig, request.AuthenticatorId) + s.Require().Equal(tc.expectSuccessfulAdded, err == nil) + err = initializedAuth.Authenticate(s.Ctx, request) + fmt.Printf("err: %v\n", err) + s.Require().Equal(tc.expectSuccessful, err == nil) + err = initializedAuth.Track(s.Ctx, request) + s.Require().NoError(err) + err = initializedAuth.ConfirmExecution(s.Ctx, request) + s.Require().Equal(tc.expectConfirm, err == nil) + }) + } +} + +// GenerateBLSPrivateKeys creates n BLS12-381 private keys. +func GenerateBLSPrivateKeysReturnBlsConfig(n int, threshold uint64, seed int64) ([]common.SecretKey, types.BlsConfig, error) { + if n < 0 { + return nil, types.BlsConfig{}, fmt.Errorf("number of keys must be non-negative, got %d", n) + } + if n == 0 { + return []common.SecretKey{}, types.BlsConfig{}, nil + } + + secretKeys := make([]common.SecretKey, n) + pubkeys := make([][]byte, n) + + for i := 0; i < n; i++ { + key, err := blst.RandKey() + if err != nil { + return nil, types.BlsConfig{}, fmt.Errorf("RandKey() returned an error: %v", err) + } + // Convert to BLS secret key + secretKey, err := bls.SecretKeyFromBytes(key.Marshal()) + if err != nil { + return nil, types.BlsConfig{}, fmt.Errorf("failed to create secret key %d: %v", i, err) + } + + secretKeys[i] = secretKey + pubkeys[i] = secretKey.PublicKey().Marshal() + + } + // fmt.Printf("pubkeys: %v\n", pubkeys) + return secretKeys, types.BlsConfig{ + Pubkeys: pubkeys, + Threshold: threshold, + }, nil +} + +// SignMessageWithKeys signs a 32-byte message hash with a list of BLS private keys +// and returns a SmartAccountAuthData object with public keys and signatures. +func SignMessageWithTestBls12Keys(gen client.TxConfig, msgHash []byte, secretKeys []common.SecretKey) (*types.AgAuthData, error) { + // Validate message hash + if len(msgHash) != 32 { + return nil, fmt.Errorf("message hash must be 32 bytes, got %d", len(msgHash)) + } + + // Validate secret keys + if len(secretKeys) == 0 { + return nil, fmt.Errorf("no secret keys provided") + } + + // Initialize SmartAccountAuthData + auth := &types.AgAuthData{ + Data: []byte{}, + } + + // Sign with each key + sigs := make([]signing.SignatureV2, 0, len(secretKeys)) + for i, sk := range secretKeys { + sig := sk.Sign(msgHash) + if sig == nil { + return nil, fmt.Errorf("failed to sk.Sign %d", i) + } + pubkey, err := btsgblst.GetCosmosBlsPubkeyFromPubkey(sk.PublicKey()) + if err != nil { // Fix: check err != nil + return nil, fmt.Errorf("failed to NewPublicKeyFromBytes %d: %w", i, err) + } + sigV2 := signing.SignatureV2{ + PubKey: pubkey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: sig.Marshal(), + }, + Sequence: 0, + } + // define signature with correct Interface + sigs = append(sigs, sigV2) + } + + // Marshal the signatures array into bytes + fmt.Printf("sigs: %v\n", sigs) + signBz, err := authenticator.MarshalSignatureJSON(sigs) + if err != nil { + return nil, fmt.Errorf("failed to marshal signatures: %w", err) + } + auth.Data = signBz + return auth, nil +} diff --git a/x/smart-account/authenticator/composition_test.go b/x/smart-account/authenticator/composition_test.go index dce08e22..be66efca 100644 --- a/x/smart-account/authenticator/composition_test.go +++ b/x/smart-account/authenticator/composition_test.go @@ -18,6 +18,7 @@ import ( "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" "github.com/bitsongofficial/go-bitsong/x/smart-account/testutils" + "github.com/bitsongofficial/go-bitsong/x/smart-account/types" smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types" ) @@ -208,7 +209,7 @@ func (s *AggregatedAuthenticatorsTest) TestAnyOf() { // sample tx tx, err := s.GenSimpleTx([]sdk.Msg{msg}, []cryptotypes.PrivKey{s.TestPrivKeys[0]}) s.Require().NoError(err) - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) // Attempt to authenticate using initialized authenticator @@ -352,7 +353,7 @@ func (s *AggregatedAuthenticatorsTest) TestAllOf() { tx, err := s.GenSimpleTx([]sdk.Msg{msg}, []cryptotypes.PrivKey{s.TestPrivKeys[0]}) s.Require().NoError(err) cdc := s.BitsongApp.AppCodec() - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, cdc, ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, cdc, ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) // Attempt to authenticate using initialized authenticator @@ -448,7 +449,7 @@ func (s *AggregatedAuthenticatorsTest) TestComposedAuthenticator() { // sample tx tx, err := s.GenSimpleTx([]sdk.Msg{msg}, []cryptotypes.PrivKey{s.TestPrivKeys[0]}) s.Require().NoError(err) - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), msg, tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) err = initializedTop.Authenticate(s.Ctx, request) diff --git a/x/smart-account/authenticator/cosmwasm_test.go b/x/smart-account/authenticator/cosmwasm_test.go index 60d8bf02..f2b2e66c 100644 --- a/x/smart-account/authenticator/cosmwasm_test.go +++ b/x/smart-account/authenticator/cosmwasm_test.go @@ -25,6 +25,7 @@ import ( "github.com/bitsongofficial/go-bitsong/app/params" "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" + "github.com/bitsongofficial/go-bitsong/x/smart-account/types" ) type CosmwasmAuthenticatorTest struct { @@ -310,7 +311,7 @@ func (s *CosmwasmAuthenticatorTest) TestGeneral() { ak := s.BitsongApp.AccountKeeper sigModeHandler := s.EncodingConfig.TxConfig.SignModeHandler() - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, accounts[0], accounts[0], nil, feeCoins, testMsg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, accounts[0], accounts[0], nil, feeCoins, testMsg, tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) request.AuthenticatorId = "0" @@ -442,7 +443,7 @@ func (s *CosmwasmAuthenticatorTest) TestCosignerContract() { s.T().Skip("TODO: this currently fails as signatures are stripped from the tx. Should we add them or maybe do a better cosigner implementation later?") ak := s.BitsongApp.AccountKeeper sigModeHandler := s.EncodingConfig.TxConfig.SignModeHandler() - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, accounts[0], accounts[0], nil, sdk.NewCoins(), testMsg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, accounts[0], accounts[0], nil, sdk.NewCoins(), testMsg, tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) status := auth.Authenticate(s.Ctx.WithBlockTime(time.Now()), request) diff --git a/x/smart-account/authenticator/message_filter_test.go b/x/smart-account/authenticator/message_filter_test.go index d8b03276..d22349d9 100644 --- a/x/smart-account/authenticator/message_filter_test.go +++ b/x/smart-account/authenticator/message_filter_test.go @@ -15,6 +15,7 @@ import ( "github.com/bitsongofficial/go-bitsong/app/params" "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" + smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types" "github.com/stretchr/testify/suite" ) @@ -175,7 +176,7 @@ func (s *MessageFilterTest) TestBankSend() { sigModeHandler := s.EncodingConfig.TxConfig.SignModeHandler() tx, err := s.GenSimpleTx([]sdk.Msg{tt.msg}, []cryptotypes.PrivKey{s.TestPrivKeys[0]}) s.Require().NoError(err) - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), tt.msg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), tt.msg, tx, 0, false, authenticator.SequenceMatch, &smartaccounttypes.AgAuthData{}) s.Require().NoError(err) err = filter.Authenticate(s.Ctx, request) @@ -374,7 +375,7 @@ func (s *MessageFilterTest) TestLimitOrder() { sigModeHandler := s.EncodingConfig.TxConfig.SignModeHandler() tx, err := s.GenSimpleTx([]sdk.Msg{tt.msg}, []cryptotypes.PrivKey{s.TestPrivKeys[0]}) s.Require().NoError(err) - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), tt.msg, tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, s.TestAccAddress[0], s.TestAccAddress[0], nil, sdk.NewCoins(), tt.msg, tx, 0, false, authenticator.SequenceMatch, &smartaccounttypes.AgAuthData{}) s.Require().NoError(err) err = filter.Authenticate(s.Ctx, request) diff --git a/x/smart-account/authenticator/signature_authenticator_test.go b/x/smart-account/authenticator/signature_authenticator_test.go index 5c9240b3..aa0baf16 100644 --- a/x/smart-account/authenticator/signature_authenticator_test.go +++ b/x/smart-account/authenticator/signature_authenticator_test.go @@ -1,23 +1,30 @@ package authenticator_test import ( + "fmt" "math/rand" "os" "testing" "time" "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/suite" "github.com/bitsongofficial/go-bitsong/app" + "github.com/bitsongofficial/go-bitsong/crypto/bls/blst" + "github.com/bitsongofficial/go-bitsong/crypto/bls/common" + "github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator" + "github.com/bitsongofficial/go-bitsong/x/smart-account/types" + smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types" ) type SigVerifyAuthenticationSuite struct { @@ -269,7 +276,7 @@ func (s *SigVerifyAuthenticationSuite) TestSignatureAuthenticator() { if tc.TestData.ShouldSucceedGettingData { // request for the first message - request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, addr, addr, nil, sdk.NewCoins(), tc.TestData.Msgs[0], tx, 0, false, authenticator.SequenceMatch) + request, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, addr, addr, nil, sdk.NewCoins(), tc.TestData.Msgs[0], tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().NoError(err) // Test Authenticate method @@ -283,7 +290,7 @@ func (s *SigVerifyAuthenticationSuite) TestSignatureAuthenticator() { s.Require().Error(err) } } else { - _, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, addr, addr, nil, sdk.NewCoins(), tc.TestData.Msgs[0], tx, 0, false, authenticator.SequenceMatch) + _, err := authenticator.GenerateAuthenticationRequest(s.Ctx, s.BitsongApp.AppCodec(), ak, sigModeHandler, addr, addr, nil, sdk.NewCoins(), tc.TestData.Msgs[0], tx, 0, false, authenticator.SequenceMatch, &types.AgAuthData{}) s.Require().Error(err) } }) @@ -384,6 +391,206 @@ func (s *SigVerifyAuthenticationSuite) TestSignatureAuthenticator() { // s.Require().True(authentication.IsAuthenticated()) //} +func MakeTxBuilderBls381( + ctx sdk.Context, + gen client.TxConfig, + msgs []sdk.Msg, + feeAmt sdk.Coins, + gas uint64, + chainID string, + accNums, accSeqs []uint64, + signers, signatures []common.SecretKey, +) (client.TxBuilder, error) { + // Validate inputs + if len(msgs) == 0 { + return nil, fmt.Errorf("no messages provided") + } + if len(signers) == 0 || len(signatures) == 0 { + return nil, fmt.Errorf("no signers or signatures provided") + } + if len(signers) != len(signatures) { + return nil, fmt.Errorf("mismatched lengths: signatures=%d, signers=%d", len(signatures), len(signers)) + } + if len(accNums) != len(signers) || len(accSeqs) != len(signers) { + return nil, fmt.Errorf("mismatched lengths: accNums=%d, accSeqs=%d, signers=%d", len(accNums), len(accSeqs), len(signers)) + } + if feeAmt.IsZero() || !feeAmt.IsValid() { + return nil, fmt.Errorf("invalid fee amount: %v", feeAmt) + } + if gas == 0 { + return nil, fmt.Errorf("gas limit cannot be zero") + } + + cosmosSigs := make([]signing.SignatureV2, len(signatures)) + pks := make([]common.PublicKey, 0) + sigsInside := make([]common.Signature, 0) + + // Create a random length memo + r := rand.New(rand.NewSource(time.Now().UnixNano())) + memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100)) + signMode, err := authsigning.APISignModeToInternal(gen.SignModeHandler().DefaultMode()) + if err != nil { + return nil, fmt.Errorf("failed to convert sign mode: %v", err) + } + + // 1st round: set SignatureV2 with derived public keys and empty signatures + for i, privKey := range signers { + pubKey, err := blst.GetCosmosBlsPubkey(privKey) + if err != nil { + return nil, fmt.Errorf("failed to derive BLS public key for signer %d: %v", i, err) + } + if pubKey == nil { + return nil, fmt.Errorf("derived BLS public key is nil for signer %d", i) + } + + cosmosSigs[i] = signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signMode, + Signature: nil, + }, + Sequence: accSeqs[i], + } + pks = append(pks, privKey.PublicKey()) + } + + // used to set the non_critical_tx_extenaion + tx, ok := gen.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + if !ok { + return nil, fmt.Errorf("failed to use ExtendTxBuilder interface: %v", err) + } + err = tx.SetMsgs(msgs...) + if err != nil { + return nil, fmt.Errorf("failed to set messages: %v", err) + } + + tx.SetMemo(memo) + tx.SetFeeAmount(feeAmt) + tx.SetGasLimit(gas) + + // Log transaction details + txObj := tx.GetTx() + if txObj == nil { + return nil, fmt.Errorf("tx.GetTx() returned nil") + } + + // Verify V2AdaptableTx implementation + // adaptableTx, ok := txObj.(authsigning.V2AdaptableTx) + // if !ok { + // return nil, fmt.Errorf("tx does not implement V2AdaptableTx, got %T", txObj) + // } + // txData := adaptableTx.GetSigningTxData() + + var anyMsgs []authenticator.LocalAny + for _, in := range msgs { + anyMsg, _ := codectypes.NewAnyWithValue(in) + anyMsgs = append(anyMsgs, authenticator.LocalAny{ + TypeURL: anyMsg.TypeUrl, + Value: anyMsg.Value, + }) + } + + msgDigestHash := authenticator.Sha256Msgs(anyMsgs) + + // fmt.Printf("msgDigestHash: %v\n", msgDigestHash) + // fmt.Printf("len(signatures): %v\n", len(signatures)) + + // 2nd round: add the signatures into the signer + for i, p := range signatures { + // var pubKey *anypb.Any + // signerData := authsigning.SignerData{ + // ChainID: chainID, + // AccountNumber: accNums[i], + // Sequence: accSeqs[i], + // } + + // if cosmosSigs[i].PubKey != nil { + // cosmosPubkey, err := blst.GetCosmosBlsPubkey(p) + // // fmt.Printf("len(cosmosPubkey.Bytes()): %v\n", len(cosmosPubkey.Bytes())) + // anyPk, err := codectypes.NewAnyWithValue(cosmosPubkey) + // if err != nil { + // return nil, fmt.Errorf("failed to GetCosmosBlsPubkey %d: %v", i, err) + // } + // if err != nil { + // return nil, fmt.Errorf("failed to encode public key for signer %d: %v", i, err) + // } + // // fmt.Printf("len(anyPk.Value): %v\n", len(anyPk.Value)) + // pubKey = &anypb.Any{ + // TypeUrl: anyPk.TypeUrl, + // Value: anyPk.Value, + // } + // } + + // txSignerData := txsigning.SignerData{ + // ChainID: signerData.ChainID, + // AccountNumber: signerData.AccountNumber, + // Sequence: signerData.Sequence, + // PubKey: pubKey, + // } + + // gets the value to sign + // signBytes, err := gen.SignModeHandler().GetSignBytes(ctx, signingv1beta1.SignMode(signMode), txSignerData, txData) + // if err != nil { + // return nil, fmt.Errorf("failed to get sign bytes for signer %d: %v", i, err) + // } + // if signBytes == nil { + // return nil, fmt.Errorf("GetSignBytes returned nil for signer %d", i) + // } + + sig := p.Sign(msgDigestHash[:]) + cosmosSigs[i].Data.(*signing.SingleSignatureData).Signature = sig.Marshal() + sigsInside = append(sigsInside, sig) + } + + authExtSignature, err := gen.MarshalSignatureJSON(cosmosSigs) + if err != nil { + return nil, fmt.Errorf("failed to set messages: %v", err) + } + // Set the signature details into the TxExtension + txExtensionData := &smartaccounttypes.TxExtension{ + SelectedAuthenticators: []uint64{1}, + AggAuth: &smartaccounttypes.AgAuthData{ + Data: authExtSignature, + }, + } + + // set all keys into non critical-extensions + extOpts, err := codectypes.NewAnyWithValue(txExtensionData) + if err != nil { + return nil, fmt.Errorf("failed to txExtensionData NewAnyWithValue: %v", err) + } + tx.SetNonCriticalExtensionOptions(extOpts) + + // aggregate signatures & pubkey, set into default signature options + aggPubkeys := blst.AggregateMultiplePubkeys(pks) + aggPubkey, err := blst.GetCosmosBlsPubkeyFromPubkey(aggPubkeys) + if err != nil { + return nil, fmt.Errorf("failed to blst.GetCosmosBlsPubkeyFromPubkey: %v", err) + } + aggSig := blst.AggregateSignatures(sigsInside) + + // // fmt.Printf("pks: %v\n", pks[0].Marshal()) + // // fmt.Printf("aggPubkeys: %v\n", aggPubkeys.Marshal()) + // // fmt.Printf("aggPubkey: %v\n", aggPubkey) + // // fmt.Printf("aggSig: %v\n", aggSig) + // // fmt.Printf("len(aggSig.Marshal()): %v\n", len(aggSig.Marshal())) + + // set aggregated key into default signing options + err = tx.SetSignatures(signing.SignatureV2{ + PubKey: aggPubkey, + Data: &signing.SingleSignatureData{ + SignMode: signMode, + Signature: aggSig.Marshal(), + }, + Sequence: accSeqs[0], + }) + if err != nil { + return nil, fmt.Errorf("failed to set final signatures: %v", err) + } + + return tx, nil +} + func MakeTxBuilder(ctx sdk.Context, gen client.TxConfig, msgs []sdk.Msg, @@ -477,6 +684,24 @@ func GenTx( return tx.GetTx(), nil } +// GenTx generates a signed mock transaction. +func GenTxBls12381( + ctx sdk.Context, + gen client.TxConfig, + msgs []sdk.Msg, + feeAmt sdk.Coins, + gas uint64, + chainID string, + accNums, accSeqs []uint64, + signers, signatures []common.SecretKey, +) (sdk.Tx, error) { + tx, err := MakeTxBuilderBls381(ctx, gen, msgs, feeAmt, gas, chainID, accNums, accSeqs, signers, signatures) + if err != nil { + return nil, err + } + return tx.GetTx(), nil +} + func generatePubKeysForMultiSig( priv ...cryptotypes.PrivKey, ) (pubkeys []cryptotypes.PubKey) { diff --git a/x/smart-account/client/cli/tx.go b/x/smart-account/client/cli/tx.go index 37bac0c1..6597f6fc 100644 --- a/x/smart-account/client/cli/tx.go +++ b/x/smart-account/client/cli/tx.go @@ -26,8 +26,8 @@ func NewAddAuthentiactorCmd() (*btsgcli.TxCliDesc, *types.MsgAddAuthenticator) { Short: "add an authenticator for an address", Long: "", Example: ` - osmosisd tx authenticator add-authenticator SigVerification --from val \ - --chain-id osmosis-1 -b sync --keyring-backend test \ + bitsongd tx authenticator add-authenticator SigVerification --from val \ + --chain-id bitsong-2b -b sync --keyring-backend test \ --fees 1000uosmo `, ParseAndBuildMsg: BuildAddAuthenticatorMsg, @@ -40,8 +40,8 @@ func NewRemoveAuthentiactorCmd() (*btsgcli.TxCliDesc, *types.MsgRemoveAuthentica Short: "add an authenticator for an address", Long: "", Example: ` - osmosisd tx authenticator remove-authenticator 1 --from val \ - --chain-id osmosis-1 -b sync --keyring-backend test \ + bitsongd tx authenticator remove-authenticator 1 --from val \ + --chain-id bitsong-2b -b sync --keyring-backend test \ --fees 1000uosmo `, }, &types.MsgRemoveAuthenticator{} diff --git a/x/smart-account/keeper/keeper.go b/x/smart-account/keeper/keeper.go index 77b80948..97355693 100644 --- a/x/smart-account/keeper/keeper.go +++ b/x/smart-account/keeper/keeper.go @@ -188,7 +188,6 @@ func (k Keeper) AddAuthenticator(ctx sdk.Context, account sdk.AccAddress, authen // Get the next global id value for authenticators from the store id := k.InitializeOrGetNextAuthenticatorId(ctx) - // Each authenticator has a custom OnAuthenticatorAdded function err := impl.OnAuthenticatorAdded(ctx, account, config, strconv.FormatUint(id, 10)) if err != nil { diff --git a/x/smart-account/post/post.go b/x/smart-account/post/post.go index 324f81c1..c474a759 100644 --- a/x/smart-account/post/post.go +++ b/x/smart-account/post/post.go @@ -113,6 +113,7 @@ func (ad AuthenticatorPostDecorator) PostHandle( msgIndex, simulate, authenticator.NoReplayProtection, + nil, ) if err != nil { return sdk.Context{}, diff --git a/x/smart-account/specs/README.md b/x/smart-account/specs/README.md index 8c4cc96b..9a9e81e7 100644 --- a/x/smart-account/specs/README.md +++ b/x/smart-account/specs/README.md @@ -1,116 +1,95 @@ # Smart Account Workflow -This modules standout feature is allowing **an account-by-account customization on how transaction are authenticated.** +This module's standout feature is allowing **an account-by-account customization on how transactions are authenticated.** -Bitsong nodes now make use of their compute resources prior to a block being finalized, in order to handle the minimum processes that are needed to verify whether or not an account is valid to be included in the mempool. - -**This logic is now programmable via smart contracts & additional configurations,vastly expanding how transactions can be implemented.** +Bitsong nodes now make use of their compute resources prior to a block being finalized, in order to handle the minimum processes needed to verify whether an account is valid to be included in the mempool. +**This logic is now programmable via smart contracts & additional configurations, vastly expanding how transactions can be implemented.** ### Minimum Requirements -- One secp256k1 keypair to register to the smart-account, funded with gas to pay for transsaction fees +- **One secp256k1 keypair:** registers authorization methods to, generally funded with gas to pay for transaction fees. - An authentication method of choice -- Optional - an `TxExtension` appended to each message, in order to specific which authenticator to use (defualts to regular key ecdsa authorization) +- `TxExtension`: appended to each message, to specify which authenticator to use (defaults to regular key ECDSA authorization) ## Workflow Module Uses To Authorize Transactions -### 1. Inital Gas Limit -To increase cost for abusing this authentication module, a maximum gas limit for actions performed by the module is enforced. If any transaction to be authenticated +### 1. Initial Gas Limit +To safegaurd gas consumtion during authorizations, a maximum gas limit for actions performed by the module is enforced. If any transaction to be authenticated exceeds this limit during the authentication phase, it is rejected before execution. -### 2. Identify Fee Payer: -**The first signer of the transaction is always the feepayer.** This means that for multiple messages, all fees these messages incur will be paid by the single account. Front end curators should keep this in mind when implementing login workflows. +### 2. Identify Fee Payer: +**The first signer of the transaction is always the fee payer.** This means that for multiple messages, all fees these messages incur will be paid by the single account. Front end developers should keep this in mind when implementing login workflows. -### 3. Authenticate Each Message -Multiple messages to be authorized at once. For each message to be authorized -- its associated account is identified -- any authenticator registered for this account is fetched +### 3. Authenticate Each Message +Multiple messages can be authorized at once. For each message to be authorized: +- its associated account is identified +- any authenticator registered for this account is fetched - its message is then either validated or rejected. -### 4. Gas Limit Resets - Once the authorizations are complete, the module resets the gas limits in preparation of the last steps, which is what w +### 4. Gas Limit Resets +Once the authorizations are complete, the module resets the gas limits in preparation for the last steps, which is what will be used for the actual transaction execution. -### 5. **Track**: -After all messages are authenticated, the `Track` function notifies each executed authenticator . This allows authenticators to store any transaction-specific data they might need for the future. +### 5. **Track**: +After all messages are authenticated, the `Track` function notifies each executed authenticator. This allows authenticators to store any transaction-specific data they might need for the future, such as execution logs or state changes. -### 6. Execute Message +### 6. Execute Message The transaction is executed -### 7. Confirm Execution -After all messages are executed, the module calls the `ConfirmExecution` function is called for each of the authenticators that authenticated the tx. -This allows authenticators to enforce rules that depend on the outcome of the message execution, like spending and transaction limits. +### 7. Confirm Execution +After all messages are executed, the module calls the `ConfirmExecution` function for each of the authenticators that authenticated the tx. +This allows authenticators to enforce rules that depend on the outcome of the message execution, like spending limits or transaction frequency caps, even if the initial authorization passed. +--- -## Default Authentication Options -___ -Below we review the available ways transactions sent to nodes can be authenticated as to whether or not they are inculded when a block gets finalized +## Default Authentication Options +___ +Below we review the available ways transactions sent to nodes can be authenticated as to whether they are included when a block gets finalized. -## AllOf -Allof means that you can stack authentication requirements together, requiring 100% of the methods to be valid. An example would be a multi-sig, or even a two step-verification making use of one of the other authentication options. +### AllOf +AllOf means that you can stack authentication requirements together, requiring 100% of the methods to be valid. An example would be a multi-sig, or even a two-step verification making use of one of the other authentication options. -## AnyOf +### AnyOf **AnyOf will recognize transactions as valid if `1 of n` authenticators that an account registered to be used is successful.** -## Signature Verification +### Signature Verification The signature verification authenticator is the default authenticator for all accounts. It verifies that the signer of a message is the same as the account associated with the message. +--- -## CosmWasm Authenticator +## CosmWasm Authenticator -**This authenticator option allows us to have custom smart contracts made to handle how we accounts can have actions authorized for them.** -When an account registers a contract address to be used as an autheentication method, the specific paramaters sent by the account registering is **not** stored in the contract state, but rather the module storage,which keeps things light & keeps the compute resources light when making use of the contract. - -### How it work's -Bitsong will make use of the contract sudo entry point, only which can be done called by the chain itself. This means when an account making use of cosmwasm authentications submits a tx to be authenticated, the CosmwasmVM is deterministically processed & either is validated or rejected processed prior to deterministically processing the actual message to perform. +**This authenticator option allows us to have custom smart contracts made to handle how accounts can have actions authorized for them.** +When an account registers a contract address to be used as an authentication method, the specific parameters sent by the account registering are **not** stored in the contract state, but rather in the module storage, which keeps things light and keeps the compute resources minimal when making use of the contract. +### How it works +Bitsong will make use of the contract `sudo` entry point, which can only be called by the chain itself. This means when an account using CosmWasm authentication submits a tx, the CosmWasmVM is deterministically processed and either validates or rejects the transaction prior to deterministically processing the actual message to perform. -### Modules Go Message Structure -To register a cosmwasm authenticator to an account, use the following format +### Module's Go Message Structure +To register a CosmWasm authenticator to an account, use the following format: `MsgAddAuthenticator` arguments: - ```text -sender: -type: "CosmwasmAuthenticatorV1" -data: json_bytes({ - contract: "", - params: [] +sender: +type: "CosmwasmAuthenticatorV1" +data: json_bytes({ + contract: "", + params: [] }) ``` -**params** field is a json encoded value for any parameters to save regarding this authenticator. This is in contrast to saving these paramters into the contract state, which is more expensive when retrieving the state in a latter date. - -**Contract storage should be used only when the authenticator needs to track any dynamic information required.** - -### Minimum Requirements For Creating Custom Authentication Contracts -Any smart contract must have theese entrypoints availal in the Sudo entry point, or else 100% of the authorization request made to a contract missing one of these will fail. - -```rs -#[cw_serde] -pub enum AuthenticatorSudoMsg { - // These two are used to handle the addition and removal of the authenticator - OnAuthenticatorAdded(OnAuthenticatorAddedRequest), - OnAuthenticatorRemoved(OnAuthenticatorRemovedRequest), - - // These three are run during authenticating a transaction, specifically during steps 3,5,& 7 in the authentication process - // link: https://github.com/permissionlessweb/go-bitsong/blob/d7962e28589e2977280cdffbd2d2ea7e62b181e0/x/smart-account/README.md#transaction-authentication-overvie - Authenticate(AuthenticationRequest), - Track(TrackRequest), - ConfirmExecution(ConfirmExecutionRequest), -} -``` +The **params** field is a JSON-encoded value for any parameters to save regarding this authenticator. This contrasts with saving these parameters into the contract state, which is more expensive when retrieving the state at a later date. -#### Rust library +**Contract storage should be used only when the authenticator needs to track dynamic information required for authentication logic.** -A [simple rust library](https://github.com/permissionlessweb/bs-nfts/tree/cosmwasm-std-v2/packages/btsg-auth) can be added to the dependencies of your contract to access type definitions used by this module. +--- -## MessageFilter +## MessageFilter -**The message filter authentications means that you can register to authenticate by default any specific message with a given pattern.** +**The message filter authentication means that you can register to authenticate by default any specific message with a given pattern.** -**This is a very powerful filter, as it can bypass default authentication for your account to perform actions, so use with care!** +**This is a very powerful filter, as it can bypass default authentication for your account to perform actions, so use with care!** -Recognizing these accounts more as a **permissionless utility account** may help visualize how this authenticator can be used. +Recognizing these accounts more as a **permissionless utility account** may help visualize how this authenticator can be used. -For example, a faucet-like account can be created by allowing allowing any spend messages with the specific values set: +For example, a faucet-like account can be created by allowing any spend messages with specific values: ```json { "@type": "/cosmos.bank.v1beta1.MsgSend", @@ -123,25 +102,34 @@ For example, a faucet-like account can be created by allowing allowing any spend } ``` -Or a way to mint new tokens during a streaming session: +Or a way to mint new tokens during a streaming session: ```json { "@type":"/bitsong.fantoken.v1beta1.MsgMint", "sender":"bitsong1...", - // ... + // ... other required fields } ``` +--- + +## Risks & Limitations Present -## Risks & Limitations Present +### Registration Of Accounts +Improperly configured authenticators during registration could lead to permanent loss of account access if not carefully managed. Security audits are recommended for custom contracts, and always test locally any deployment workflow first. -### Registration Of Accounts +### Fees and Gas Consumption +Custom authentication logic may incur higher gas costs compared to standard signature verification. Gas metering during authentication phases must be strictly enforced to prevent resource exhaustion attacks. -### Fee's and Gas Consumptions +### Composite Authenticators +Combining multiple authentication methods (AllOf/AnyOf) increases complexity. Ensure logic interactions don't create unintended authorization pathways or denial-of-service vectors. -### Composite Authenticators +### Composite IDs +Account identifiers used in composite authenticators have size limitations due to protobuf encoding constraints. Avoid deeply nested composite structures that exceed chain-specific message size limits. -### Composite Ids +### Composite Signatures +When using multiple signature-based authenticators, ensure proper validation of signature uniqueness and replay protection mechanisms to prevent cryptographic vulnerabilities. -### Composite Signatures +--- +This module provides unprecedented flexibility in transaction authentication while maintaining the security guarantees of the Cosmos SDK. Developers should carefully consider the tradeoffs between customizability and computational overhead when designing authentication contracts. \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/allOf.md b/x/smart-account/specs/authenticators/allOf.md new file mode 100644 index 00000000..4738303c --- /dev/null +++ b/x/smart-account/specs/authenticators/allOf.md @@ -0,0 +1 @@ +# AllOf \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/anyOf.md b/x/smart-account/specs/authenticators/anyOf.md new file mode 100644 index 00000000..dbccb7d6 --- /dev/null +++ b/x/smart-account/specs/authenticators/anyOf.md @@ -0,0 +1 @@ +# AnyOf \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/bls12381.md b/x/smart-account/specs/authenticators/bls12381.md new file mode 100644 index 00000000..fb07effa --- /dev/null +++ b/x/smart-account/specs/authenticators/bls12381.md @@ -0,0 +1,12 @@ +# Bls12381 + +## Scope + +## Design + +### + +## Defining Signature Data for Txs + +### TxExtension + diff --git a/x/smart-account/specs/authenticators/composite.md b/x/smart-account/specs/authenticators/composite.md new file mode 100644 index 00000000..7072ec1b --- /dev/null +++ b/x/smart-account/specs/authenticators/composite.md @@ -0,0 +1 @@ +# Composite \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/cosmwasm.md b/x/smart-account/specs/authenticators/cosmwasm.md new file mode 100644 index 00000000..815b1ded --- /dev/null +++ b/x/smart-account/specs/authenticators/cosmwasm.md @@ -0,0 +1 @@ +# Cosmwasm Authenticators \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/messageFilter.md b/x/smart-account/specs/authenticators/messageFilter.md new file mode 100644 index 00000000..52b689f3 --- /dev/null +++ b/x/smart-account/specs/authenticators/messageFilter.md @@ -0,0 +1 @@ +# Message Filter \ No newline at end of file diff --git a/x/smart-account/specs/authenticators/sig.md b/x/smart-account/specs/authenticators/sig.md new file mode 100644 index 00000000..f799ad0e --- /dev/null +++ b/x/smart-account/specs/authenticators/sig.md @@ -0,0 +1 @@ +# Signature Verification \ No newline at end of file diff --git a/x/smart-account/specs/performing-actions.md b/x/smart-account/specs/performing-actions.md new file mode 100644 index 00000000..209e757a --- /dev/null +++ b/x/smart-account/specs/performing-actions.md @@ -0,0 +1,205 @@ +# Performing Actions +Lets review the workflow to perform on-chain actions with custom authenti + +## 0. Registering Authenticators To Accounts +The available types to register an authenticator are currently: +- `CosmwasmAuthenticatorV1` +- `SignatureVerification` +- `Bls12381V1` +- `AllOf` +- `AnyOf` +- `MessageFilter` +- `PartitionedAnyOf` +- `PartitionedAllOf` + +Registering an account requires a default signature from the accounts public key, including any parameters the authentictor may require. +```rs + let register_smart_account = Any { + type_url: "/bitsong.smartaccount.v1beta1.MsgAddAuthenticator".into(), + value: to_json_binary(&MsgAddAuthenticator { + sender: chain.sender_addr().to_string(), + authenticator_type: "CosmwasmAuthenticatorV1".into(), + data: to_json_binary(&CosmwasmAuthenticatorInitData { + contract: suite.wavs.address()?.to_string(), + params: vec![], + })? + .to_vec(), + })? + .to_vec(), + }; +``` + +## 1. Broadcasting Tx's using custom authenticator + +### `TxExtension` +The `TxExtension` object is expected to be included in a transaction's `non_critical_extension_options`. This allows the module to identify the authenticator to use and optionally, any public keys and signatures used for aggregated signature authentication. + +### Authenticator IDs +An account can have multiple authenticators, each identified by a unique numerical value. When a transaction includes multiple messages, each message requires an authenticator, and they must be provided in the order of the messages. If no authenticator is provided for a message, the module will attempt to authenticate the transaction using a standard secp256k1 signature from the original public key associated with the address. + +### Aggregated Authentication (Optional) +To use aggregated authentication, data must be set in the `ag_auth` object. This signals to the module that the transaction has been authorized by a set of keys and signatures that can be aggregated together, as specified in the `AuthenticationRequest`. This is particularly relevant for authenticators that verify aggregated ECDSA public keys and signatures, such as the BLS12-381 example. It is crucial to note that if an account is not configured to use an aggregated key authenticator and data is still set in this object, any transaction will fail. + +The `SignatureV2` and `SingleSignatureData` structures are used to represent signature data. +```go go/types/signature.go +type SignatureV2 struct { + // PubKey is the public key to use for verifying the signature + PubKey cryptotypes.PubKey + + // Data is the actual data of the signature which includes SignMode's and + // the actual signature from the pubkey. + Data SingleSignatureData + + // Sequence is the sequence of this account. Only populated in + // SIGN_MODE_DIRECT. + Sequence uint64 +} + +// SingleSignatureData represents the signature and SignMode of a single (non-multisig) signer +type SingleSignatureData struct { + // SignMode represents the SignMode of the signature. use SIGN_MODE_DIRECT + SignMode SignMode + + // Signature is the raw signature. + Signature []byte +} +``` +Currently, an array of JSON marshaled signature data is expected. + +#### JSON Marshalling & Unmarshalling +To work with signature data, the following functions are used: +```go go/authenticator/signature.go +var sigs []signing.SignatureV2 +// when forming msg to broadcast +signBz, err := authenticator.MarshalSignatureJSON(sigs) +// when writing custom aggregate authenticators +aggAuthData, err := UnmarshalSignatureJSON(cdc, aggSig.GetData()) +``` +#### Defining `AuthInfo` +A single signature object set in `AuthInfo.SignerInfos`, which is to be the single aggregated signature and pubkey. + +### Examples +#### Rust +```rs +// - A: Define the actions the smart-account is to perform +let wavs_action = Any {}.to_bytes()?, +// - B: Define the tx extension, specifying which smart-account to authenticate & any aggregate keys used +let non_critical_extension_options = vec![Any { + type_url: "/bitsong.smartaccount.v1beta1.TxExtension".into(), + value: to_json_binary(&TxExtension { + selected_authenticators: vec![1], + agg_auth: SmartAccountAuthData{signatures:vec!["==35h3jb63"]} + })?.to_vec(), +}].to_vec(); + +// - C: Form the main Tx Body +let wavs_broadcast_msg: TxBody = TxBody { + messages: vec![wavs_action], + memo: "Cosmic Wavs Account Action".into(), + timeout_height: block_height + 100u64, + extension_options: vec![], + non_critical_extension_options, +}; + +// - D: Generate signature using ECDSA method of choice (this example makes use of BLS12-381) +// ... +``` + +#### TS/JS +```ts + +// Define the TxExtension +const txExtension = TxExtension.fromPartial({ + selectedAuthenticators: [1], + aggAuth: { + signatures: ['==35h3jb63'], + }, +}); + +// Serialize TxExtension to Any +const nonCriticalExtensionOptions = [ + Any.fromPartial({ + typeUrl: '/bitsong.smartaccount.v1beta1.TxExtension', + value: TxExtension.encode(txExtension).finish(), + }), +]; + +// Form the main TxBody +const txBody = TxBody.fromPartial({ + messages: [wavsAction], + memo: 'Cosmic Wavs Account Action', + timeoutHeight: '100', + extensionOptions: [], + nonCriticalExtensionOptions: nonCriticalExtensionOptions, +}); + +``` +#### Go + +```go +// Create the TxExtension +txExtension := &types.TxExtension{ + SelectedAuthenticators: []uint64{1}, + AggAuth: &pb.SmartAccountAuthData{ + Signatures: []string{"==35h3jb63"}, + }, +} + +// Marshal TxExtension to Any +txExtensionAny, err := codectypes.NewAnyWithValue(txExtension) +if err != nil { + fmt.Println(err) + return +} + +// Form the main TxBody +txBody := &tx.TxBody{ + Messages: []*codectypes.Any{wavsAction}, + Memo: "Cosmic Wavs Account Action", + TimeoutHeight: 100, // uint64 + ExtensionOptions: nil, + NonCriticalExtensionOptions: []*codectypes.Any{txExtensionAny}, +} +``` + +#### Python +```python +def create_tx_body(wavs_action, tx_extension): + # Serialize TxExtension to Any + tx_extension_any = any_pb2.Any( + type_url='/bitsong.smartaccount.v1beta1.TxExtension', + value=tx_extension.SerializeToString() + ) + + # Form the main TxBody + tx_body = tx_pb2.TxBody( + messages=[wavs_action], + memo='Cosmic Wavs Account Action', + timeout_height='100', + extension_options=[], + nonCriticalExtensionOptions=[tx_extension_any] + ) + + return tx_body + +def create_tx_extension(selected_authenticators, agg_auth_signatures): + # Define the TxExtension + tx_extension = tx_extension_pb2.TxExtension( + selected_authenticators=selected_authenticators, + agg_auth=tx_extension_pb2.AggAuth( + signatures=agg_auth_signatures + ) + ) + + return tx_extension + +# Example usage +wavs_action = ... # Assuming wavs_action is defined elsewhere +selected_authenticators = [1] +agg_auth_signatures = ['==35h3jb63'] + +tx_extension = create_tx_extension(selected_authenticators, agg_auth_signatures) +tx_body = create_tx_body(wavs_action, tx_extension) +``` + + diff --git a/x/smart-account/testutils/contracts/cosigner-authenticator/src/contract.rs b/x/smart-account/testutils/contracts/cosigner-authenticator/src/contract.rs index 5a7d4f1e..df3972c4 100644 --- a/x/smart-account/testutils/contracts/cosigner-authenticator/src/contract.rs +++ b/x/smart-account/testutils/contracts/cosigner-authenticator/src/contract.rs @@ -2,7 +2,6 @@ use crate::types::Signature; use cosmwasm_schema::cw_serde; use cosmwasm_std::{entry_point, Binary, DepsMut, Env, Response, StdError, StdResult}; use cw_storage_plus::Item; -use osmosis_authenticators as oa; use sylvia::types::{InstantiateCtx, QueryCtx}; use sylvia::{contract, entry_points}; @@ -35,13 +34,13 @@ impl CosignerAuthenticator { fn sudo_authenticate( &self, deps: DepsMut, - auth_request: oa::AuthenticationRequest, + auth_request: btsg_auth::AuthenticationRequest, ) -> Result { deps.api.debug(&format!("auth_request {:?}", auth_request)); let sigs: Vec = cosmwasm_std::from_json(&auth_request.signature)?; if sigs.len() != self.pubkeys.load(deps.storage)?.len() { - return Ok(Response::new().set_data(oa::AuthenticationResult::NotAuthenticated {})); + return Ok(Response::new().set_data(btsg_auth::AuthenticationResult::NotAuthenticated {})); } let mut pubkeys = self.pubkeys.load(deps.storage)?; @@ -49,7 +48,7 @@ impl CosignerAuthenticator { // The message hash is what gets signed for (i, pubkey) in pubkeys.iter().enumerate() { - let hash = oa::sha256(&concat( + let hash = btsg_auth::sha256(&concat( &auth_request.sign_mode_tx_data.sign_mode_direct, &sigs[i].salt, )); @@ -63,11 +62,11 @@ impl CosignerAuthenticator { })?; if !valid { - return Ok(Response::new().set_data(oa::AuthenticationResult::NotAuthenticated {})); + return Ok(Response::new().set_data(btsg_auth::AuthenticationResult::NotAuthenticated {})); } } - Ok(Response::new().set_data(oa::AuthenticationResult::Authenticated {})) + Ok(Response::new().set_data(btsg_auth::AuthenticationResult::Authenticated {})) } } @@ -79,7 +78,7 @@ fn concat(a: &Binary, b: &Binary) -> Binary { #[cw_serde] pub enum SudoMsg { - Authenticate(oa::AuthenticationRequest), + Authenticate(btsg_auth::AuthenticationRequest), } #[entry_point] diff --git a/x/smart-account/testutils/contracts/cosigner-authenticator/src/multitest.rs b/x/smart-account/testutils/contracts/cosigner-authenticator/src/multitest.rs index 00311e8b..d97d7124 100644 --- a/x/smart-account/testutils/contracts/cosigner-authenticator/src/multitest.rs +++ b/x/smart-account/testutils/contracts/cosigner-authenticator/src/multitest.rs @@ -3,7 +3,6 @@ use crate::contract::SudoMsg; use crate::test_utils::{generate_keys_and_sign, sign_message}; use crate::types::Pubkey; use cosmwasm_std::{from_json, to_json_binary, Addr, Binary}; -use osmosis_authenticators as oa; use sylvia::multitest::App; const OWNER: &str = "owner"; @@ -84,18 +83,18 @@ fn test_successful_authentication() { let sigs: Vec = vec![sig1.clone(), sig2.clone()]; let compound_sig: Binary = to_json_binary(&sigs).unwrap(); - let mut auth_request = oa::AuthenticationRequest { + let mut auth_request = btsg_auth::AuthenticationRequest { signature: compound_sig.clone(), - sign_mode_tx_data: oa::SignModeTxData { + sign_mode_tx_data: btsg_auth::SignModeTxData { sign_mode_direct: Binary::from(message.as_bytes()), sign_mode_textual: None, }, account: Addr::unchecked("account"), - msg: oa::Any { + msg: btsg_auth::Any { type_url: "".to_string(), value: Binary::from("msg".as_bytes()), }, - tx_data: oa::TxData { + tx_data: btsg_auth::TxData { chain_id: "chain_id".to_string(), account_number: 1, sequence: 1, @@ -103,7 +102,7 @@ fn test_successful_authentication() { msgs: vec![], memo: "".to_string(), }, - signature_data: oa::SignatureData { + signature_data: btsg_auth::SignatureData { signers: vec![], signatures: vec![compound_sig.to_string()], }, @@ -118,13 +117,13 @@ fn test_successful_authentication() { .wasm_sudo(contract.contract_addr.clone(), &msg) .unwrap(); - let auth_result: oa::AuthenticationResult = from_json(result.data.unwrap()).unwrap(); + let auth_result: btsg_auth::AuthenticationResult = from_json(result.data.unwrap()).unwrap(); println!("result: {:?}", auth_result); // Check if the result is Authenticated assert!(matches!( auth_result, - oa::AuthenticationResult::Authenticated {} + btsg_auth::AuthenticationResult::Authenticated {} )); // Modify the signatures to be something invalid @@ -140,13 +139,13 @@ fn test_successful_authentication() { .wasm_sudo(contract.contract_addr.clone(), &msg) .unwrap(); - let auth_result: oa::AuthenticationResult = from_json(result.data.unwrap()).unwrap(); + let auth_result: btsg_auth::AuthenticationResult = from_json(result.data.unwrap()).unwrap(); println!("result: {:?}", auth_result); // Check if the result is Authenticated assert!(matches!( auth_result, - oa::AuthenticationResult::NotAuthenticated {} + btsg_auth::AuthenticationResult::NotAuthenticated {} )); // Now let's use an invalid signature. This should be an error. diff --git a/x/smart-account/testutils/contracts/echo/Cargo.toml b/x/smart-account/testutils/contracts/echo/Cargo.toml index 253b968f..b2f02c2f 100644 --- a/x/smart-account/testutils/contracts/echo/Cargo.toml +++ b/x/smart-account/testutils/contracts/echo/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["osmosis contributors"] +authors = ["bitsong contributors"] description = "Cosmwasm contract that always returns the same response" edition = "2021" name = "echo" @@ -30,17 +30,16 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -cosmwasm-schema = "1.1.3" -cosmwasm-std = { version = "1.5", features = ["stargate"] } -cosmwasm-storage = "1.1.3" -cw-storage-plus = "1.0.1" -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } +cosmwasm-schema = "2.2.0" +cosmwasm-std = "2.2.0" +cw-storage-plus = "2.0.0" +cosmos-sdk-proto = { version = "0.26.1", default-features = false } +schemars = "0.8.21" +serde = { version = "1.0.140", default-features = false, features = ["derive"] } #serde-cw-value = "0.7.0" -osmosis-std = "0.20.1" -thiserror = { version = "1.0.31" } +thiserror = "1.0.31" #base64-simd = "0.8.0" -osmosis-authenticators = "0.22.0-alpha.19" +btsg-auth = { package = "btsg-auth", git = "https://github.com/permissionlessweb/bs-nfts", branch = "cosmwasm-std-v2" } sha2 = "0.10.8" [dev-dependencies] diff --git a/x/smart-account/testutils/contracts/echo/src/lib.rs b/x/smart-account/testutils/contracts/echo/src/lib.rs index b4bdf3e3..afa5b031 100644 --- a/x/smart-account/testutils/contracts/echo/src/lib.rs +++ b/x/smart-account/testutils/contracts/echo/src/lib.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{ from_json, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, }; use cw_storage_plus::Item; -use osmosis_authenticators::{ +use btsg_auth::{ AuthenticationRequest, ConfirmExecutionRequest, OnAuthenticatorAddedRequest, OnAuthenticatorRemovedRequest, TrackRequest, }; @@ -118,7 +118,7 @@ fn on_authenticator_removed( fn authenticate(deps: DepsMut, auth_request: AuthenticationRequest) -> Result { deps.api.debug(&format!("auth_request {:?}", auth_request)); if auth_request.msg.type_url == "/cosmos.bank.v1beta1.MsgSend" { - let send: osmosis_std::types::cosmos::bank::v1beta1::MsgSend = + let send: cosmos_sdk_proto::cosmos::bank::v1beta1::MsgSend = auth_request.msg.value.try_into()?; deps.api.debug(&format!("send {:?}", send)); @@ -127,7 +127,7 @@ fn authenticate(deps: DepsMut, auth_request: AuthenticationRequest) -> Result 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintTx(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *TxExtension) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -807,27 +956,76 @@ func (m *TxExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.AggAuth != nil { + { + size, err := m.AggAuth.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } if len(m.SelectedAuthenticators) > 0 { - dAtA2 := make([]byte, len(m.SelectedAuthenticators)*10) - var j1 int + dAtA3 := make([]byte, len(m.SelectedAuthenticators)*10) + var j2 int for _, num := range m.SelectedAuthenticators { for num >= 1<<7 { - dAtA2[j1] = uint8(uint64(num)&0x7f | 0x80) + dAtA3[j2] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j1++ + j2++ } - dAtA2[j1] = uint8(num) - j1++ + dAtA3[j2] = uint8(num) + j2++ } - i -= j1 - copy(dAtA[i:], dAtA2[:j1]) - i = encodeVarintTx(dAtA, i, uint64(j1)) + i -= j2 + copy(dAtA[i:], dAtA3[:j2]) + i = encodeVarintTx(dAtA, i, uint64(j2)) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } +func (m *BlsConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BlsConfig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BlsConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Threshold != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Threshold)) + i-- + dAtA[i] = 0x10 + } + if len(m.Pubkeys) > 0 { + for iNdEx := len(m.Pubkeys) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Pubkeys[iNdEx]) + copy(dAtA[i:], m.Pubkeys[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Pubkeys[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -925,6 +1123,19 @@ func (m *MsgSetActiveStateResponse) Size() (n int) { return n } +func (m *AgAuthData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func (m *TxExtension) Size() (n int) { if m == nil { return 0 @@ -938,6 +1149,28 @@ func (m *TxExtension) Size() (n int) { } n += 1 + sovTx(uint64(l)) + l } + if m.AggAuth != nil { + l = m.AggAuth.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *BlsConfig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Pubkeys) > 0 { + for _, b := range m.Pubkeys { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } + } + if m.Threshold != 0 { + n += 1 + sovTx(uint64(m.Threshold)) + } return n } @@ -1488,6 +1721,90 @@ func (m *MsgSetActiveStateResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *AgAuthData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AgAuthData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AgAuthData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *TxExtension) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1593,6 +1910,143 @@ func (m *TxExtension) Unmarshal(dAtA []byte) error { } else { return fmt.Errorf("proto: wrong wireType = %d for field SelectedAuthenticators", wireType) } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggAuth", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AggAuth == nil { + m.AggAuth = &AgAuthData{} + } + if err := m.AggAuth.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BlsConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BlsConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BlsConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pubkeys", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pubkeys = append(m.Pubkeys, make([]byte, postIndex-iNdEx)) + copy(m.Pubkeys[len(m.Pubkeys)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Threshold", wireType) + } + m.Threshold = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Threshold |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:])