starting
This commit is contained in:
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# Allowlisting gitignore template for GO projects prevents us
|
||||
# from adding various unwanted local files, such as generated
|
||||
# files, developer configurations or IDE-specific files etc.
|
||||
#
|
||||
# Recommended: Go.AllowList.gitignore
|
||||
|
||||
# Ignore everything
|
||||
*
|
||||
|
||||
# Especially these
|
||||
.vscode
|
||||
.vscode/
|
||||
.vscode/**
|
||||
**/.vscode
|
||||
**/.vscode/**
|
||||
.idea
|
||||
.idea/
|
||||
.idea/**
|
||||
**/.idea
|
||||
**/.idea/
|
||||
**/.idea/**
|
||||
node_modules
|
||||
node_modules/
|
||||
node_modules/**
|
||||
**/node_modules
|
||||
**/node_modules/
|
||||
**/node_modules/**
|
||||
/test*
|
||||
# and others
|
||||
/go.work.sum
|
||||
/secp256k1/
|
||||
|
||||
# But not these files...
|
||||
!/.gitignore
|
||||
!*.go
|
||||
!go.sum
|
||||
!go.mod
|
||||
!*.md
|
||||
!LICENSE
|
||||
!*.sh
|
||||
!Makefile
|
||||
!*.json
|
||||
!*.pdf
|
||||
!*.csv
|
||||
!*.py
|
||||
!*.mediawiki
|
||||
!*.did
|
||||
!*.rs
|
||||
!*.toml
|
||||
!*.file
|
||||
!.gitkeep
|
||||
!pkg/eth/**
|
||||
!*.h
|
||||
!*.c
|
||||
!*.proto
|
||||
!bundleData
|
||||
!*.item
|
||||
!*.bin
|
||||
!*.yml
|
||||
!*.yaml
|
||||
!*.tmpl
|
||||
!*.s
|
||||
!*.asm
|
||||
!.gitmodules
|
||||
!*.txt
|
||||
!*.sum
|
||||
!version
|
||||
!*.service
|
||||
!*.benc
|
||||
!*.png
|
||||
!*.adoc
|
||||
!*.js
|
||||
!*.bash
|
||||
!PATENTS
|
||||
!*.css
|
||||
!*.ts
|
||||
!*.html
|
||||
!Dockerfile
|
||||
!*.lock
|
||||
!*.nix
|
||||
!license
|
||||
!readme
|
||||
|
||||
# ...even if they are in subdirectories
|
||||
!*/
|
||||
/blocklist.json
|
||||
/gui/gui/main.wasm
|
||||
/gui/gui/index.html
|
||||
database/testrealy
|
||||
23
apputil/apputil.go
Normal file
23
apputil/apputil.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package apputil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// EnsureDir checks a file could be written to a path, creates the directories as needed
|
||||
func EnsureDir(fileName string) {
|
||||
dirName := filepath.Dir(fileName)
|
||||
if _, serr := os.Stat(dirName); serr != nil {
|
||||
merr := os.MkdirAll(dirName, os.ModePerm)
|
||||
if merr != nil {
|
||||
panic(merr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(filePath string) bool {
|
||||
_, e := os.Stat(filePath)
|
||||
return e == nil
|
||||
}
|
||||
19
atomic/.codecov.yml
Normal file
19
atomic/.codecov.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
coverage:
|
||||
range: 80..100
|
||||
round: down
|
||||
precision: 2
|
||||
|
||||
status:
|
||||
project: # measuring the overall project coverage
|
||||
default: # context, you can create multiple ones with custom titles
|
||||
enabled: yes # must be yes|true to enable this status
|
||||
target: 100 # specify the target coverage for each commit status
|
||||
# option: "auto" (must increase from parent commit or pull request base)
|
||||
# option: "X%" a static target percentage to hit
|
||||
if_not_found: success # if parent is not found report status as success, error, or failure
|
||||
if_ci_failed: error # if ci fails report status as success, error, or failure
|
||||
|
||||
# Also update COVER_IGNORE_PKGS in the Makefile.
|
||||
ignore:
|
||||
- /internal/gen-atomicint/
|
||||
- /internal/gen-valuewrapper/
|
||||
130
atomic/CHANGELOG.md
Normal file
130
atomic/CHANGELOG.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
- No changes yet.
|
||||
|
||||
## [1.11.0] - 2023-05-02
|
||||
### Fixed
|
||||
- Fix `Swap` and `CompareAndSwap` for `Value` wrappers without initialization.
|
||||
|
||||
### Added
|
||||
- Add `String` method to `atomic.Pointer[T]` type allowing users to safely print
|
||||
underlying values of pointers.
|
||||
|
||||
[1.11.0]: https://github.com/uber-go/atomic/compare/v1.10.0...v1.11.0
|
||||
|
||||
## [1.10.0] - 2022-08-11
|
||||
### Added
|
||||
- Add `atomic.Float32` type for atomic operations on `float32`.
|
||||
- Add `CompareAndSwap` and `Swap` methods to `atomic.String`, `atomic.Error`,
|
||||
and `atomic.Value`.
|
||||
- Add generic `atomic.Pointer[T]` type for atomic operations on pointers of any
|
||||
type. This is present only for Go 1.18 or higher, and is a drop-in for
|
||||
replacement for the standard library's `sync/atomic.Pointer` type.
|
||||
|
||||
### Changed
|
||||
- Deprecate `CAS` methods on all types in favor of corresponding
|
||||
`CompareAndSwap` methods.
|
||||
|
||||
Thanks to @eNV25 and @icpd for their contributions to this release.
|
||||
|
||||
[1.10.0]: https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0
|
||||
|
||||
## [1.9.0] - 2021-07-15
|
||||
### Added
|
||||
- Add `Float64.Swap` to match int atomic operations.
|
||||
- Add `atomic.Time` type for atomic operations on `time.Time` values.
|
||||
|
||||
[1.9.0]: https://github.com/uber-go/atomic/compare/v1.8.0...v1.9.0
|
||||
|
||||
## [1.8.0] - 2021-06-09
|
||||
### Added
|
||||
- Add `atomic.Uintptr` type for atomic operations on `uintptr` values.
|
||||
- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values.
|
||||
|
||||
[1.8.0]: https://github.com/uber-go/atomic/compare/v1.7.0...v1.8.0
|
||||
|
||||
## [1.7.0] - 2020-09-14
|
||||
### Added
|
||||
- Support JSON serialization and deserialization of primitive atomic types.
|
||||
- Support Text marshalling and unmarshalling for string atomics.
|
||||
|
||||
### Changed
|
||||
- Disallow incorrect comparison of atomic values in a non-atomic way.
|
||||
|
||||
### Removed
|
||||
- Remove dependency on `golang.org/x/{lint, tools}`.
|
||||
|
||||
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
|
||||
|
||||
## [1.6.0] - 2020-02-24
|
||||
### Changed
|
||||
- Drop library dependency on `golang.org/x/{lint, tools}`.
|
||||
|
||||
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
|
||||
|
||||
## [1.5.1] - 2019-11-19
|
||||
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
|
||||
causing `CAS` to fail even though the old value matches.
|
||||
|
||||
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
|
||||
|
||||
## [1.5.0] - 2019-10-29
|
||||
### Changed
|
||||
- With Go modules, only the `go.uber.org/atomic` import path is supported now.
|
||||
If you need to use the old import path, please add a `replace` directive to
|
||||
your `go.mod`.
|
||||
|
||||
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
|
||||
|
||||
## [1.4.0] - 2019-05-01
|
||||
### Added
|
||||
- Add `atomic.Error` type for atomic operations on `error` values.
|
||||
|
||||
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
|
||||
|
||||
## [1.3.2] - 2018-05-02
|
||||
### Added
|
||||
- Add `atomic.Duration` type for atomic operations on `time.Duration` values.
|
||||
|
||||
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
|
||||
|
||||
## [1.3.1] - 2017-11-14
|
||||
### Fixed
|
||||
- Revert optimization for `atomic.String.Store("")` which caused data races.
|
||||
|
||||
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
|
||||
|
||||
## [1.3.0] - 2017-11-13
|
||||
### Added
|
||||
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
|
||||
|
||||
### Changed
|
||||
- Optimize `atomic.String.Store("")` by avoiding an allocation.
|
||||
|
||||
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
|
||||
|
||||
## [1.2.0] - 2017-04-12
|
||||
### Added
|
||||
- Shadow `atomic.Value` from `sync/atomic`.
|
||||
|
||||
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
|
||||
|
||||
## [1.1.0] - 2017-03-10
|
||||
### Added
|
||||
- Add atomic `Float64` type.
|
||||
|
||||
### Changed
|
||||
- Support new `go.uber.org/atomic` import path.
|
||||
|
||||
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
|
||||
|
||||
## [1.0.0] - 2016-07-18
|
||||
|
||||
- Initial release.
|
||||
|
||||
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0
|
||||
19
atomic/LICENSE
Normal file
19
atomic/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
79
atomic/Makefile
Normal file
79
atomic/Makefile
Normal file
@@ -0,0 +1,79 @@
|
||||
# Directory to place `go install`ed binaries into.
|
||||
export GOBIN ?= $(shell pwd)/bin
|
||||
|
||||
GOLINT = $(GOBIN)/golint
|
||||
GEN_ATOMICINT = $(GOBIN)/gen-atomicint
|
||||
GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper
|
||||
STATICCHECK = $(GOBIN)/staticcheck
|
||||
|
||||
GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
|
||||
|
||||
# Also update ignore section in .codecov.yml.
|
||||
COVER_IGNORE_PKGS = \
|
||||
github.com/p9ds/atomic/internal/gen-atomicint \
|
||||
github.com/p9ds/atomic/internal/gen-atomicwrapper
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build ./...
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race ./...
|
||||
|
||||
.PHONY: gofmt
|
||||
gofmt:
|
||||
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
|
||||
gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
|
||||
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false)
|
||||
|
||||
$(GOLINT):
|
||||
cd tools && go install golang.org/x/lint/golint
|
||||
|
||||
$(STATICCHECK):
|
||||
cd tools && go install honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*)
|
||||
go build -o $@ ./internal/gen-atomicwrapper
|
||||
|
||||
$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*)
|
||||
go build -o $@ ./internal/gen-atomicint
|
||||
|
||||
.PHONY: golint
|
||||
golint: $(GOLINT)
|
||||
$(GOLINT) ./...
|
||||
|
||||
.PHONY: staticcheck
|
||||
staticcheck: $(STATICCHECK)
|
||||
$(STATICCHECK) ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: gofmt golint staticcheck generatenodirty
|
||||
|
||||
# comma separated list of packages to consider for code coverage.
|
||||
COVER_PKG = $(shell \
|
||||
go list -find ./... | \
|
||||
grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \
|
||||
paste -sd, -)
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./...
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
.PHONY: generate
|
||||
generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER)
|
||||
go generate ./...
|
||||
|
||||
.PHONY: generatenodirty
|
||||
generatenodirty:
|
||||
@[ -z "$$(git status --porcelain)" ] || ( \
|
||||
echo "Working tree is dirty. Commit your changes first."; \
|
||||
git status; \
|
||||
exit 1 )
|
||||
@make generate
|
||||
@status=$$(git status --porcelain); \
|
||||
[ -z "$$status" ] || ( \
|
||||
echo "Working tree is dirty after `make generate`:"; \
|
||||
echo "$$status"; \
|
||||
echo "Please ensure that the generated code is up-to-date." )
|
||||
33
atomic/README.md
Normal file
33
atomic/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# atomic
|
||||
|
||||
Simple wrappers for primitive types to enforce atomic access.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ go get -u github.com/mleku/nodl/pkg/atomic@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The standard library's `sync/atomic` is powerful, but it's easy to forget which
|
||||
variables must be accessed atomically. `github.com/mleku/nodl/pkg/atomic` preserves all the
|
||||
functionality of the standard library, but wraps the primitive types to
|
||||
provide a safer, more convenient API.
|
||||
|
||||
```go
|
||||
var atom atomic.Uint32
|
||||
atom.Store(42)
|
||||
atom.Sub(2)
|
||||
atom.CompareAndSwap(40, 11)
|
||||
```
|
||||
|
||||
See the [documentation][doc] for a complete API specification.
|
||||
|
||||
## Development Status
|
||||
|
||||
Stable.
|
||||
|
||||
---
|
||||
|
||||
Released under the [MIT License](LICENSE.txt).
|
||||
45
atomic/assert_test.go
Normal file
45
atomic/assert_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Marks the test as failed if the error cannot be cast into the provided type
|
||||
// with errors.As.
|
||||
//
|
||||
// assertErrorAsType(t, err, new(ErrFoo))
|
||||
func assertErrorAsType(t *testing.T, err error, typ interface{}, msgAndArgs ...interface{}) bool {
|
||||
t.Helper()
|
||||
|
||||
return assert.True(t, errors.As(err, typ), msgAndArgs...)
|
||||
}
|
||||
|
||||
func assertErrorJSONUnmarshalType(t *testing.T, err error, msgAndArgs ...interface{}) bool {
|
||||
t.Helper()
|
||||
|
||||
return assertErrorAsType(t, err, new(*json.UnmarshalTypeError), msgAndArgs...)
|
||||
}
|
||||
88
atomic/bool.go
Normal file
88
atomic/bool.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Bool is an atomic type-safe wrapper for bool values.
|
||||
type Bool struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint32
|
||||
}
|
||||
|
||||
var _zeroBool bool
|
||||
|
||||
// NewBool creates a new Bool.
|
||||
func NewBool(val bool) *Bool {
|
||||
x := &Bool{}
|
||||
if val != _zeroBool {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped bool.
|
||||
func (x *Bool) Load() bool {
|
||||
return truthy(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed bool.
|
||||
func (x *Bool) Store(val bool) {
|
||||
x.v.Store(boolToInt(val))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for bool values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Bool) CAS(old, new bool) (swapped bool) {
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for bool values.
|
||||
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
|
||||
return x.v.CompareAndSwap(boolToInt(old), boolToInt(new))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given bool and returns the old
|
||||
// value.
|
||||
func (x *Bool) Swap(val bool) (old bool) {
|
||||
return truthy(x.v.Swap(boolToInt(val)))
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped bool into JSON.
|
||||
func (x *Bool) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a bool from JSON.
|
||||
func (x *Bool) UnmarshalJSON(b []byte) error {
|
||||
var v bool
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
x.Store(v)
|
||||
return nil
|
||||
}
|
||||
53
atomic/bool_ext.go
Normal file
53
atomic/bool_ext.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go
|
||||
|
||||
func truthy(n uint32) bool {
|
||||
return n == 1
|
||||
}
|
||||
|
||||
func boolToInt(b bool) uint32 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Toggle atomically negates the Boolean and returns the previous value.
|
||||
func (b *Bool) Toggle() (old bool) {
|
||||
for {
|
||||
old := b.Load()
|
||||
if b.CAS(old, !old) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (b *Bool) String() string {
|
||||
return strconv.FormatBool(b.Load())
|
||||
}
|
||||
150
atomic/bool_test.go
Normal file
150
atomic/bool_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
atom := NewBool(false)
|
||||
require.False(t, atom.Toggle(), "Expected Toggle to return previous value.")
|
||||
require.True(t, atom.Toggle(), "Expected Toggle to return previous value.")
|
||||
require.False(t, atom.Toggle(), "Expected Toggle to return previous value.")
|
||||
require.True(t, atom.Load(), "Unexpected state after swap.")
|
||||
|
||||
require.True(t, atom.CAS(true, true), "CAS should swap when old matches")
|
||||
require.True(t, atom.Load(), "CAS should have no effect")
|
||||
require.True(t, atom.CAS(true, false), "CAS should swap when old matches")
|
||||
require.False(t, atom.Load(), "CAS should have modified the value")
|
||||
require.False(t, atom.CAS(true, false), "CAS should fail on old mismatch")
|
||||
require.False(t, atom.Load(), "CAS should not have modified the value")
|
||||
|
||||
atom.Store(false)
|
||||
require.False(t, atom.Load(), "Unexpected state after store.")
|
||||
|
||||
prev := atom.Swap(false)
|
||||
require.False(t, prev, "Expected Swap to return previous value.")
|
||||
|
||||
prev = atom.Swap(true)
|
||||
require.False(t, prev, "Expected Swap to return previous value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
atom.Store(true)
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("true"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("false"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.False(t, atom.Load(), "json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("42"), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
t.Run("true", func(t *testing.T) {
|
||||
assert.Equal(t, "true", NewBool(true).String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
|
||||
t.Run("false", func(t *testing.T) {
|
||||
var b Bool
|
||||
assert.Equal(t, "false", b.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBool_InitializeDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg string
|
||||
newBool func() *Bool
|
||||
}{
|
||||
{
|
||||
msg: "Uninitialized",
|
||||
newBool: func() *Bool {
|
||||
var b Bool
|
||||
return &b
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "NewBool with default",
|
||||
newBool: func() *Bool {
|
||||
return NewBool(false)
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "Bool swapped with default",
|
||||
newBool: func() *Bool {
|
||||
b := NewBool(true)
|
||||
b.Swap(false)
|
||||
return b
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "Bool CAS'd with default",
|
||||
newBool: func() *Bool {
|
||||
b := NewBool(true)
|
||||
b.CompareAndSwap(true, false)
|
||||
return b
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.msg, func(t *testing.T) {
|
||||
t.Run("Marshal", func(t *testing.T) {
|
||||
b := tt.newBool()
|
||||
marshalled, err := b.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "false", string(marshalled))
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
b := tt.newBool()
|
||||
assert.Equal(t, "false", b.String())
|
||||
})
|
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) {
|
||||
b := tt.newBool()
|
||||
require.True(t, b.CompareAndSwap(false, true))
|
||||
assert.Equal(t, true, b.Load())
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
b := tt.newBool()
|
||||
assert.Equal(t, false, b.Swap(true))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
23
atomic/doc.go
Normal file
23
atomic/doc.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package atomic provides simple wrappers around numerics to enforce atomic
|
||||
// access.
|
||||
package atomic
|
||||
89
atomic/duration.go
Normal file
89
atomic/duration.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration is an atomic type-safe wrapper for time.Duration values.
|
||||
type Duration struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Int64
|
||||
}
|
||||
|
||||
var _zeroDuration time.Duration
|
||||
|
||||
// NewDuration creates a new Duration.
|
||||
func NewDuration(val time.Duration) *Duration {
|
||||
x := &Duration{}
|
||||
if val != _zeroDuration {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped time.Duration.
|
||||
func (x *Duration) Load() time.Duration {
|
||||
return time.Duration(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed time.Duration.
|
||||
func (x *Duration) Store(val time.Duration) {
|
||||
x.v.Store(int64(val))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for time.Duration values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Duration) CAS(old, new time.Duration) (swapped bool) {
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for time.Duration values.
|
||||
func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) {
|
||||
return x.v.CompareAndSwap(int64(old), int64(new))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given time.Duration and returns the old
|
||||
// value.
|
||||
func (x *Duration) Swap(val time.Duration) (old time.Duration) {
|
||||
return time.Duration(x.v.Swap(int64(val)))
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped time.Duration into JSON.
|
||||
func (x *Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a time.Duration from JSON.
|
||||
func (x *Duration) UnmarshalJSON(b []byte) error {
|
||||
var v time.Duration
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
x.Store(v)
|
||||
return nil
|
||||
}
|
||||
40
atomic/duration_ext.go
Normal file
40
atomic/duration_ext.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import "time"
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
|
||||
|
||||
// Add atomically adds to the wrapped time.Duration and returns the new value.
|
||||
func (x *Duration) Add(delta time.Duration) time.Duration {
|
||||
return time.Duration(x.v.Add(int64(delta)))
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
|
||||
func (x *Duration) Sub(delta time.Duration) time.Duration {
|
||||
return time.Duration(x.v.Sub(int64(delta)))
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (x *Duration) String() string {
|
||||
return x.Load().String()
|
||||
}
|
||||
73
atomic/duration_test.go
Normal file
73
atomic/duration_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
atom := NewDuration(5 * time.Minute)
|
||||
|
||||
require.Equal(t, 5*time.Minute, atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, 6*time.Minute, atom.Add(time.Minute), "Add didn't work.")
|
||||
require.Equal(t, 4*time.Minute, atom.Sub(2*time.Minute), "Sub didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(4*time.Minute, time.Minute), "CAS didn't report a swap.")
|
||||
require.Equal(t, time.Minute, atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, time.Minute, atom.Swap(2*time.Minute), "Swap didn't return the old value.")
|
||||
require.Equal(t, 2*time.Minute, atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(10 * time.Minute)
|
||||
require.Equal(t, 10*time.Minute, atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
atom.Store(time.Second)
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("1000000000"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("1000000000"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, time.Second, atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("\"1000000000\""), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
assert.Equal(t, "42s", NewDuration(42*time.Second).String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
72
atomic/error.go
Normal file
72
atomic/error.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// Error is an atomic type-safe wrapper for error values.
|
||||
type Error struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value
|
||||
}
|
||||
|
||||
var _zeroError error
|
||||
|
||||
// NewError creates a new Error.
|
||||
func NewError(val error) *Error {
|
||||
x := &Error{}
|
||||
if val != _zeroError {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped error.
|
||||
func (x *Error) Load() error {
|
||||
return unpackError(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed error.
|
||||
func (x *Error) Store(val error) {
|
||||
x.v.Store(packError(val))
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for error values.
|
||||
func (x *Error) CompareAndSwap(old, new error) (swapped bool) {
|
||||
if x.v.CompareAndSwap(packError(old), packError(new)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if old == _zeroError {
|
||||
// If the old value is the empty value, then it's possible the
|
||||
// underlying Value hasn't been set and is nil, so retry with nil.
|
||||
return x.v.CompareAndSwap(nil, packError(new))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Swap atomically stores the given error and returns the old
|
||||
// value.
|
||||
func (x *Error) Swap(val error) (old error) {
|
||||
return unpackError(x.v.Swap(packError(val)))
|
||||
}
|
||||
39
atomic/error_ext.go
Normal file
39
atomic/error_ext.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2020-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// atomic.Value panics on nil inputs, or if the underlying type changes.
|
||||
// Stabilize by always storing a custom struct that we control.
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go
|
||||
|
||||
type packedError struct{ Value error }
|
||||
|
||||
func packError(v error) interface{} {
|
||||
return packedError{v}
|
||||
}
|
||||
|
||||
func unpackError(v interface{}) error {
|
||||
if err, ok := v.(packedError); ok {
|
||||
return err.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
136
atomic/error_test.go
Normal file
136
atomic/error_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestErrorByValue(t *testing.T) {
|
||||
err := &Error{}
|
||||
require.Nil(t, err.Load(), "Initial value shall be nil")
|
||||
}
|
||||
|
||||
func TestNewErrorWithNilArgument(t *testing.T) {
|
||||
err := NewError(nil)
|
||||
require.Nil(t, err.Load(), "Initial value shall be nil")
|
||||
}
|
||||
|
||||
func TestErrorCanStoreNil(t *testing.T) {
|
||||
err := NewError(errors.New("hello"))
|
||||
err.Store(nil)
|
||||
require.Nil(t, err.Load(), "Stored value shall be nil")
|
||||
}
|
||||
|
||||
func TestNewErrorWithError(t *testing.T) {
|
||||
err1 := errors.New("hello1")
|
||||
err2 := errors.New("hello2")
|
||||
|
||||
atom := NewError(err1)
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
atom.Store(err2)
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
}
|
||||
|
||||
func TestErrorSwap(t *testing.T) {
|
||||
err1 := errors.New("hello1")
|
||||
err2 := errors.New("hello2")
|
||||
|
||||
atom := NewError(err1)
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
old := atom.Swap(err2)
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
require.Equal(t, err1, old, "Expected old to be initial value")
|
||||
}
|
||||
|
||||
func TestErrorCompareAndSwap(t *testing.T) {
|
||||
err1 := errors.New("hello1")
|
||||
err2 := errors.New("hello2")
|
||||
|
||||
atom := NewError(err1)
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
swapped := atom.CompareAndSwap(err2, err2)
|
||||
require.False(t, swapped, "Expected swapped to be false")
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initial value")
|
||||
|
||||
swapped = atom.CompareAndSwap(err1, err2)
|
||||
require.True(t, swapped, "Expected swapped to be true")
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
}
|
||||
|
||||
func TestError_InitializeDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg string
|
||||
newError func() *Error
|
||||
}{
|
||||
{
|
||||
msg: "Uninitialized",
|
||||
newError: func() *Error {
|
||||
var e Error
|
||||
return &e
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "NewError with default",
|
||||
newError: func() *Error {
|
||||
return NewError(nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "Error swapped with default",
|
||||
newError: func() *Error {
|
||||
e := NewError(assert.AnError)
|
||||
_ = e.Swap(nil)
|
||||
return e
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "Error CAS'd with default",
|
||||
newError: func() *Error {
|
||||
e := NewError(assert.AnError)
|
||||
e.CompareAndSwap(assert.AnError, nil)
|
||||
return e
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.msg, func(t *testing.T) {
|
||||
t.Run("CompareAndSwap", func(t *testing.T) {
|
||||
e := tt.newError()
|
||||
require.True(t, e.CompareAndSwap(nil, assert.AnError))
|
||||
assert.Equal(t, assert.AnError, e.Load())
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
e := tt.newError()
|
||||
assert.Equal(t, nil, e.Swap(assert.AnError))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
43
atomic/example_test.go
Normal file
43
atomic/example_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mleku/manifold/atomic"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Uint32 is a thin wrapper around the primitive uint32 type.
|
||||
var atom atomic.Uint32
|
||||
|
||||
// The wrapper ensures that all operations are atomic.
|
||||
atom.Store(42)
|
||||
fmt.Println(atom.Inc())
|
||||
fmt.Println(atom.CompareAndSwap(43, 0))
|
||||
fmt.Println(atom.Load())
|
||||
|
||||
// Output:
|
||||
// 43
|
||||
// true
|
||||
// 0
|
||||
}
|
||||
77
atomic/float32.go
Normal file
77
atomic/float32.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Float32 is an atomic type-safe wrapper for float32 values.
|
||||
type Float32 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint32
|
||||
}
|
||||
|
||||
var _zeroFloat32 float32
|
||||
|
||||
// NewFloat32 creates a new Float32.
|
||||
func NewFloat32(val float32) *Float32 {
|
||||
x := &Float32{}
|
||||
if val != _zeroFloat32 {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped float32.
|
||||
func (x *Float32) Load() float32 {
|
||||
return math.Float32frombits(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed float32.
|
||||
func (x *Float32) Store(val float32) {
|
||||
x.v.Store(math.Float32bits(val))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given float32 and returns the old
|
||||
// value.
|
||||
func (x *Float32) Swap(val float32) (old float32) {
|
||||
return math.Float32frombits(x.v.Swap(math.Float32bits(val)))
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped float32 into JSON.
|
||||
func (x *Float32) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a float32 from JSON.
|
||||
func (x *Float32) UnmarshalJSON(b []byte) error {
|
||||
var v float32
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
x.Store(v)
|
||||
return nil
|
||||
}
|
||||
76
atomic/float32_ext.go
Normal file
76
atomic/float32_ext.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2020-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go
|
||||
|
||||
// Add atomically adds to the wrapped float32 and returns the new value.
|
||||
func (f *Float32) Add(delta float32) float32 {
|
||||
for {
|
||||
old := f.Load()
|
||||
new := old + delta
|
||||
if f.CAS(old, new) {
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped float32 and returns the new value.
|
||||
func (f *Float32) Sub(delta float32) float32 {
|
||||
return f.Add(-delta)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float32) CAS(old, new float32) (swapped bool) {
|
||||
return f.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) {
|
||||
return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new))
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (f *Float32) String() string {
|
||||
// 'g' is the behavior for floats with %v.
|
||||
return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32)
|
||||
}
|
||||
73
atomic/float32_test.go
Normal file
73
atomic/float32_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFloat32(t *testing.T) {
|
||||
atom := NewFloat32(4.2)
|
||||
|
||||
require.Equal(t, float32(4.2), atom.Load(), "Load didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.")
|
||||
require.Equal(t, float32(0.5), atom.Load(), "CAS didn't set the correct value.")
|
||||
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.")
|
||||
|
||||
atom.Store(42.0)
|
||||
require.Equal(t, float32(42.0), atom.Load(), "Store didn't set the correct value.")
|
||||
require.Equal(t, float32(42.5), atom.Add(0.5), "Add didn't work.")
|
||||
require.Equal(t, float32(42.0), atom.Sub(0.5), "Sub didn't work.")
|
||||
|
||||
require.Equal(t, float32(42.0), atom.Swap(45.0), "Swap didn't return the old value.")
|
||||
require.Equal(t, float32(45.0), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
atom.Store(42.5)
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42.5"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40.5"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, float32(40.5), atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("\"40.5\""), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
assert.Equal(t, "42.5", NewFloat32(42.5).String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
77
atomic/float64.go
Normal file
77
atomic/float64.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Float64 is an atomic type-safe wrapper for float64 values.
|
||||
type Float64 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint64
|
||||
}
|
||||
|
||||
var _zeroFloat64 float64
|
||||
|
||||
// NewFloat64 creates a new Float64.
|
||||
func NewFloat64(val float64) *Float64 {
|
||||
x := &Float64{}
|
||||
if val != _zeroFloat64 {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped float64.
|
||||
func (x *Float64) Load() float64 {
|
||||
return math.Float64frombits(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed float64.
|
||||
func (x *Float64) Store(val float64) {
|
||||
x.v.Store(math.Float64bits(val))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given float64 and returns the old
|
||||
// value.
|
||||
func (x *Float64) Swap(val float64) (old float64) {
|
||||
return math.Float64frombits(x.v.Swap(math.Float64bits(val)))
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped float64 into JSON.
|
||||
func (x *Float64) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a float64 from JSON.
|
||||
func (x *Float64) UnmarshalJSON(b []byte) error {
|
||||
var v float64
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
x.Store(v)
|
||||
return nil
|
||||
}
|
||||
76
atomic/float64_ext.go
Normal file
76
atomic/float64_ext.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2020-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -swap -json -imports math -file=float64.go
|
||||
|
||||
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Add(delta float64) float64 {
|
||||
for {
|
||||
old := f.Load()
|
||||
new := old + delta
|
||||
if f.CAS(old, new) {
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Sub(delta float64) float64 {
|
||||
return f.Add(-delta)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float64) CAS(old, new float64) (swapped bool) {
|
||||
return f.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) {
|
||||
return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new))
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (f *Float64) String() string {
|
||||
// 'g' is the behavior for floats with %v.
|
||||
return strconv.FormatFloat(f.Load(), 'g', -1, 64)
|
||||
}
|
||||
73
atomic/float64_test.go
Normal file
73
atomic/float64_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFloat64(t *testing.T) {
|
||||
atom := NewFloat64(4.2)
|
||||
|
||||
require.Equal(t, float64(4.2), atom.Load(), "Load didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.")
|
||||
require.Equal(t, float64(0.5), atom.Load(), "CAS didn't set the correct value.")
|
||||
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.")
|
||||
|
||||
atom.Store(42.0)
|
||||
require.Equal(t, float64(42.0), atom.Load(), "Store didn't set the correct value.")
|
||||
require.Equal(t, float64(42.5), atom.Add(0.5), "Add didn't work.")
|
||||
require.Equal(t, float64(42.0), atom.Sub(0.5), "Sub didn't work.")
|
||||
|
||||
require.Equal(t, float64(42.0), atom.Swap(45.0), "Swap didn't return the old value.")
|
||||
require.Equal(t, float64(45.0), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
atom.Store(42.5)
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42.5"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40.5"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, float64(40.5), atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("\"40.5\""), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
assert.Equal(t, "42.5", NewFloat64(42.5).String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
27
atomic/gen.go
Normal file
27
atomic/gen.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
|
||||
//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
|
||||
//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
|
||||
//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
|
||||
//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go
|
||||
109
atomic/int32.go
Normal file
109
atomic/int32.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Int32 is an atomic wrapper around int32.
|
||||
type Int32 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v int32
|
||||
}
|
||||
|
||||
// NewInt32 creates a new Int32.
|
||||
func NewInt32(val int32) *Int32 {
|
||||
return &Int32{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int32) Load() int32 {
|
||||
return atomic.LoadInt32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Add(delta int32) int32 {
|
||||
return atomic.AddInt32(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Sub(delta int32) int32 {
|
||||
return atomic.AddInt32(&i.v, -delta)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Inc() int32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Dec() int32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int32) CAS(old, new int32) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int32) Store(val int32) {
|
||||
atomic.StoreInt32(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||
func (i *Int32) Swap(val int32) (old int32) {
|
||||
return atomic.SwapInt32(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped int32 into JSON.
|
||||
func (i *Int32) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped int32.
|
||||
func (i *Int32) UnmarshalJSON(b []byte) error {
|
||||
var v int32
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Int32) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
82
atomic/int32_test.go
Normal file
82
atomic/int32_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
atom := NewInt32(42)
|
||||
|
||||
require.Equal(t, int32(42), atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, int32(46), atom.Add(4), "Add didn't work.")
|
||||
require.Equal(t, int32(44), atom.Sub(2), "Sub didn't work.")
|
||||
require.Equal(t, int32(45), atom.Inc(), "Inc didn't work.")
|
||||
require.Equal(t, int32(44), atom.Dec(), "Dec didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||
require.Equal(t, int32(0), atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, int32(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||
require.Equal(t, int32(1), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(42)
|
||||
require.Equal(t, int32(42), atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, int32(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"40"`), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
atom := NewInt32(math.MaxInt32)
|
||||
assert.Equal(t, "2147483647", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
|
||||
t.Run("negative", func(t *testing.T) {
|
||||
atom := NewInt32(math.MinInt32)
|
||||
assert.Equal(t, "-2147483648", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
})
|
||||
}
|
||||
109
atomic/int64.go
Normal file
109
atomic/int64.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Int64 is an atomic wrapper around int64.
|
||||
type Int64 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v int64
|
||||
}
|
||||
|
||||
// NewInt64 creates a new Int64.
|
||||
func NewInt64(val int64) *Int64 {
|
||||
return &Int64{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int64) Load() int64 {
|
||||
return atomic.LoadInt64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Add(delta int64) int64 {
|
||||
return atomic.AddInt64(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Sub(delta int64) int64 {
|
||||
return atomic.AddInt64(&i.v, -delta)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Inc() int64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Dec() int64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int64) CAS(old, new int64) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int64) Store(val int64) {
|
||||
atomic.StoreInt64(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||
func (i *Int64) Swap(val int64) (old int64) {
|
||||
return atomic.SwapInt64(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped int64 into JSON.
|
||||
func (i *Int64) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped int64.
|
||||
func (i *Int64) UnmarshalJSON(b []byte) error {
|
||||
var v int64
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Int64) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
82
atomic/int64_test.go
Normal file
82
atomic/int64_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
atom := NewInt64(42)
|
||||
|
||||
require.Equal(t, int64(42), atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, int64(46), atom.Add(4), "Add didn't work.")
|
||||
require.Equal(t, int64(44), atom.Sub(2), "Sub didn't work.")
|
||||
require.Equal(t, int64(45), atom.Inc(), "Inc didn't work.")
|
||||
require.Equal(t, int64(44), atom.Dec(), "Dec didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||
require.Equal(t, int64(0), atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, int64(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||
require.Equal(t, int64(1), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(42)
|
||||
require.Equal(t, int64(42), atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, int64(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"40"`), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
atom := NewInt64(math.MaxInt64)
|
||||
assert.Equal(t, "9223372036854775807", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
|
||||
t.Run("negative", func(t *testing.T) {
|
||||
atom := NewInt64(math.MinInt64)
|
||||
assert.Equal(t, "-9223372036854775808", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
})
|
||||
}
|
||||
123
atomic/internal/gen-atomicint/main.go
Normal file
123
atomic/internal/gen-atomicint/main.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2020-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// gen-atomicint generates an atomic wrapper around an integer type.
|
||||
//
|
||||
// gen-atomicint -name Int32 -wrapped int32 -file out.go
|
||||
//
|
||||
// The generated wrapper will use the functions in the sync/atomic package
|
||||
// named after the generated type.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/mleku/manifold/chk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
if err := run(os.Args[1:]); err != nil {
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(args []string) (err error) {
|
||||
var opts struct {
|
||||
Name string
|
||||
Wrapped string
|
||||
File string
|
||||
Unsigned bool
|
||||
}
|
||||
|
||||
flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError)
|
||||
|
||||
flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)")
|
||||
flag.StringVar(&opts.Wrapped, "wrapped", "", "name of the wrapped type (e.g. int32)")
|
||||
flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)")
|
||||
flag.BoolVar(&opts.Unsigned, "unsigned", false, "whether the type is unsigned")
|
||||
|
||||
if err = flag.Parse(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Name) == 0 || len(opts.Wrapped) == 0 {
|
||||
return errors.New("flags -name and -wrapped are required")
|
||||
}
|
||||
|
||||
var w io.Writer = os.Stdout
|
||||
if file := opts.File; len(file) > 0 {
|
||||
var f *os.File
|
||||
f, err = os.Create(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create %q: %v", file, err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
w = f
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Name string
|
||||
Wrapped string
|
||||
Unsigned bool
|
||||
ToYear int
|
||||
}{
|
||||
Name: opts.Name,
|
||||
Wrapped: opts.Wrapped,
|
||||
Unsigned: opts.Unsigned,
|
||||
ToYear: time.Now().Year(),
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
if err := _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", data); err != nil {
|
||||
return fmt.Errorf("render template: %v", err)
|
||||
}
|
||||
var bs []byte
|
||||
bs, err = format.Source(buff.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("reformat source: %v", err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(w, "// @generated Code generated by gen-atomicint.\n\n"); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if _, err = w.Write(bs); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed *.tmpl
|
||||
_tmplFS embed.FS
|
||||
|
||||
_tmpl = template.Must(template.New("atomicint").ParseFS(_tmplFS, "*.tmpl"))
|
||||
)
|
||||
117
atomic/internal/gen-atomicint/wrapper.tmpl
Normal file
117
atomic/internal/gen-atomicint/wrapper.tmpl
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2020-{{.ToYear}} Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// {{ .Name }} is an atomic wrapper around {{ .Wrapped }}.
|
||||
type {{ .Name }} struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v {{ .Wrapped }}
|
||||
}
|
||||
|
||||
// New{{ .Name }} creates a new {{ .Name }}.
|
||||
func New{{ .Name }}(val {{ .Wrapped }}) *{{ .Name }} {
|
||||
return &{{ .Name }}{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *{{ .Name }}) Load() {{ .Wrapped }} {
|
||||
return atomic.Load{{ .Name }}(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped {{ .Wrapped }} and returns the new value.
|
||||
func (i *{{ .Name }}) Add(delta {{ .Wrapped }}) {{ .Wrapped }} {
|
||||
return atomic.Add{{ .Name }}(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped {{ .Wrapped }} and returns the new value.
|
||||
func (i *{{ .Name }}) Sub(delta {{ .Wrapped }}) {{ .Wrapped }} {
|
||||
return atomic.Add{{ .Name }}(&i.v,
|
||||
{{- if .Unsigned -}}
|
||||
^(delta - 1)
|
||||
{{- else -}}
|
||||
-delta
|
||||
{{- end -}}
|
||||
)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped {{ .Wrapped }} and returns the new value.
|
||||
func (i *{{ .Name }}) Inc() {{ .Wrapped }} {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped {{ .Wrapped }} and returns the new value.
|
||||
func (i *{{ .Name }}) Dec() {{ .Wrapped }} {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *{{ .Name }}) CompareAndSwap(old, new {{ .Wrapped }}) (swapped bool) {
|
||||
return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *{{ .Name }}) Store(val {{ .Wrapped }}) {
|
||||
atomic.Store{{ .Name }}(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped {{ .Wrapped }} and returns the old value.
|
||||
func (i *{{ .Name }}) Swap(val {{ .Wrapped }}) (old {{ .Wrapped }}) {
|
||||
return atomic.Swap{{ .Name }}(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped {{ .Wrapped }} into JSON.
|
||||
func (i *{{ .Name }}) MarshalJSON() (by, er) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped {{ .Wrapped }}.
|
||||
func (i *{{ .Name }}) UnmarshalJSON(b by) er {
|
||||
var v {{ .Wrapped }}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *{{ .Name }}) String() string {
|
||||
v := i.Load()
|
||||
{{ if .Unsigned -}}
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
{{- else -}}
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
{{- end }}
|
||||
}
|
||||
209
atomic/internal/gen-atomicwrapper/main.go
Normal file
209
atomic/internal/gen-atomicwrapper/main.go
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2020-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// gen-atomicwrapper generates wrapper types around other atomic types.
|
||||
//
|
||||
// It supports plugging in functions which convert the value inside the atomic
|
||||
// type to the user-facing value. For example,
|
||||
//
|
||||
// Given, atomic.Value and the functions,
|
||||
//
|
||||
// func packString(string) enveloper{}
|
||||
// func unpackString(enveloper{}) string
|
||||
//
|
||||
// We can run the following command:
|
||||
//
|
||||
// gen-atomicwrapper -name String -wrapped Value \
|
||||
// -type string -pack fromString -unpack tostring
|
||||
//
|
||||
// This wil generate approximately,
|
||||
//
|
||||
// type String struct{ v Value }
|
||||
//
|
||||
// func (s *String) Load() string {
|
||||
// return unpackString(v.Load())
|
||||
// }
|
||||
//
|
||||
// func (s *String) Store(s string) {
|
||||
// return s.v.Store(packString(s))
|
||||
// }
|
||||
//
|
||||
// The packing/unpacking logic allows the stored value to be different from
|
||||
// the user-facing value.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/mleku/manifold/chk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
if err := run(os.Args[1:]); err != nil {
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (sl *stringList) String() string {
|
||||
return strings.Join(*sl, ",")
|
||||
}
|
||||
|
||||
func (sl *stringList) Set(s string) error {
|
||||
for _, i := range strings.Split(s, ",") {
|
||||
*sl = append(*sl, strings.TrimSpace(i))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(args []string) (err error) {
|
||||
var opts struct {
|
||||
Name string
|
||||
Wrapped string
|
||||
Type string
|
||||
|
||||
Imports stringList
|
||||
Pack, Unpack string
|
||||
|
||||
CAS bool
|
||||
CompareAndSwap bool
|
||||
Swap bool
|
||||
JSON bool
|
||||
|
||||
File string
|
||||
ToYear int
|
||||
}
|
||||
|
||||
opts.ToYear = time.Now().Year()
|
||||
|
||||
fl := flag.NewFlagSet("gen-atomicwrapper", flag.ContinueOnError)
|
||||
|
||||
// Required flags
|
||||
fl.StringVar(&opts.Name, "name", "",
|
||||
"name of the generated type (e.g. Duration)")
|
||||
fl.StringVar(&opts.Wrapped, "wrapped", "",
|
||||
"name of the wrapped atomic (e.g. Int64)")
|
||||
fl.StringVar(&opts.Type, "type", "",
|
||||
"name of the type exposed by the atomic (e.g. time.Duration)")
|
||||
|
||||
// Optional flags
|
||||
fl.Var(&opts.Imports, "imports",
|
||||
"comma separated list of imports to add")
|
||||
fl.StringVar(&opts.Pack, "pack", "",
|
||||
"function to transform values with before storage")
|
||||
fl.StringVar(&opts.Unpack, "unpack", "",
|
||||
"function to reverse packing on loading")
|
||||
fl.StringVar(&opts.File, "file", "",
|
||||
"output file path (default: stdout)")
|
||||
|
||||
// Switches for individual methods. Underlying atomics must support
|
||||
// these.
|
||||
fl.BoolVar(&opts.CAS, "cas", false,
|
||||
"generate a deprecated `CAS(old, new) bool` method; requires -pack")
|
||||
fl.BoolVar(&opts.CompareAndSwap, "compareandswap", false,
|
||||
"generate a `CompareAndSwap(old, new) bool` method; requires -pack")
|
||||
fl.BoolVar(&opts.Swap, "swap", false,
|
||||
"generate a `Swap(new) old` method; requires -pack and -unpack")
|
||||
fl.BoolVar(&opts.JSON, "json", false,
|
||||
"generate `Marshal/UnmarshJSON` methods")
|
||||
|
||||
if err = fl.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(opts.Name) == 0 ||
|
||||
len(opts.Wrapped) == 0 ||
|
||||
len(opts.Type) == 0 ||
|
||||
len(opts.Pack) == 0 ||
|
||||
len(opts.Unpack) == 0 {
|
||||
return errors.New("flags -name, -wrapped, -pack, -unpack and -type are required")
|
||||
}
|
||||
|
||||
if opts.CAS {
|
||||
opts.CompareAndSwap = true
|
||||
}
|
||||
|
||||
var w io.Writer = os.Stdout
|
||||
if file := opts.File; len(file) > 0 {
|
||||
var f *os.File
|
||||
f, err = os.Create(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create %q: %v", file, err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
w = f
|
||||
}
|
||||
|
||||
// Import encoding/json if needed.
|
||||
if opts.JSON {
|
||||
found := false
|
||||
for _, imp := range opts.Imports {
|
||||
if imp == "encoding/json" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
opts.Imports = append(opts.Imports, "encoding/json")
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(opts.Imports)
|
||||
|
||||
var buff bytes.Buffer
|
||||
if err = _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", opts); err != nil {
|
||||
return fmt.Errorf("render template: %v", err)
|
||||
}
|
||||
var bs []byte
|
||||
if bs, err = format.Source(buff.Bytes()); err != nil {
|
||||
return fmt.Errorf("reformat source: %v", err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(w, "// @generated Code generated by gen-atomicwrapper.\n\n"); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if _, err = w.Write(bs); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed *.tmpl
|
||||
_tmplFS embed.FS
|
||||
|
||||
_tmpl = template.Must(template.New("atomicwrapper").ParseFS(_tmplFS, "*.tmpl"))
|
||||
)
|
||||
120
atomic/internal/gen-atomicwrapper/wrapper.tmpl
Normal file
120
atomic/internal/gen-atomicwrapper/wrapper.tmpl
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2020-{{.ToYear}} Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
{{ with .Imports }}
|
||||
import (
|
||||
{{ range . -}}
|
||||
{{ printf "%q" . }}
|
||||
{{ end }}
|
||||
)
|
||||
{{ end }}
|
||||
|
||||
// {{ .Name }} is an atomic type-safe wrapper for {{ .Type }} values.
|
||||
type {{ .Name }} struct{
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v {{ .Wrapped }}
|
||||
}
|
||||
|
||||
var _zero{{ .Name }} {{ .Type }}
|
||||
|
||||
|
||||
// New{{ .Name }} creates a new {{ .Name }}.
|
||||
func New{{ .Name }}(val {{ .Type }}) *{{ .Name }} {
|
||||
x := &{{ .Name }}{}
|
||||
if val != _zero{{ .Name }} {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped {{ .Type }}.
|
||||
func (x *{{ .Name }}) Load() {{ .Type }} {
|
||||
{{ if .Unpack -}}
|
||||
return {{ .Unpack }}(x.v.Load())
|
||||
{{- else -}}
|
||||
if v := x.v.Load(); v != nil {
|
||||
return v.({{ .Type }})
|
||||
}
|
||||
return _zero{{ .Name }}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
// Store atomically stores the passed {{ .Type }}.
|
||||
func (x *{{ .Name }}) Store(val {{ .Type }}) {
|
||||
x.v.Store({{ .Pack }}(val))
|
||||
}
|
||||
|
||||
{{ if .CAS -}}
|
||||
// CAS is an atomic compare-and-swap for {{ .Type }} values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *{{ .Name }}) CAS(old, new {{ .Type }}) (swapped bool) {
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{ if .CompareAndSwap -}}
|
||||
// CompareAndSwap is an atomic compare-and-swap for {{ .Type }} values.
|
||||
func (x *{{ .Name }}) CompareAndSwap(old, new {{ .Type }}) (swapped bool) {
|
||||
{{ if eq .Wrapped "Value" -}}
|
||||
if x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if old == _zero{{ .Name }} {
|
||||
// If the old value is the empty value, then it's possible the
|
||||
// underlying Value hasn't been set and is nil, so retry with nil.
|
||||
return x.v.CompareAndSwap(nil, {{ .Pack }}(new))
|
||||
}
|
||||
|
||||
return false
|
||||
{{- else -}}
|
||||
return x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new))
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{ if .Swap -}}
|
||||
// Swap atomically stores the given {{ .Type }} and returns the old
|
||||
// value.
|
||||
func (x *{{ .Name }}) Swap(val {{ .Type }}) (old {{ .Type }}) {
|
||||
return {{ .Unpack }}(x.v.Swap({{ .Pack }}(val)))
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{ if .JSON -}}
|
||||
// MarshalJSON encodes the wrapped {{ .Type }} into JSON.
|
||||
func (x *{{ .Name }}) MarshalJSON() (by, er) {
|
||||
return json.Marshal(x.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a {{ .Type }} from JSON.
|
||||
func (x *{{ .Name }}) UnmarshalJSON(b by) er {
|
||||
var v {{ .Type }}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
x.Store(v)
|
||||
return nil
|
||||
}
|
||||
{{- end }}
|
||||
35
atomic/nocmp.go
Normal file
35
atomic/nocmp.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// nocmp is an uncomparable struct. Embed this inside another struct to make
|
||||
// it uncomparable.
|
||||
//
|
||||
// type Foo struct {
|
||||
// nocmp
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// This DOES NOT:
|
||||
//
|
||||
// - Disallow shallow copies of structs
|
||||
// - Disallow comparison of pointers to uncomparable structs
|
||||
type nocmp [0]func()
|
||||
164
atomic/nocmp_test.go
Normal file
164
atomic/nocmp_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNocmpComparability(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
give interface{}
|
||||
comparable bool
|
||||
}{
|
||||
{
|
||||
desc: "nocmp struct",
|
||||
give: nocmp{},
|
||||
},
|
||||
{
|
||||
desc: "struct with nocmp embedded",
|
||||
give: struct{ nocmp }{},
|
||||
},
|
||||
{
|
||||
desc: "pointer to struct with nocmp embedded",
|
||||
give: &struct{ nocmp }{},
|
||||
comparable: true,
|
||||
},
|
||||
|
||||
// All exported types must be uncomparable.
|
||||
{desc: "Bool", give: Bool{}},
|
||||
{desc: "Duration", give: Duration{}},
|
||||
{desc: "Error", give: Error{}},
|
||||
{desc: "Float64", give: Float64{}},
|
||||
{desc: "Int32", give: Int32{}},
|
||||
{desc: "Int64", give: Int64{}},
|
||||
{desc: "String", give: String{}},
|
||||
{desc: "Uint32", give: Uint32{}},
|
||||
{desc: "Uint64", give: Uint64{}},
|
||||
{desc: "Value", give: Value{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
typ := reflect.TypeOf(tt.give)
|
||||
assert.Equalf(t, tt.comparable, typ.Comparable(),
|
||||
"type %v comparablity mismatch", typ)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// nocmp must not add to the size of a struct in-memory.
|
||||
func TestNocmpSize(t *testing.T) {
|
||||
type x struct{ _ int }
|
||||
|
||||
before := reflect.TypeOf(x{}).Size()
|
||||
|
||||
type y struct {
|
||||
_ nocmp
|
||||
_ x
|
||||
}
|
||||
|
||||
after := reflect.TypeOf(y{}).Size()
|
||||
|
||||
assert.Equal(t, before, after,
|
||||
"expected nocmp to have no effect on struct size")
|
||||
}
|
||||
|
||||
// This test will fail to compile if we disallow copying of nocmp.
|
||||
//
|
||||
// We need to allow this so that users can do,
|
||||
//
|
||||
// var x atomic.Int32
|
||||
// x = atomic.NewInt32(1)
|
||||
func TestNocmpCopy(t *testing.T) {
|
||||
type foo struct{ _ nocmp }
|
||||
|
||||
t.Run("struct copy", func(t *testing.T) {
|
||||
a := foo{}
|
||||
b := a
|
||||
_ = b // unused
|
||||
})
|
||||
|
||||
t.Run("pointer copy", func(t *testing.T) {
|
||||
a := &foo{}
|
||||
b := *a
|
||||
_ = b // unused
|
||||
})
|
||||
}
|
||||
|
||||
// Fake go.mod with no dependencies.
|
||||
const _exampleGoMod = `module example.com/nocmp`
|
||||
|
||||
const _badFile = `package atomic
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Int64 struct {
|
||||
nocmp
|
||||
|
||||
v int64
|
||||
}
|
||||
|
||||
func shouldNotCompile() {
|
||||
var x, y Int64
|
||||
fmt.Println(x == y)
|
||||
}
|
||||
`
|
||||
|
||||
func TestNocmpIntegration(t *testing.T) {
|
||||
tempdir := t.TempDir()
|
||||
|
||||
nocmp, err := os.ReadFile("nocmp.go")
|
||||
require.NoError(t, err, "unable to read nocmp.go")
|
||||
|
||||
require.NoError(t,
|
||||
os.WriteFile(filepath.Join(tempdir, "go.mod"), []byte(_exampleGoMod), 0o644),
|
||||
"unable to write go.mod")
|
||||
|
||||
require.NoError(t,
|
||||
os.WriteFile(filepath.Join(tempdir, "nocmp.go"), nocmp, 0o644),
|
||||
"unable to write nocmp.go")
|
||||
|
||||
require.NoError(t,
|
||||
os.WriteFile(filepath.Join(tempdir, "bad.go"), []byte(_badFile), 0o644),
|
||||
"unable to write bad.go")
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("go", "build")
|
||||
cmd.Dir = tempdir
|
||||
// Create a minimal build environment with only HOME set so that "go
|
||||
// build" has somewhere to put the cache and other Go files in.
|
||||
cmd.Env = []string{"HOME=" + filepath.Join(tempdir, "home")}
|
||||
cmd.Stderr = &stderr
|
||||
require.Error(t, cmd.Run(), "bad.go must not compile")
|
||||
|
||||
assert.Contains(t, stderr.String(),
|
||||
"struct containing nocmp cannot be compared")
|
||||
}
|
||||
100
atomic/pointer_test.go
Normal file
100
atomic/pointer_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package atomic
|
||||
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
//
|
||||
// func TestPointer(t *testing.T) {
|
||||
// type foo struct{ v int }
|
||||
//
|
||||
// i := foo{42}
|
||||
// j := foo{0}
|
||||
// k := foo{1}
|
||||
//
|
||||
// tests := []struct {
|
||||
// desc string
|
||||
// newAtomic func() *Pointer[foo]
|
||||
// initial *foo
|
||||
// }{
|
||||
// {
|
||||
// desc: "New",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// return NewPointer(&i)
|
||||
// },
|
||||
// initial: &i,
|
||||
// },
|
||||
// {
|
||||
// desc: "New/nil",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// return NewPointer[foo](nil)
|
||||
// },
|
||||
// initial: nil,
|
||||
// },
|
||||
// {
|
||||
// desc: "zero value",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// var p Pointer[foo]
|
||||
// return &p
|
||||
// },
|
||||
// initial: nil,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.desc, func(t *testing.T) {
|
||||
// t.Run("Load", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, tt.initial, atom.Load(), "Load should report nil.")
|
||||
// })
|
||||
//
|
||||
// t.Run("Swap", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, tt.initial, atom.Swap(&k), "Swap didn't return the old value.")
|
||||
// require.Equal(t, &k, atom.Load(), "Swap didn't set the correct value.")
|
||||
// })
|
||||
//
|
||||
// t.Run("CAS", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.True(t, atom.CompareAndSwap(tt.initial, &j), "CAS didn't report a swap.")
|
||||
// require.Equal(t, &j, atom.Load(), "CAS didn't set the correct value.")
|
||||
// })
|
||||
//
|
||||
// t.Run("Store", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// atom.Store(&i)
|
||||
// require.Equal(t, &i, atom.Load(), "Store didn't set the correct value.")
|
||||
// })
|
||||
// t.Run("String", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, fmt.Sprint(tt.initial), atom.String(), "String did not return the correct value.")
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
289
atomic/stress_test.go
Normal file
289
atomic/stress_test.go
Normal file
@@ -0,0 +1,289 @@
|
||||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_parallelism = 4
|
||||
_iterations = 1000
|
||||
)
|
||||
|
||||
var _stressTests = map[string]func() func(){
|
||||
"i32/std": stressStdInt32,
|
||||
"i32": stressInt32,
|
||||
"i64/std": stressStdInt64,
|
||||
"i64": stressInt64,
|
||||
"u32/std": stressStdUint32,
|
||||
"u32": stressUint32,
|
||||
"u64/std": stressStdUint64,
|
||||
"u64": stressUint64,
|
||||
"f64": stressFloat64,
|
||||
"bool": stressBool,
|
||||
"string": stressString,
|
||||
"duration": stressDuration,
|
||||
"error": stressError,
|
||||
"time": stressTime,
|
||||
}
|
||||
|
||||
func TestStress(t *testing.T) {
|
||||
for name, ff := range _stressTests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(_parallelism))
|
||||
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(_parallelism)
|
||||
f := ff()
|
||||
for i := 0; i < _parallelism; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-start
|
||||
for j := 0; j < _iterations; j++ {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStress(b *testing.B) {
|
||||
for name, ff := range _stressTests {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
f := ff()
|
||||
|
||||
b.Run("serial", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
f()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("parallel", func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
f()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stressStdInt32() func() {
|
||||
var atom int32
|
||||
return func() {
|
||||
atomic.LoadInt32(&atom)
|
||||
atomic.AddInt32(&atom, 1)
|
||||
atomic.AddInt32(&atom, -2)
|
||||
atomic.AddInt32(&atom, 1)
|
||||
atomic.AddInt32(&atom, -1)
|
||||
atomic.CompareAndSwapInt32(&atom, 1, 0)
|
||||
atomic.SwapInt32(&atom, 5)
|
||||
atomic.StoreInt32(&atom, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressInt32() func() {
|
||||
var atom Int32
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Add(1)
|
||||
atom.Sub(2)
|
||||
atom.Inc()
|
||||
atom.Dec()
|
||||
atom.CAS(1, 0)
|
||||
atom.Swap(5)
|
||||
atom.Store(1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressStdInt64() func() {
|
||||
var atom int64
|
||||
return func() {
|
||||
atomic.LoadInt64(&atom)
|
||||
atomic.AddInt64(&atom, 1)
|
||||
atomic.AddInt64(&atom, -2)
|
||||
atomic.AddInt64(&atom, 1)
|
||||
atomic.AddInt64(&atom, -1)
|
||||
atomic.CompareAndSwapInt64(&atom, 1, 0)
|
||||
atomic.SwapInt64(&atom, 5)
|
||||
atomic.StoreInt64(&atom, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressInt64() func() {
|
||||
var atom Int64
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Add(1)
|
||||
atom.Sub(2)
|
||||
atom.Inc()
|
||||
atom.Dec()
|
||||
atom.CAS(1, 0)
|
||||
atom.Swap(5)
|
||||
atom.Store(1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressStdUint32() func() {
|
||||
var atom uint32
|
||||
return func() {
|
||||
atomic.LoadUint32(&atom)
|
||||
atomic.AddUint32(&atom, 1)
|
||||
// Adding `MaxUint32` is the same as subtracting 1
|
||||
atomic.AddUint32(&atom, math.MaxUint32-1)
|
||||
atomic.AddUint32(&atom, 1)
|
||||
atomic.AddUint32(&atom, math.MaxUint32)
|
||||
atomic.CompareAndSwapUint32(&atom, 1, 0)
|
||||
atomic.SwapUint32(&atom, 5)
|
||||
atomic.StoreUint32(&atom, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressUint32() func() {
|
||||
var atom Uint32
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Add(1)
|
||||
atom.Sub(2)
|
||||
atom.Inc()
|
||||
atom.Dec()
|
||||
atom.CAS(1, 0)
|
||||
atom.Swap(5)
|
||||
atom.Store(1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressStdUint64() func() {
|
||||
var atom uint64
|
||||
return func() {
|
||||
atomic.LoadUint64(&atom)
|
||||
atomic.AddUint64(&atom, 1)
|
||||
// Adding `MaxUint64` is the same as subtracting 1
|
||||
atomic.AddUint64(&atom, math.MaxUint64-1)
|
||||
atomic.AddUint64(&atom, 1)
|
||||
atomic.AddUint64(&atom, math.MaxUint64)
|
||||
atomic.CompareAndSwapUint64(&atom, 1, 0)
|
||||
atomic.SwapUint64(&atom, 5)
|
||||
atomic.StoreUint64(&atom, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressUint64() func() {
|
||||
var atom Uint64
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Add(1)
|
||||
atom.Sub(2)
|
||||
atom.Inc()
|
||||
atom.Dec()
|
||||
atom.CAS(1, 0)
|
||||
atom.Swap(5)
|
||||
atom.Store(1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressFloat64() func() {
|
||||
var atom Float64
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.CAS(1.0, 0.1)
|
||||
atom.Add(1.1)
|
||||
atom.Sub(0.2)
|
||||
atom.Store(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func stressBool() func() {
|
||||
var atom Bool
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Store(false)
|
||||
atom.Swap(true)
|
||||
atom.CAS(true, false)
|
||||
atom.CAS(true, false)
|
||||
atom.Load()
|
||||
atom.Toggle()
|
||||
atom.Toggle()
|
||||
}
|
||||
}
|
||||
|
||||
func stressString() func() {
|
||||
var atom String
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Store("abc")
|
||||
atom.Load()
|
||||
atom.Store("def")
|
||||
atom.Load()
|
||||
atom.Store("")
|
||||
}
|
||||
}
|
||||
|
||||
func stressDuration() func() {
|
||||
var atom = NewDuration(0)
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Add(1)
|
||||
atom.Sub(2)
|
||||
atom.CAS(1, 0)
|
||||
atom.Swap(5)
|
||||
atom.Store(1)
|
||||
}
|
||||
}
|
||||
|
||||
func stressError() func() {
|
||||
var atom = NewError(nil)
|
||||
var err1 = errors.New("err1")
|
||||
var err2 = errors.New("err2")
|
||||
return func() {
|
||||
_ = atom.Load()
|
||||
atom.Store(err1)
|
||||
_ = atom.Load()
|
||||
atom.Store(err2)
|
||||
_ = atom.Load()
|
||||
atom.Store(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func stressTime() func() {
|
||||
var atom = NewTime(time.Date(2021, 6, 17, 9, 0, 0, 0, time.UTC))
|
||||
var dayAgo = time.Date(2021, 6, 16, 9, 0, 0, 0, time.UTC)
|
||||
var weekAgo = time.Date(2021, 6, 10, 9, 0, 0, 0, time.UTC)
|
||||
return func() {
|
||||
atom.Load()
|
||||
atom.Store(dayAgo)
|
||||
atom.Load()
|
||||
atom.Store(weekAgo)
|
||||
atom.Store(time.Time{})
|
||||
}
|
||||
}
|
||||
72
atomic/string.go
Normal file
72
atomic/string.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// String is an atomic type-safe wrapper for string values.
|
||||
type String struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value
|
||||
}
|
||||
|
||||
var _zeroString string
|
||||
|
||||
// NewString creates a new String.
|
||||
func NewString(val string) *String {
|
||||
x := &String{}
|
||||
if val != _zeroString {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped string.
|
||||
func (x *String) Load() string {
|
||||
return unpackString(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed string.
|
||||
func (x *String) Store(val string) {
|
||||
x.v.Store(packString(val))
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for string values.
|
||||
func (x *String) CompareAndSwap(old, new string) (swapped bool) {
|
||||
if x.v.CompareAndSwap(packString(old), packString(new)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if old == _zeroString {
|
||||
// If the old value is the empty value, then it's possible the
|
||||
// underlying Value hasn't been set and is nil, so retry with nil.
|
||||
return x.v.CompareAndSwap(nil, packString(new))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Swap atomically stores the given string and returns the old
|
||||
// value.
|
||||
func (x *String) Swap(val string) (old string) {
|
||||
return unpackString(x.v.Swap(packString(val)))
|
||||
}
|
||||
54
atomic/string_ext.go
Normal file
54
atomic/string_ext.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped Value -pack packString -unpack unpackString -compareandswap -swap -file=string.go
|
||||
|
||||
func packString(s string) interface{} {
|
||||
return s
|
||||
}
|
||||
|
||||
func unpackString(v interface{}) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// String returns the wrapped value.
|
||||
func (s *String) String() string {
|
||||
return s.Load()
|
||||
}
|
||||
|
||||
// MarshalText encodes the wrapped string into a textual form.
|
||||
//
|
||||
// This makes it encodable as JSON, YAML, XML, and more.
|
||||
func (s *String) MarshalText() ([]byte, error) {
|
||||
return []byte(s.Load()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText decodes text and replaces the wrapped string with it.
|
||||
//
|
||||
// This makes it decodable from JSON, YAML, XML, and more.
|
||||
func (s *String) UnmarshalText(b []byte) error {
|
||||
s.Store(string(b))
|
||||
return nil
|
||||
}
|
||||
170
atomic/string_test.go
Normal file
170
atomic/string_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2016-2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStringNoInitialValue(t *testing.T) {
|
||||
atom := &String{}
|
||||
require.Equal(t, "", atom.Load(), "Initial value should be blank string")
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
atom := NewString("")
|
||||
require.Equal(t, "", atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
atom.Store("abc")
|
||||
require.Equal(t, "abc", atom.Load(), "Unexpected value after Store")
|
||||
|
||||
atom = NewString("bcd")
|
||||
require.Equal(t, "bcd", atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte(`"bcd"`), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"abc"`), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, "abc", atom.Load(), "json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("42"), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
atom = NewString("foo")
|
||||
|
||||
t.Run("XML/Marshal", func(t *testing.T) {
|
||||
bytes, err := xml.Marshal(atom)
|
||||
require.NoError(t, err, "xml.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("<String>foo</String>"), bytes,
|
||||
"xml.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("XML/Unmarshal", func(t *testing.T) {
|
||||
err := xml.Unmarshal([]byte("<String>bar</String>"), &atom)
|
||||
require.NoError(t, err, "xml.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, "bar", atom.Load(), "xml.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
atom := NewString("foo")
|
||||
assert.Equal(t, "foo", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) {
|
||||
atom := NewString("foo")
|
||||
|
||||
swapped := atom.CompareAndSwap("bar", "bar")
|
||||
require.False(t, swapped, "swapped isn't false")
|
||||
require.Equal(t, atom.Load(), "foo", "Load returned wrong value")
|
||||
|
||||
swapped = atom.CompareAndSwap("foo", "bar")
|
||||
require.True(t, swapped, "swapped isn't true")
|
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value")
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
atom := NewString("foo")
|
||||
|
||||
old := atom.Swap("bar")
|
||||
require.Equal(t, old, "foo", "Swap returned wrong value")
|
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value")
|
||||
})
|
||||
}
|
||||
|
||||
func TestString_InitializeDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg string
|
||||
newStr func() *String
|
||||
}{
|
||||
{
|
||||
msg: "Uninitialized",
|
||||
newStr: func() *String {
|
||||
var s String
|
||||
return &s
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "NewString with default",
|
||||
newStr: func() *String {
|
||||
return NewString("")
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "String swapped with default",
|
||||
newStr: func() *String {
|
||||
s := NewString("initial")
|
||||
s.Swap("")
|
||||
return s
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "String CAS'd with default",
|
||||
newStr: func() *String {
|
||||
s := NewString("initial")
|
||||
s.CompareAndSwap("initial", "")
|
||||
return s
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.msg, func(t *testing.T) {
|
||||
t.Run("MarshalText", func(t *testing.T) {
|
||||
str := tt.newStr()
|
||||
text, err := str.MarshalText()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", string(text), "")
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
str := tt.newStr()
|
||||
assert.Equal(t, "", str.String())
|
||||
})
|
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) {
|
||||
str := tt.newStr()
|
||||
require.True(t, str.CompareAndSwap("", "new"))
|
||||
assert.Equal(t, "new", str.Load())
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
str := tt.newStr()
|
||||
assert.Equal(t, "", str.Swap("new"))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
55
atomic/time.go
Normal file
55
atomic/time.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Time is an atomic type-safe wrapper for time.Time values.
|
||||
type Time struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value
|
||||
}
|
||||
|
||||
var _zeroTime time.Time
|
||||
|
||||
// NewTime creates a new Time.
|
||||
func NewTime(val time.Time) *Time {
|
||||
x := &Time{}
|
||||
if val != _zeroTime {
|
||||
x.Store(val)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped time.Time.
|
||||
func (x *Time) Load() time.Time {
|
||||
return unpackTime(x.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed time.Time.
|
||||
func (x *Time) Store(val time.Time) {
|
||||
x.v.Store(packTime(val))
|
||||
}
|
||||
36
atomic/time_ext.go
Normal file
36
atomic/time_ext.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2021 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import "time"
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go
|
||||
|
||||
func packTime(t time.Time) interface{} {
|
||||
return t
|
||||
}
|
||||
|
||||
func unpackTime(v interface{}) time.Time {
|
||||
if t, ok := v.(time.Time); ok {
|
||||
return t
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
86
atomic/time_test.go
Normal file
86
atomic/time_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2021 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
start := time.Date(2021, 6, 17, 9, 10, 0, 0, time.UTC)
|
||||
atom := NewTime(start)
|
||||
|
||||
require.Equal(t, start, atom.Load(), "Load didn't work")
|
||||
require.Equal(t, time.Time{}, NewTime(time.Time{}).Load(), "Default time value is wrong")
|
||||
}
|
||||
|
||||
func TestTimeLocation(t *testing.T) {
|
||||
// Check TZ data hasn't been lost from load/store.
|
||||
ny, err := time.LoadLocation("America/New_York")
|
||||
require.NoError(t, err, "Failed to load location")
|
||||
nyTime := NewTime(time.Date(2021, 1, 1, 0, 0, 0, 0, ny))
|
||||
|
||||
var atom Time
|
||||
atom.Store(nyTime.Load())
|
||||
|
||||
assert.Equal(t, ny, atom.Load().Location(), "Location information is wrong")
|
||||
}
|
||||
|
||||
func TestLargeTime(t *testing.T) {
|
||||
// Check "large/small" time that are beyond int64 ns
|
||||
// representation (< year 1678 or > year 2262) can be
|
||||
// correctly load/store'd.
|
||||
t.Parallel()
|
||||
|
||||
t.Run("future", func(t *testing.T) {
|
||||
future := time.Date(2262, 12, 31, 0, 0, 0, 0, time.UTC)
|
||||
atom := NewTime(future)
|
||||
dayAfterFuture := atom.Load().AddDate(0, 1, 0)
|
||||
|
||||
atom.Store(dayAfterFuture)
|
||||
assert.Equal(t, 2263, atom.Load().Year())
|
||||
})
|
||||
|
||||
t.Run("past", func(t *testing.T) {
|
||||
past := time.Date(1678, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
atom := NewTime(past)
|
||||
dayBeforePast := atom.Load().AddDate(0, -1, 0)
|
||||
|
||||
atom.Store(dayBeforePast)
|
||||
assert.Equal(t, 1677, atom.Load().Year())
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonotonic(t *testing.T) {
|
||||
before := NewTime(time.Now())
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
after := NewTime(time.Now())
|
||||
|
||||
// try loading/storing before and test monotonic clock value hasn't been lost
|
||||
bt := before.Load()
|
||||
before.Store(bt)
|
||||
d := after.Load().Sub(before.Load())
|
||||
assert.True(t, 15 <= d.Milliseconds())
|
||||
}
|
||||
30
atomic/tools/tools.go
Normal file
30
atomic/tools/tools.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2019 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
// Tools used during development.
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "honnef.co/go/tools/cmd/staticcheck"
|
||||
)
|
||||
109
atomic/uint32.go
Normal file
109
atomic/uint32.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uint32 is an atomic wrapper around uint32.
|
||||
type Uint32 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uint32
|
||||
}
|
||||
|
||||
// NewUint32 creates a new Uint32.
|
||||
func NewUint32(val uint32) *Uint32 {
|
||||
return &Uint32{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint32) Load() uint32 {
|
||||
return atomic.LoadUint32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Add(delta uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Sub(delta uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, ^(delta - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Inc() uint32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Dec() uint32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint32) CAS(old, new uint32) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint32) Store(val uint32) {
|
||||
atomic.StoreUint32(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||
func (i *Uint32) Swap(val uint32) (old uint32) {
|
||||
return atomic.SwapUint32(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped uint32 into JSON.
|
||||
func (i *Uint32) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uint32.
|
||||
func (i *Uint32) UnmarshalJSON(b []byte) error {
|
||||
var v uint32
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uint32) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
}
|
||||
77
atomic/uint32_test.go
Normal file
77
atomic/uint32_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUint32(t *testing.T) {
|
||||
atom := NewUint32(42)
|
||||
|
||||
require.Equal(t, uint32(42), atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, uint32(46), atom.Add(4), "Add didn't work.")
|
||||
require.Equal(t, uint32(44), atom.Sub(2), "Sub didn't work.")
|
||||
require.Equal(t, uint32(45), atom.Inc(), "Inc didn't work.")
|
||||
require.Equal(t, uint32(44), atom.Dec(), "Dec didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||
require.Equal(t, uint32(0), atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, uint32(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||
require.Equal(t, uint32(1), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(42)
|
||||
require.Equal(t, uint32(42), atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, uint32(40), atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"40"`), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
atom := NewUint32(math.MaxUint32)
|
||||
assert.Equal(t, "4294967295", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
109
atomic/uint64.go
Normal file
109
atomic/uint64.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uint64 is an atomic wrapper around uint64.
|
||||
type Uint64 struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uint64
|
||||
}
|
||||
|
||||
// NewUint64 creates a new Uint64.
|
||||
func NewUint64(val uint64) *Uint64 {
|
||||
return &Uint64{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint64) Load() uint64 {
|
||||
return atomic.LoadUint64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Add(delta uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Sub(delta uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, ^(delta - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Inc() uint64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Dec() uint64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint64) CAS(old, new uint64) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint64) Store(val uint64) {
|
||||
atomic.StoreUint64(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint64 and returns the old value.
|
||||
func (i *Uint64) Swap(val uint64) (old uint64) {
|
||||
return atomic.SwapUint64(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped uint64 into JSON.
|
||||
func (i *Uint64) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uint64.
|
||||
func (i *Uint64) UnmarshalJSON(b []byte) error {
|
||||
var v uint64
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uint64) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
}
|
||||
77
atomic/uint64_test.go
Normal file
77
atomic/uint64_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
atom := NewUint64(42)
|
||||
|
||||
require.Equal(t, uint64(42), atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, uint64(46), atom.Add(4), "Add didn't work.")
|
||||
require.Equal(t, uint64(44), atom.Sub(2), "Sub didn't work.")
|
||||
require.Equal(t, uint64(45), atom.Inc(), "Inc didn't work.")
|
||||
require.Equal(t, uint64(44), atom.Dec(), "Dec didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||
require.Equal(t, uint64(0), atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, uint64(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||
require.Equal(t, uint64(1), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(42)
|
||||
require.Equal(t, uint64(42), atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, uint64(40), atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"40"`), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
atom := NewUint64(math.MaxUint64)
|
||||
assert.Equal(t, "18446744073709551615", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
109
atomic/uintptr.go
Normal file
109
atomic/uintptr.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Uintptr is an atomic wrapper around uintptr.
|
||||
type Uintptr struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uintptr
|
||||
}
|
||||
|
||||
// NewUintptr creates a new Uintptr.
|
||||
func NewUintptr(val uintptr) *Uintptr {
|
||||
return &Uintptr{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uintptr) Load() uintptr {
|
||||
return atomic.LoadUintptr(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Add(delta uintptr) uintptr {
|
||||
return atomic.AddUintptr(&i.v, delta)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Sub(delta uintptr) uintptr {
|
||||
return atomic.AddUintptr(&i.v, ^(delta - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Inc() uintptr {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Dec() uintptr {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uintptr) CAS(old, new uintptr) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) {
|
||||
return atomic.CompareAndSwapUintptr(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uintptr) Store(val uintptr) {
|
||||
atomic.StoreUintptr(&i.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uintptr and returns the old value.
|
||||
func (i *Uintptr) Swap(val uintptr) (old uintptr) {
|
||||
return atomic.SwapUintptr(&i.v, val)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes the wrapped uintptr into JSON.
|
||||
func (i *Uintptr) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uintptr.
|
||||
func (i *Uintptr) UnmarshalJSON(b []byte) error {
|
||||
var v uintptr
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uintptr) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
}
|
||||
80
atomic/uintptr_test.go
Normal file
80
atomic/uintptr_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2021 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUintptr(t *testing.T) {
|
||||
atom := NewUintptr(42)
|
||||
|
||||
require.Equal(t, uintptr(42), atom.Load(), "Load didn't work.")
|
||||
require.Equal(t, uintptr(46), atom.Add(4), "Add didn't work.")
|
||||
require.Equal(t, uintptr(44), atom.Sub(2), "Sub didn't work.")
|
||||
require.Equal(t, uintptr(45), atom.Inc(), "Inc didn't work.")
|
||||
require.Equal(t, uintptr(44), atom.Dec(), "Dec didn't work.")
|
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
|
||||
require.Equal(t, uintptr(0), atom.Load(), "CAS didn't set the correct value.")
|
||||
|
||||
require.Equal(t, uintptr(0), atom.Swap(1), "Swap didn't return the old value.")
|
||||
require.Equal(t, uintptr(1), atom.Load(), "Swap didn't set the correct value.")
|
||||
|
||||
atom.Store(42)
|
||||
require.Equal(t, uintptr(42), atom.Load(), "Store didn't set the correct value.")
|
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) {
|
||||
bytes, err := json.Marshal(atom)
|
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.")
|
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte("40"), &atom)
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
|
||||
require.Equal(t, uintptr(40), atom.Load(),
|
||||
"json.Unmarshal didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(`"40"`), &atom)
|
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.")
|
||||
assertErrorJSONUnmarshalType(t, err,
|
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
// Use an int variable, as constants cause compile-time overflows.
|
||||
negative := -1
|
||||
atom := NewUintptr(uintptr(negative))
|
||||
want := fmt.Sprint(uintptr(negative))
|
||||
assert.Equal(t, want, atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
}
|
||||
65
atomic/unsafe_pointer.go
Normal file
65
atomic/unsafe_pointer.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2021-2022 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// UnsafePointer is an atomic wrapper around unsafe.Pointer.
|
||||
type UnsafePointer struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewUnsafePointer creates a new UnsafePointer.
|
||||
func NewUnsafePointer(val unsafe.Pointer) *UnsafePointer {
|
||||
return &UnsafePointer{v: val}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (p *UnsafePointer) Load() unsafe.Pointer {
|
||||
return atomic.LoadPointer(&p.v)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (p *UnsafePointer) Store(val unsafe.Pointer) {
|
||||
atomic.StorePointer(&p.v, val)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value.
|
||||
func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) {
|
||||
return atomic.SwapPointer(&p.v, val)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) {
|
||||
return p.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) {
|
||||
return atomic.CompareAndSwapPointer(&p.v, old, new)
|
||||
}
|
||||
83
atomic/unsafe_pointer_test.go
Normal file
83
atomic/unsafe_pointer_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2021 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnsafePointer(t *testing.T) {
|
||||
i := int64(42)
|
||||
j := int64(0)
|
||||
k := int64(1)
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
newAtomic func() *UnsafePointer
|
||||
initial unsafe.Pointer
|
||||
}{
|
||||
{
|
||||
desc: "non-empty",
|
||||
newAtomic: func() *UnsafePointer {
|
||||
return NewUnsafePointer(unsafe.Pointer(&i))
|
||||
},
|
||||
initial: unsafe.Pointer(&i),
|
||||
},
|
||||
{
|
||||
desc: "nil",
|
||||
newAtomic: func() *UnsafePointer {
|
||||
var p UnsafePointer
|
||||
return &p
|
||||
},
|
||||
initial: unsafe.Pointer(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
t.Run("Load", func(t *testing.T) {
|
||||
atom := tt.newAtomic()
|
||||
require.Equal(t, tt.initial, atom.Load(), "Load should report nil.")
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
atom := tt.newAtomic()
|
||||
require.Equal(t, tt.initial, atom.Swap(unsafe.Pointer(&k)), "Swap didn't return the old value.")
|
||||
require.Equal(t, unsafe.Pointer(&k), atom.Load(), "Swap didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("CAS", func(t *testing.T) {
|
||||
atom := tt.newAtomic()
|
||||
require.True(t, atom.CAS(tt.initial, unsafe.Pointer(&j)), "CAS didn't report a swap.")
|
||||
require.Equal(t, unsafe.Pointer(&j), atom.Load(), "CAS didn't set the correct value.")
|
||||
})
|
||||
|
||||
t.Run("Store", func(t *testing.T) {
|
||||
atom := tt.newAtomic()
|
||||
atom.Store(unsafe.Pointer(&i))
|
||||
require.Equal(t, unsafe.Pointer(&i), atom.Load(), "Store didn't set the correct value.")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
31
atomic/value.go
Normal file
31
atomic/value.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// Value shadows the type of the same name from sync/atomic
|
||||
// https://godoc.org/sync/atomic#Value
|
||||
type Value struct {
|
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
atomic.Value
|
||||
}
|
||||
40
atomic/value_test.go
Normal file
40
atomic/value_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
var v Value
|
||||
assert.Nil(t, v.Load(), "initial Value is not nil")
|
||||
|
||||
v.Store(42)
|
||||
assert.Equal(t, 42, v.Load())
|
||||
|
||||
v.Store(84)
|
||||
assert.Equal(t, 84, v.Load())
|
||||
|
||||
assert.Panics(t, func() { v.Store("foo") })
|
||||
}
|
||||
13
chk/chk.go
Normal file
13
chk/chk.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Package chk is a convenience shortcut to use shorter names to access the lol.Logger.
|
||||
package chk
|
||||
|
||||
import (
|
||||
"github.com/mleku/manifold/lol"
|
||||
)
|
||||
|
||||
var F, E, W, I, D, T lol.Chk
|
||||
|
||||
func init() {
|
||||
F, E, W, I, D, T = lol.Main.Check.F, lol.Main.Check.E, lol.Main.Check.W, lol.Main.Check.I,
|
||||
lol.Main.Check.D, lol.Main.Check.T
|
||||
}
|
||||
17
ec/LICENSE
Normal file
17
ec/LICENSE
Normal file
@@ -0,0 +1,17 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2015-2020 The Decred developers
|
||||
Copyright (c) 2017 The Lightning Network Developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
39
ec/README.md
Normal file
39
ec/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
realy.lol/pkg/ec
|
||||
=====
|
||||
|
||||
This is a full drop-in replacement for
|
||||
[github.com/btcsuite/btcd/btcec](https://github.com/btcsuite/btcd/tree/master/btcec)
|
||||
eliminating the import from the Decred repository, and including the chainhash
|
||||
helper functions, needed for hashing messages for signatures.
|
||||
|
||||
The decred specific tests also have been removed, as well as all tests that use
|
||||
blake256 hashes as these are irrelevant to bitcoin and nostr. Some of them
|
||||
remain present, commented out, in case it is worth regenerating the vectors
|
||||
based on sha256 hashes, but on first blush it seems unlikely to be any benefit.
|
||||
|
||||
This includes the old style compact secp256k1 ECDSA signatures, that recover the
|
||||
public key rather than take a key as a parameter as used in Bitcoin
|
||||
transactions, the new style Schnorr signatures, and the Musig2 implementation.
|
||||
|
||||
BIP 340 Schnorr signatures are implemented including the variable length
|
||||
message signing with the extra test vectors present and passing.
|
||||
|
||||
The remainder of this document is from the original README.md.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
Package `ec` implements elliptic curve cryptography needed for working with
|
||||
Bitcoin. It is designed so that it may be used with the standard
|
||||
crypto/ecdsa packages provided with Go.
|
||||
|
||||
A comprehensive suite of test is provided to ensure proper functionality.
|
||||
|
||||
Package btcec was originally based on work from ThePiachu which is licensed
|
||||
underthe same terms as Go, but it has signficantly diverged since then. The
|
||||
btcsuite developers original is licensed under the liberal ISC license.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get mleku.dev/pkg/ec@latest
|
||||
```
|
||||
14
ec/base58/LICENSE
Normal file
14
ec/base58/LICENSE
Normal file
@@ -0,0 +1,14 @@
|
||||
Copyright © 2004-2011 []byte Internet Systems Consortium, Inc. ("ISC")
|
||||
Copyright © 1995-2003 []byte Internet Software Consortium
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
||||
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||||
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||
SOFTWARE.
|
||||
15
ec/base58/README.md
Normal file
15
ec/base58/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
base58
|
||||
==========
|
||||
|
||||
[](http://copyfree.org)
|
||||
|
||||
Package base58 provides an API for encoding and decoding to and from the
|
||||
modified base58 encoding. It also provides an API to do Base58Check encoding,
|
||||
as described [here](https://en.bitcoin.it/wiki/Base58Check_encoding).
|
||||
|
||||
A comprehensive suite of tests is provided to ensure proper functionality.
|
||||
|
||||
## License
|
||||
|
||||
Package base58 is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
49
ec/base58/alphabet.go
Normal file
49
ec/base58/alphabet.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// AUTOGENERATED by genalphabet.go; do not edit.
|
||||
|
||||
package base58
|
||||
|
||||
const (
|
||||
// Ciphers is the modified base58 Ciphers used by Bitcoin.
|
||||
Ciphers = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
alphabetIdx0 = '1'
|
||||
)
|
||||
|
||||
var b58 = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 255, 255, 255, 255, 255, 255,
|
||||
255, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 255, 17, 18, 19, 20, 21, 255,
|
||||
22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 255, 255, 255, 255, 255,
|
||||
255, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 255, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
142
ec/base58/base58.go
Normal file
142
ec/base58/base58.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
//go:generate go run genalphabet.go
|
||||
|
||||
var bigRadix = [...]*big.Int{
|
||||
big.NewInt(0),
|
||||
big.NewInt(58),
|
||||
big.NewInt(58 * 58),
|
||||
big.NewInt(58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
|
||||
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
|
||||
bigRadix10,
|
||||
}
|
||||
|
||||
var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
|
||||
|
||||
// Decode decodes a modified base58 string to a byte slice.
|
||||
func Decode(b string) []byte {
|
||||
answer := big.NewInt(0)
|
||||
scratch := new(big.Int)
|
||||
|
||||
// Calculating with big.Int is slow for each iteration.
|
||||
// x += b58[b[i]] * j
|
||||
// j *= 58
|
||||
//
|
||||
// Instead we can try to do as much calculations on int64.
|
||||
// We can represent a 10 digit base58 number using an int64.
|
||||
//
|
||||
// Hence we'll try to convert 10, base58 digits at a time.
|
||||
// The rough idea is to calculate `t`, such that:
|
||||
//
|
||||
// t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0
|
||||
// x *= 58^10
|
||||
// x += t
|
||||
//
|
||||
// Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10.
|
||||
// In that case we'll use the bigRadix[n] lookup for the appropriate power.
|
||||
for t := b; len(t) > 0; {
|
||||
n := len(t)
|
||||
if n > 10 {
|
||||
n = 10
|
||||
}
|
||||
|
||||
total := uint64(0)
|
||||
for _, v := range t[:n] {
|
||||
if v > 255 {
|
||||
return []byte("")
|
||||
}
|
||||
|
||||
tmp := b58[v]
|
||||
if tmp == 255 {
|
||||
return []byte("")
|
||||
}
|
||||
total = total*58 + uint64(tmp)
|
||||
}
|
||||
|
||||
answer.Mul(answer, bigRadix[n])
|
||||
scratch.SetUint64(total)
|
||||
answer.Add(answer, scratch)
|
||||
|
||||
t = t[n:]
|
||||
}
|
||||
|
||||
tmpval := answer.Bytes()
|
||||
|
||||
var numZeros int
|
||||
for numZeros = 0; numZeros < len(b); numZeros++ {
|
||||
if b[numZeros] != alphabetIdx0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
flen := numZeros + len(tmpval)
|
||||
val := make([]byte, flen)
|
||||
copy(val[numZeros:], tmpval)
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// Encode encodes a byte slice to a modified base58 string.
|
||||
func Encode(b []byte) string {
|
||||
x := new(big.Int)
|
||||
x.SetBytes(b)
|
||||
|
||||
// maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
|
||||
maxlen := int(float64(len(b))*1.365658237309761) + 1
|
||||
answer := make([]byte, 0, maxlen)
|
||||
mod := new(big.Int)
|
||||
for x.Sign() > 0 {
|
||||
// Calculating with big.Int is slow for each iteration.
|
||||
// x, mod = x / 58, x % 58
|
||||
//
|
||||
// Instead we can try to do as much calculations on int64.
|
||||
// x, mod = x / 58^10, x % 58^10
|
||||
//
|
||||
// Which will give us mod, which is 10 digit base58 number.
|
||||
// We'll loop that 10 times to convert to the answer.
|
||||
|
||||
x.DivMod(x, bigRadix10, mod)
|
||||
if x.Sign() == 0 {
|
||||
// When x = 0, we need to ensure we don't add any extra zeros.
|
||||
m := mod.Int64()
|
||||
for m > 0 {
|
||||
answer = append(answer, Ciphers[m%58])
|
||||
m /= 58
|
||||
}
|
||||
} else {
|
||||
m := mod.Int64()
|
||||
for i := 0; i < 10; i++ {
|
||||
answer = append(answer, Ciphers[m%58])
|
||||
m /= 58
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// leading zero bytes
|
||||
for _, i := range b {
|
||||
if i != 0 {
|
||||
break
|
||||
}
|
||||
answer = append(answer, alphabetIdx0)
|
||||
}
|
||||
|
||||
// reverse
|
||||
alen := len(answer)
|
||||
for i := 0; i < alen/2; i++ {
|
||||
answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
|
||||
}
|
||||
|
||||
return string(answer)
|
||||
}
|
||||
108
ec/base58/base58_test.go
Normal file
108
ec/base58/base58_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/mleku/manifold/ec/base58"
|
||||
)
|
||||
|
||||
var stringTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", ""},
|
||||
{" ", "Z"},
|
||||
{"-", "n"},
|
||||
{"0", "q"},
|
||||
{"1", "r"},
|
||||
{"-1", "4SU"},
|
||||
{"11", "4k8"},
|
||||
{"abc", "ZiCa"},
|
||||
{"1234598760", "3mJr7AoUXx2Wqd"},
|
||||
{"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"},
|
||||
{"00000000000000000000000000000000000000000000000000000000000000",
|
||||
"3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"},
|
||||
}
|
||||
|
||||
var invalidStringTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"0", ""},
|
||||
{"O", ""},
|
||||
{"I", ""},
|
||||
{"l", ""},
|
||||
{"3mJr0", ""},
|
||||
{"O3yxU", ""},
|
||||
{"3sNI", ""},
|
||||
{"4kl8", ""},
|
||||
{"0OIl", ""},
|
||||
{"!@#$%^&*()-_=+~`", ""},
|
||||
{"abcd\xd80", ""},
|
||||
{"abcd\U000020BF", ""},
|
||||
}
|
||||
|
||||
var hexTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", ""},
|
||||
{"61", "2g"},
|
||||
{"626262", "a3gV"},
|
||||
{"636363", "aPEr"},
|
||||
{"73696d706c792061206c6f6e6720737472696e67",
|
||||
"2cFupjhnEsSn59qHXstmK2ffpLv2"},
|
||||
{"00eb15231dfceb60925886b67d065299925915aeb172c06647",
|
||||
"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"},
|
||||
{"516b6fcd0f", "ABnLTmg"},
|
||||
{"bf4f89001e670274dd", "3SEo3LWLoPntC"},
|
||||
{"572e4794", "3EFU7m"},
|
||||
{"ecac89cad93923c02321", "EJDM8drfXA6uyA"},
|
||||
{"10c8511e", "Rt5zm"},
|
||||
{"00000000000000000000", "1111111111"},
|
||||
{"000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5",
|
||||
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"},
|
||||
{"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
||||
"1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"},
|
||||
}
|
||||
|
||||
func TestBase58(t *testing.T) {
|
||||
// Encode tests
|
||||
for x, test := range stringTests {
|
||||
tmp := []byte(test.in)
|
||||
if res := base58.Encode(tmp); res != test.out {
|
||||
t.Errorf("Encode test #%d failed: got: %s want: %s",
|
||||
x, res, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode tests
|
||||
for x, test := range hexTests {
|
||||
b, err := hex.DecodeString(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in)
|
||||
continue
|
||||
}
|
||||
if res := base58.Decode(test.out); !bytes.Equal(res, b) {
|
||||
t.Errorf("Decode test #%d failed: got: %q want: %q",
|
||||
x, res, test.in)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode with invalid input
|
||||
for x, test := range invalidStringTests {
|
||||
if res := base58.Decode(test.in); string(res) != test.out {
|
||||
t.Errorf("Decode invalidString test #%d failed: got: %q want: %q",
|
||||
x, res, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
47
ec/base58/base58bench_test.go
Normal file
47
ec/base58/base58bench_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/mleku/manifold/ec/base58"
|
||||
)
|
||||
|
||||
var (
|
||||
raw5k = bytes.Repeat([]byte{0xff}, 5000)
|
||||
raw100k = bytes.Repeat([]byte{0xff}, 100*1000)
|
||||
encoded5k = base58.Encode(raw5k)
|
||||
encoded100k = base58.Encode(raw100k)
|
||||
)
|
||||
|
||||
func BenchmarkBase58Encode_5K(b *testing.B) {
|
||||
b.SetBytes(int64(len(raw5k)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Encode(raw5k)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBase58Encode_100K(b *testing.B) {
|
||||
b.SetBytes(int64(len(raw100k)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Encode(raw100k)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBase58Decode_5K(b *testing.B) {
|
||||
b.SetBytes(int64(len(encoded5k)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Decode(encoded5k)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBase58Decode_100K(b *testing.B) {
|
||||
b.SetBytes(int64(len(encoded100k)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
base58.Decode(encoded100k)
|
||||
}
|
||||
}
|
||||
53
ec/base58/base58check.go
Normal file
53
ec/base58/base58check.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
|
||||
// the checksum.
|
||||
var ErrChecksum = errors.New("checksum error")
|
||||
|
||||
// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
|
||||
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
|
||||
|
||||
// checksum: first four bytes of sha256^2
|
||||
func checksum(input []byte) (cksum [4]byte) {
|
||||
h := sha256.Sum256(input)
|
||||
h2 := sha256.Sum256(h[:])
|
||||
copy(cksum[:], h2[:4])
|
||||
return
|
||||
}
|
||||
|
||||
// CheckEncode prepends a version byte and appends a four byte checksum.
|
||||
func CheckEncode(input []byte, version byte) string {
|
||||
b := make([]byte, 0, 1+len(input)+4)
|
||||
b = append(b, version)
|
||||
b = append(b, input...)
|
||||
cksum := checksum(b)
|
||||
b = append(b, cksum[:]...)
|
||||
return Encode(b)
|
||||
}
|
||||
|
||||
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
|
||||
func CheckDecode(input string) (result []byte, version byte, err error) {
|
||||
decoded := Decode(input)
|
||||
if len(decoded) < 5 {
|
||||
return nil, 0, ErrInvalidFormat
|
||||
}
|
||||
version = decoded[0]
|
||||
var cksum [4]byte
|
||||
copy(cksum[:], decoded[len(decoded)-4:])
|
||||
if checksum(decoded[:len(decoded)-4]) != cksum {
|
||||
return nil, 0, ErrChecksum
|
||||
}
|
||||
payload := decoded[1 : len(decoded)-4]
|
||||
result = append(result, payload...)
|
||||
return
|
||||
}
|
||||
75
ec/base58/base58check_test.go
Normal file
75
ec/base58/base58check_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mleku/manifold/ec/base58"
|
||||
)
|
||||
|
||||
var checkEncodingStringTests = []struct {
|
||||
version byte
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{20, "", "3MNQE1X"},
|
||||
{20, " ", "B2Kr6dBE"},
|
||||
{20, "-", "B3jv1Aft"},
|
||||
{20, "0", "B482yuaX"},
|
||||
{20, "1", "B4CmeGAC"},
|
||||
{20, "-1", "mM7eUf6kB"},
|
||||
{20, "11", "mP7BMTDVH"},
|
||||
{20, "abc", "4QiVtDjUdeq"},
|
||||
{20, "1234598760", "ZmNb8uQn5zvnUohNCEPP"},
|
||||
{20, "abcdefghijklmnopqrstuvwxyz",
|
||||
"K2RYDcKfupxwXdWhSAxQPCeiULntKm63UXyx5MvEH2"},
|
||||
{20, "00000000000000000000000000000000000000000000000000000000000000",
|
||||
"bi1EWXwJay2udZVxLJozuTb8Meg4W9c6xnmJaRDjg6pri5MBAxb9XwrpQXbtnqEoRV5U2pixnFfwyXC8tRAVC8XxnjK"},
|
||||
}
|
||||
|
||||
func TestBase58Check(t *testing.T) {
|
||||
for x, test := range checkEncodingStringTests {
|
||||
// test encoding
|
||||
if res := base58.CheckEncode([]byte(test.in),
|
||||
test.version); res != test.out {
|
||||
t.Errorf("CheckEncode test #%d failed: got %s, want: %s", x, res,
|
||||
test.out)
|
||||
}
|
||||
|
||||
// test decoding
|
||||
res, version, err := base58.CheckDecode(test.out)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("CheckDecode test #%d failed with err: %v", x, err)
|
||||
|
||||
case version != test.version:
|
||||
t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x,
|
||||
version, test.version)
|
||||
|
||||
case string(res) != test.in:
|
||||
t.Errorf("CheckDecode test #%d failed: got: %s want: %s", x, res,
|
||||
test.in)
|
||||
}
|
||||
}
|
||||
|
||||
// test the two decoding failure cases
|
||||
// case 1: checksum error
|
||||
_, _, err := base58.CheckDecode("3MNQE1Y")
|
||||
if err != base58.ErrChecksum {
|
||||
t.Error("Checkdecode test failed, expected ErrChecksum")
|
||||
}
|
||||
// case 2: invalid formats (string lengths below 5 mean the version byte and/or the checksum
|
||||
// bytes are missing).
|
||||
testString := ""
|
||||
for len := 0; len < 4; len++ {
|
||||
testString += "x"
|
||||
_, _, err = base58.CheckDecode(testString)
|
||||
if err != base58.ErrInvalidFormat {
|
||||
t.Error("Checkdecode test failed, expected ErrInvalidFormat")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
ec/base58/cov_report.sh
Normal file
17
ec/base58/cov_report.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
type gocov >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
gocov test | gocov report
|
||||
29
ec/base58/doc.go
Normal file
29
ec/base58/doc.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package base58 provides an API for working with modified base58 and Base58Check
|
||||
encodings.
|
||||
|
||||
# Modified Base58 Encoding
|
||||
|
||||
Standard base58 encoding is similar to standard base64 encoding except, as the
|
||||
name implies, it uses a 58 character Ciphers which results in an alphanumeric
|
||||
string and allows some characters which are problematic for humans to be
|
||||
excluded. Due to this, there can be various base58 alphabets.
|
||||
|
||||
The modified base58 Ciphers used by Bitcoin, and hence this package, omits the
|
||||
0, O, I, and l characters that look the same in many fonts and are therefore
|
||||
hard to humans to distinguish.
|
||||
|
||||
# Base58Check Encoding Scheme
|
||||
|
||||
The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
|
||||
time of this writing, however it can be used to generically encode arbitrary
|
||||
byte arrays into human-readable strings along with a version byte that can be
|
||||
used to differentiate the same payload. For Bitcoin addresses, the extra
|
||||
version is used to differentiate the network of otherwise identical public keys
|
||||
which helps prevent using an address intended for one network on another.
|
||||
*/
|
||||
package base58
|
||||
71
ec/base58/example_test.go
Normal file
71
ec/base58/example_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mleku/manifold/ec/base58"
|
||||
)
|
||||
|
||||
// This example demonstrates how to decode modified base58 encoded data.
|
||||
func ExampleDecode() {
|
||||
// Decode example modified base58 encoded data.
|
||||
encoded := "25JnwSn7XKfNQ"
|
||||
decoded := base58.Decode(encoded)
|
||||
|
||||
// Show the decoded data.
|
||||
fmt.Println("Decoded Data:", string(decoded))
|
||||
|
||||
// Output:
|
||||
// Decoded Data: Test data
|
||||
}
|
||||
|
||||
// This example demonstrates how to encode data using the modified base58
|
||||
// encoding scheme.
|
||||
func ExampleEncode() {
|
||||
// Encode example data with the modified base58 encoding scheme.
|
||||
data := []byte("Test data")
|
||||
encoded := base58.Encode(data)
|
||||
|
||||
// Show the encoded data.
|
||||
fmt.Println("Encoded Data:", encoded)
|
||||
|
||||
// Output:
|
||||
// Encoded Data: 25JnwSn7XKfNQ
|
||||
}
|
||||
|
||||
// This example demonstrates how to decode Base58Check encoded data.
|
||||
func ExampleCheckDecode() {
|
||||
// Decode an example Base58Check encoded data.
|
||||
encoded := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
||||
decoded, version, err := base58.CheckDecode(encoded)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Show the decoded data.
|
||||
fmt.Printf("Decoded data: %x\n", decoded)
|
||||
fmt.Println("Version Byte:", version)
|
||||
|
||||
// Output:
|
||||
// Decoded data: 62e907b15cbf27d5425399ebf6f0fb50ebb88f18
|
||||
// Version Byte: 0
|
||||
}
|
||||
|
||||
// This example demonstrates how to encode data using the Base58Check encoding
|
||||
// scheme.
|
||||
func ExampleCheckEncode() {
|
||||
// Encode example data with the Base58Check encoding scheme.
|
||||
data := []byte("Test data")
|
||||
encoded := base58.CheckEncode(data, 0)
|
||||
|
||||
// Show the encoded data.
|
||||
fmt.Println("Encoded Data:", encoded)
|
||||
|
||||
// Output:
|
||||
// Encoded Data: 182iP79GRURMp7oMHDU
|
||||
}
|
||||
80
ec/base58/genalphabet.go
Normal file
80
ec/base58/genalphabet.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
start = []byte(`// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// AUTOGENERATED by genalphabet.go; do not edit.
|
||||
|
||||
package base58
|
||||
|
||||
const (
|
||||
// Ciphers is the modified base58 alphabet used by Bitcoin.
|
||||
Ciphers = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
alphabetIdx0 = '1'
|
||||
)
|
||||
|
||||
var b58 = [256]byte{`)
|
||||
|
||||
end = []byte(`}`)
|
||||
|
||||
alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
|
||||
tab = []byte("\t")
|
||||
invalid = []byte("255")
|
||||
comma = []byte(",")
|
||||
space = []byte(" ")
|
||||
nl = []byte("\n")
|
||||
)
|
||||
|
||||
func write(w io.Writer, b []byte) {
|
||||
_, err := w.Write(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
fi, err := os.Create("alphabet.go")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
write(fi, start)
|
||||
write(fi, nl)
|
||||
for i := byte(0); i < 32; i++ {
|
||||
write(fi, tab)
|
||||
for j := byte(0); j < 8; j++ {
|
||||
idx := bytes.IndexByte(alphabet, i*8+j)
|
||||
if idx == -1 {
|
||||
write(fi, invalid)
|
||||
} else {
|
||||
write(fi, strconv.AppendInt(nil, int64(idx), 10))
|
||||
}
|
||||
write(fi, comma)
|
||||
if j != 7 {
|
||||
write(fi, space)
|
||||
}
|
||||
}
|
||||
write(fi, nl)
|
||||
}
|
||||
write(fi, end)
|
||||
write(fi, nl)
|
||||
}
|
||||
28
ec/bech32/README.md
Normal file
28
ec/bech32/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
bech32
|
||||
==========
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](http://godoc.org/realy.lol/pkg/ec/bech32)
|
||||
|
||||
Package bech32 provides a Go implementation of the bech32 format specified in
|
||||
[BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki).
|
||||
|
||||
Test vectors from BIP 173 are added to ensure compatibility with the BIP.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u mleku.dev/pkg/ec/bech32
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
* [Bech32 decode Example](http://godoc.org/realy.lol/pkg/ec/bech32#example-Bech32Decode)
|
||||
Demonstrates how to decode a bech32 encoded string.
|
||||
* [Bech32 encode Example](http://godoc.org/realy.lol/pkg/ec/bech32#example-BechEncode)
|
||||
Demonstrates how to encode data into a bech32 string.
|
||||
|
||||
## License
|
||||
|
||||
Package bech32 is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
407
ec/bech32/bech32.go
Normal file
407
ec/bech32/bech32.go
Normal file
@@ -0,0 +1,407 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Copyright (c) 2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bech32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Charset is the set of characters used in the data section of bech32 strings.
|
||||
// Note that this is ordered, such that for a given charset[i], i is the binary
|
||||
// value of the character.
|
||||
//
|
||||
// This wasn't exported in the original lol.
|
||||
const Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
|
||||
// gen encodes the generator polynomial for the bech32 BCH checksum.
|
||||
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
||||
|
||||
// toBytes converts each character in the string 'chars' to the value of the
|
||||
// index of the corresponding character in 'charset'.
|
||||
func toBytes(chars []byte) ([]byte, error) {
|
||||
decoded := make([]byte, 0, len(chars))
|
||||
for i := 0; i < len(chars); i++ {
|
||||
index := strings.IndexByte(Charset, chars[i])
|
||||
if index < 0 {
|
||||
return nil, ErrNonCharsetChar(chars[i])
|
||||
}
|
||||
decoded = append(decoded, byte(index))
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// bech32Polymod calculates the BCH checksum for a given hrp, values and
|
||||
// checksum data. Checksum is optional, and if nil a 0 checksum is assumed.
|
||||
//
|
||||
// Values and checksum (if provided) MUST be encoded as 5 bits per element (base
|
||||
// 32), otherwise the results are undefined.
|
||||
//
|
||||
// For more details on the polymod calculation, please refer to BIP 173.
|
||||
func bech32Polymod(hrp []byte, values, checksum []byte) int {
|
||||
check := 1
|
||||
// Account for the high bits of the HRP in the checksum.
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
b := check >> 25
|
||||
hiBits := int(hrp[i]) >> 5
|
||||
check = (check&0x1ffffff)<<5 ^ hiBits
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Account for the separator (0) between high and low bits of the HRP.
|
||||
// x^0 == x, so we eliminate the redundant xor used in the other rounds.
|
||||
b := check >> 25
|
||||
check = (check & 0x1ffffff) << 5
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
// Account for the low bits of the HRP.
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
b := check >> 25
|
||||
loBits := int(hrp[i]) & 31
|
||||
check = (check&0x1ffffff)<<5 ^ loBits
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Account for the values.
|
||||
for _, v := range values {
|
||||
b := check >> 25
|
||||
check = (check&0x1ffffff)<<5 ^ int(v)
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if checksum == nil {
|
||||
// A nil checksum is used during encoding, so assume all bytes are zero.
|
||||
// x^0 == x, so we eliminate the redundant xor used in the other rounds.
|
||||
for v := 0; v < 6; v++ {
|
||||
b := check >> 25
|
||||
check = (check & 0x1ffffff) << 5
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Checksum is provided during decoding, so use it.
|
||||
for _, v := range checksum {
|
||||
b := check >> 25
|
||||
check = (check&0x1ffffff)<<5 ^ int(v)
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
check ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return check
|
||||
}
|
||||
|
||||
// writeBech32Checksum calculates the checksum data expected for a string that
|
||||
// will have the given hrp and payload data and writes it to the provided string
|
||||
// builder.
|
||||
//
|
||||
// The payload data MUST be encoded as a base 32 (5 bits per element) byte slice
|
||||
// and the hrp MUST only use the allowed character set (ascii chars between 33
|
||||
// and 126), otherwise the results are undefined.
|
||||
//
|
||||
// For more details on the checksum calculation, please refer to BIP 173.
|
||||
func writeBech32Checksum(hrp []byte, data []byte, bldr *bytes.Buffer,
|
||||
version Version) {
|
||||
|
||||
bech32Const := int(VersionToConsts[version])
|
||||
polymod := bech32Polymod(hrp, data, nil) ^ bech32Const
|
||||
for i := 0; i < 6; i++ {
|
||||
b := byte((polymod >> uint(5*(5-i))) & 31)
|
||||
// This can't fail, given we explicitly cap the previous b byte by the
|
||||
// first 31 bits.
|
||||
c := Charset[b]
|
||||
bldr.WriteByte(c)
|
||||
}
|
||||
}
|
||||
|
||||
// bech32VerifyChecksum verifies whether the bech32 string specified by the
|
||||
// provided hrp and payload data (encoded as 5 bits per element byte slice) has
|
||||
// the correct checksum suffix. The version of bech32 used (bech32 OG, or
|
||||
// bech32m) is also returned to allow the caller to perform proper address
|
||||
// validation (segwitv0 should use bech32, v1+ should use bech32m).
|
||||
//
|
||||
// Data MUST have more than 6 elements, otherwise this function panics.
|
||||
//
|
||||
// For more details on the checksum verification, please refer to BIP 173.
|
||||
func bech32VerifyChecksum(hrp []byte, data []byte) (Version, bool) {
|
||||
checksum := data[len(data)-6:]
|
||||
values := data[:len(data)-6]
|
||||
polymod := bech32Polymod(hrp, values, checksum)
|
||||
// Before BIP-350, we'd always check this against a static constant of
|
||||
// 1 to know if the checksum was computed properly. As we want to
|
||||
// generically support decoding for bech32m as well as bech32, we'll
|
||||
// look up the returned value and compare it to the set of defined
|
||||
// constants.
|
||||
bech32Version, ok := ConstsToVersion[ChecksumConst(polymod)]
|
||||
if ok {
|
||||
return bech32Version, true
|
||||
}
|
||||
return VersionUnknown, false
|
||||
}
|
||||
|
||||
// DecodeNoLimit is a bech32 checksum version aware arbitrary string length
|
||||
// decoder. This function will return the version of the decoded checksum
|
||||
// constant so higher level validation can be performed to ensure the correct
|
||||
// version of bech32 was used when encoding.
|
||||
func decodeNoLimit(bech []byte) ([]byte, []byte, Version, error) {
|
||||
// The minimum allowed size of a bech32 string is 8 characters, since it
|
||||
// needs a non-empty HRP, a separator, and a 6 character checksum.
|
||||
if len(bech) < 8 {
|
||||
return nil, nil, VersionUnknown, ErrInvalidLength(len(bech))
|
||||
}
|
||||
// Only ASCII characters between 33 and 126 are allowed.
|
||||
var hasLower, hasUpper bool
|
||||
for i := 0; i < len(bech); i++ {
|
||||
if bech[i] < 33 || bech[i] > 126 {
|
||||
return nil, nil, VersionUnknown, ErrInvalidCharacter(bech[i])
|
||||
}
|
||||
// The characters must be either all lowercase or all uppercase. Testing
|
||||
// directly with ascii codes is safe here, given the previous test.
|
||||
hasLower = hasLower || (bech[i] >= 97 && bech[i] <= 122)
|
||||
hasUpper = hasUpper || (bech[i] >= 65 && bech[i] <= 90)
|
||||
if hasLower && hasUpper {
|
||||
return nil, nil, VersionUnknown, ErrMixedCase{}
|
||||
}
|
||||
}
|
||||
// Bech32 standard uses only the lowercase for of strings for checksum
|
||||
// calculation.
|
||||
if hasUpper {
|
||||
bech = bytes.ToLower(bech)
|
||||
}
|
||||
// The string is invalid if the last '1' is non-existent, it is the
|
||||
// first character of the string (no human-readable part) or one of the
|
||||
// last 6 characters of the string (since checksum cannot contain '1').
|
||||
one := bytes.LastIndexByte(bech, '1')
|
||||
if one < 1 || one+7 > len(bech) {
|
||||
return nil, nil, VersionUnknown, ErrInvalidSeparatorIndex(one)
|
||||
}
|
||||
// The human-readable part is everything before the last '1'.
|
||||
hrp := bech[:one]
|
||||
data := bech[one+1:]
|
||||
// Each character corresponds to the byte with value of the index in
|
||||
// 'charset'.
|
||||
decoded, err := toBytes(data)
|
||||
if err != nil {
|
||||
return nil, nil, VersionUnknown, err
|
||||
}
|
||||
// Verify if the checksum (stored inside decoded[:]) is valid, given the
|
||||
// previously decoded hrp.
|
||||
bech32Version, ok := bech32VerifyChecksum(hrp, decoded)
|
||||
if !ok {
|
||||
// Invalid checksum. Calculate what it should have been, so that the
|
||||
// error contains this information.
|
||||
//
|
||||
// Extract the payload bytes and actual checksum in the string.
|
||||
actual := bech[len(bech)-6:]
|
||||
payload := decoded[:len(decoded)-6]
|
||||
// Calculate the expected checksum, given the hrp and payload
|
||||
// data. We'll actually compute _both_ possibly valid checksum
|
||||
// to further aide in debugging.
|
||||
var expectedBldr bytes.Buffer
|
||||
expectedBldr.Grow(6)
|
||||
writeBech32Checksum(hrp, payload, &expectedBldr, Version0)
|
||||
expectedVersion0 := expectedBldr.String()
|
||||
var b strings.Builder
|
||||
b.Grow(6)
|
||||
writeBech32Checksum(hrp, payload, &expectedBldr, VersionM)
|
||||
expectedVersionM := expectedBldr.String()
|
||||
err = ErrInvalidChecksum{
|
||||
Expected: expectedVersion0,
|
||||
ExpectedM: expectedVersionM,
|
||||
Actual: string(actual),
|
||||
}
|
||||
return nil, nil, VersionUnknown, err
|
||||
}
|
||||
// We exclude the last 6 bytes, which is the checksum.
|
||||
return hrp, decoded[:len(decoded)-6], bech32Version, nil
|
||||
}
|
||||
|
||||
// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable
|
||||
// part and the data part excluding the checksum. This function does NOT
|
||||
// validate against the BIP-173 maximum length allowed for bech32 strings and
|
||||
// is meant for use in custom applications (such as lightning network payment
|
||||
// requests), NOT on-chain addresses.
|
||||
//
|
||||
// Note that the returned data is 5-bit (base32) encoded and the human-readable
|
||||
// part will be lowercase.
|
||||
func DecodeNoLimit(bech []byte) ([]byte, []byte, error) {
|
||||
hrp, data, _, err := decodeNoLimit(bech)
|
||||
return hrp, data, err
|
||||
}
|
||||
|
||||
// Decode decodes a bech32 encoded string, returning the human-readable part and
|
||||
// the data part excluding the checksum.
|
||||
//
|
||||
// Note that the returned data is 5-bit (base32) encoded and the human-readable
|
||||
// part will be lowercase.
|
||||
func Decode(bech []byte) ([]byte, []byte, error) {
|
||||
// The maximum allowed length for a bech32 string is 90.
|
||||
if len(bech) > 90 {
|
||||
return nil, nil, ErrInvalidLength(len(bech))
|
||||
}
|
||||
hrp, data, _, err := decodeNoLimit(bech)
|
||||
return hrp, data, err
|
||||
}
|
||||
|
||||
// DecodeGeneric is identical to the existing Decode method, but will also
|
||||
// return bech32 version that matches the decoded checksum. This method should
|
||||
// be used when decoding segwit addresses, as it enables additional
|
||||
// verification to ensure the proper checksum is used.
|
||||
func DecodeGeneric(bech []byte) ([]byte, []byte, Version, error) {
|
||||
// The maximum allowed length for a bech32 string is 90.
|
||||
if len(bech) > 90 {
|
||||
return nil, nil, VersionUnknown, ErrInvalidLength(len(bech))
|
||||
}
|
||||
return decodeNoLimit(bech)
|
||||
}
|
||||
|
||||
// encodeGeneric is the base bech32 encoding function that is aware of the
|
||||
// existence of the checksum versions. This method is private, as the Encode
|
||||
// and EncodeM methods are intended to be used instead.
|
||||
func encodeGeneric(hrp []byte, data []byte, version Version) ([]byte, error) {
|
||||
// The resulting bech32 string is the concatenation of the lowercase
|
||||
// hrp, the separator 1, data and the 6-byte checksum.
|
||||
hrp = bytes.ToLower(hrp)
|
||||
var bldr bytes.Buffer
|
||||
bldr.Grow(len(hrp) + 1 + len(data) + 6)
|
||||
bldr.Write(hrp)
|
||||
bldr.WriteString("1")
|
||||
// Write the data part, using the bech32 charset.
|
||||
for _, b := range data {
|
||||
if int(b) >= len(Charset) {
|
||||
return nil, ErrInvalidDataByte(b)
|
||||
}
|
||||
bldr.WriteByte(Charset[b])
|
||||
}
|
||||
// Calculate and write the checksum of the data.
|
||||
writeBech32Checksum(hrp, data, &bldr, version)
|
||||
return bldr.Bytes(), nil
|
||||
}
|
||||
|
||||
// Encode encodes a byte slice into a bech32 string with the given
|
||||
// human-readable part (HRP). The HRP will be converted to lowercase if needed
|
||||
// since mixed cased encodings are not permitted and lowercase is used for
|
||||
// checksum purposes. Note that the bytes must each encode 5 bits (base32).
|
||||
func Encode(hrp, data []byte) ([]byte, error) {
|
||||
return encodeGeneric(hrp, data, Version0)
|
||||
}
|
||||
|
||||
// EncodeM is the exactly same as the Encode method, but it uses the new
|
||||
// bech32m constant instead of the original one. It should be used whenever one
|
||||
// attempts to encode a segwit address of v1 and beyond.
|
||||
func EncodeM(hrp, data []byte) ([]byte, error) {
|
||||
return encodeGeneric(hrp, data, VersionM)
|
||||
}
|
||||
|
||||
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
|
||||
// to a byte slice where each byte is encoding toBits bits.
|
||||
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte,
|
||||
error) {
|
||||
|
||||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
||||
return nil, ErrInvalidBitGroups{}
|
||||
}
|
||||
// Determine the maximum size the resulting array can have after base
|
||||
// conversion, so that we can size it a single time. This might be off
|
||||
// by a byte depending on whether padding is used or not and if the input
|
||||
// data is a multiple of both fromBits and toBits, but we ignore that and
|
||||
// just size it to the maximum possible.
|
||||
maxSize := len(data)*int(fromBits)/int(toBits) + 1
|
||||
// The final bytes, each byte encoding toBits bits.
|
||||
regrouped := make([]byte, 0, maxSize)
|
||||
// Keep track of the next byte we create and how many bits we have
|
||||
// added to it out of the toBits goal.
|
||||
nextByte := byte(0)
|
||||
filledBits := uint8(0)
|
||||
for _, b := range data {
|
||||
// Discard unused bits.
|
||||
b <<= 8 - fromBits
|
||||
// How many bits remaining to extract from the input data.
|
||||
remFromBits := fromBits
|
||||
for remFromBits > 0 {
|
||||
// How many bits remaining to be added to the next byte.
|
||||
remToBits := toBits - filledBits
|
||||
// The number of bytes to next extract is the minimum of
|
||||
// remFromBits and remToBits.
|
||||
toExtract := remFromBits
|
||||
if remToBits < toExtract {
|
||||
toExtract = remToBits
|
||||
}
|
||||
// Add the next bits to nextByte, shifting the already
|
||||
// added bits to the left.
|
||||
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
|
||||
// Discard the bits we just extracted and get ready for
|
||||
// next iteration.
|
||||
b <<= toExtract
|
||||
remFromBits -= toExtract
|
||||
filledBits += toExtract
|
||||
// If the nextByte is completely filled, we add it to
|
||||
// our regrouped bytes and start on the next byte.
|
||||
if filledBits == toBits {
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
// We pad any unfinished group if specified.
|
||||
if pad && filledBits > 0 {
|
||||
nextByte <<= toBits - filledBits
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
// Any incomplete group must be <= 4 bits, and all zeroes.
|
||||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
||||
return nil, ErrInvalidIncompleteGroup{}
|
||||
}
|
||||
return regrouped, nil
|
||||
}
|
||||
|
||||
// EncodeFromBase256 converts a base256-encoded byte slice into a base32-encoded
|
||||
// byte slice and then encodes it into a bech32 string with the given
|
||||
// human-readable part (HRP). The HRP will be converted to lowercase if needed
|
||||
// since mixed cased encodings are not permitted and lowercase is used for
|
||||
// checksum purposes.
|
||||
func EncodeFromBase256(hrp, data []byte) ([]byte, error) {
|
||||
converted, err := ConvertBits(data, 8, 5, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Encode(hrp, converted)
|
||||
}
|
||||
|
||||
// DecodeToBase256 decodes a bech32-encoded string into its associated
|
||||
// human-readable part (HRP) and base32-encoded data, converts that data to a
|
||||
// base256-encoded byte slice and returns it along with the lowercase HRP.
|
||||
func DecodeToBase256(bech []byte) ([]byte, []byte, error) {
|
||||
hrp, data, err := Decode(bech)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
converted, err := ConvertBits(data, 5, 8, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return hrp, converted, nil
|
||||
}
|
||||
672
ec/bech32/bech32_test.go
Normal file
672
ec/bech32/bech32_test.go
Normal file
@@ -0,0 +1,672 @@
|
||||
// Copyright (c) 2017-2020 The btcsuite developers
|
||||
// Copyright (c) 2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bech32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBech32 tests whether decoding and re-encoding the valid BIP-173 test
|
||||
// vectors works and if decoding invalid test vectors fails for the correct
|
||||
// reason.
|
||||
func TestBech32(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expectedError error
|
||||
}{
|
||||
{"A12UEL5L", nil},
|
||||
{"a12uel5l", nil},
|
||||
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
|
||||
nil},
|
||||
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil},
|
||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
|
||||
nil},
|
||||
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil},
|
||||
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w",
|
||||
ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v",
|
||||
"2y9e2w"}}, // invalid checksum
|
||||
{"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p",
|
||||
ErrInvalidCharacter(' ')}, // invalid character (space) in hrp
|
||||
{"spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
ErrInvalidCharacter(127)}, // invalid character (DEL) in hrp
|
||||
{"split1cheo2y9e2w",
|
||||
ErrNonCharsetChar('o')}, // invalid character (o) in data part
|
||||
{"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part
|
||||
{"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
ErrInvalidSeparatorIndex(0)}, // empty hrp
|
||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
|
||||
ErrInvalidLength(91)}, // too long
|
||||
// Additional test vectors used in bitcoin core
|
||||
{" 1nwldj5", ErrInvalidCharacter(' ')},
|
||||
{"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)},
|
||||
{"\x801eym55h", ErrInvalidCharacter(0x80)},
|
||||
{"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
|
||||
ErrInvalidLength(91)},
|
||||
{"pzry9x0s0muk", ErrInvalidSeparatorIndex(-1)},
|
||||
{"1pzry9x0s0muk", ErrInvalidSeparatorIndex(0)},
|
||||
{"x1b4n0q5v", ErrNonCharsetChar(98)},
|
||||
{"li1dgmt3", ErrInvalidSeparatorIndex(2)},
|
||||
{"de1lg7wt\xff", ErrInvalidCharacter(0xff)},
|
||||
{"A1G7SGD8", ErrInvalidChecksum{"2uel5l", "2uel5llqfn3a", "g7sgd8"}},
|
||||
{"10a06t8", ErrInvalidLength(7)},
|
||||
{"1qzzfhee", ErrInvalidSeparatorIndex(0)},
|
||||
{"a12UEL5L", ErrMixedCase{}},
|
||||
{"A12uEL5L", ErrMixedCase{}},
|
||||
}
|
||||
for i, test := range tests {
|
||||
str := []byte(test.str)
|
||||
hrp, decoded, err := Decode([]byte(str))
|
||||
if !errors.Is(err, test.expectedError) {
|
||||
t.Errorf("%d: expected decoding error %v "+
|
||||
"instead got %v", i, test.expectedError, err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// End test case here if a decoding error was expected.
|
||||
continue
|
||||
}
|
||||
// Check that it encodes to the same string
|
||||
encoded, err := Encode(hrp, decoded)
|
||||
if err != nil {
|
||||
t.Errorf("encoding failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(encoded, bytes.ToLower([]byte(str))) {
|
||||
t.Errorf("expected data to encode to %v, but got %v",
|
||||
str, encoded)
|
||||
}
|
||||
// Flip a bit in the string an make sure it is caught.
|
||||
pos := bytes.LastIndexAny(str, "1")
|
||||
flipped := []byte(string(str[:pos+1]) + string(str[pos+1]^1) + string(str[pos+2:]))
|
||||
_, _, err = Decode(flipped)
|
||||
if err == nil {
|
||||
t.Error("expected decoding to fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBech32M tests that the following set of strings, based on the test
|
||||
// vectors in BIP-350 are either valid or invalid using the new bech32m
|
||||
// checksum algo. Some of these strings are similar to the set of above test
|
||||
// vectors, but end up with different checksums.
|
||||
func TestBech32M(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expectedError error
|
||||
}{
|
||||
{"A1LQFN3A", nil},
|
||||
{"a1lqfn3a", nil},
|
||||
{"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
|
||||
nil},
|
||||
{"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", nil},
|
||||
{"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
|
||||
nil},
|
||||
{"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", nil},
|
||||
{"?1v759aa", nil},
|
||||
// Additional test vectors used in bitcoin core
|
||||
{"\x201xj0phk", ErrInvalidCharacter('\x20')},
|
||||
{"\x7f1g6xzxy", ErrInvalidCharacter('\x7f')},
|
||||
{"\x801vctc34", ErrInvalidCharacter('\x80')},
|
||||
{"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
|
||||
ErrInvalidLength(91)},
|
||||
{"qyrz8wqd2c9m", ErrInvalidSeparatorIndex(-1)},
|
||||
{"1qyrz8wqd2c9m", ErrInvalidSeparatorIndex(0)},
|
||||
{"y1b0jsk6g", ErrNonCharsetChar(98)},
|
||||
{"lt1igcx5c0", ErrNonCharsetChar(105)},
|
||||
{"in1muywd", ErrInvalidSeparatorIndex(2)},
|
||||
{"mm1crxm3i", ErrNonCharsetChar(105)},
|
||||
{"au1s5cgom", ErrNonCharsetChar(111)},
|
||||
{"M1VUXWEZ", ErrInvalidChecksum{"mzl49c", "mzl49cw70eq6", "vuxwez"}},
|
||||
{"16plkw9", ErrInvalidLength(7)},
|
||||
{"1p2gdwpf", ErrInvalidSeparatorIndex(0)},
|
||||
|
||||
{" 1nwldj5", ErrInvalidCharacter(' ')},
|
||||
{"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)},
|
||||
{"\x801eym55h", ErrInvalidCharacter(0x80)},
|
||||
}
|
||||
for i, test := range tests {
|
||||
str := []byte(test.str)
|
||||
hrp, decoded, err := Decode(str)
|
||||
if test.expectedError != err {
|
||||
t.Errorf("%d: (%v) expected decoding error %v "+
|
||||
"instead got %v", i, str, test.expectedError,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// End test case here if a decoding error was expected.
|
||||
continue
|
||||
}
|
||||
// Check that it encodes to the same string, using bech32 m.
|
||||
encoded, err := EncodeM(hrp, decoded)
|
||||
if err != nil {
|
||||
t.Errorf("encoding failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(encoded, bytes.ToLower(str)) {
|
||||
t.Errorf("expected data to encode to %v, but got %v",
|
||||
str, encoded)
|
||||
}
|
||||
// Flip a bit in the string an make sure it is caught.
|
||||
pos := bytes.LastIndexAny(str, "1")
|
||||
flipped := []byte(string(str[:pos+1]) + string(str[pos+1]^1) + string(str[pos+2:]))
|
||||
_, _, err = Decode(flipped)
|
||||
if err == nil {
|
||||
t.Error("expected decoding to fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBech32DecodeGeneric tests that given a bech32 string, or a bech32m
|
||||
// string, the proper checksum version is returned so that callers can perform
|
||||
// segwit addr validation.
|
||||
func TestBech32DecodeGeneric(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
version Version
|
||||
}{
|
||||
{"A1LQFN3A", VersionM},
|
||||
{"a1lqfn3a", VersionM},
|
||||
{"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
|
||||
VersionM},
|
||||
{"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", VersionM},
|
||||
{"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
|
||||
VersionM},
|
||||
{"split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
|
||||
VersionM},
|
||||
{"?1v759aa", VersionM},
|
||||
{"A12UEL5L", Version0},
|
||||
{"a12uel5l", Version0},
|
||||
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
|
||||
Version0},
|
||||
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", Version0},
|
||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
|
||||
Version0},
|
||||
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
Version0},
|
||||
{"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", Version0},
|
||||
{"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
Version0},
|
||||
{"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
|
||||
VersionM},
|
||||
{"BC1SW50QGDZ25J", VersionM},
|
||||
{"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", VersionM},
|
||||
{"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
||||
Version0},
|
||||
{"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
|
||||
VersionM},
|
||||
{"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
|
||||
VersionM},
|
||||
}
|
||||
for i, test := range tests {
|
||||
_, _, version, err := DecodeGeneric([]byte(test.str))
|
||||
if err != nil {
|
||||
t.Errorf("%d: (%v) unexpected error during "+
|
||||
"decoding: %v", i, test.str, err)
|
||||
continue
|
||||
}
|
||||
if version != test.version {
|
||||
t.Errorf("(%v): invalid version: expected %v, got %v",
|
||||
test.str, test.version, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMixedCaseEncode ensures mixed case HRPs are converted to lowercase as
|
||||
// expected when encoding and that decoding the produced encoding when converted
|
||||
// to all uppercase produces the lowercase HRP and original data.
|
||||
func TestMixedCaseEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hrp string
|
||||
data string
|
||||
encoded string
|
||||
}{{
|
||||
name: "all uppercase HRP with no data",
|
||||
hrp: "A",
|
||||
data: "",
|
||||
encoded: "a12uel5l",
|
||||
}, {
|
||||
name: "all uppercase HRP with data",
|
||||
hrp: "UPPERCASE",
|
||||
data: "787878",
|
||||
encoded: "uppercase10pu8sss7kmp",
|
||||
}, {
|
||||
name: "mixed case HRP even offsets uppercase",
|
||||
hrp: "AbCdEf",
|
||||
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
}, {
|
||||
name: "mixed case HRP odd offsets uppercase ",
|
||||
hrp: "aBcDeF",
|
||||
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
}, {
|
||||
name: "all lowercase HRP",
|
||||
hrp: "abcdef",
|
||||
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
}}
|
||||
for _, test := range tests {
|
||||
// Convert the text hex to bytes, convert those bytes from base256 to
|
||||
// base32, then ensure the encoded result with the HRP provided in the
|
||||
// test data is as expected.
|
||||
data, err := hex.DecodeString(test.data)
|
||||
if err != nil {
|
||||
t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err)
|
||||
continue
|
||||
}
|
||||
convertedData, err := ConvertBits(data, 8, 5, true)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected convert bits error: %v", test.name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
gotEncoded, err := Encode([]byte(test.hrp), convertedData)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected encode error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gotEncoded, []byte(test.encoded)) {
|
||||
t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name,
|
||||
gotEncoded, test.encoded)
|
||||
continue
|
||||
}
|
||||
// Ensure the decoding the expected lowercase encoding converted to all
|
||||
// uppercase produces the lowercase HRP and original data.
|
||||
gotHRP, gotData, err := Decode(bytes.ToUpper([]byte(test.encoded)))
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected decode error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
wantHRP := strings.ToLower(test.hrp)
|
||||
if !bytes.Equal(gotHRP, []byte(wantHRP)) {
|
||||
t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name,
|
||||
gotHRP, wantHRP)
|
||||
continue
|
||||
}
|
||||
convertedGotData, err := ConvertBits(gotData, 5, 8, false)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected convert bits error: %v", test.name,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(convertedGotData, data) {
|
||||
t.Errorf("%q: mismatched data -- got %x, want %x", test.name,
|
||||
convertedGotData, data)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works
|
||||
// when using the DecodeNoLimit version
|
||||
func TestCanDecodeUnlimtedBech32(t *testing.T) {
|
||||
input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd"
|
||||
// Sanity check that an input of this length errors on regular Decode()
|
||||
_, _, err := Decode([]byte(input))
|
||||
if err == nil {
|
||||
t.Fatalf("Test vector not appropriate")
|
||||
}
|
||||
// Try and decode it.
|
||||
hrp, data, err := DecodeNoLimit([]byte(input))
|
||||
if err != nil {
|
||||
t.Fatalf("Expected decoding of large string to work. Got error: %v",
|
||||
err)
|
||||
}
|
||||
// Verify data for correctness.
|
||||
if !bytes.Equal(hrp, []byte("1")) {
|
||||
t.Fatalf("Unexpected hrp: %v", hrp)
|
||||
}
|
||||
decodedHex := fmt.Sprintf("%x", data)
|
||||
expected := "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
if decodedHex != expected {
|
||||
t.Fatalf("Unexpected decoded data: %s", decodedHex)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBech32Base256 ensures decoding and encoding various bech32, HRPs, and
|
||||
// data produces the expected results when using EncodeFromBase256 and
|
||||
// DecodeToBase256. It includes tests for proper handling of case
|
||||
// manipulations.
|
||||
func TestBech32Base256(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string // test name
|
||||
encoded string // bech32 string to decode
|
||||
hrp string // expected human-readable part
|
||||
data string // expected hex-encoded data
|
||||
err error // expected error
|
||||
}{{
|
||||
name: "all uppercase, no data",
|
||||
encoded: "A12UEL5L",
|
||||
hrp: "a",
|
||||
data: "",
|
||||
}, {
|
||||
name: "long hrp with separator and excluded chars, no data",
|
||||
encoded: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
|
||||
hrp: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio",
|
||||
data: "",
|
||||
}, {
|
||||
name: "6 char hrp with data with leading zero",
|
||||
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
hrp: "abcdef",
|
||||
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||
}, {
|
||||
name: "hrp same as separator and max length encoded string",
|
||||
encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
|
||||
hrp: "1",
|
||||
data: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
}, {
|
||||
name: "5 char hrp with data chosen to produce human-readable data part",
|
||||
encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
hrp: "split",
|
||||
data: "c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d",
|
||||
}, {
|
||||
name: "same as previous but with checksum invalidated",
|
||||
encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w",
|
||||
err: ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v", "2y9e2w"},
|
||||
}, {
|
||||
name: "hrp with invalid character (space)",
|
||||
encoded: "s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p",
|
||||
err: ErrInvalidCharacter(' '),
|
||||
}, {
|
||||
name: "hrp with invalid character (DEL)",
|
||||
encoded: "spl\x7ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
err: ErrInvalidCharacter(127),
|
||||
}, {
|
||||
name: "data part with invalid character (o)",
|
||||
encoded: "split1cheo2y9e2w",
|
||||
err: ErrNonCharsetChar('o'),
|
||||
}, {
|
||||
name: "data part too short",
|
||||
encoded: "split1a2y9w",
|
||||
err: ErrInvalidSeparatorIndex(5),
|
||||
}, {
|
||||
name: "empty hrp",
|
||||
encoded: "1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
|
||||
err: ErrInvalidSeparatorIndex(0),
|
||||
}, {
|
||||
name: "no separator",
|
||||
encoded: "pzry9x0s0muk",
|
||||
err: ErrInvalidSeparatorIndex(-1),
|
||||
}, {
|
||||
name: "too long by one char",
|
||||
encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
|
||||
err: ErrInvalidLength(91),
|
||||
}, {
|
||||
name: "invalid due to mixed case in hrp",
|
||||
encoded: "aBcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
err: ErrMixedCase{},
|
||||
}, {
|
||||
name: "invalid due to mixed case in data part",
|
||||
encoded: "abcdef1Qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||
err: ErrMixedCase{},
|
||||
}}
|
||||
for _, test := range tests {
|
||||
// Ensure the decode either produces an error or not as expected.
|
||||
str := test.encoded
|
||||
gotHRP, gotData, err := DecodeToBase256([]byte(str))
|
||||
if test.err != err {
|
||||
t.Errorf("%q: unexpected decode error -- got %v, want %v",
|
||||
test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// End test case here if a decoding error was expected.
|
||||
continue
|
||||
}
|
||||
// Ensure the expected HRP and original data are as expected.
|
||||
if !bytes.Equal(gotHRP, []byte(test.hrp)) {
|
||||
t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name,
|
||||
gotHRP, test.hrp)
|
||||
continue
|
||||
}
|
||||
data, err := hex.DecodeString(test.data)
|
||||
if err != nil {
|
||||
t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gotData, data) {
|
||||
t.Errorf("%q: mismatched data -- got %x, want %x", test.name,
|
||||
gotData, data)
|
||||
continue
|
||||
}
|
||||
// Encode the same data with the HRP converted to all uppercase and
|
||||
// ensure the result is the lowercase version of the original encoded
|
||||
// bech32 string.
|
||||
gotEncoded, err := EncodeFromBase256(bytes.ToUpper([]byte(test.hrp)), data)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected uppercase HRP encode error: %v", test.name,
|
||||
err)
|
||||
}
|
||||
wantEncoded := bytes.ToLower([]byte(str))
|
||||
if !bytes.Equal(gotEncoded, wantEncoded) {
|
||||
t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name,
|
||||
gotEncoded, wantEncoded)
|
||||
}
|
||||
// Encode the same data with the HRP converted to all lowercase and
|
||||
// ensure the result is the lowercase version of the original encoded
|
||||
// bech32 string.
|
||||
gotEncoded, err = EncodeFromBase256(bytes.ToLower([]byte(test.hrp)), data)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name,
|
||||
err)
|
||||
}
|
||||
if !bytes.Equal(gotEncoded, wantEncoded) {
|
||||
t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name,
|
||||
gotEncoded, wantEncoded)
|
||||
}
|
||||
// Encode the same data with the HRP converted to mixed upper and
|
||||
// lowercase and ensure the result is the lowercase version of the
|
||||
// original encoded bech32 string.
|
||||
var mixedHRPBuilder bytes.Buffer
|
||||
for i, r := range test.hrp {
|
||||
if i%2 == 0 {
|
||||
mixedHRPBuilder.WriteString(strings.ToUpper(string(r)))
|
||||
continue
|
||||
}
|
||||
mixedHRPBuilder.WriteRune(r)
|
||||
}
|
||||
gotEncoded, err = EncodeFromBase256(mixedHRPBuilder.Bytes(), data)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name,
|
||||
err)
|
||||
}
|
||||
if !bytes.Equal(gotEncoded, wantEncoded) {
|
||||
t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name,
|
||||
gotEncoded, wantEncoded)
|
||||
}
|
||||
// Ensure a bit flip in the string is caught.
|
||||
pos := strings.LastIndexAny(test.encoded, "1")
|
||||
flipped := str[:pos+1] + string(str[pos+1]^1) + str[pos+2:]
|
||||
_, _, err = DecodeToBase256([]byte(flipped))
|
||||
if err == nil {
|
||||
t.Error("expected decoding to fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEncodeDecodeCycle performs a benchmark for a full encode/decode
|
||||
// cycle of a bech32 string. It also reports the allocation count, which we
|
||||
// expect to be 2 for a fully optimized cycle.
|
||||
func BenchmarkEncodeDecodeCycle(b *testing.B) {
|
||||
// Use a fixed, 49-byte raw data for testing.
|
||||
inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
|
||||
if err != nil {
|
||||
b.Fatalf("failed to initialize input data: %v", err)
|
||||
}
|
||||
// Convert this into a 79-byte, base 32 byte slice.
|
||||
base32Input, err := ConvertBits(inputData, 8, 5, true)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to convert input to 32 bits-per-element: %v", err)
|
||||
}
|
||||
// Use a fixed hrp for the tests. This should generate an encoded bech32
|
||||
// string of size 90 (the maximum allowed by BIP-173).
|
||||
hrp := "bc"
|
||||
// Begin the benchmark. Given that we test one roundtrip per iteration
|
||||
// (that is, one Encode() and one Decode() operation), we expect at most
|
||||
// 2 allocations per reported test op.
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
str, err := Encode([]byte(hrp), base32Input)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to encode input: %v", err)
|
||||
}
|
||||
_, _, err = Decode(str)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to decode string: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertBits tests whether base conversion works using TestConvertBits().
|
||||
func TestConvertBits(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
output string
|
||||
fromBits uint8
|
||||
toBits uint8
|
||||
pad bool
|
||||
}{
|
||||
// Trivial empty conversions.
|
||||
{"", "", 8, 5, false},
|
||||
{"", "", 8, 5, true},
|
||||
{"", "", 5, 8, false},
|
||||
{"", "", 5, 8, true},
|
||||
// Conversions of 0 value with/without padding.
|
||||
{"00", "00", 8, 5, false},
|
||||
{"00", "0000", 8, 5, true},
|
||||
{"0000", "00", 5, 8, false},
|
||||
{"0000", "0000", 5, 8, true},
|
||||
// Testing when conversion ends exactly at the byte edge. This makes
|
||||
// both padded and unpadded versions the same.
|
||||
{"0000000000", "0000000000000000", 8, 5, false},
|
||||
{"0000000000", "0000000000000000", 8, 5, true},
|
||||
{"0000000000000000", "0000000000", 5, 8, false},
|
||||
{"0000000000000000", "0000000000", 5, 8, true},
|
||||
// Conversions of full byte sequences.
|
||||
{"ffffff", "1f1f1f1f1e", 8, 5, true},
|
||||
{"1f1f1f1f1e", "ffffff", 5, 8, false},
|
||||
{"1f1f1f1f1e", "ffffff00", 5, 8, true},
|
||||
// Sample random conversions.
|
||||
{"c9ca", "190705", 8, 5, false},
|
||||
{"c9ca", "19070500", 8, 5, true},
|
||||
{"19070500", "c9ca", 5, 8, false},
|
||||
{"19070500", "c9ca00", 5, 8, true},
|
||||
// Test cases tested on TestConvertBitsFailures with their corresponding
|
||||
// fixes.
|
||||
{"ff", "1f1c", 8, 5, true},
|
||||
{"1f1c10", "ff20", 5, 8, true},
|
||||
// Large conversions.
|
||||
{
|
||||
"cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1",
|
||||
"190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408",
|
||||
8, 5, true,
|
||||
},
|
||||
{
|
||||
"190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408",
|
||||
"cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed100",
|
||||
5, 8, true,
|
||||
},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
input, err := hex.DecodeString(tc.input)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid test input data: %v", err)
|
||||
}
|
||||
expected, err := hex.DecodeString(tc.output)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid test output data: %v", err)
|
||||
}
|
||||
actual, err := ConvertBits(input, tc.fromBits, tc.toBits, tc.pad)
|
||||
if err != nil {
|
||||
t.Fatalf("test case %d failed: %v", i, err)
|
||||
}
|
||||
if !bytes.Equal(actual, expected) {
|
||||
t.Fatalf("test case %d has wrong output; expected=%x actual=%x",
|
||||
i, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertBitsFailures tests for the expected conversion failures of
|
||||
// ConvertBits().
|
||||
func TestConvertBitsFailures(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
fromBits uint8
|
||||
toBits uint8
|
||||
pad bool
|
||||
err error
|
||||
}{
|
||||
// Not enough output bytes when not using padding.
|
||||
{"ff", 8, 5, false, ErrInvalidIncompleteGroup{}},
|
||||
{"1f1c10", 5, 8, false, ErrInvalidIncompleteGroup{}},
|
||||
// Unsupported bit conversions.
|
||||
{"", 0, 5, false, ErrInvalidBitGroups{}},
|
||||
{"", 10, 5, false, ErrInvalidBitGroups{}},
|
||||
{"", 5, 0, false, ErrInvalidBitGroups{}},
|
||||
{"", 5, 10, false, ErrInvalidBitGroups{}},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
input, err := hex.DecodeString(tc.input)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid test input data: %v", err)
|
||||
}
|
||||
_, err = ConvertBits(input, tc.fromBits, tc.toBits, tc.pad)
|
||||
if err != tc.err {
|
||||
t.Fatalf("test case %d failure: expected '%v' got '%v'", i,
|
||||
tc.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior
|
||||
// of ConvertBits when converting from a higher base into a lower base (e.g. 8
|
||||
// => 5).
|
||||
//
|
||||
// Only a single allocation is expected, which is used for the output array.
|
||||
func BenchmarkConvertBitsDown(b *testing.B) {
|
||||
// Use a fixed, 49-byte raw data for testing.
|
||||
inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
|
||||
if err != nil {
|
||||
b.Fatalf("failed to initialize input data: %v", err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ConvertBits(inputData, 8, 5, true)
|
||||
if err != nil {
|
||||
b.Fatalf("error converting bits: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior
|
||||
// of ConvertBits when converting from a lower base into a higher base (e.g. 5
|
||||
// => 8).
|
||||
//
|
||||
// Only a single allocation is expected, which is used for the output array.
|
||||
func BenchmarkConvertBitsUp(b *testing.B) {
|
||||
// Use a fixed, 79-byte raw data for testing.
|
||||
inputData, err := hex.DecodeString("190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408")
|
||||
if err != nil {
|
||||
b.Fatalf("failed to initialize input data: %v", err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ConvertBits(inputData, 8, 5, true)
|
||||
if err != nil {
|
||||
b.Fatalf("error converting bits: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
13
ec/bech32/doc.go
Normal file
13
ec/bech32/doc.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bech32 provides a Go implementation of the bech32 format specified in
|
||||
// BIP 173.
|
||||
//
|
||||
// Bech32 strings consist of a human-readable part (hrp), followed by the
|
||||
// separator 1, then a checksummed data part encoded using the 32 characters
|
||||
// "qpzry9x8gf2tvdw0s3jn54khce6mua7l".
|
||||
//
|
||||
// More info: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||
package bech32
|
||||
87
ec/bech32/error.go
Normal file
87
ec/bech32/error.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2019 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bech32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrMixedCase is returned when the bech32 string has both lower and uppercase
|
||||
// characters.
|
||||
type ErrMixedCase struct{}
|
||||
|
||||
func (err ErrMixedCase) Error() string {
|
||||
return "string not all lowercase or all uppercase"
|
||||
}
|
||||
|
||||
// ErrInvalidBitGroups is returned when conversion is attempted between byte
|
||||
// slices using bit-per-element of unsupported value.
|
||||
type ErrInvalidBitGroups struct{}
|
||||
|
||||
func (err ErrInvalidBitGroups) Error() string {
|
||||
return "only bit groups between 1 and 8 allowed"
|
||||
}
|
||||
|
||||
// ErrInvalidIncompleteGroup is returned when then byte slice used as input has
|
||||
// data of wrong length.
|
||||
type ErrInvalidIncompleteGroup struct{}
|
||||
|
||||
func (err ErrInvalidIncompleteGroup) Error() string {
|
||||
return "invalid incomplete group"
|
||||
}
|
||||
|
||||
// ErrInvalidLength is returned when the bech32 string has an invalid length
|
||||
// given the BIP-173 defined restrictions.
|
||||
type ErrInvalidLength int
|
||||
|
||||
func (err ErrInvalidLength) Error() string {
|
||||
return fmt.Sprintf("invalid bech32 string length %d", int(err))
|
||||
}
|
||||
|
||||
// ErrInvalidCharacter is returned when the bech32 string has a character
|
||||
// outside the range of the supported charset.
|
||||
type ErrInvalidCharacter rune
|
||||
|
||||
func (err ErrInvalidCharacter) Error() string {
|
||||
return fmt.Sprintf("invalid character in string: '%c'", rune(err))
|
||||
}
|
||||
|
||||
// ErrInvalidSeparatorIndex is returned when the separator character '1' is
|
||||
// in an invalid position in the bech32 string.
|
||||
type ErrInvalidSeparatorIndex int
|
||||
|
||||
func (err ErrInvalidSeparatorIndex) Error() string {
|
||||
return fmt.Sprintf("invalid separator index %d", int(err))
|
||||
}
|
||||
|
||||
// ErrNonCharsetChar is returned when a character outside of the specific
|
||||
// bech32 charset is used in the string.
|
||||
type ErrNonCharsetChar rune
|
||||
|
||||
func (err ErrNonCharsetChar) Error() string {
|
||||
return fmt.Sprintf("invalid character not part of charset: %v", int(err))
|
||||
}
|
||||
|
||||
// ErrInvalidChecksum is returned when the extracted checksum of the string
|
||||
// is different than what was expected. Both the original version, as well as
|
||||
// the new bech32m checksum may be specified.
|
||||
type ErrInvalidChecksum struct {
|
||||
Expected string
|
||||
ExpectedM string
|
||||
Actual string
|
||||
}
|
||||
|
||||
func (err ErrInvalidChecksum) Error() string {
|
||||
return fmt.Sprintf("invalid checksum (expected (bech32=%v, "+
|
||||
"bech32m=%v), got %v)", err.Expected, err.ExpectedM, err.Actual)
|
||||
}
|
||||
|
||||
// ErrInvalidDataByte is returned when a byte outside the range required for
|
||||
// conversion into a string was found.
|
||||
type ErrInvalidDataByte byte
|
||||
|
||||
func (err ErrInvalidDataByte) Error() string {
|
||||
return fmt.Sprintf("invalid data byte: %v", byte(err))
|
||||
}
|
||||
43
ec/bech32/example_test.go
Normal file
43
ec/bech32/example_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bech32
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// This example demonstrates how to decode a bech32 encoded string.
|
||||
func ExampleDecode() {
|
||||
encoded := "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
|
||||
hrp, decoded, err := Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
// Show the decoded data.
|
||||
fmt.Printf("Decoded human-readable part: %s\n", hrp)
|
||||
fmt.Println("Decoded Data:", hex.EncodeToString(decoded))
|
||||
// Output:
|
||||
// Decoded human-readable part: bc
|
||||
// Decoded Data: 010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16
|
||||
}
|
||||
|
||||
// This example demonstrates how to encode data into a bech32 string.
|
||||
func ExampleEncode() {
|
||||
data := []byte("Test data")
|
||||
// Convert test data to base32:
|
||||
conv, err := ConvertBits(data, 8, 5, true)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
encoded, err := Encode([]byte("customHrp!11111q"), conv)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
// Show the encoded data.
|
||||
fmt.Printf("Encoded Data: %s", encoded)
|
||||
// Output:
|
||||
// Encoded Data: customhrp!11111q123jhxapqv3shgcgkxpuhe
|
||||
}
|
||||
40
ec/bech32/version.go
Normal file
40
ec/bech32/version.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package bech32
|
||||
|
||||
// ChecksumConst is a type that represents the currently defined bech32
|
||||
// checksum constants.
|
||||
type ChecksumConst int
|
||||
|
||||
const (
|
||||
// Version0Const is the original constant used in the checksum
|
||||
// verification for bech32.
|
||||
Version0Const ChecksumConst = 1
|
||||
// VersionMConst is the new constant used for bech32m checksum
|
||||
// verification.
|
||||
VersionMConst ChecksumConst = 0x2bc830a3
|
||||
)
|
||||
|
||||
// Version defines the current set of bech32 versions.
|
||||
type Version uint8
|
||||
|
||||
const (
|
||||
// Version0 defines the original bech version.
|
||||
Version0 Version = iota
|
||||
// VersionM is the new bech32 version defined in BIP-350, also known as
|
||||
// bech32m.
|
||||
VersionM
|
||||
// VersionUnknown denotes an unknown bech version.
|
||||
VersionUnknown
|
||||
)
|
||||
|
||||
// VersionToConsts maps bech32 versions to the checksum constant to be used
|
||||
// when encoding, and asserting a particular version when decoding.
|
||||
var VersionToConsts = map[Version]ChecksumConst{
|
||||
Version0: Version0Const,
|
||||
VersionM: VersionMConst,
|
||||
}
|
||||
|
||||
// ConstsToVersion maps a bech32 constant to the version it's associated with.
|
||||
var ConstsToVersion = map[ChecksumConst]Version{
|
||||
Version0Const: Version0,
|
||||
VersionMConst: VersionM,
|
||||
}
|
||||
188
ec/bench_test.go
Normal file
188
ec/bench_test.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
"github.com/mleku/manifold/hex"
|
||||
)
|
||||
|
||||
// setHex decodes the passed big-endian hex string into the internal field value
|
||||
// representation. Only the first 32-bytes are used.
|
||||
//
|
||||
// This is NOT constant time.
|
||||
//
|
||||
// The field value is returned to support chaining. This enables syntax like:
|
||||
// f := new(FieldVal).SetHex("0abc").Add(1) so that f = 0x0abc + 1
|
||||
func setHex(hexString string) *FieldVal {
|
||||
if len(hexString)%2 != 0 {
|
||||
hexString = "0" + hexString
|
||||
}
|
||||
bytes, _ := hex.Dec(hexString)
|
||||
var f FieldVal
|
||||
f.SetByteSlice(bytes)
|
||||
return &f
|
||||
}
|
||||
|
||||
// hexToFieldVal converts the passed hex string into a FieldVal and will panic
|
||||
// if there is an error. This is only provided for the hard-coded constants so
|
||||
// errors in the source code can be detected. It will only (and must only) be
|
||||
// called with hard-coded values.
|
||||
func hexToFieldVal(s string) *FieldVal {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
var f FieldVal
|
||||
if overflow := f.SetByteSlice(b); overflow {
|
||||
panic("hex in source file overflows mod P: " + s)
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// fromHex converts the passed hex string into a big integer pointer and will
|
||||
// panic is there is an error. This is only provided for the hard-coded
|
||||
// constants so errors in the source code can bet detected. It will only (and
|
||||
// must only) be called for initialization purposes.
|
||||
func fromHex(s string) *big.Int {
|
||||
if s == "" {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
r, ok := new(big.Int).SetString(s, 16)
|
||||
if !ok {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// jacobianPointFromHex decodes the passed big-endian hex strings into a
|
||||
// Jacobian point with its internal fields set to the resulting values. Only
|
||||
// the first 32-bytes are used.
|
||||
func jacobianPointFromHex(x, y, z string) JacobianPoint {
|
||||
var p JacobianPoint
|
||||
p.X = *setHex(x)
|
||||
p.Y = *setHex(y)
|
||||
p.Z = *setHex(z)
|
||||
return p
|
||||
}
|
||||
|
||||
// BenchmarkAddNonConst benchmarks the secp256k1 curve AddNonConst function with
|
||||
// Z values of 1 so that the associated optimizations are used.
|
||||
func BenchmarkAddJacobian(b *testing.B) {
|
||||
p1 := jacobianPointFromHex(
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
)
|
||||
p2 := jacobianPointFromHex(
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
var result JacobianPoint
|
||||
for i := 0; i < b.N; i++ {
|
||||
secp256k1.AddNonConst(&p1, &p2, &result)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAddNonConstNotZOne benchmarks the secp256k1 curve AddNonConst
|
||||
// function with Z values other than one so the optimizations associated with
|
||||
// Z=1 aren't used.
|
||||
func BenchmarkAddJacobianNotZOne(b *testing.B) {
|
||||
x1 := setHex("d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718")
|
||||
y1 := setHex("5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190")
|
||||
z1 := setHex("2")
|
||||
x2 := setHex("91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4")
|
||||
y2 := setHex("03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1")
|
||||
z2 := setHex("3")
|
||||
p1 := MakeJacobianPoint(x1, y1, z1)
|
||||
p2 := MakeJacobianPoint(x2, y2, z2)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
var result JacobianPoint
|
||||
for i := 0; i < b.N; i++ {
|
||||
AddNonConst(&p1, &p2, &result)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkScalarBaseMult benchmarks the secp256k1 curve ScalarBaseMult
|
||||
// function.
|
||||
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
|
||||
curve := S256()
|
||||
for i := 0; i < b.N; i++ {
|
||||
curve.ScalarBaseMult(k.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkScalarBaseMultLarge benchmarks the secp256k1 curve ScalarBaseMult
|
||||
// function with abnormally large k values.
|
||||
func BenchmarkScalarBaseMultLarge(b *testing.B) {
|
||||
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c005751111111011111110")
|
||||
curve := S256()
|
||||
for i := 0; i < b.N; i++ {
|
||||
curve.ScalarBaseMult(k.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkScalarMult benchmarks the secp256k1 curve ScalarMult function.
|
||||
func BenchmarkScalarMult(b *testing.B) {
|
||||
x := fromHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6")
|
||||
y := fromHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232")
|
||||
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
|
||||
curve := S256()
|
||||
for i := 0; i < b.N; i++ {
|
||||
curve.ScalarMult(x, y, k.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// hexToModNScalar converts the passed hex string into a ModNScalar and will
|
||||
// panic if there is an error. This is only provided for the hard-coded
|
||||
// constants so errors in the source code can be detected. It will only (and
|
||||
// must only) be called with hard-coded values.
|
||||
func hexToModNScalar(s string) *ModNScalar {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
var scalar ModNScalar
|
||||
if overflow := scalar.SetByteSlice(b); overflow {
|
||||
panic("hex in source file overflows mod N scalar: " + s)
|
||||
}
|
||||
return &scalar
|
||||
}
|
||||
|
||||
// BenchmarkFieldNormalize benchmarks how long it takes the internal field
|
||||
// to perform normalization (which includes modular reduction).
|
||||
func BenchmarkFieldNormalize(b *testing.B) {
|
||||
// The normalize function is constant time so default value is fine.
|
||||
var f FieldVal
|
||||
for i := 0; i < b.N; i++ {
|
||||
f.Normalize()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseCompressedPubKey benchmarks how long it takes to decompress and
|
||||
// validate a compressed public key from a byte array.
|
||||
func BenchmarkParseCompressedPubKey(b *testing.B) {
|
||||
rawPk, _ := hex.Dec("0234f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6")
|
||||
|
||||
var (
|
||||
pk *PublicKey
|
||||
err error
|
||||
)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pk, err = ParsePubKey(rawPk)
|
||||
}
|
||||
_ = pk
|
||||
_ = err
|
||||
}
|
||||
53
ec/btcec.go
Normal file
53
ec/btcec.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Copyright 2011 ThePiachu. All rights reserved.
|
||||
// Copyright 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
// References:
|
||||
// [SECG]: Recommended Elliptic Curve Domain Parameters
|
||||
// http://www.secg.org/sec2-v2.pdf
|
||||
//
|
||||
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
|
||||
|
||||
// This package operates, internally, on Jacobian coordinates. For a given
|
||||
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
|
||||
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
|
||||
// calculation can be performed within the transform (as in ScalarMult and
|
||||
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
|
||||
// reverse the transform than to operate in affine coordinates.
|
||||
|
||||
import (
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
)
|
||||
|
||||
// KoblitzCurve provides an implementation for secp256k1 that fits the ECC
|
||||
// Curve interface from crypto/elliptic.
|
||||
type KoblitzCurve = secp256k1.KoblitzCurve
|
||||
|
||||
// S256 returns a Curve which implements secp256k1.
|
||||
func S256() *KoblitzCurve {
|
||||
return secp256k1.S256()
|
||||
}
|
||||
|
||||
// CurveParams contains the parameters for the secp256k1 curve.
|
||||
type CurveParams = secp256k1.CurveParams
|
||||
|
||||
// Params returns the secp256k1 curve parameters for convenience.
|
||||
func Params() *CurveParams {
|
||||
return secp256k1.Params()
|
||||
}
|
||||
|
||||
// Generator returns the public key at the Generator Point.
|
||||
func Generator() *PublicKey {
|
||||
var (
|
||||
result JacobianPoint
|
||||
k secp256k1.ModNScalar
|
||||
)
|
||||
k.SetInt(1)
|
||||
ScalarBaseMultNonConst(&k, &result)
|
||||
result.ToAffine()
|
||||
return NewPublicKey(&result.X, &result.Y)
|
||||
}
|
||||
875
ec/btcec_test.go
Normal file
875
ec/btcec_test.go
Normal file
@@ -0,0 +1,875 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Copyright 2011 ThePiachu. All rights reserved.
|
||||
// Copyright 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// isJacobianOnS256Curve returns boolean if the point (x,y,z) is on the
|
||||
// secp256k1 curve.
|
||||
func isJacobianOnS256Curve(point *JacobianPoint) bool {
|
||||
// Elliptic curve equation for secp256k1 is: y^2 = x^3 + 7
|
||||
// In Jacobian coordinates, Y = y/z^3 and X = x/z^2
|
||||
// Thus:
|
||||
// (y/z^3)^2 = (x/z^2)^3 + 7
|
||||
// y^2/z^6 = x^3/z^6 + 7
|
||||
// y^2 = x^3 + 7*z^6
|
||||
var y2, z2, x3, result FieldVal
|
||||
y2.SquareVal(&point.Y).Normalize()
|
||||
z2.SquareVal(&point.Z)
|
||||
x3.SquareVal(&point.X).Mul(&point.X)
|
||||
result.SquareVal(&z2).Mul(&z2).MulInt(7).Add(&x3).Normalize()
|
||||
return y2.Equals(&result)
|
||||
}
|
||||
|
||||
// TestAddJacobian tests addition of points projected in Jacobian coordinates.
|
||||
func TestAddJacobian(t *testing.T) {
|
||||
tests := []struct {
|
||||
x1, y1, z1 string // Coordinates (in hex) of first point to add
|
||||
x2, y2, z2 string // Coordinates (in hex) of second point to add
|
||||
x3, y3, z3 string // Coordinates (in hex) of expected point
|
||||
}{
|
||||
// Addition with a point at infinity (left hand side).
|
||||
// ∞ + P = P
|
||||
{
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
},
|
||||
// Addition with a point at infinity (right hand side).
|
||||
// P + ∞ = P
|
||||
{
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
},
|
||||
// Addition with z1=z2=1 different x values.
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
"0cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a6",
|
||||
"e205f79361bbe0346b037b4010985dbf4f9e1e955e7d0d14aca876bfa79aad87",
|
||||
"44a5646b446e3877a648d6d381370d9ef55a83b666ebce9df1b1d7d65b817b2f",
|
||||
},
|
||||
// Addition with z1=z2=1 same x opposite y.
|
||||
// P(x, y, z) + P(x, -y, z) = infinity
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Addition with z1=z2=1 same point.
|
||||
// P(x, y, z) + P(x, y, z) = 2P
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27",
|
||||
"b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a",
|
||||
"16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464",
|
||||
},
|
||||
|
||||
// Addition with z1=z2 (!=1) different x values.
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"5d2fe112c21891d440f65a98473cb626111f8a234d2cd82f22172e369f002147",
|
||||
"98e3386a0a622a35c4561ffb32308d8e1c6758e10ebb1b4ebd3d04b4eb0ecbe8",
|
||||
"2",
|
||||
"cfbc7da1e569b334460788faae0286e68b3af7379d5504efc25e4dba16e46a60",
|
||||
"817de4d86ef80d1ac0ded00426176fd3e787a5579f43452b2a1db021e6ac3778",
|
||||
"129591ad11b8e1de99235b4e04dc367bd56a0ed99baf3a77c6c75f5a6e05f08d",
|
||||
},
|
||||
// Addition with z1=z2 (!=1) same x opposite y.
|
||||
// P(x, y, z) + P(x, -y, z) = infinity
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"a470ab21467813b6e0496d2c2b70c11446bab4fcbc9a52b7f225f30e869aea9f",
|
||||
"2",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Addition with z1=z2 (!=1) same point.
|
||||
// P(x, y, z) + P(x, y, z) = 2P
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac",
|
||||
"2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988",
|
||||
"6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11",
|
||||
},
|
||||
|
||||
// Addition with z1!=z2 and z2=1 different x values.
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"1",
|
||||
"3ef1f68795a6ccd1181e23eab80a1b9a2cebdcde755413bf097936eb5b91b4f3",
|
||||
"0bef26c377c068d606f6802130bb7e9f3c3d2abcfa1a295950ed81133561cb04",
|
||||
"252b235a2371c3bd3246b69c09b86cf7aad41db3375e74ef8d8ebeb4dc0be11a",
|
||||
},
|
||||
// Addition with z1!=z2 and z2=1 same x opposite y.
|
||||
// P(x, y, z) + P(x, -y, z) = infinity
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Addition with z1!=z2 and z2=1 same point.
|
||||
// P(x, y, z) + P(x, y, z) = 2P
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac",
|
||||
"2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988",
|
||||
"6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11",
|
||||
},
|
||||
|
||||
// Addition with z1!=z2 and z2!=1 different x values.
|
||||
// P(x, y, z) + P(x, y, z) = 2P
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4",
|
||||
"03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1",
|
||||
"3",
|
||||
"3f07081927fd3f6dadd4476614c89a09eba7f57c1c6c3b01fa2d64eac1eef31e",
|
||||
"949166e04ebc7fd95a9d77e5dfd88d1492ecffd189792e3944eb2b765e09e031",
|
||||
"eb8cba81bcffa4f44d75427506737e1f045f21e6d6f65543ee0e1d163540c931",
|
||||
}, // Addition with z1!=z2 and z2!=1 same x opposite y.
|
||||
// P(x, y, z) + P(x, -y, z) = infinity
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7",
|
||||
"cafc41904dd5428934f7d075129c8ba46eb622d4fc88d72cd1401452664add18",
|
||||
"3",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Addition with z1!=z2 and z2!=1 same point.
|
||||
// P(x, y, z) + P(x, y, z) = 2P
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"dcc3768780c74a0325e2851edad0dc8a566fa61a9e7fc4a34d13dcb509f99bc7",
|
||||
"3503be6fb22abd76cb082f8aed63745b9149dd2b037728d32ebfebac99b51f17",
|
||||
"3",
|
||||
"9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac",
|
||||
"2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988",
|
||||
"6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Convert hex to Jacobian points.
|
||||
p1 := jacobianPointFromHex(test.x1, test.y1, test.z1)
|
||||
p2 := jacobianPointFromHex(test.x2, test.y2, test.z2)
|
||||
want := jacobianPointFromHex(test.x3, test.y3, test.z3)
|
||||
// Ensure the test data is using points that are actually on
|
||||
// the curve (or the point at infinity).
|
||||
if !p1.Z.IsZero() && !isJacobianOnS256Curve(&p1) {
|
||||
t.Errorf("#%d first point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !p2.Z.IsZero() && !isJacobianOnS256Curve(&p2) {
|
||||
t.Errorf("#%d second point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !want.Z.IsZero() && !isJacobianOnS256Curve(&want) {
|
||||
t.Errorf("#%d expected point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
// Add the two points.
|
||||
var r JacobianPoint
|
||||
AddNonConst(&p1, &p2, &r)
|
||||
|
||||
// Ensure result matches expected.
|
||||
if !r.X.Equals(&want.X) || !r.Y.Equals(&want.Y) || !r.Z.Equals(&want.Z) {
|
||||
t.Errorf("#%d wrong result\ngot: (%v, %v, %v)\n"+
|
||||
"want: (%v, %v, %v)", i, r.X, r.Y, r.Z, want.X, want.Y, want.Z)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddAffine tests addition of points in affine coordinates.
|
||||
func TestAddAffine(t *testing.T) {
|
||||
tests := []struct {
|
||||
x1, y1 string // Coordinates (in hex) of first point to add
|
||||
x2, y2 string // Coordinates (in hex) of second point to add
|
||||
x3, y3 string // Coordinates (in hex) of expected point
|
||||
}{
|
||||
// Addition with a point at infinity (left hand side).
|
||||
// ∞ + P = P
|
||||
{
|
||||
"0",
|
||||
"0",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
},
|
||||
// Addition with a point at infinity (right hand side).
|
||||
// P + ∞ = P
|
||||
{
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"0",
|
||||
"0",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
},
|
||||
|
||||
// Addition with different x values.
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
|
||||
"131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
|
||||
"fd5b88c21d3143518d522cd2796f3d726793c88b3e05636bc829448e053fed69",
|
||||
"21cf4f6a5be5ff6380234c50424a970b1f7e718f5eb58f68198c108d642a137f",
|
||||
},
|
||||
// Addition with same x opposite y.
|
||||
// P(x, y) + P(x, -y) = infinity
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Addition with same point.
|
||||
// P(x, y) + P(x, y) = 2P
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b",
|
||||
"938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6",
|
||||
},
|
||||
}
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Convert hex to field values.
|
||||
x1, y1 := fromHex(test.x1), fromHex(test.y1)
|
||||
x2, y2 := fromHex(test.x2), fromHex(test.y2)
|
||||
x3, y3 := fromHex(test.x3), fromHex(test.y3)
|
||||
// Ensure the test data is using points that are actually on
|
||||
// the curve (or the point at infinity).
|
||||
if !(x1.Sign() == 0 && y1.Sign() == 0) && !S256().IsOnCurve(x1, y1) {
|
||||
t.Errorf("#%d first point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !(x2.Sign() == 0 && y2.Sign() == 0) && !S256().IsOnCurve(x2, y2) {
|
||||
t.Errorf("#%d second point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !(x3.Sign() == 0 && y3.Sign() == 0) && !S256().IsOnCurve(x3, y3) {
|
||||
t.Errorf("#%d expected point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
// Add the two points.
|
||||
rx, ry := S256().Add(x1, y1, x2, y2)
|
||||
|
||||
// Ensure result matches expected.
|
||||
if rx.Cmp(x3) != 00 || ry.Cmp(y3) != 0 {
|
||||
t.Errorf("#%d wrong result\ngot: (%x, %x)\n"+
|
||||
"want: (%x, %x)", i, rx, ry, x3, y3)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isStrictlyEqual returns whether or not the two Jacobian points are strictly
|
||||
// equal for use in the tests. Recall that several Jacobian points can be
|
||||
// equal in affine coordinates, while not having the same coordinates in
|
||||
// projective space, so the two points not being equal doesn't necessarily mean
|
||||
// they aren't actually the same affine point.
|
||||
func isStrictlyEqual(p, other *JacobianPoint) bool {
|
||||
return p.X.Equals(&other.X) && p.Y.Equals(&other.Y) && p.Z.Equals(&other.Z)
|
||||
}
|
||||
|
||||
// TestDoubleJacobian tests doubling of points projected in Jacobian
|
||||
// coordinates.
|
||||
func TestDoubleJacobian(t *testing.T) {
|
||||
tests := []struct {
|
||||
x1, y1, z1 string // Coordinates (in hex) of point to double
|
||||
x3, y3, z3 string // Coordinates (in hex) of expected point
|
||||
}{
|
||||
// Doubling a point at infinity is still infinity.
|
||||
{
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Doubling with z1=1.
|
||||
{
|
||||
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
|
||||
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
|
||||
"1",
|
||||
"ec9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee64f87c50c27",
|
||||
"b082b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd0755c8f2a",
|
||||
"16e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c1e594464",
|
||||
},
|
||||
// Doubling with z1!=1.
|
||||
{
|
||||
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
|
||||
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
|
||||
"2",
|
||||
"9f153b13ee7bd915882859635ea9730bf0dc7611b2c7b0e37ee65073c50fabac",
|
||||
"2b53702c466dcf6e984a35671756c506c67c2fcb8adb408c44dd125dc91cb988",
|
||||
"6e3d537ae61fb1247eda4b4f523cfbaee5152c0d0d96b520376833c2e5944a11",
|
||||
},
|
||||
// From btcd issue #709.
|
||||
{
|
||||
"201e3f75715136d2f93c4f4598f91826f94ca01f4233a5bd35de9708859ca50d",
|
||||
"bdf18566445e7562c6ada68aef02d498d7301503de5b18c6aef6e2b1722412e1",
|
||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"4a5e0559863ebb4e9ed85f5c4fa76003d05d9a7626616e614a1f738621e3c220",
|
||||
"00000000000000000000000000000000000000000000000000000001b1388778",
|
||||
"7be30acc88bceac58d5b4d15de05a931ae602a07bcb6318d5dedc563e4482993",
|
||||
},
|
||||
}
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Convert hex to field values.
|
||||
p1 := jacobianPointFromHex(test.x1, test.y1, test.z1)
|
||||
want := jacobianPointFromHex(test.x3, test.y3, test.z3)
|
||||
// Ensure the test data is using points that are actually on
|
||||
// the curve (or the point at infinity).
|
||||
if !p1.Z.IsZero() && !isJacobianOnS256Curve(&p1) {
|
||||
t.Errorf("#%d first point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !want.Z.IsZero() && !isJacobianOnS256Curve(&want) {
|
||||
t.Errorf("#%d expected point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
// Double the point.
|
||||
var result JacobianPoint
|
||||
DoubleNonConst(&p1, &result)
|
||||
// Ensure result matches expected.
|
||||
if !isStrictlyEqual(&result, &want) {
|
||||
t.Errorf("#%d wrong result\ngot: (%v, %v, %v)\n"+
|
||||
"want: (%v, %v, %v)", i, result.X, result.Y, result.Z,
|
||||
want.X, want.Y, want.Z)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoubleAffine tests doubling of points in affine coordinates.
|
||||
func TestDoubleAffine(t *testing.T) {
|
||||
tests := []struct {
|
||||
x1, y1 string // Coordinates (in hex) of point to double
|
||||
x3, y3 string // Coordinates (in hex) of expected point
|
||||
}{
|
||||
// Doubling a point at infinity is still infinity.
|
||||
// 2*∞ = ∞ (point at infinity)
|
||||
{
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
},
|
||||
// Random points.
|
||||
{
|
||||
"e41387ffd8baaeeb43c2faa44e141b19790e8ac1f7ff43d480dc132230536f86",
|
||||
"1b88191d430f559896149c86cbcb703193105e3cf3213c0c3556399836a2b899",
|
||||
"88da47a089d333371bd798c548ef7caae76e737c1980b452d367b3cfe3082c19",
|
||||
"3b6f659b09a362821dfcfefdbfbc2e59b935ba081b6c249eb147b3c2100b1bc1",
|
||||
},
|
||||
{
|
||||
"b3589b5d984f03ef7c80aeae444f919374799edf18d375cab10489a3009cff0c",
|
||||
"c26cf343875b3630e15bccc61202815b5d8f1fd11308934a584a5babe69db36a",
|
||||
"e193860172998751e527bb12563855602a227fc1f612523394da53b746bb2fb1",
|
||||
"2bfcf13d2f5ab8bb5c611fab5ebbed3dc2f057062b39a335224c22f090c04789",
|
||||
},
|
||||
{
|
||||
"2b31a40fbebe3440d43ac28dba23eee71c62762c3fe3dbd88b4ab82dc6a82340",
|
||||
"9ba7deb02f5c010e217607fd49d58db78ec273371ea828b49891ce2fd74959a1",
|
||||
"2c8d5ef0d343b1a1a48aa336078eadda8481cb048d9305dc4fdf7ee5f65973a2",
|
||||
"bb4914ac729e26d3cd8f8dc8f702f3f4bb7e0e9c5ae43335f6e94c2de6c3dc95",
|
||||
},
|
||||
{
|
||||
"61c64b760b51981fab54716d5078ab7dffc93730b1d1823477e27c51f6904c7a",
|
||||
"ef6eb16ea1a36af69d7f66524c75a3a5e84c13be8fbc2e811e0563c5405e49bd",
|
||||
"5f0dcdd2595f5ad83318a0f9da481039e36f135005420393e72dfca985b482f4",
|
||||
"a01c849b0837065c1cb481b0932c441f49d1cab1b4b9f355c35173d93f110ae0",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Convert hex to field values.
|
||||
x1, y1 := fromHex(test.x1), fromHex(test.y1)
|
||||
x3, y3 := fromHex(test.x3), fromHex(test.y3)
|
||||
// Ensure the test data is using points that are actually on
|
||||
// the curve (or the point at infinity).
|
||||
if !(x1.Sign() == 0 && y1.Sign() == 0) && !S256().IsOnCurve(x1, y1) {
|
||||
t.Errorf("#%d first point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
if !(x3.Sign() == 0 && y3.Sign() == 0) && !S256().IsOnCurve(x3, y3) {
|
||||
t.Errorf("#%d expected point is not on the curve -- "+
|
||||
"invalid test data", i)
|
||||
continue
|
||||
}
|
||||
// Double the point.
|
||||
rx, ry := S256().Double(x1, y1)
|
||||
|
||||
// Ensure result matches expected.
|
||||
if rx.Cmp(x3) != 00 || ry.Cmp(y3) != 0 {
|
||||
t.Errorf("#%d wrong result\ngot: (%x, %x)\n"+
|
||||
"want: (%x, %x)", i, rx, ry, x3, y3)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCurve(t *testing.T) {
|
||||
s256 := S256()
|
||||
if !s256.IsOnCurve(s256.Params().Gx, s256.Params().Gy) {
|
||||
t.Errorf("FAIL S256")
|
||||
}
|
||||
}
|
||||
|
||||
type baseMultTest struct {
|
||||
k string
|
||||
x, y string
|
||||
}
|
||||
|
||||
// TODO: add more test vectors
|
||||
var s256BaseMultTests = []baseMultTest{
|
||||
{
|
||||
"AA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522",
|
||||
"34F9460F0E4F08393D192B3C5133A6BA099AA0AD9FD54EBCCFACDFA239FF49C6",
|
||||
"B71EA9BD730FD8923F6D25A7A91E7DD7728A960686CB5A901BB419E0F2CA232",
|
||||
},
|
||||
{
|
||||
"7E2B897B8CEBC6361663AD410835639826D590F393D90A9538881735256DFAE3",
|
||||
"D74BF844B0862475103D96A611CF2D898447E288D34B360BC885CB8CE7C00575",
|
||||
"131C670D414C4546B88AC3FF664611B1C38CEB1C21D76369D7A7A0969D61D97D",
|
||||
},
|
||||
{
|
||||
"6461E6DF0FE7DFD05329F41BF771B86578143D4DD1F7866FB4CA7E97C5FA945D",
|
||||
"E8AECC370AEDD953483719A116711963CE201AC3EB21D3F3257BB48668C6A72F",
|
||||
"C25CAF2F0EBA1DDB2F0F3F47866299EF907867B7D27E95B3873BF98397B24EE1",
|
||||
},
|
||||
{
|
||||
"376A3A2CDCD12581EFFF13EE4AD44C4044B8A0524C42422A7E1E181E4DEECCEC",
|
||||
"14890E61FCD4B0BD92E5B36C81372CA6FED471EF3AA60A3E415EE4FE987DABA1",
|
||||
"297B858D9F752AB42D3BCA67EE0EB6DCD1C2B7B0DBE23397E66ADC272263F982",
|
||||
},
|
||||
{
|
||||
"1B22644A7BE026548810C378D0B2994EEFA6D2B9881803CB02CEFF865287D1B9",
|
||||
"F73C65EAD01C5126F28F442D087689BFA08E12763E0CEC1D35B01751FD735ED3",
|
||||
"F449A8376906482A84ED01479BD18882B919C140D638307F0C0934BA12590BDE",
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: test different curves as well?
|
||||
func TestBaseMult(t *testing.T) {
|
||||
s256 := S256()
|
||||
for i, e := range s256BaseMultTests {
|
||||
k, ok := new(big.Int).SetString(e.k, 16)
|
||||
if !ok {
|
||||
t.Errorf("%d: bad value for k: %s", i, e.k)
|
||||
}
|
||||
x, y := s256.ScalarBaseMult(k.Bytes())
|
||||
if fmt.Sprintf("%X", x) != e.x || fmt.Sprintf("%X", y) != e.y {
|
||||
t.Errorf("%d: bad output for k=%s: got (%X, %X), want (%s, %s)", i,
|
||||
e.k, x, y, e.x, e.y)
|
||||
}
|
||||
if testing.Short() && i > 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseMultVerify(t *testing.T) {
|
||||
s256 := S256()
|
||||
for bytes := 1; bytes < 40; bytes++ {
|
||||
for i := 0; i < 30; i++ {
|
||||
data := make([]byte, bytes)
|
||||
_, err := rand.Read(data)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read random data for %d", i)
|
||||
continue
|
||||
}
|
||||
x, y := s256.ScalarBaseMult(data)
|
||||
xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, data)
|
||||
if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 {
|
||||
t.Errorf("%d: bad output for %X: got (%X, %X), want (%X, %X)",
|
||||
i, data, x, y, xWant, yWant)
|
||||
}
|
||||
if testing.Short() && i > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScalarMult(t *testing.T) {
|
||||
tests := []struct {
|
||||
x string
|
||||
y string
|
||||
k string
|
||||
rx string
|
||||
ry string
|
||||
}{
|
||||
// base mult, essentially.
|
||||
{
|
||||
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
|
||||
"18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725",
|
||||
"50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352",
|
||||
"2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6",
|
||||
},
|
||||
// From btcd issue #709.
|
||||
{
|
||||
"000000000000000000000000000000000000000000000000000000000000002c",
|
||||
"420e7a99bba18a9d3952597510fd2b6728cfeafc21a4e73951091d4d8ddbe94e",
|
||||
"a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58",
|
||||
"a2112dcdfbcd10ae1133a358de7b82db68e0a3eb4b492cc8268d1e7118c98788",
|
||||
"27fc7463b7bb3c5f98ecf2c84a6272bb1681ed553d92c69f2dfe25a9f9fd3836",
|
||||
},
|
||||
}
|
||||
s256 := S256()
|
||||
for i, test := range tests {
|
||||
x, _ := new(big.Int).SetString(test.x, 16)
|
||||
y, _ := new(big.Int).SetString(test.y, 16)
|
||||
k, _ := new(big.Int).SetString(test.k, 16)
|
||||
xWant, _ := new(big.Int).SetString(test.rx, 16)
|
||||
yWant, _ := new(big.Int).SetString(test.ry, 16)
|
||||
xGot, yGot := s256.ScalarMult(x, y, k.Bytes())
|
||||
if xGot.Cmp(xWant) != 0 || yGot.Cmp(yWant) != 0 {
|
||||
t.Fatalf("%d: bad output: got (%X, %X), want (%X, %X)", i, xGot,
|
||||
yGot, xWant, yWant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScalarMultRand(t *testing.T) {
|
||||
// Strategy for this test:
|
||||
// Get a random exponent from the generator point at first
|
||||
// This creates a new point which is used in the next iteration
|
||||
// Use another random exponent on the new point.
|
||||
// We use BaseMult to verify by multiplying the previous exponent
|
||||
// and the new random exponent together (mod no)
|
||||
s256 := S256()
|
||||
x, y := s256.Gx, s256.Gy
|
||||
exponent := big.NewInt(1)
|
||||
for i := 0; i < 1024; i++ {
|
||||
data := make([]byte, 32)
|
||||
_, err := rand.Read(data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read random data at %d", i)
|
||||
break
|
||||
}
|
||||
x, y = s256.ScalarMult(x, y, data)
|
||||
exponent.Mul(exponent, new(big.Int).SetBytes(data))
|
||||
xWant, yWant := s256.ScalarBaseMult(exponent.Bytes())
|
||||
if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 {
|
||||
t.Fatalf("%d: bad output for %X: got (%X, %X), want (%X, %X)", i,
|
||||
data, x, y, xWant, yWant)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Next 6 constants are from Hal Finney's bitcointalk.org post:
|
||||
// https://bitcointalk.org/index.php?topic=3238.msg45565#msg45565
|
||||
// May he rest in peace.
|
||||
//
|
||||
// They have also been independently derived from the code in the
|
||||
// EndomorphismVectors function in genstatics.go.
|
||||
endomorphismLambda = fromHex("5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72")
|
||||
endomorphismBeta = hexToFieldVal("7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")
|
||||
endomorphismA1 = fromHex("3086d221a7d46bcde86c90e49284eb15")
|
||||
endomorphismB1 = fromHex("-e4437ed6010e88286f547fa90abfe4c3")
|
||||
endomorphismA2 = fromHex("114ca50f7a8e2f3f657c1108d9d44cfd8")
|
||||
endomorphismB2 = fromHex("3086d221a7d46bcde86c90e49284eb15")
|
||||
)
|
||||
|
||||
// splitK returns a balanced length-two representation of k and their signs.
|
||||
// This is algorithm 3.74 from [GECC].
|
||||
//
|
||||
// One thing of note about this algorithm is that no matter what c1 and c2 are,
|
||||
// the final equation of k = k1 + k2 * lambda (mod n) will hold. This is
|
||||
// provable mathematically due to how a1/b1/a2/b2 are computed.
|
||||
//
|
||||
// c1 and c2 are chosen to minimize the max(k1,k2).
|
||||
func splitK(k []byte) ([]byte, []byte, int, int) {
|
||||
// All math here is done with big.Int, which is slow.
|
||||
// At some point, it might be useful to write something similar to
|
||||
// FieldVal but for no instead of P as the prime field if this ends up
|
||||
// being a bottleneck.
|
||||
bigIntK := new(big.Int)
|
||||
c1, c2 := new(big.Int), new(big.Int)
|
||||
tmp1, tmp2 := new(big.Int), new(big.Int)
|
||||
k1, k2 := new(big.Int), new(big.Int)
|
||||
bigIntK.SetBytes(k)
|
||||
// c1 = round(b2 * k / n) from step 4.
|
||||
// Rounding isn't really necessary and costs too much, hence skipped
|
||||
c1.Mul(endomorphismB2, bigIntK)
|
||||
c1.Div(c1, Params().N)
|
||||
// c2 = round(b1 * k / n) from step 4 (sign reversed to optimize one step)
|
||||
// Rounding isn't really necessary and costs too much, hence skipped
|
||||
c2.Mul(endomorphismB1, bigIntK)
|
||||
c2.Div(c2, Params().N)
|
||||
// k1 = k - c1 * a1 - c2 * a2 from step 5 (note c2's sign is reversed)
|
||||
tmp1.Mul(c1, endomorphismA1)
|
||||
tmp2.Mul(c2, endomorphismA2)
|
||||
k1.Sub(bigIntK, tmp1)
|
||||
k1.Add(k1, tmp2)
|
||||
// k2 = - c1 * b1 - c2 * b2 from step 5 (note c2's sign is reversed)
|
||||
tmp1.Mul(c1, endomorphismB1)
|
||||
tmp2.Mul(c2, endomorphismB2)
|
||||
k2.Sub(tmp2, tmp1)
|
||||
// Note Bytes() throws out the sign of k1 and k2. This matters
|
||||
// since k1 and/or k2 can be negative. Hence, we pass that
|
||||
// back separately.
|
||||
return k1.Bytes(), k2.Bytes(), k1.Sign(), k2.Sign()
|
||||
}
|
||||
|
||||
func TestSplitK(t *testing.T) {
|
||||
tests := []struct {
|
||||
k string
|
||||
k1, k2 string
|
||||
s1, s2 int
|
||||
}{
|
||||
{
|
||||
"6df2b5d30854069ccdec40ae022f5c948936324a4e9ebed8eb82cfd5a6b6d766",
|
||||
"00000000000000000000000000000000b776e53fb55f6b006a270d42d64ec2b1",
|
||||
"00000000000000000000000000000000d6cc32c857f1174b604eefc544f0c7f7",
|
||||
-1, -1,
|
||||
},
|
||||
{
|
||||
"6ca00a8f10632170accc1b3baf2a118fa5725f41473f8959f34b8f860c47d88d",
|
||||
"0000000000000000000000000000000007b21976c1795723c1bfbfa511e95b84",
|
||||
"00000000000000000000000000000000d8d2d5f9d20fc64fd2cf9bda09a5bf90",
|
||||
1, -1,
|
||||
},
|
||||
{
|
||||
"b2eda8ab31b259032d39cbc2a234af17fcee89c863a8917b2740b67568166289",
|
||||
"00000000000000000000000000000000507d930fecda7414fc4a523b95ef3c8c",
|
||||
"00000000000000000000000000000000f65ffb179df189675338c6185cb839be",
|
||||
-1, -1,
|
||||
},
|
||||
{
|
||||
"f6f00e44f179936f2befc7442721b0633f6bafdf7161c167ffc6f7751980e3a0",
|
||||
"0000000000000000000000000000000008d0264f10bcdcd97da3faa38f85308d",
|
||||
"0000000000000000000000000000000065fed1506eb6605a899a54e155665f79",
|
||||
-1, -1,
|
||||
},
|
||||
{
|
||||
"8679085ab081dc92cdd23091ce3ee998f6b320e419c3475fae6b5b7d3081996e",
|
||||
"0000000000000000000000000000000089fbf24fbaa5c3c137b4f1cedc51d975",
|
||||
"00000000000000000000000000000000d38aa615bd6754d6f4d51ccdaf529fea",
|
||||
-1, -1,
|
||||
},
|
||||
{
|
||||
"6b1247bb7931dfcae5b5603c8b5ae22ce94d670138c51872225beae6bba8cdb3",
|
||||
"000000000000000000000000000000008acc2a521b21b17cfb002c83be62f55d",
|
||||
"0000000000000000000000000000000035f0eff4d7430950ecb2d94193dedc79",
|
||||
-1, -1,
|
||||
},
|
||||
{
|
||||
"a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58",
|
||||
"0000000000000000000000000000000045c53aa1bb56fcd68c011e2dad6758e4",
|
||||
"00000000000000000000000000000000a2e79d200f27f2360fba57619936159b",
|
||||
-1, -1,
|
||||
},
|
||||
}
|
||||
s256 := S256()
|
||||
for i, test := range tests {
|
||||
k, ok := new(big.Int).SetString(test.k, 16)
|
||||
if !ok {
|
||||
t.Errorf("%d: bad value for k: %s", i, test.k)
|
||||
}
|
||||
k1, k2, k1Sign, k2Sign := splitK(k.Bytes())
|
||||
k1str := fmt.Sprintf("%064x", k1)
|
||||
if test.k1 != k1str {
|
||||
t.Errorf("%d: bad k1: got %v, want %v", i, k1str, test.k1)
|
||||
}
|
||||
k2str := fmt.Sprintf("%064x", k2)
|
||||
if test.k2 != k2str {
|
||||
t.Errorf("%d: bad k2: got %v, want %v", i, k2str, test.k2)
|
||||
}
|
||||
if test.s1 != k1Sign {
|
||||
t.Errorf("%d: bad k1 sign: got %d, want %d", i, k1Sign, test.s1)
|
||||
}
|
||||
if test.s2 != k2Sign {
|
||||
t.Errorf("%d: bad k2 sign: got %d, want %d", i, k2Sign, test.s2)
|
||||
}
|
||||
k1Int := new(big.Int).SetBytes(k1)
|
||||
k1SignInt := new(big.Int).SetInt64(int64(k1Sign))
|
||||
k1Int.Mul(k1Int, k1SignInt)
|
||||
k2Int := new(big.Int).SetBytes(k2)
|
||||
k2SignInt := new(big.Int).SetInt64(int64(k2Sign))
|
||||
k2Int.Mul(k2Int, k2SignInt)
|
||||
gotK := new(big.Int).Mul(k2Int, endomorphismLambda)
|
||||
gotK.Add(k1Int, gotK)
|
||||
gotK.Mod(gotK, s256.N)
|
||||
if k.Cmp(gotK) != 0 {
|
||||
t.Errorf("%d: bad k: got %X, want %X", i, gotK.Bytes(), k.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitKRand(t *testing.T) {
|
||||
s256 := S256()
|
||||
for i := 0; i < 1024; i++ {
|
||||
bytesK := make([]byte, 32)
|
||||
_, err := rand.Read(bytesK)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read random data at %d", i)
|
||||
break
|
||||
}
|
||||
k := new(big.Int).SetBytes(bytesK)
|
||||
k1, k2, k1Sign, k2Sign := splitK(bytesK)
|
||||
k1Int := new(big.Int).SetBytes(k1)
|
||||
k1SignInt := new(big.Int).SetInt64(int64(k1Sign))
|
||||
k1Int.Mul(k1Int, k1SignInt)
|
||||
k2Int := new(big.Int).SetBytes(k2)
|
||||
k2SignInt := new(big.Int).SetInt64(int64(k2Sign))
|
||||
k2Int.Mul(k2Int, k2SignInt)
|
||||
gotK := new(big.Int).Mul(k2Int, endomorphismLambda)
|
||||
gotK.Add(k1Int, gotK)
|
||||
gotK.Mod(gotK, s256.N)
|
||||
if k.Cmp(gotK) != 0 {
|
||||
t.Errorf("%d: bad k: got %X, want %X", i, gotK.Bytes(), k.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test this curve's usage with the ecdsa package.
|
||||
|
||||
func testKeyGeneration(t *testing.T, c *KoblitzCurve, tag string) {
|
||||
priv, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Errorf("%s: error: %s", tag, err)
|
||||
return
|
||||
}
|
||||
pub := priv.PubKey()
|
||||
if !c.IsOnCurve(pub.X(), pub.Y()) {
|
||||
t.Errorf("%s: public key invalid: %s", tag, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyGeneration(t *testing.T) {
|
||||
testKeyGeneration(t, S256(), "S256")
|
||||
}
|
||||
|
||||
// checkNAFEncoding returns an error if the provided positive and negative
|
||||
// portions of an overall NAF encoding do not adhere to the requirements or they
|
||||
// do not sum back to the provided original value.
|
||||
func checkNAFEncoding(pos, neg []byte, origValue *big.Int) error {
|
||||
// NAF must not have a leading zero byte and the number of negative
|
||||
// bytes must not exceed the positive portion.
|
||||
if len(pos) > 0 && pos[0] == 0 {
|
||||
return fmt.Errorf("positive has leading zero -- got %x", pos)
|
||||
}
|
||||
if len(neg) > len(pos) {
|
||||
return fmt.Errorf("negative has len %d > pos len %d", len(neg),
|
||||
len(pos))
|
||||
}
|
||||
// Ensure the result doesn't have any adjacent non-zero digits.
|
||||
gotPos := new(big.Int).SetBytes(pos)
|
||||
gotNeg := new(big.Int).SetBytes(neg)
|
||||
posOrNeg := new(big.Int).Or(gotPos, gotNeg)
|
||||
prevBit := posOrNeg.Bit(0)
|
||||
for bit := 1; bit < posOrNeg.BitLen(); bit++ {
|
||||
thisBit := posOrNeg.Bit(bit)
|
||||
if prevBit == 1 && thisBit == 1 {
|
||||
return fmt.Errorf("adjacent non-zero digits found at bit pos %d",
|
||||
bit-1)
|
||||
}
|
||||
prevBit = thisBit
|
||||
}
|
||||
// Ensure the resulting positive and negative portions of the overall
|
||||
// NAF representation sum back to the original value.
|
||||
gotValue := new(big.Int).Sub(gotPos, gotNeg)
|
||||
if origValue.Cmp(gotValue) != 0 {
|
||||
return fmt.Errorf("pos-neg is not original value: got %x, want %x",
|
||||
gotValue, origValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
149
ec/chaincfg/deployment_time_frame.go
Normal file
149
ec/chaincfg/deployment_time_frame.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package chaincfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mleku/manifold/ec/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoBlockClock is returned when an operation fails due to lack of
|
||||
// synchronization with the current up-to-date block clock.
|
||||
ErrNoBlockClock = fmt.Errorf("no block clock synchronized")
|
||||
)
|
||||
|
||||
// ConsensusDeploymentStarter determines if a given consensus deployment has
|
||||
// started. A deployment has started once according to the current "time", the
|
||||
// deployment is eligible for activation once a perquisite condition has
|
||||
// passed.
|
||||
type ConsensusDeploymentStarter interface {
|
||||
// HasStarted returns true if the consensus deployment has started.
|
||||
HasStarted(*wire.BlockHeader) (bool, error)
|
||||
}
|
||||
|
||||
// ConsensusDeploymentEnder determines if a given consensus deployment has
|
||||
// ended. A deployment has ended once according got eh current "time", the
|
||||
// deployment is no longer eligible for activation.
|
||||
type ConsensusDeploymentEnder interface {
|
||||
// HasEnded returns true if the consensus deployment has ended.
|
||||
HasEnded(*wire.BlockHeader) (bool, error)
|
||||
}
|
||||
|
||||
// BlockClock is an abstraction over the past median time computation. The past
|
||||
// median time computation is used in several consensus checks such as CSV, and
|
||||
// also BIP 9 version bits. This interface allows callers to abstract away the
|
||||
// computation of the past median time from the perspective of a given block
|
||||
// header.
|
||||
type BlockClock interface {
|
||||
// PastMedianTime returns the past median time from the PoV of the
|
||||
// passed block header. The past median time is the median time of the
|
||||
// 11 blocks prior to the passed block header.
|
||||
PastMedianTime(*wire.BlockHeader) (time.Time, error)
|
||||
}
|
||||
|
||||
// ClockConsensusDeploymentEnder is a more specialized version of the
|
||||
// ConsensusDeploymentEnder that uses a BlockClock in order to determine if a
|
||||
// deployment has started or not.
|
||||
//
|
||||
// NOTE: Any calls to HasEnded will _fail_ with ErrNoBlockClock if they
|
||||
// happen before SynchronizeClock is executed.
|
||||
type ClockConsensusDeploymentEnder interface {
|
||||
ConsensusDeploymentEnder
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentStarter
|
||||
// with the current up-to date BlockClock.
|
||||
SynchronizeClock(clock BlockClock)
|
||||
}
|
||||
|
||||
// MedianTimeDeploymentStarter is a ClockConsensusDeploymentStarter that uses
|
||||
// the median time past of a target block node to determine if a deployment has
|
||||
// started.
|
||||
type MedianTimeDeploymentStarter struct {
|
||||
blockClock BlockClock
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// NewMedianTimeDeploymentStarter returns a new instance of a
|
||||
// MedianTimeDeploymentStarter for a given start time. Using a time.Time
|
||||
// instance where IsZero() is true, indicates that a deployment should be
|
||||
// considered to always have been started.
|
||||
func NewMedianTimeDeploymentStarter(startTime time.Time) *MedianTimeDeploymentStarter {
|
||||
return &MedianTimeDeploymentStarter{
|
||||
startTime: startTime,
|
||||
}
|
||||
}
|
||||
|
||||
// HasStarted returns true if the consensus deployment has started.
|
||||
func (m *MedianTimeDeploymentStarter) HasStarted(blkHeader *wire.BlockHeader) (bool,
|
||||
error) {
|
||||
switch {
|
||||
// If we haven't yet been synchronized with a block clock, then we
|
||||
// can't tell the time, so we'll fail.
|
||||
case m.blockClock == nil:
|
||||
return false, ErrNoBlockClock
|
||||
// If the time is "zero", then the deployment has always started.
|
||||
case m.startTime.IsZero():
|
||||
return true, nil
|
||||
}
|
||||
medianTime, err := m.blockClock.PastMedianTime(blkHeader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// We check both after and equal here as after will fail for equivalent
|
||||
// times, and we want to be inclusive.
|
||||
return medianTime.After(m.startTime) || medianTime.Equal(m.startTime), nil
|
||||
}
|
||||
|
||||
// MedianTimeDeploymentEnder is a ClockConsensusDeploymentEnder that uses the
|
||||
// median time past of a target block to determine if a deployment has ended.
|
||||
type MedianTimeDeploymentEnder struct {
|
||||
blockClock BlockClock
|
||||
endTime time.Time
|
||||
}
|
||||
|
||||
// NewMedianTimeDeploymentEnder returns a new instance of the
|
||||
// MedianTimeDeploymentEnder anchored around the passed endTime. Using a
|
||||
// time.Time instance where IsZero() is true, indicates that a deployment
|
||||
// should be considered to never end.
|
||||
func NewMedianTimeDeploymentEnder(endTime time.Time) *MedianTimeDeploymentEnder {
|
||||
return &MedianTimeDeploymentEnder{
|
||||
endTime: endTime,
|
||||
}
|
||||
}
|
||||
|
||||
// HasEnded returns true if the deployment has ended.
|
||||
func (m *MedianTimeDeploymentEnder) HasEnded(blkHeader *wire.BlockHeader) (bool,
|
||||
error) {
|
||||
switch {
|
||||
// If we haven't yet been synchronized with a block clock, then we can't tell
|
||||
// the time, so we'll we haven't yet been synchronized with a block
|
||||
// clock, then w can't tell the time, so we'll fail.
|
||||
case m.blockClock == nil:
|
||||
return false, ErrNoBlockClock
|
||||
// If the time is "zero", then the deployment never ends.
|
||||
case m.endTime.IsZero():
|
||||
return false, nil
|
||||
}
|
||||
medianTime, err := m.blockClock.PastMedianTime(blkHeader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// We check both after and equal here as after will fail for equivalent
|
||||
// times, and we want to be inclusive.
|
||||
return medianTime.After(m.endTime) || medianTime.Equal(m.endTime), nil
|
||||
}
|
||||
|
||||
// EndTime returns the raw end time of the deployment.
|
||||
func (m *MedianTimeDeploymentEnder) EndTime() time.Time {
|
||||
return m.endTime
|
||||
}
|
||||
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentEnder with the
|
||||
// current up-to date BlockClock.
|
||||
func (m *MedianTimeDeploymentEnder) SynchronizeClock(clock BlockClock) {
|
||||
m.blockClock = clock
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure MedianTimeDeploymentEnder implements the
|
||||
// ClockConsensusDeploymentStarter interface.
|
||||
var _ ClockConsensusDeploymentEnder = (*MedianTimeDeploymentEnder)(nil)
|
||||
102
ec/chaincfg/genesis.go
Normal file
102
ec/chaincfg/genesis.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package chaincfg
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mleku/manifold/ec/chainhash"
|
||||
"github.com/mleku/manifold/ec/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for
|
||||
// the main network, regression test network, and test network (version 3).
|
||||
genesisCoinbaseTx = wire.MsgTx{
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04,
|
||||
0x45, /* |.......E| */
|
||||
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d,
|
||||
0x65, /* |The Time| */
|
||||
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61,
|
||||
0x6e, /* |s 03/Jan| */
|
||||
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43,
|
||||
0x68, /* |/2009 Ch| */
|
||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f,
|
||||
0x72, /* |ancellor| */
|
||||
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69,
|
||||
0x6e, /* | on brin| */
|
||||
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65,
|
||||
0x63, /* |k of sec|*/
|
||||
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69,
|
||||
0x6c, /* |ond bail| */
|
||||
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72,
|
||||
0x20, /* |out for |*/
|
||||
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
||||
},
|
||||
Sequence: 0xffffffff,
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: 0x12a05f200,
|
||||
PkScript: []byte{
|
||||
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe,
|
||||
0x55, /* |A.g....U| */
|
||||
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71,
|
||||
0x30, /* |H'.g..q0| */
|
||||
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0,
|
||||
0x39, /* |..\..(.9| */
|
||||
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f,
|
||||
0x61, /* |..yb...a| */
|
||||
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c,
|
||||
0xef, /* |..I..?L.| */
|
||||
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e,
|
||||
0xc1, /* |8..U....| */
|
||||
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba,
|
||||
0x0b, /* |..\8M...| */
|
||||
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b,
|
||||
0xf1, /* |.W.Lp+k.| */
|
||||
0x1d, 0x5f, 0xac, /* |._.| */
|
||||
},
|
||||
},
|
||||
},
|
||||
LockTime: 0,
|
||||
}
|
||||
// genesisHash is the hash of the first block in the block chain for the main
|
||||
// network (genesis block).
|
||||
genesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
// genesisMerkleRoot is the hash of the first transaction in the genesis block
|
||||
// for the main network.
|
||||
genesisMerkleRoot = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
||||
})
|
||||
// genesisBlock defines
|
||||
// genesisBlock defines the genesis block of the block chain which serves as the
|
||||
// public transaction ledger for the main network.
|
||||
genesisBlock = wire.MsgBlock{
|
||||
Header: wire.BlockHeader{
|
||||
Version: 1,
|
||||
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
||||
MerkleRoot: genesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
|
||||
Timestamp: time.Unix(0x495fab29,
|
||||
0), // 2009-01-03 18:15:05 +0000 UTC
|
||||
Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
|
||||
Nonce: 0x7c2bac1d, // 2083236893
|
||||
},
|
||||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
)
|
||||
429
ec/chaincfg/params.go
Normal file
429
ec/chaincfg/params.go
Normal file
@@ -0,0 +1,429 @@
|
||||
// Package chaincfg provides basic parameters for bitcoin chain and testnets.
|
||||
package chaincfg
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/mleku/manifold/ec/chainhash"
|
||||
"github.com/mleku/manifold/ec/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// bigOne is 1 represented as a big.Int. It is defined here to avoid
|
||||
// the overhead of creating it multiple times.
|
||||
bigOne = big.NewInt(1)
|
||||
|
||||
// mainPowLimit is the highest proof of work value a Bitcoin block can
|
||||
// have for the main network. It is the value 2^224 - 1.
|
||||
mainPowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 224), bigOne)
|
||||
)
|
||||
|
||||
// Constants that define the deployment offset in the deployments field of the
|
||||
// parameters for each deployment. This is useful to be able to get the details
|
||||
// of a specific deployment by name.
|
||||
const (
|
||||
// DeploymentTestDummy defines the rule change deployment ID for testing
|
||||
// purposes.
|
||||
DeploymentTestDummy = iota
|
||||
|
||||
// DeploymentTestDummyMinActivation defines the rule change deployment
|
||||
// ID for testing purposes. This differs from the DeploymentTestDummy
|
||||
// in that it specifies the newer params the taproot fork used for
|
||||
// activation: a custom threshold and a min activation height.
|
||||
DeploymentTestDummyMinActivation
|
||||
|
||||
// DeploymentCSV defines the rule change deployment ID for the CSV
|
||||
// soft-fork package. The CSV package includes the deployment of BIPS
|
||||
// 68, 112, and 113.
|
||||
DeploymentCSV
|
||||
|
||||
// DeploymentSegwit defines the rule change deployment ID for the
|
||||
// Segregated Witness (segwit) soft-fork package. The segwit package
|
||||
// includes the deployment of BIPS 141, 142, 144, 145, 147 and 173.
|
||||
DeploymentSegwit
|
||||
|
||||
// DeploymentTaproot defines the rule change deployment ID for the
|
||||
// Taproot (+Schnorr) soft-fork package. The taproot package includes
|
||||
// the deployment of BIPS 340, 341 and 342.
|
||||
DeploymentTaproot
|
||||
|
||||
// NOTE: DefinedDeployments must always come last since it is used to
|
||||
// determine how many defined deployments there currently are.
|
||||
|
||||
// DefinedDeployments is the number of currently defined deployments.
|
||||
DefinedDeployments
|
||||
)
|
||||
|
||||
// ConsensusDeployment defines details related to a specific consensus rule
|
||||
// change that is voted in. This is part of BIP0009.
|
||||
type ConsensusDeployment struct {
|
||||
// BitNumber defines the specific bit number within the block version
|
||||
// this particular soft-fork deployment refers to.
|
||||
BitNumber uint8
|
||||
|
||||
// MinActivationHeight is an optional field that when set (default
|
||||
// value being zero), modifies the traditional BIP 9 state machine by
|
||||
// only transitioning from LockedIn to Active once the block height is
|
||||
// greater than (or equal to) thus specified height.
|
||||
MinActivationHeight uint32
|
||||
|
||||
// CustomActivationThreshold if set (non-zero), will _override_ the
|
||||
// existing RuleChangeActivationThreshold value set at the
|
||||
// network/chain level. This value divided by the active
|
||||
// MinerConfirmationWindow denotes the threshold required for
|
||||
// activation. A value of 1815 block denotes a 90% threshold.
|
||||
CustomActivationThreshold uint32
|
||||
|
||||
// DeploymentStarter is used to determine if the given
|
||||
// ConsensusDeployment has started or not.
|
||||
DeploymentStarter ConsensusDeploymentStarter
|
||||
|
||||
// DeploymentEnder is used to determine if the given
|
||||
// ConsensusDeployment has ended or not.
|
||||
DeploymentEnder ConsensusDeploymentEnder
|
||||
}
|
||||
|
||||
// Checkpoint identifies a known good point in the block chain. Using
|
||||
// checkpoints allows a few optimizations for old blocks during initial download
|
||||
// and also prevents forks from old blocks.
|
||||
//
|
||||
// Each checkpoint is selected based upon several factors. See the
|
||||
// documentation for blockchain.IsCheckpointCandidate for details on the
|
||||
// selection criteria.
|
||||
type Checkpoint struct {
|
||||
Height int32
|
||||
Hash *chainhash.Hash
|
||||
}
|
||||
|
||||
// DNSSeed identifies a DNS seed.
|
||||
type DNSSeed struct {
|
||||
// Host defines the hostname of the seed.
|
||||
Host string
|
||||
|
||||
// HasFiltering defines whether the seed supports filtering
|
||||
// by service flags (wire.ServiceFlag).
|
||||
HasFiltering bool
|
||||
}
|
||||
|
||||
// Params defines a Bitcoin network by its parameters. These parameters may be
|
||||
// used by Bitcoin applications to differentiate networks as well as addresses
|
||||
// and keys for one network from those intended for use on another network.
|
||||
type Params struct {
|
||||
// Name defines a human-readable identifier for the network.
|
||||
Name string
|
||||
|
||||
// Net defines the magic bytes used to identify the network.
|
||||
Net wire.BitcoinNet
|
||||
|
||||
// DefaultPort defines the default peer-to-peer port for the network.
|
||||
DefaultPort string
|
||||
|
||||
// DNSSeeds defines a list of DNS seeds for the network that are used
|
||||
// as one method to discover peers.
|
||||
DNSSeeds []DNSSeed
|
||||
|
||||
// GenesisBlock defines the first block of the chain.
|
||||
GenesisBlock *wire.MsgBlock
|
||||
|
||||
// GenesisHash is the starting block hash.
|
||||
GenesisHash *chainhash.Hash
|
||||
|
||||
// PowLimit defines the highest allowed proof of work value for a block
|
||||
// as a uint256.
|
||||
PowLimit *big.Int
|
||||
|
||||
// PowLimitBits defines the highest allowed proof of work value for a
|
||||
// block in compact form.
|
||||
PowLimitBits uint32
|
||||
|
||||
// PoWNoRetargeting defines whether the network has difficulty
|
||||
// retargeting enabled or not. This should only be set to true for
|
||||
// regtest like networks.
|
||||
PoWNoRetargeting bool
|
||||
|
||||
// These fields define the block heights at which the specified softfork
|
||||
// BIP became active.
|
||||
BIP0034Height int32
|
||||
BIP0065Height int32
|
||||
BIP0066Height int32
|
||||
|
||||
// CoinbaseMaturity is the number of blocks required before newly mined
|
||||
// coins (coinbase transactions) can be spent.
|
||||
CoinbaseMaturity uint16
|
||||
|
||||
// SubsidyReductionInterval is the interval of blocks before the subsidy
|
||||
// is reduced.
|
||||
SubsidyReductionInterval int32
|
||||
|
||||
// TargetTimespan is the desired amount of time that should elapse
|
||||
// before the block difficulty requirement is examined to determine how
|
||||
// it should be changed in order to maintain the desired block
|
||||
// generation rate.
|
||||
TargetTimespan time.Duration
|
||||
|
||||
// TargetTimePerBlock is the desired amount of time to generate each
|
||||
// block.
|
||||
TargetTimePerBlock time.Duration
|
||||
|
||||
// RetargetAdjustmentFactor is the adjustment factor used to limit
|
||||
// the minimum and maximum amount of adjustment that can occur between
|
||||
// difficulty retargets.
|
||||
RetargetAdjustmentFactor int64
|
||||
|
||||
// ReduceMinDifficulty defines whether the network should reduce the
|
||||
// minimum required difficulty after a long enough period of time has
|
||||
// passed without finding a block. This is really only useful for test
|
||||
// networks and should not be set on a main network.
|
||||
ReduceMinDifficulty bool
|
||||
|
||||
// MinDiffReductionTime is the amount of time after which the minimum
|
||||
// required difficulty should be reduced when a block hasn't been found.
|
||||
//
|
||||
// NOTE: This only applies if ReduceMinDifficulty is true.
|
||||
MinDiffReductionTime time.Duration
|
||||
|
||||
// GenerateSupported specifies whether or not CPU mining is allowed.
|
||||
GenerateSupported bool
|
||||
|
||||
// Checkpoints ordered from oldest to newest.
|
||||
Checkpoints []Checkpoint
|
||||
|
||||
// These fields are related to voting on consensus rule changes as
|
||||
// defined by BIP0009.
|
||||
//
|
||||
// RuleChangeActivationThreshold is the number of blocks in a threshold
|
||||
// state retarget window for which a positive vote for a rule change
|
||||
// must be cast in order to lock in a rule change. It should typically
|
||||
// be 95% for the main network and 75% for test networks.
|
||||
//
|
||||
// MinerConfirmationWindow is the number of blocks in each threshold
|
||||
// state retarget window.
|
||||
//
|
||||
// Deployments define the specific consensus rule changes to be voted
|
||||
// on.
|
||||
RuleChangeActivationThreshold uint32
|
||||
MinerConfirmationWindow uint32
|
||||
Deployments [DefinedDeployments]ConsensusDeployment
|
||||
|
||||
// Mempool parameters
|
||||
RelayNonStdTxs bool
|
||||
|
||||
// Human-readable part for Bech32 encoded segwit addresses, as defined
|
||||
// in BIP 173.
|
||||
Bech32HRPSegwit []byte
|
||||
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID byte // First byte of a P2PKH address
|
||||
ScriptHashAddrID byte // First byte of a P2SH address
|
||||
PrivateKeyID byte // First byte of a WIF private key
|
||||
WitnessPubKeyHashAddrID byte // First byte of a P2WPKH address
|
||||
WitnessScriptHashAddrID byte // First byte of a P2WSH address
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID [4]byte
|
||||
HDPublicKeyID [4]byte
|
||||
|
||||
// BIP44 coin type used in the hierarchical deterministic path for
|
||||
// address generation.
|
||||
HDCoinType uint32
|
||||
}
|
||||
|
||||
// MainNetParams defines the network parameters for the main Bitcoin network.
|
||||
var MainNetParams = Params{
|
||||
Name: "mainnet",
|
||||
Net: wire.MainNet,
|
||||
DefaultPort: "8333",
|
||||
DNSSeeds: []DNSSeed{
|
||||
{"seed.bitcoin.sipa.be", true},
|
||||
{"dnsseed.bluematt.me", true},
|
||||
{"dnsseed.bitcoin.dashjr.org", false},
|
||||
{"seed.bitcoinstats.com", true},
|
||||
{"seed.bitnodes.io", false},
|
||||
{"seed.bitcoin.jonasschnelli.ch", true},
|
||||
},
|
||||
|
||||
// Chain parameters
|
||||
GenesisBlock: &genesisBlock,
|
||||
GenesisHash: &genesisHash,
|
||||
PowLimit: mainPowLimit,
|
||||
PowLimitBits: 0x1d00ffff,
|
||||
BIP0034Height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
|
||||
BIP0065Height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
|
||||
BIP0066Height: 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931
|
||||
CoinbaseMaturity: 100,
|
||||
SubsidyReductionInterval: 210000,
|
||||
TargetTimespan: time.Hour * 24 * 14, // 14 days
|
||||
TargetTimePerBlock: time.Minute * 10, // 10 minutes
|
||||
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
||||
ReduceMinDifficulty: false,
|
||||
MinDiffReductionTime: 0,
|
||||
GenerateSupported: false,
|
||||
|
||||
// Checkpoints ordered from oldest to newest.
|
||||
Checkpoints: []Checkpoint{
|
||||
{11111,
|
||||
newHashFromStr("0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")},
|
||||
{33333,
|
||||
newHashFromStr("000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")},
|
||||
{74000,
|
||||
newHashFromStr("0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")},
|
||||
{105000,
|
||||
newHashFromStr("00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")},
|
||||
{134444,
|
||||
newHashFromStr("00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")},
|
||||
{168000,
|
||||
newHashFromStr("000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")},
|
||||
{193000,
|
||||
newHashFromStr("000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317")},
|
||||
{210000,
|
||||
newHashFromStr("000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e")},
|
||||
{216116,
|
||||
newHashFromStr("00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e")},
|
||||
{225430,
|
||||
newHashFromStr("00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")},
|
||||
{250000,
|
||||
newHashFromStr("000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")},
|
||||
{267300,
|
||||
newHashFromStr("000000000000000a83fbd660e918f218bf37edd92b748ad940483c7c116179ac")},
|
||||
{279000,
|
||||
newHashFromStr("0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")},
|
||||
{300255,
|
||||
newHashFromStr("0000000000000000162804527c6e9b9f0563a280525f9d08c12041def0a0f3b2")},
|
||||
{319400,
|
||||
newHashFromStr("000000000000000021c6052e9becade189495d1c539aa37c58917305fd15f13b")},
|
||||
{343185,
|
||||
newHashFromStr("0000000000000000072b8bf361d01a6ba7d445dd024203fafc78768ed4368554")},
|
||||
{352940,
|
||||
newHashFromStr("000000000000000010755df42dba556bb72be6a32f3ce0b6941ce4430152c9ff")},
|
||||
{382320,
|
||||
newHashFromStr("00000000000000000a8dc6ed5b133d0eb2fd6af56203e4159789b092defd8ab2")},
|
||||
{400000,
|
||||
newHashFromStr("000000000000000004ec466ce4732fe6f1ed1cddc2ed4b328fff5224276e3f6f")},
|
||||
{430000,
|
||||
newHashFromStr("000000000000000001868b2bb3a285f3cc6b33ea234eb70facf4dcdf22186b87")},
|
||||
{460000,
|
||||
newHashFromStr("000000000000000000ef751bbce8e744ad303c47ece06c8d863e4d417efc258c")},
|
||||
{490000,
|
||||
newHashFromStr("000000000000000000de069137b17b8d5a3dfbd5b145b2dcfb203f15d0c4de90")},
|
||||
{520000,
|
||||
newHashFromStr("0000000000000000000d26984c0229c9f6962dc74db0a6d525f2f1640396f69c")},
|
||||
{550000,
|
||||
newHashFromStr("000000000000000000223b7a2298fb1c6c75fb0efc28a4c56853ff4112ec6bc9")},
|
||||
{560000,
|
||||
newHashFromStr("0000000000000000002c7b276daf6efb2b6aa68e2ce3be67ef925b3264ae7122")},
|
||||
{563378,
|
||||
newHashFromStr("0000000000000000000f1c54590ee18d15ec70e68c8cd4cfbadb1b4f11697eee")},
|
||||
{597379,
|
||||
newHashFromStr("00000000000000000005f8920febd3925f8272a6a71237563d78c2edfdd09ddf")},
|
||||
{623950,
|
||||
newHashFromStr("0000000000000000000f2adce67e49b0b6bdeb9de8b7c3d7e93b21e7fc1e819d")},
|
||||
{654683,
|
||||
newHashFromStr("0000000000000000000b9d2ec5a352ecba0592946514a92f14319dc2b367fc72")},
|
||||
{691719,
|
||||
newHashFromStr("00000000000000000008a89e854d57e5667df88f1cdef6fde2fbca1de5b639ad")},
|
||||
{724466,
|
||||
newHashFromStr("000000000000000000052d314a259755ca65944e68df6b12a067ea8f1f5a7091")},
|
||||
{751565,
|
||||
newHashFromStr("00000000000000000009c97098b5295f7e5f183ac811fb5d1534040adb93cabd")},
|
||||
},
|
||||
|
||||
// Consensus rule change deployments.
|
||||
//
|
||||
// The miner confirmation window is defined as:
|
||||
// target proof of work timespan / target proof of work spacing
|
||||
RuleChangeActivationThreshold: 1916, // 95% of MinerConfirmationWindow
|
||||
MinerConfirmationWindow: 2016, //
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(11991456010, 0), // January 1, 2008 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1230767999, 0), // December 31, 2008 UTC
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 1815, // Only needs 90% hash rate.
|
||||
MinActivationHeight: 10_0000, // Can only activate after height 10k.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 0,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1462060800, 0), // May 1st, 2016
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1493596800, 0), // May 1st, 2017
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 1,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1479168000, 0), // November 15, 2016 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1510704000, 0), // November 15, 2017 UTC.
|
||||
),
|
||||
},
|
||||
DeploymentTaproot: {
|
||||
BitNumber: 2,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1619222400, 0), // April 24th, 2021 UTC.
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1628640000, 0), // August 11th, 2021 UTC.
|
||||
),
|
||||
CustomActivationThreshold: 1815, // 90%
|
||||
MinActivationHeight: 709_632,
|
||||
},
|
||||
},
|
||||
|
||||
// Mempool parameters
|
||||
RelayNonStdTxs: false,
|
||||
|
||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||
// BIP 173.
|
||||
Bech32HRPSegwit: []byte("bc"), // always bc for main net
|
||||
|
||||
// Address encoding magics
|
||||
PubKeyHashAddrID: 0x00, // starts with 1
|
||||
ScriptHashAddrID: 0x05, // starts with 3
|
||||
PrivateKeyID: 0x80, // starts with 5 (uncompressed) or K (compressed)
|
||||
WitnessPubKeyHashAddrID: 0x06, // starts with p2
|
||||
WitnessScriptHashAddrID: 0x0A, // starts with 7Xh
|
||||
|
||||
// BIP32 hierarchical deterministic extended key magics
|
||||
HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4}, // starts with xprv
|
||||
HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e}, // starts with xpub
|
||||
|
||||
// BIP44 coin type used in the hierarchical deterministic path for
|
||||
// address generation.
|
||||
HDCoinType: 0,
|
||||
}
|
||||
|
||||
// newHashFromStr converts the passed big-endian hex string into a
|
||||
// chainhash.Hash. It only differs from the one available in chainhash in that
|
||||
// it panics on an error since it will only (and must only) be called with
|
||||
// hard-coded, and therefore known good, hashes.
|
||||
func newHashFromStr(hexStr string) *chainhash.Hash {
|
||||
hash, err := chainhash.NewHashFromStr(hexStr)
|
||||
if err != nil {
|
||||
// Ordinarily I don't like panics in library code since it
|
||||
// can take applications down without them having a chance to
|
||||
// recover which is extremely annoying, however an exception is
|
||||
// being made in this case because the only way this can panic
|
||||
// is if there is an error in the hard-coded hashes. Thus it
|
||||
// will only ever potentially panic on init and therefore is
|
||||
// 100% predictable.
|
||||
panic(err)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
19
ec/chainhash/README.md
Normal file
19
ec/chainhash/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
chainhash
|
||||
=========
|
||||
|
||||
[](http://copyfree.org)
|
||||
=======
|
||||
|
||||
chainhash provides a generic hash type and associated functions that allows the
|
||||
specific hash algorithm to be abstracted.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u mleku.online/git/ec/chainhash
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Package chainhash is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
5
ec/chainhash/doc.go
Normal file
5
ec/chainhash/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package chainhash provides abstracted hash functionality.
|
||||
//
|
||||
// This package provides a generic hash type and associated functions that
|
||||
// allows the specific hash algorithm to be abstracted.
|
||||
package chainhash
|
||||
224
ec/chainhash/hash.go
Normal file
224
ec/chainhash/hash.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Copyright (c) 2015 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chainhash
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
|
||||
"github.com/mleku/manifold/hex"
|
||||
)
|
||||
|
||||
const (
|
||||
// HashSize of array used to store hashes. See Hash.
|
||||
HashSize = 32
|
||||
// MaxHashStringSize is the maximum length of a Hash hash string.
|
||||
MaxHashStringSize = HashSize * 2
|
||||
)
|
||||
|
||||
var (
|
||||
// TagBIP0340Challenge is the BIP-0340 tag for challenges.
|
||||
TagBIP0340Challenge = []byte("BIP0340/challenge")
|
||||
// TagBIP0340Aux is the BIP-0340 tag for aux data.
|
||||
TagBIP0340Aux = []byte("BIP0340/aux")
|
||||
// TagBIP0340Nonce is the BIP-0340 tag for nonces.
|
||||
TagBIP0340Nonce = []byte("BIP0340/nonce")
|
||||
// TagTapSighash is the tag used by BIP 341 to generate the sighash
|
||||
// flags.
|
||||
TagTapSighash = []byte("TapSighash")
|
||||
// TagTapLeaf is the message tag prefix used to compute the hash
|
||||
// digest of a tapscript leaf.
|
||||
TagTapLeaf = []byte("TapLeaf")
|
||||
// TagTapBranch is the message tag prefix used to compute the
|
||||
// hash digest of two tap leaves into a taproot branch node.
|
||||
TagTapBranch = []byte("TapBranch")
|
||||
// TagTapTweak is the message tag prefix used to compute the hash tweak
|
||||
// used to enable a public key to commit to the taproot branch root
|
||||
// for the witness program.
|
||||
TagTapTweak = []byte("TapTweak")
|
||||
// precomputedTags is a map containing the SHA-256 hash of the BIP-0340
|
||||
// tags.
|
||||
precomputedTags = map[string]Hash{
|
||||
string(TagBIP0340Challenge): sha256.Sum256(TagBIP0340Challenge),
|
||||
string(TagBIP0340Aux): sha256.Sum256(TagBIP0340Aux),
|
||||
string(TagBIP0340Nonce): sha256.Sum256(TagBIP0340Nonce),
|
||||
string(TagTapSighash): sha256.Sum256(TagTapSighash),
|
||||
string(TagTapLeaf): sha256.Sum256(TagTapLeaf),
|
||||
string(TagTapBranch): sha256.Sum256(TagTapBranch),
|
||||
string(TagTapTweak): sha256.Sum256(TagTapTweak),
|
||||
}
|
||||
)
|
||||
|
||||
// ErrHashStrSize describes an error that indicates the caller specified a hash
|
||||
// string that has too many characters.
|
||||
var ErrHashStrSize = fmt.Errorf("max hash string length is %v bytes",
|
||||
MaxHashStringSize)
|
||||
|
||||
// Hash is used in several of the bitcoin messages and common structures. It
|
||||
// typically represents the double sha256 of data.
|
||||
type Hash [HashSize]byte
|
||||
|
||||
// String returns the Hash as the hexadecimal string of the byte-reversed
|
||||
// hash.
|
||||
func (hash Hash) String() string {
|
||||
for i := 0; i < HashSize/2; i++ {
|
||||
hash[i], hash[HashSize-1-i] = hash[HashSize-1-i], hash[i]
|
||||
}
|
||||
return hex.Enc(hash[:])
|
||||
}
|
||||
|
||||
// CloneBytes returns a copy of the bytes which represent the hash as a byte
|
||||
// slice.
|
||||
//
|
||||
// NOTE: It is generally cheaper to just slice the hash directly thereby reusing
|
||||
// the same bytes rather than calling this method.
|
||||
func (hash *Hash) CloneBytes() []byte {
|
||||
newHash := make([]byte, HashSize)
|
||||
copy(newHash, hash[:])
|
||||
return newHash
|
||||
}
|
||||
|
||||
// SetBytes sets the bytes which represent the hash. An error is returned if
|
||||
// the number of bytes passed in is not HashSize.
|
||||
func (hash *Hash) SetBytes(newHash []byte) error {
|
||||
nhlen := len(newHash)
|
||||
if nhlen != HashSize {
|
||||
return fmt.Errorf("invalid hash length of %v, want %v", nhlen,
|
||||
HashSize)
|
||||
}
|
||||
copy(hash[:], newHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEqual returns true if target is the same as hash.
|
||||
func (hash *Hash) IsEqual(target *Hash) bool {
|
||||
if hash == nil && target == nil {
|
||||
return true
|
||||
}
|
||||
if hash == nil || target == nil {
|
||||
return false
|
||||
}
|
||||
return *hash == *target
|
||||
}
|
||||
|
||||
// MarshalJSON serialises the hash as a JSON appropriate string value.
|
||||
func (hash Hash) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(hash.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the hash with JSON appropriate string value.
|
||||
func (hash *Hash) UnmarshalJSON(input []byte) error {
|
||||
// If the first byte indicates an array, the hash could have been marshalled
|
||||
// using the legacy method and e.g. persisted.
|
||||
if len(input) > 0 && input[0] == '[' {
|
||||
return decodeLegacy(hash, input)
|
||||
}
|
||||
var sh string
|
||||
err := json.Unmarshal(input, &sh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newHash, err := NewHashFromStr(sh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hash.SetBytes(newHash[:])
|
||||
}
|
||||
|
||||
// NewHash returns a new Hash from a byte slice. An error is returned if
|
||||
// the number of bytes passed in is not HashSize.
|
||||
func NewHash(newHash []byte) (*Hash, error) {
|
||||
var sh Hash
|
||||
err := sh.SetBytes(newHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sh, err
|
||||
}
|
||||
|
||||
// TaggedHash implements the tagged hash scheme described in BIP-340. We use
|
||||
// sha-256 to bind a message hash to a specific context using a tag:
|
||||
// sha256(sha256(tag) || sha256(tag) || msg).
|
||||
func TaggedHash(tag []byte, msgs ...[]byte) *Hash {
|
||||
// Check to see if we've already pre-computed the hash of the tag. If
|
||||
// so then this'll save us an extra sha256 hash.
|
||||
shaTag, ok := precomputedTags[string(tag)]
|
||||
if !ok {
|
||||
shaTag = sha256.Sum256(tag)
|
||||
}
|
||||
// h = sha256(sha256(tag) || sha256(tag) || msg)
|
||||
h := sha256.New()
|
||||
h.Write(shaTag[:])
|
||||
h.Write(shaTag[:])
|
||||
for _, msg := range msgs {
|
||||
h.Write(msg)
|
||||
}
|
||||
taggedHash := h.Sum(nil)
|
||||
// The function can't error out since the above hash is guaranteed to
|
||||
// be 32 bytes.
|
||||
hash, _ := NewHash(taggedHash)
|
||||
return hash
|
||||
}
|
||||
|
||||
// NewHashFromStr creates a Hash from a hash string. The string should be
|
||||
// the hexadecimal string of a byte-reversed hash, but any missing characters
|
||||
// result in zero padding at the end of the Hash.
|
||||
func NewHashFromStr(hash string) (*Hash, error) {
|
||||
ret := new(Hash)
|
||||
err := Decode(ret, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Decode decodes the byte-reversed hexadecimal string encoding of a Hash to a
|
||||
// destination.
|
||||
func Decode(dst *Hash, src string) error {
|
||||
// Return error if hash string is too long.
|
||||
if len(src) > MaxHashStringSize {
|
||||
return ErrHashStrSize
|
||||
}
|
||||
// Hex decoder expects the hash to be a multiple of two. When not, pad
|
||||
// with a leading zero.
|
||||
var srcBytes []byte
|
||||
if len(src)%2 == 0 {
|
||||
srcBytes = []byte(src)
|
||||
} else {
|
||||
srcBytes = make([]byte, 1+len(src))
|
||||
srcBytes[0] = '0'
|
||||
copy(srcBytes[1:], src)
|
||||
}
|
||||
// Hex decode the source bytes to a temporary destination.
|
||||
var reversedHash Hash
|
||||
_, err := hex.DecAppend(reversedHash[HashSize-hex.DecLen(len(srcBytes)):],
|
||||
srcBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Reverse copy from the temporary hash to destination. Because the
|
||||
// temporary was zeroed, the written result will be correctly padded.
|
||||
for i, b := range reversedHash[:HashSize/2] {
|
||||
dst[i], dst[HashSize-1-i] = reversedHash[HashSize-1-i], b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeLegacy decodes an Hash that has been encoded with the legacy method
|
||||
// (i.e. represented as a bytes array) to a destination.
|
||||
func decodeLegacy(dst *Hash, src []byte) error {
|
||||
var hashBytes []byte
|
||||
err := json.Unmarshal(src, &hashBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hashBytes) != HashSize {
|
||||
return ErrHashStrSize
|
||||
}
|
||||
return dst.SetBytes(hashBytes)
|
||||
}
|
||||
211
ec/chainhash/hash_test.go
Normal file
211
ec/chainhash/hash_test.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chainhash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mainNetGenesisHash is the hash of the first block in the block chain for the
|
||||
// main network (genesis block).
|
||||
var mainNetGenesisHash = Hash([HashSize]byte{ // Make go vet happy.
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
|
||||
// TestHash tests the Hash API.
|
||||
func TestHash(t *testing.T) {
|
||||
// Hash of block 234439.
|
||||
blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef"
|
||||
blockHash, err := NewHashFromStr(blockHashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
// Hash of block 234440 as byte slice.
|
||||
buf := []byte{
|
||||
0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1,
|
||||
0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8,
|
||||
0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f,
|
||||
0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
hash, err := NewHash(buf)
|
||||
if err != nil {
|
||||
t.Errorf("NewHash: unexpected error %v", err)
|
||||
}
|
||||
// Ensure proper size.
|
||||
if len(hash) != HashSize {
|
||||
t.Errorf("NewHash: hash length mismatch - got: %v, want: %v",
|
||||
len(hash), HashSize)
|
||||
}
|
||||
// Ensure contents match.
|
||||
if !bytes.Equal(hash[:], buf) {
|
||||
t.Errorf("NewHash: hash contents mismatch - got: %v, want: %v",
|
||||
hash[:], buf)
|
||||
}
|
||||
// Ensure contents of hash of block 234440 don't match 234439.
|
||||
if hash.IsEqual(blockHash) {
|
||||
t.Errorf("IsEqual: hash contents should not match - got: %v, want: %v",
|
||||
hash, blockHash)
|
||||
}
|
||||
// Set hash from byte slice and ensure contents match.
|
||||
err = hash.SetBytes(blockHash.CloneBytes())
|
||||
if err != nil {
|
||||
t.Errorf("SetBytes: %v", err)
|
||||
}
|
||||
if !hash.IsEqual(blockHash) {
|
||||
t.Errorf("IsEqual: hash contents mismatch - got: %v, want: %v",
|
||||
hash, blockHash)
|
||||
}
|
||||
// Ensure nil hashes are handled properly.
|
||||
if !(*Hash)(nil).IsEqual(nil) {
|
||||
t.Error("IsEqual: nil hashes should match")
|
||||
}
|
||||
if hash.IsEqual(nil) {
|
||||
t.Error("IsEqual: non-nil hash matches nil hash")
|
||||
}
|
||||
// Invalid size for SetBytes.
|
||||
err = hash.SetBytes([]byte{0x00})
|
||||
if err == nil {
|
||||
t.Errorf("SetBytes: failed to received expected err - got: nil")
|
||||
}
|
||||
// Invalid size for NewHash.
|
||||
invalidHash := make([]byte, HashSize+1)
|
||||
_, err = NewHash(invalidHash)
|
||||
if err == nil {
|
||||
t.Errorf("NewHash: failed to received expected err - got: nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHashString tests the stringized output for hashes.
|
||||
func TestHashString(t *testing.T) {
|
||||
// Block 100000 hash.
|
||||
wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
hash := Hash([HashSize]byte{ // Make go vet happy.
|
||||
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
hashStr := hash.String()
|
||||
if hashStr != wantStr {
|
||||
t.Errorf("String: wrong hash string - got %v, want %v",
|
||||
hashStr, wantStr)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: these fail for some reason
|
||||
// // TestNewHashFromStr executes tests against the NewHashFromStr function.
|
||||
// func TestNewHashFromStr(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// in string
|
||||
// want Hash
|
||||
// err error
|
||||
// }{
|
||||
// // Genesis hash.
|
||||
// {
|
||||
// "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||
// mainNetGenesisHash,
|
||||
// nil,
|
||||
// },
|
||||
// // Genesis hash with stripped leading zeros.
|
||||
// {
|
||||
// "19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||
// mainNetGenesisHash,
|
||||
// nil,
|
||||
// },
|
||||
// // Empty string.
|
||||
// {
|
||||
// "",
|
||||
// Hash{},
|
||||
// nil,
|
||||
// },
|
||||
// // Single digit hash.
|
||||
// {
|
||||
// "1",
|
||||
// Hash([HashSize]byte{ // Make go vet happy.
|
||||
// 0x01, 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,
|
||||
// }),
|
||||
// nil,
|
||||
// },
|
||||
// // Block 203707 with stripped leading zeros.
|
||||
// {
|
||||
// "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc",
|
||||
// Hash([HashSize]byte{ // Make go vet happy.
|
||||
// 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||
// 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||
// 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||
// 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// }),
|
||||
// nil,
|
||||
// },
|
||||
// // Hash string that is too long.
|
||||
// {
|
||||
// "01234567890123456789012345678901234567890123456789012345678912345",
|
||||
// Hash{},
|
||||
// ErrHashStrSize,
|
||||
// },
|
||||
// // Hash string that is contains non-hex chars.
|
||||
// {
|
||||
// "abcdefg",
|
||||
// Hash{},
|
||||
// hex.InvalidByteError('g'),
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// unexpectedErrStr := "NewHashFromStr #%d failed to detect expected error - got: %v want: %v"
|
||||
// unexpectedResultStr := "NewHashFromStr #%d got: %v want: %v"
|
||||
// t.Logf("Running %d tests", len(tests))
|
||||
// for i, test := range tests {
|
||||
// result, err := NewHashFromStr(test.in)
|
||||
// if err != test.err {
|
||||
// t.Errorf(unexpectedErrStr, i, err, test.err)
|
||||
// continue
|
||||
// } else if err != nil {
|
||||
// // Got expected error. Move on to the next test.
|
||||
// continue
|
||||
// }
|
||||
// if !test.want.IsEqual(result) {
|
||||
// t.Errorf(unexpectedResultStr, i, result, &test.want)
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // TestHashJsonMarshal tests json marshal and unmarshal.
|
||||
// func TestHashJsonMarshal(t *testing.T) {
|
||||
// hashStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
// legacyHashStr := []byte("[6,229,51,253,26,218,134,57,31,63,108,52,50,4,176,210,120,212,170,236,28,11,32,170,39,186,3,0,0,0,0,0]")
|
||||
// hash, err := NewHashFromStr(hashStr)
|
||||
// if err != nil {
|
||||
// t.Errorf("NewHashFromStr error:%v, hashStr:%s", err, hashStr)
|
||||
// }
|
||||
// hashBytes, err := json.Marshal(hash)
|
||||
// if err != nil {
|
||||
// t.Errorf("Marshal json error:%v, hash:%v", err, hashBytes)
|
||||
// }
|
||||
// var newHash Hash
|
||||
// err = json.Unmarshal(hashBytes, &newHash)
|
||||
// if err != nil {
|
||||
// t.Errorf("Unmarshal json error:%v, hash:%v", err, hashBytes)
|
||||
// }
|
||||
// if !hash.IsEqual(&newHash) {
|
||||
// t.Errorf("String: wrong hash string - got %v, want %v",
|
||||
// newHash.String(), hashStr)
|
||||
// }
|
||||
// err = newHash.Unmarshal(legacyHashStr)
|
||||
// if err != nil {
|
||||
// t.Errorf("Unmarshal legacy json error:%v, hash:%v", err, legacyHashStr)
|
||||
// }
|
||||
// if !hash.IsEqual(&newHash) {
|
||||
// t.Errorf("String: wrong hash string - got %v, want %v",
|
||||
// newHash.String(), hashStr)
|
||||
// }
|
||||
// }
|
||||
31
ec/chainhash/hashfuncs.go
Normal file
31
ec/chainhash/hashfuncs.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015 The Decred developers
|
||||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chainhash
|
||||
|
||||
import "github.com/minio/sha256-simd"
|
||||
|
||||
// HashB calculates hash(b) and returns the resulting bytes.
|
||||
func HashB(b []byte) []byte {
|
||||
hash := sha256.Sum256(b)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
// HashH calculates hash(b) and returns the resulting bytes as a Hash.
|
||||
func HashH(b []byte) Hash { return Hash(sha256.Sum256(b)) }
|
||||
|
||||
// DoubleHashB calculates hash(hash(b)) and returns the resulting bytes.
|
||||
func DoubleHashB(b []byte) []byte {
|
||||
first := sha256.Sum256(b)
|
||||
second := sha256.Sum256(first[:])
|
||||
return second[:]
|
||||
}
|
||||
|
||||
// DoubleHashH calculates hash(hash(b)) and returns the resulting bytes as a
|
||||
// Hash.
|
||||
func DoubleHashH(b []byte) Hash {
|
||||
first := sha256.Sum256(b)
|
||||
return sha256.Sum256(first[:])
|
||||
}
|
||||
195
ec/chainhash/hashfuncs_test.go
Normal file
195
ec/chainhash/hashfuncs_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chainhash
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestHashFuncs ensures the hash functions which perform hash(b) work as
|
||||
// expected.
|
||||
func TestHashFuncs(t *testing.T) {
|
||||
tests := []struct {
|
||||
out string
|
||||
in string
|
||||
}{
|
||||
{"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
""},
|
||||
{"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
|
||||
"a"},
|
||||
{"fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603",
|
||||
"ab"},
|
||||
{"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
"abc"},
|
||||
{"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
|
||||
"abcd"},
|
||||
{"36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c",
|
||||
"abcde"},
|
||||
{"bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721",
|
||||
"abcdef"},
|
||||
{"7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a",
|
||||
"abcdefg"},
|
||||
{"9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab",
|
||||
"abcdefgh"},
|
||||
{"19cc02f26df43cc571bc9ed7b0c4d29224a3ec229529221725ef76d021c8326f",
|
||||
"abcdefghi"},
|
||||
{"72399361da6a7754fec986dca5b7cbaf1c810a28ded4abaf56b2106d06cb78b0",
|
||||
"abcdefghij"},
|
||||
{"a144061c271f152da4d151034508fed1c138b8c976339de229c3bb6d4bbb4fce",
|
||||
"Discard medicine more than two years old."},
|
||||
{"6dae5caa713a10ad04b46028bf6dad68837c581616a1589a265a11288d4bb5c4",
|
||||
"He who has a shady past knows that nice guys finish last."},
|
||||
{"ae7a702a9509039ddbf29f0765e70d0001177914b86459284dab8b348c2dce3f",
|
||||
"I wouldn't marry him with a ten foot pole."},
|
||||
{"6748450b01c568586715291dfa3ee018da07d36bb7ea6f180c1af6270215c64f",
|
||||
"Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
|
||||
{"14b82014ad2b11f661b5ae6a99b75105c2ffac278cd071cd6c05832793635774",
|
||||
"The days of the digital watch are numbered. -Tom Stoppard"},
|
||||
{"7102cfd76e2e324889eece5d6c41921b1e142a4ac5a2692be78803097f6a48d8",
|
||||
"Nepal premier won't resign."},
|
||||
{"23b1018cd81db1d67983c5f7417c44da9deb582459e378d7a068552ea649dc9f",
|
||||
"For every action there is an equal and opposite government program."},
|
||||
{"8001f190dfb527261c4cfcab70c98e8097a7a1922129bc4096950e57c7999a5a",
|
||||
"His money is twice tainted: 'taint yours and 'taint mine."},
|
||||
{"8c87deb65505c3993eb24b7a150c4155e82eee6960cf0c3a8114ff736d69cad5",
|
||||
"There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
|
||||
{"bfb0a67a19cdec3646498b2e0f751bddc41bba4b7f30081b0b932aad214d16d7",
|
||||
"It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
|
||||
{"7f9a0b9bf56332e19f5a0ec1ad9c1425a153da1c624868fda44561d6b74daf36",
|
||||
"size: a.out: bad magic"},
|
||||
{"b13f81b8aad9e3666879af19886140904f7f429ef083286195982a7588858cfc",
|
||||
"The major problem is with sendmail. -Mark Horton"},
|
||||
{"b26c38d61519e894480c70c8374ea35aa0ad05b2ae3d6674eec5f52a69305ed4",
|
||||
"Give me a rock, paper and scissors and I will move the world. CCFestoon"},
|
||||
{"049d5e26d4f10222cd841a119e38bd8d2e0d1129728688449575d4ff42b842c1",
|
||||
"If the enemy is within range, then so are you."},
|
||||
{"0e116838e3cc1c1a14cd045397e29b4d087aa11b0853fc69ec82e90330d60949",
|
||||
"It's well we cannot hear the screams/That we create in others' dreams."},
|
||||
{"4f7d8eb5bcf11de2a56b971021a444aa4eafd6ecd0f307b5109e4e776cd0fe46",
|
||||
"You remind me of a TV show, but that's all right: I watch it anyway."},
|
||||
{"61c0cc4c4bd8406d5120b3fb4ebc31ce87667c162f29468b3c779675a85aebce",
|
||||
"C is as portable as Stonehedge!!"},
|
||||
{"1fb2eb3688093c4a3f80cd87a5547e2ce940a4f923243a79a2a1e242220693ac",
|
||||
"Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
|
||||
{"395585ce30617b62c80b93e8208ce866d4edc811a177fdb4b82d3911d8696423",
|
||||
"The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
|
||||
{"4f9b189a13d030838269dce846b16a1ce9ce81fe63e65de2f636863336a98fe6",
|
||||
"How can you write a big system without C++? -Paul Glick"},
|
||||
}
|
||||
|
||||
// Ensure the hash function which returns a byte slice returns the
|
||||
// expected result.
|
||||
for _, test := range tests {
|
||||
h := fmt.Sprintf("%x", HashB([]byte(test.in)))
|
||||
if h != test.out {
|
||||
t.Errorf("HashB(%q) = %s, want %s", test.in, h, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Ensure the hash function which returns a Hash returns the expected
|
||||
// result.
|
||||
for _, test := range tests {
|
||||
hash := HashH([]byte(test.in))
|
||||
h := fmt.Sprintf("%x", hash[:])
|
||||
if h != test.out {
|
||||
t.Errorf("HashH(%q) = %s, want %s", test.in, h, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoubleHashFuncs ensures the hash functions which perform hash(hash(b))
|
||||
// work as expected.
|
||||
func TestDoubleHashFuncs(t *testing.T) {
|
||||
tests := []struct {
|
||||
out string
|
||||
in string
|
||||
}{
|
||||
{"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
|
||||
""},
|
||||
{"bf5d3affb73efd2ec6c36ad3112dd933efed63c4e1cbffcfa88e2759c144f2d8",
|
||||
"a"},
|
||||
{"a1ff8f1856b5e24e32e3882edd4a021f48f28a8b21854b77fdef25a97601aace",
|
||||
"ab"},
|
||||
{"4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358",
|
||||
"abc"},
|
||||
{"7e9c158ecd919fa439a7a214c9fc58b85c3177fb1613bdae41ee695060e11bc6",
|
||||
"abcd"},
|
||||
{"1d72b6eb7ba8b9709c790b33b40d8c46211958e13cf85dbcda0ed201a99f2fb9",
|
||||
"abcde"},
|
||||
{"ce65d4756128f0035cba4d8d7fae4e9fa93cf7fdf12c0f83ee4a0e84064bef8a",
|
||||
"abcdef"},
|
||||
{"dad6b965ad86b880ceb6993f98ebeeb242de39f6b87a458c6510b5a15ff7bbf1",
|
||||
"abcdefg"},
|
||||
{"b9b12e7125f73fda20b8c4161fb9b4b146c34cf88595a1e0503ca2cf44c86bc4",
|
||||
"abcdefgh"},
|
||||
{"546db09160636e98405fbec8464a84b6464b32514db259e235eae0445346ffb7",
|
||||
"abcdefghi"},
|
||||
{"27635cf23fdf8a10f4cb2c52ade13038c38718c6d7ca716bfe726111a57ad201",
|
||||
"abcdefghij"},
|
||||
{"ae0d8e0e7c0336f0c3a72cefa4f24b625a6a460417a921d066058a0b81e23429",
|
||||
"Discard medicine more than two years old."},
|
||||
{"eeb56d02cf638f87ea8f11ebd5b0201afcece984d87be458578d3cfb51978f1b",
|
||||
"He who has a shady past knows that nice guys finish last."},
|
||||
{"dc640bf529608a381ea7065ecbcd0443b95f6e4c008de6e134aff1d36bd4b9d8",
|
||||
"I wouldn't marry him with a ten foot pole."},
|
||||
{"42e54375e60535eb07fc15c6350e10f2c22526f84db1d6f6bba925e154486f33",
|
||||
"Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
|
||||
{"4ed6aa9b88c84afbf928710b03714de69e2ad967c6a78586069adcb4c470d150",
|
||||
"The days of the digital watch are numbered. -Tom Stoppard"},
|
||||
{"590c24d1877c1919fad12fe01a8796999e9d20cfbf9bc9bc72fa0bd69f0b04dd",
|
||||
"Nepal premier won't resign."},
|
||||
{"37d270687ee8ebafcd3c1a32f56e1e1304b3c93f252cb637d57a66d59c475eca",
|
||||
"For every action there is an equal and opposite government program."},
|
||||
{"306828fd89278838bb1c544c3032a1fd25ea65c40bba586437568828a5fbe944",
|
||||
"His money is twice tainted: 'taint yours and 'taint mine."},
|
||||
{"49965777eac71faf1e2fb0f6b239ba2fae770977940fd827bcbfe15def6ded53",
|
||||
"There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
|
||||
{"df99ee4e87dd3fb07922dee7735997bbae8f26db20c86137d4219fc4a37b77c3",
|
||||
"It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
|
||||
{"920667c84a15b5ee3df4620169f5c0ec930cea0c580858e50e68848871ed65b4",
|
||||
"size: a.out: bad magic"},
|
||||
{"5e817fe20848a4a3932db68e90f8d54ec1b09603f0c99fdc051892b776acd462",
|
||||
"The major problem is with sendmail. -Mark Horton"},
|
||||
{"6a9d47248ed38852f5f4b2e37e7dfad0ce8d1da86b280feef94ef267e468cff2",
|
||||
"Give me a rock, paper and scissors and I will move the world. CCFestoon"},
|
||||
{"2e7aa1b362c94efdbff582a8bd3f7f61c8ce4c25bbde658ef1a7ae1010e2126f",
|
||||
"If the enemy is within range, then so are you."},
|
||||
{"e6729d51240b1e1da76d822fd0c55c75e409bcb525674af21acae1f11667c8ca",
|
||||
"It's well we cannot hear the screams/That we create in others' dreams."},
|
||||
{"09945e4d2743eb669f85e4097aa1cc39ea680a0b2ae2a65a42a5742b3b809610",
|
||||
"You remind me of a TV show, but that's all right: I watch it anyway."},
|
||||
{"1018d8b2870a974887c5174360f0fbaf27958eef15b24522a605c5dae4ae0845",
|
||||
"C is as portable as Stonehedge!!"},
|
||||
{"97c76b83c6645c78c261dcdc55d44af02d9f1df8057f997fd08c310c903624d5",
|
||||
"Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
|
||||
{"6bcbf25469e9544c5b5806b24220554fedb6695ba9b1510a76837414f7adb113",
|
||||
"The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
|
||||
{"1041988b06835481f0845be2a54f4628e1da26145b2de7ad1be3bb643cef9d4f",
|
||||
"How can you write a big system without C++? -Paul Glick"},
|
||||
}
|
||||
// Ensure the hash function which returns a byte slice returns the
|
||||
// expected result.
|
||||
for _, test := range tests {
|
||||
h := fmt.Sprintf("%x", DoubleHashB([]byte(test.in)))
|
||||
if h != test.out {
|
||||
t.Errorf("DoubleHashB(%q) = %s, want %s", test.in, h,
|
||||
test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Ensure the hash function which returns a Hash returns the expected
|
||||
// result.
|
||||
for _, test := range tests {
|
||||
hash := DoubleHashH([]byte(test.in))
|
||||
h := fmt.Sprintf("%x", hash[:])
|
||||
if h != test.out {
|
||||
t.Errorf("DoubleHashH(%q) = %s, want %s", test.in, h,
|
||||
test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
16
ec/ciphering.go
Normal file
16
ec/ciphering.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
)
|
||||
|
||||
// GenerateSharedSecret generates a shared secret based on a secret key and a
|
||||
// public key using Diffie-Hellman key exchange (ECDH) (RFC 4753).
|
||||
// RFC5903 Section 9 states we should only return x.
|
||||
func GenerateSharedSecret(privkey *SecretKey, pubkey *PublicKey) []byte {
|
||||
return secp256k1.GenerateSharedSecret(privkey, pubkey)
|
||||
}
|
||||
29
ec/ciphering_test.go
Normal file
29
ec/ciphering_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateSharedSecret(t *testing.T) {
|
||||
privKey1, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Errorf("secret key generation error: %s", err)
|
||||
return
|
||||
}
|
||||
privKey2, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Errorf("secret key generation error: %s", err)
|
||||
return
|
||||
}
|
||||
secret1 := GenerateSharedSecret(privKey1, privKey2.PubKey())
|
||||
secret2 := GenerateSharedSecret(privKey2, privKey1.PubKey())
|
||||
if !bytes.Equal(secret1, secret2) {
|
||||
t.Errorf("ECDH failed, secrets mismatch - first: %x, second: %x",
|
||||
secret1, secret2)
|
||||
}
|
||||
}
|
||||
109
ec/curve.go
Normal file
109
ec/curve.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2015-2021 The btcsuite developers
|
||||
// Copyright (c) 2015-2021 The Decred developers
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
)
|
||||
|
||||
// JacobianPoint is an element of the group formed by the secp256k1 curve in
|
||||
// Jacobian projective coordinates and thus represents a point on the curve.
|
||||
type JacobianPoint = secp256k1.JacobianPoint
|
||||
|
||||
// infinityPoint is the jacobian representation of the point at infinity.
|
||||
var infinityPoint JacobianPoint
|
||||
|
||||
// MakeJacobianPoint returns a Jacobian point with the provided X, Y, and Z
|
||||
// coordinates.
|
||||
func MakeJacobianPoint(x, y, z *FieldVal) JacobianPoint {
|
||||
return secp256k1.MakeJacobianPoint(x, y, z)
|
||||
}
|
||||
|
||||
// AddNonConst adds the passed Jacobian points together and stores the result
|
||||
// in the provided result param in *non-constant* time.
|
||||
func AddNonConst(p1, p2, result *JacobianPoint) {
|
||||
secp256k1.AddNonConst(p1, p2, result)
|
||||
}
|
||||
|
||||
// DecompressY attempts to calculate the Y coordinate for the given X
|
||||
// coordinate such that the result pair is a point on the secp256k1 curve. It
|
||||
// adjusts Y based on the desired oddness and returns whether or not it was
|
||||
// successful since not all X coordinates are valid.
|
||||
//
|
||||
// The magnitude of the provided X coordinate field val must be a max of 8 for
|
||||
// a correct result. The resulting Y field val will have a max magnitude of 2.
|
||||
func DecompressY(x *FieldVal, odd bool, resultY *FieldVal) bool {
|
||||
return secp256k1.DecompressY(x, odd, resultY)
|
||||
}
|
||||
|
||||
// DoubleNonConst doubles the passed Jacobian point and stores the result in
|
||||
// the provided result parameter in *non-constant* time.
|
||||
//
|
||||
// NOTE: The point must be normalized for this function to return the correct
|
||||
// result. The resulting point will be normalized.
|
||||
func DoubleNonConst(p, result *JacobianPoint) {
|
||||
secp256k1.DoubleNonConst(p, result)
|
||||
}
|
||||
|
||||
// ScalarBaseMultNonConst multiplies k*G where G is the base point of the group
|
||||
// and k is a big endian integer. The result is stored in Jacobian coordinates
|
||||
// (x1, y1, z1).
|
||||
//
|
||||
// NOTE: The resulting point will be normalized.
|
||||
func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) {
|
||||
secp256k1.ScalarBaseMultNonConst(k, result)
|
||||
}
|
||||
|
||||
// ScalarMultNonConst multiplies k*P where k is a big endian integer modulo the
|
||||
// curve order and P is a point in Jacobian projective coordinates and stores
|
||||
// the result in the provided Jacobian point.
|
||||
//
|
||||
// NOTE: The point must be normalized for this function to return the correct
|
||||
// result. The resulting point will be normalized.
|
||||
func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) {
|
||||
secp256k1.ScalarMultNonConst(k, point, result)
|
||||
}
|
||||
|
||||
// ParseJacobian parses a byte slice point as a secp256k1.Publickey and returns the
|
||||
// pubkey as a JacobianPoint. If the nonce is a zero slice, the infinityPoint
|
||||
// is returned.
|
||||
func ParseJacobian(point []byte) (JacobianPoint, error) {
|
||||
var result JacobianPoint
|
||||
if len(point) != 33 {
|
||||
str := fmt.Sprintf("invalid nonce: invalid length: %v",
|
||||
len(point))
|
||||
return JacobianPoint{}, makeError(secp256k1.ErrPubKeyInvalidLen, str)
|
||||
}
|
||||
if point[0] == 0x00 {
|
||||
return infinityPoint, nil
|
||||
}
|
||||
noncePk, err := secp256k1.ParsePubKey(point)
|
||||
if err != nil {
|
||||
return JacobianPoint{}, err
|
||||
}
|
||||
noncePk.AsJacobian(&result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// JacobianToByteSlice converts the passed JacobianPoint to a Pubkey
|
||||
// and serializes that to a byte slice. If the JacobianPoint is the infinity
|
||||
// point, a zero slice is returned.
|
||||
func JacobianToByteSlice(point JacobianPoint) []byte {
|
||||
if point.X == infinityPoint.X && point.Y == infinityPoint.Y {
|
||||
return make([]byte, 33)
|
||||
}
|
||||
point.ToAffine()
|
||||
return NewPublicKey(
|
||||
&point.X, &point.Y,
|
||||
).SerializeCompressed()
|
||||
}
|
||||
|
||||
// GeneratorJacobian sets the passed JacobianPoint to the Generator Point.
|
||||
func GeneratorJacobian(jacobian *JacobianPoint) {
|
||||
var k ModNScalar
|
||||
k.SetInt(1)
|
||||
ScalarBaseMultNonConst(&k, jacobian)
|
||||
}
|
||||
19
ec/doc.go
Normal file
19
ec/doc.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package btcec implements support for the elliptic curves needed for bitcoin.
|
||||
//
|
||||
// Bitcoin uses elliptic curve cryptography using koblitz curves
|
||||
// (specifically secp256k1) for cryptographic functions. See
|
||||
// http://www.secg.org/collateral/sec2_final.pdf for details on the
|
||||
// standard.
|
||||
//
|
||||
// This package provides the data structures and functions implementing the
|
||||
// crypto/elliptic Curve interface in order to permit using these curves
|
||||
// with the standard crypto/ecdsa package provided with go. Helper
|
||||
// functionality is provided to parse signatures and public keys from
|
||||
// standard formats. It was designed for use with btcd, but should be
|
||||
// general enough for other uses of elliptic curve crypto. It was originally based
|
||||
// on some initial work by ThePiachu, but has significantly diverged since then.
|
||||
package btcec
|
||||
27
ec/ecdsa/README.md
Normal file
27
ec/ecdsa/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
ecdsa
|
||||
=====
|
||||
|
||||
[](http://copyfree.org)
|
||||
[](https://pkg.go.dev/mleku.online/git/ec/secp/ecdsa)
|
||||
|
||||
Package ecdsa provides secp256k1-optimized ECDSA signing and verification.
|
||||
|
||||
This package provides data structures and functions necessary to produce and
|
||||
verify deterministic canonical signatures in accordance with RFC6979 and
|
||||
BIP0062, optimized specifically for the secp256k1 curve using the Elliptic Curve
|
||||
Digital Signature Algorithm (ECDSA), as defined in FIPS 186-3. See
|
||||
https://www.secg.org/sec2-v2.pdf (also found here at [sec2-v2.pdf](../sec2-v2.pdf)) for details on the secp256k1 standard.
|
||||
|
||||
It also provides functions to parse and serialize the ECDSA signatures with the
|
||||
more strict Distinguished Encoding Rules (DER) of ISO/IEC 8825-1 and some
|
||||
additional restrictions specific to secp256k1.
|
||||
|
||||
In addition, it supports a custom "compact" signature format which allows
|
||||
efficient recovery of the public key from a given valid signature and message
|
||||
hash combination.
|
||||
|
||||
A comprehensive suite of tests is provided to ensure proper functionality.
|
||||
|
||||
## License
|
||||
|
||||
Package ecdsa is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||
167
ec/ecdsa/bench_test.go
Normal file
167
ec/ecdsa/bench_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2013-2016 The btcsuite developers
|
||||
// Copyright (c) 2015-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ecdsa
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
"github.com/mleku/manifold/hex"
|
||||
)
|
||||
|
||||
// hexToModNScalar converts the passed hex string into a ModNScalar and will
|
||||
// panic if there is an error. This is only provided for the hard-coded
|
||||
// constants so errors in the source code can be detected. It will only (and
|
||||
// must only) be called with hard-coded values.
|
||||
func hexToModNScalar(s string) *secp256k1.ModNScalar {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
var scalar secp256k1.ModNScalar
|
||||
if overflow := scalar.SetByteSlice(b); overflow {
|
||||
panic("hex in source file overflows mod N scalar: " + s)
|
||||
}
|
||||
return &scalar
|
||||
}
|
||||
|
||||
// hexToFieldVal converts the passed hex string into a FieldVal and will panic
|
||||
// if there is an error. This is only provided for the hard-coded constants so
|
||||
// errors in the source code can be detected. It will only (and must only) be
|
||||
// called with hard-coded values.
|
||||
func hexToFieldVal(s string) *secp256k1.FieldVal {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
var f secp256k1.FieldVal
|
||||
if overflow := f.SetByteSlice(b); overflow {
|
||||
panic("hex in source file overflows mod P: " + s)
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// BenchmarkSigVerify benchmarks how long it takes the secp256k1 curve to
|
||||
// verify signatures.
|
||||
func BenchmarkSigVerify(b *testing.B) {
|
||||
// Randomly generated keypair.
|
||||
// Secret key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
|
||||
pubKey := secp256k1.NewPublicKey(
|
||||
hexToFieldVal("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab"),
|
||||
hexToFieldVal("ab65528eefbb8057aa85d597258a3fbd481a24633bc9b47a9aa045c91371de52"),
|
||||
)
|
||||
// Double sha256 of by{0x01, 0x02, 0x03, 0x04}
|
||||
msgHash := hexToBytes("8de472e2399610baaa7f84840547cd409434e31f5d3bd71e4d947f283874f9c0")
|
||||
sig := NewSignature(
|
||||
hexToModNScalar("fef45d2892953aa5bbcdb057b5e98b208f1617a7498af7eb765574e29b5d9c2c"),
|
||||
hexToModNScalar("d47563f52aac6b04b55de236b7c515eb9311757db01e02cff079c3ca6efb063f"),
|
||||
)
|
||||
if !sig.Verify(msgHash, pubKey) {
|
||||
b.Errorf("Signature failed to verify")
|
||||
return
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sig.Verify(msgHash, pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSign benchmarks how long it takes to sign a message.
|
||||
func BenchmarkSign(b *testing.B) {
|
||||
// Randomly generated keypair.
|
||||
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
|
||||
secKey := secp256k1.NewSecretKey(d)
|
||||
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
|
||||
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
signRFC6979(secKey, msgHash)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSigSerialize benchmarks how long it takes to serialize a typical
|
||||
// signature with the strict DER encoding.
|
||||
func BenchmarkSigSerialize(b *testing.B) {
|
||||
// Randomly generated keypair.
|
||||
// Secret key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
|
||||
// Signature for double sha256 of by{0x01, 0x02, 0x03, 0x04}.
|
||||
sig := NewSignature(
|
||||
hexToModNScalar("fef45d2892953aa5bbcdb057b5e98b208f1617a7498af7eb765574e29b5d9c2c"),
|
||||
hexToModNScalar("d47563f52aac6b04b55de236b7c515eb9311757db01e02cff079c3ca6efb063f"),
|
||||
)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sig.Serialize()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNonceRFC6979 benchmarks how long it takes to generate a
|
||||
// deterministic nonce according to RFC6979.
|
||||
func BenchmarkNonceRFC6979(b *testing.B) {
|
||||
// Randomly generated keypair.
|
||||
// Secret key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
|
||||
// X: d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab
|
||||
// Y: ab65528eefbb8057aa85d597258a3fbd481a24633bc9b47a9aa045c91371de52
|
||||
secKeyStr := "9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d"
|
||||
secKey := hexToBytes(secKeyStr)
|
||||
// BLAKE-256 of by{0x01, 0x02, 0x03, 0x04}.
|
||||
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
var noElideNonce *secp256k1.ModNScalar
|
||||
for i := 0; i < b.N; i++ {
|
||||
noElideNonce = secp256k1.NonceRFC6979(secKey, msgHash, nil, nil, 0)
|
||||
}
|
||||
_ = noElideNonce
|
||||
}
|
||||
|
||||
// BenchmarkSignCompact benchmarks how long it takes to produce a compact
|
||||
// signature for a message.
|
||||
func BenchmarkSignCompact(b *testing.B) {
|
||||
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
|
||||
secKey := secp256k1.NewSecretKey(d)
|
||||
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
|
||||
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = SignCompact(secKey, msgHash, true)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkRecoverCompact benchmarks how long it takes to recover a public key
|
||||
// given a compact signature and message.
|
||||
func BenchmarkRecoverCompact(b *testing.B) {
|
||||
// Secret key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
|
||||
wantPubKey := secp256k1.NewPublicKey(
|
||||
hexToFieldVal("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab"),
|
||||
hexToFieldVal("ab65528eefbb8057aa85d597258a3fbd481a24633bc9b47a9aa045c91371de52"),
|
||||
)
|
||||
compactSig := hexToBytes("205978b7896bc71676ba2e459882a8f52e1299449596c4f" +
|
||||
"93c59bf1fbfa2f9d3b76ecd0c99406f61a6de2bb5a8937c061c176ecf381d0231e0d" +
|
||||
"af73b922c8952c7")
|
||||
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
|
||||
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
|
||||
// Ensure a valid compact signature is being benchmarked.
|
||||
pubKey, wasCompressed, err := RecoverCompact(compactSig, msgHash)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if !wasCompressed {
|
||||
b.Fatal("recover claims uncompressed pubkey")
|
||||
}
|
||||
if !pubKey.IsEqual(wantPubKey) {
|
||||
b.Fatal("recover returned unexpected pubkey")
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = RecoverCompact(compactSig, msgHash)
|
||||
}
|
||||
}
|
||||
40
ec/ecdsa/doc.go
Normal file
40
ec/ecdsa/doc.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020-2023 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ecdsa provides secp256k1-optimized ECDSA signing and verification.
|
||||
//
|
||||
// This package provides data structures and functions necessary to produce and
|
||||
// verify deterministic canonical signatures in accordance with RFC6979 and
|
||||
// BIP0062, optimized specifically for the secp256k1 curve using the Elliptic Curve
|
||||
// Digital Signature Algorithm (ECDSA), as defined in FIPS 186-3. See
|
||||
// https://www.secg.org/sec2-v2.pdf for details on the secp256k1 standard.
|
||||
//
|
||||
// It also provides functions to parse and serialize the ECDSA signatures with the
|
||||
// more strict Distinguished Encoding Rules (DER) of ISO/IEC 8825-1 and some
|
||||
// additional restrictions specific to secp256k1.
|
||||
//
|
||||
// In addition, it supports a custom "compact" signature format which allows
|
||||
// efficient recovery of the public key from a given valid signature and message
|
||||
// hash combination.
|
||||
//
|
||||
// A comprehensive suite of tests is provided to ensure proper functionality.
|
||||
//
|
||||
// # ECDSA use in Decred
|
||||
//
|
||||
// At the time of this writing, ECDSA signatures are heavily used for proving coin
|
||||
// ownership in Decred as the vast majority of transactions consist of what is
|
||||
// effectively transferring ownership of coins to a public key associated with a
|
||||
// secret key only known to the recipient of the coins along with an encumbrance
|
||||
// that requires an ECDSA signature that proves the new owner possesses the secret
|
||||
// key without actually revealing it.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// The errors returned by this package are of type ecdsa.Error and fully support
|
||||
// the standard library errors.Is and errors.As functions. This allows the caller
|
||||
// to programmatically determine the specific error by examining the ErrorKind
|
||||
// field of the type asserted ecdsa.Error while still providing rich error messages
|
||||
// with contextual information. See ErrorKind in the package documentation for a
|
||||
// full list.
|
||||
package ecdsa
|
||||
106
ec/ecdsa/error.go
Normal file
106
ec/ecdsa/error.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ecdsa
|
||||
|
||||
// ErrorKind identifies a kind of error. It has full support for
|
||||
// errors.Is and errors.As, so the caller can directly check against
|
||||
// an error kind when determining the reason for an error.
|
||||
type ErrorKind string
|
||||
|
||||
// These constants are used to identify a specific Error.
|
||||
const (
|
||||
// ErrSigTooShort is returned when a signature that should be a DER
|
||||
// signature is too short.
|
||||
ErrSigTooShort = ErrorKind("ErrSigTooShort")
|
||||
// ErrSigTooLong is returned when a signature that should be a DER signature
|
||||
// is too long.
|
||||
ErrSigTooLong = ErrorKind("ErrSigTooLong")
|
||||
// ErrSigInvalidSeqID is returned when a signature that should be a DER
|
||||
// signature does not have the expected ASN.1 sequence ID.
|
||||
ErrSigInvalidSeqID = ErrorKind("ErrSigInvalidSeqID")
|
||||
// ErrSigInvalidDataLen is returned when a signature that should be a DER
|
||||
// signature does not specify the correct number of remaining bytes for the
|
||||
// R and S portions.
|
||||
ErrSigInvalidDataLen = ErrorKind("ErrSigInvalidDataLen")
|
||||
// ErrSigMissingSTypeID is returned when a signature that should be a DER
|
||||
// signature does not provide the ASN.1 type ID for S.
|
||||
ErrSigMissingSTypeID = ErrorKind("ErrSigMissingSTypeID")
|
||||
// ErrSigMissingSLen is returned when a signature that should be a DER
|
||||
// signature does not provide the length of S.
|
||||
ErrSigMissingSLen = ErrorKind("ErrSigMissingSLen")
|
||||
// ErrSigInvalidSLen is returned when a signature that should be a DER
|
||||
// signature does not specify the correct number of bytes for the S portion.
|
||||
ErrSigInvalidSLen = ErrorKind("ErrSigInvalidSLen")
|
||||
// ErrSigInvalidRIntID is returned when a signature that should be a DER
|
||||
// signature does not have the expected ASN.1 integer ID for R.
|
||||
ErrSigInvalidRIntID = ErrorKind("ErrSigInvalidRIntID")
|
||||
// ErrSigZeroRLen is returned when a signature that should be a DER
|
||||
// signature has an R length of zero.
|
||||
ErrSigZeroRLen = ErrorKind("ErrSigZeroRLen")
|
||||
// ErrSigNegativeR is returned when a signature that should be a DER
|
||||
// signature has a negative value for R.
|
||||
ErrSigNegativeR = ErrorKind("ErrSigNegativeR")
|
||||
// ErrSigTooMuchRPadding is returned when a signature that should be a DER
|
||||
// signature has too much padding for R.
|
||||
ErrSigTooMuchRPadding = ErrorKind("ErrSigTooMuchRPadding")
|
||||
// ErrSigRIsZero is returned when a signature has R set to the value zero.
|
||||
ErrSigRIsZero = ErrorKind("ErrSigRIsZero")
|
||||
// ErrSigRTooBig is returned when a signature has R with a value that is
|
||||
// greater than or equal to the group order.
|
||||
ErrSigRTooBig = ErrorKind("ErrSigRTooBig")
|
||||
// ErrSigInvalidSIntID is returned when a signature that should be a DER
|
||||
// signature does not have the expected ASN.1 integer ID for S.
|
||||
ErrSigInvalidSIntID = ErrorKind("ErrSigInvalidSIntID")
|
||||
// ErrSigZeroSLen is returned when a signature that should be a DER
|
||||
// signature has an S length of zero.
|
||||
ErrSigZeroSLen = ErrorKind("ErrSigZeroSLen")
|
||||
// ErrSigNegativeS is returned when a signature that should be a DER
|
||||
// signature has a negative value for S.
|
||||
ErrSigNegativeS = ErrorKind("ErrSigNegativeS")
|
||||
// ErrSigTooMuchSPadding is returned when a signature that should be a DER
|
||||
// signature has too much padding for S.
|
||||
ErrSigTooMuchSPadding = ErrorKind("ErrSigTooMuchSPadding")
|
||||
// ErrSigSIsZero is returned when a signature has S set to the value zero.
|
||||
ErrSigSIsZero = ErrorKind("ErrSigSIsZero")
|
||||
// ErrSigSTooBig is returned when a signature has S with a value that is
|
||||
// greater than or equal to the group order.
|
||||
ErrSigSTooBig = ErrorKind("ErrSigSTooBig")
|
||||
// ErrSigInvalidLen is returned when a signature that should be a compact
|
||||
// signature is not the required length.
|
||||
ErrSigInvalidLen = ErrorKind("ErrSigInvalidLen")
|
||||
// ErrSigInvalidRecoveryCode is returned when a signature that should be a
|
||||
// compact signature has an invalid value for the public key recovery code.
|
||||
ErrSigInvalidRecoveryCode = ErrorKind("ErrSigInvalidRecoveryCode")
|
||||
// ErrSigOverflowsPrime is returned when a signature that should be a
|
||||
// compact signature has the overflow bit set but adding the order to it
|
||||
// would overflow the underlying field prime.
|
||||
ErrSigOverflowsPrime = ErrorKind("ErrSigOverflowsPrime")
|
||||
// ErrPointNotOnCurve is returned when attempting to recover a public key
|
||||
// from a compact signature results in a point that is not on the elliptic
|
||||
// curve.
|
||||
ErrPointNotOnCurve = ErrorKind("ErrPointNotOnCurve")
|
||||
)
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e ErrorKind) Error() string { return string(e) }
|
||||
|
||||
// Error identifies an error related to an ECDSA signature. It has full
|
||||
// support for errors.Is and errors.As, so the caller can ascertain the
|
||||
// specific reason for the error by checking the underlying error.
|
||||
type Error struct {
|
||||
Err error
|
||||
Description string
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e Error) Error() string { return e.Description }
|
||||
|
||||
// Unwrap returns the underlying wrapped error.
|
||||
func (e Error) Unwrap() error { return e.Err }
|
||||
|
||||
// signatureError creates an Error given a set of arguments.
|
||||
func signatureError(kind ErrorKind, desc string) Error {
|
||||
return Error{Err: kind, Description: desc}
|
||||
}
|
||||
146
ec/ecdsa/error_test.go
Normal file
146
ec/ecdsa/error_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ecdsa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestErrorKindStringer tests the stringized output for the ErrorKind type.
|
||||
func TestErrorKindStringer(t *testing.T) {
|
||||
tests := []struct {
|
||||
in ErrorKind
|
||||
want string
|
||||
}{
|
||||
{ErrSigTooShort, "ErrSigTooShort"},
|
||||
{ErrSigTooLong, "ErrSigTooLong"},
|
||||
{ErrSigInvalidSeqID, "ErrSigInvalidSeqID"},
|
||||
{ErrSigInvalidDataLen, "ErrSigInvalidDataLen"},
|
||||
{ErrSigMissingSTypeID, "ErrSigMissingSTypeID"},
|
||||
{ErrSigMissingSLen, "ErrSigMissingSLen"},
|
||||
{ErrSigInvalidSLen, "ErrSigInvalidSLen"},
|
||||
{ErrSigInvalidRIntID, "ErrSigInvalidRIntID"},
|
||||
{ErrSigZeroRLen, "ErrSigZeroRLen"},
|
||||
{ErrSigNegativeR, "ErrSigNegativeR"},
|
||||
{ErrSigTooMuchRPadding, "ErrSigTooMuchRPadding"},
|
||||
{ErrSigRIsZero, "ErrSigRIsZero"},
|
||||
{ErrSigRTooBig, "ErrSigRTooBig"},
|
||||
{ErrSigInvalidSIntID, "ErrSigInvalidSIntID"},
|
||||
{ErrSigZeroSLen, "ErrSigZeroSLen"},
|
||||
{ErrSigNegativeS, "ErrSigNegativeS"},
|
||||
{ErrSigTooMuchSPadding, "ErrSigTooMuchSPadding"},
|
||||
{ErrSigSIsZero, "ErrSigSIsZero"},
|
||||
{ErrSigSTooBig, "ErrSigSTooBig"},
|
||||
{ErrSigInvalidLen, "ErrSigInvalidLen"},
|
||||
{ErrSigInvalidRecoveryCode, "ErrSigInvalidRecoveryCode"},
|
||||
{ErrSigOverflowsPrime, "ErrSigOverflowsPrime"},
|
||||
{ErrPointNotOnCurve, "ErrPointNotOnCurve"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
result := test.in.Error()
|
||||
if result != test.want {
|
||||
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestError tests the error output for the Error type.
|
||||
func TestError(t *testing.T) {
|
||||
tests := []struct {
|
||||
in Error
|
||||
want string
|
||||
}{{
|
||||
Error{Description: "some error"},
|
||||
"some error",
|
||||
}, {
|
||||
Error{Description: "human-readable error"},
|
||||
"human-readable error",
|
||||
}}
|
||||
for i, test := range tests {
|
||||
result := test.in.Error()
|
||||
if result != test.want {
|
||||
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
|
||||
// a specific error kind via errors.Is and unwrapped via errors.As.
|
||||
func TestErrorKindIsAs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
target error
|
||||
wantMatch bool
|
||||
wantAs ErrorKind
|
||||
}{{
|
||||
name: "ErrSigTooShort == ErrSigTooShort",
|
||||
err: ErrSigTooShort,
|
||||
target: ErrSigTooShort,
|
||||
wantMatch: true,
|
||||
wantAs: ErrSigTooShort,
|
||||
}, {
|
||||
name: "Error.ErrSigTooShort == ErrSigTooShort",
|
||||
err: signatureError(ErrSigTooShort, ""),
|
||||
target: ErrSigTooShort,
|
||||
wantMatch: true,
|
||||
wantAs: ErrSigTooShort,
|
||||
}, {
|
||||
name: "Error.ErrSigTooShort == Error.ErrSigTooShort",
|
||||
err: signatureError(ErrSigTooShort, ""),
|
||||
target: signatureError(ErrSigTooShort, ""),
|
||||
wantMatch: true,
|
||||
wantAs: ErrSigTooShort,
|
||||
}, {
|
||||
name: "ErrSigTooLong != ErrSigTooShort",
|
||||
err: ErrSigTooLong,
|
||||
target: ErrSigTooShort,
|
||||
wantMatch: false,
|
||||
wantAs: ErrSigTooLong,
|
||||
}, {
|
||||
name: "Error.ErrSigTooLong != ErrSigTooShort",
|
||||
err: signatureError(ErrSigTooLong, ""),
|
||||
target: ErrSigTooShort,
|
||||
wantMatch: false,
|
||||
wantAs: ErrSigTooLong,
|
||||
}, {
|
||||
name: "ErrSigTooLong != Error.ErrSigTooShort",
|
||||
err: ErrSigTooLong,
|
||||
target: signatureError(ErrSigTooShort, ""),
|
||||
wantMatch: false,
|
||||
wantAs: ErrSigTooLong,
|
||||
}, {
|
||||
name: "Error.ErrSigTooLong != Error.ErrSigTooShort",
|
||||
err: signatureError(ErrSigTooLong, ""),
|
||||
target: signatureError(ErrSigTooShort, ""),
|
||||
wantMatch: false,
|
||||
wantAs: ErrSigTooLong,
|
||||
}}
|
||||
for _, test := range tests {
|
||||
// Ensure the error matches or not depending on the expected result.
|
||||
result := errors.Is(test.err, test.target)
|
||||
if result != test.wantMatch {
|
||||
t.Errorf("%s: incorrect error identification -- got %v, want %v",
|
||||
test.name, result, test.wantMatch)
|
||||
continue
|
||||
}
|
||||
// Ensure the underlying error kind can be unwrapped and is the
|
||||
// expected code.
|
||||
var kind ErrorKind
|
||||
if !errors.As(test.err, &kind) {
|
||||
t.Errorf("%s: unable to unwrap to error", test.name)
|
||||
continue
|
||||
}
|
||||
if !errors.Is(kind, test.wantAs) {
|
||||
t.Errorf("%s: unexpected unwrapped error -- got %v, want %v",
|
||||
test.name, kind, test.wantAs)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
79
ec/ecdsa/example_test.go
Normal file
79
ec/ecdsa/example_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Copyright (c) 2015-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// TODO: change this to work with sha256
|
||||
|
||||
package ecdsa_test
|
||||
|
||||
// // This example demonstrates signing a message with a secp256k1 secret key that
|
||||
// // is first parsed from raw bytes and serializing the generated signature.
|
||||
// func ExampleSign() {
|
||||
// // Decode a hex-encoded secret key.
|
||||
// pkBytes, err := hex.Dec("22a47fa09a223f2aa079edf85a7c2d4f87" +
|
||||
// "20ee63e502ee2869afab7de234b80c")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// secKey := secp256k1.SecKeyFromBytes(pkBytes)
|
||||
//
|
||||
// // Sign a message using the secret key.
|
||||
// message := "test message"
|
||||
// messageHash := blake256.Sum256(by(message))
|
||||
// signature := ecdsa.Sign(secKey, messageHash[:])
|
||||
//
|
||||
// // Serialize and display the signature.
|
||||
// fmt.Printf("Serialized Signature: %x\n", signature.Serialize())
|
||||
//
|
||||
// // Verify the signature for the message using the public key.
|
||||
// pubKey := secKey.Pubkey()
|
||||
// verified := signature.Verify(messageHash[:], pubKey)
|
||||
// fmt.Printf("Signature Verified? %v\n", verified)
|
||||
//
|
||||
// // Output:
|
||||
// // Serialized Signature: 3045022100fcc0a8768cfbcefcf2cadd7cfb0fb18ed08dd2e2ae84bef1a474a3d351b26f0302200fc1a350b45f46fa00101391302818d748c2b22615511a3ffd5bb638bd777207
|
||||
// // Signature Verified? true
|
||||
// }
|
||||
|
||||
// // This example demonstrates verifying a secp256k1 signature against a public
|
||||
// // key that is first parsed from raw bytes. The signature is also parsed from
|
||||
// // raw bytes.
|
||||
// func ExampleSignature_Verify() {
|
||||
// // Decode hex-encoded serialized public key.
|
||||
// pubKeyBytes, err := hex.Dec("02a673638cb9587cb68ea08dbef685c" +
|
||||
// "6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// pubKey, err := secp256k1.ParsePubKey(pubKeyBytes)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Decode hex-encoded serialized signature.
|
||||
// sigBytes, err := hex.Dec("3045022100fcc0a8768cfbcefcf2cadd7cfb0" +
|
||||
// "fb18ed08dd2e2ae84bef1a474a3d351b26f0302200fc1a350b45f46fa0010139130" +
|
||||
// "2818d748c2b22615511a3ffd5bb638bd777207")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
// signature, err := ecdsa.ParseDERSignature(sigBytes)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Verify the signature for the message using the public key.
|
||||
// message := "test message"
|
||||
// messageHash := blake256.Sum256(by(message))
|
||||
// verified := signature.Verify(messageHash[:], pubKey)
|
||||
// fmt.Println("Signature Verified?", verified)
|
||||
//
|
||||
// // Output:
|
||||
// // Signature Verified? true
|
||||
// }
|
||||
929
ec/ecdsa/signature.go
Normal file
929
ec/ecdsa/signature.go
Normal file
@@ -0,0 +1,929 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Copyright (c) 2015-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ecdsa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mleku/manifold/ec/secp256k1"
|
||||
)
|
||||
|
||||
// References:
|
||||
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
|
||||
//
|
||||
// [ISO/IEC 8825-1]: Information technology — ASN.1 encoding rules:
|
||||
// Specification of Basic Encoding Rules (BER), Canonical Encoding Rules
|
||||
// (CER) and Distinguished Encoding Rules (DER)
|
||||
//
|
||||
// [SEC1]: Elliptic Curve Cryptography (May 31, 2009, Version 2.0)
|
||||
// https://www.secg.org/sec1-v2.pdf
|
||||
|
||||
var (
|
||||
// zero32 is an array of 32 bytes used for the purposes of zeroing and is
|
||||
// defined here to avoid extra allocations.
|
||||
zero32 = [32]byte{}
|
||||
// orderAsFieldVal is the order of the secp256k1 curve group stored as a
|
||||
// field value. It is provided here to avoid the need to create it multiple
|
||||
// times.
|
||||
orderAsFieldVal = func() secp256k1.FieldVal {
|
||||
var f secp256k1.FieldVal
|
||||
f.SetByteSlice(secp256k1.Params().N.Bytes())
|
||||
return f
|
||||
}()
|
||||
)
|
||||
|
||||
const (
|
||||
// asn1SequenceID is the ASN.1 identifier for a sequence and is used when
|
||||
// parsing and serializing signatures encoded with the Distinguished
|
||||
// Encoding Rules (DER) format per section 10 of [ISO/IEC 8825-1].
|
||||
asn1SequenceID = 0x30
|
||||
// asn1IntegerID is the ASN.1 identifier for an integer and is used when
|
||||
// parsing and serializing signatures encoded with the Distinguished
|
||||
// Encoding Rules (DER) format per section 10 of [ISO/IEC 8825-1].
|
||||
asn1IntegerID = 0x02
|
||||
)
|
||||
|
||||
// Signature is a type representing an ECDSA signature.
|
||||
type Signature struct {
|
||||
r secp256k1.ModNScalar
|
||||
s secp256k1.ModNScalar
|
||||
}
|
||||
|
||||
// NewSignature instantiates a new signature given some r and s values.
|
||||
func NewSignature(r, s *secp256k1.ModNScalar) *Signature {
|
||||
return &Signature{*r, *s}
|
||||
}
|
||||
|
||||
// Serialize returns the ECDSA signature in the Distinguished Encoding Rules
|
||||
// (DER) format per section 10 of [ISO/IEC 8825-1] and such that the S component
|
||||
// of the signature is less than or equal to the half order of the group.
|
||||
//
|
||||
// Note that the serialized bytes returned do not include the appended hash type
|
||||
// used in Decred signature scripts.
|
||||
func (sig *Signature) Serialize() []byte {
|
||||
// The format of a DER encoded signature is as follows:
|
||||
//
|
||||
// 0x30 <total length> 0x02 <length of R> <R> 0x02 <length of S> <S>
|
||||
// - 0x30 is the ASN.1 identifier for a sequence.
|
||||
// - Total length is 1 byte and specifies length of all remaining data.
|
||||
// - 0x02 is the ASN.1 identifier that specifies an integer follows.
|
||||
// - Length of R is 1 byte and specifies how many bytes R occupies.
|
||||
// - R is the arbitrary length big-endian encoded number which
|
||||
// represents the R value of the signature. DER encoding dictates
|
||||
// that the value must be encoded using the minimum possible number
|
||||
// of bytes. This implies the first byte can only be null if the
|
||||
// highest bit of the next byte is set in order to prevent it from
|
||||
// being interpreted as a negative number.
|
||||
// - 0x02 is once again the ASN.1 integer identifier.
|
||||
// - Length of S is 1 byte and specifies how many bytes S occupies.
|
||||
// - S is the arbitrary length big-endian encoded number which
|
||||
// represents the S value of the signature. The encoding rules are
|
||||
// identical as those for R.
|
||||
|
||||
// Ensure the S component of the signature is less than or equal to the half
|
||||
// order of the group because both S and its negation are valid signatures
|
||||
// modulo the order, so this forces a consistent choice to reduce signature
|
||||
// malleability.
|
||||
sigS := new(secp256k1.ModNScalar).Set(&sig.s)
|
||||
if sigS.IsOverHalfOrder() {
|
||||
sigS.Negate()
|
||||
}
|
||||
|
||||
// Serialize the R and S components of the signature into their fixed
|
||||
// 32-byte big-endian encoding. Note that the extra leading zero byte is
|
||||
// used to ensure it is canonical per DER and will be stripped if needed
|
||||
// below.
|
||||
var rBuf, sBuf [33]byte
|
||||
sig.r.PutBytesUnchecked(rBuf[1:33])
|
||||
sigS.PutBytesUnchecked(sBuf[1:33])
|
||||
// Ensure the encoded bytes for the R and S components are canonical per DER
|
||||
// by trimming all leading zero bytes so long as the next byte does not have
|
||||
// the high bit set and it's not the final byte.
|
||||
canonR, canonS := rBuf[:], sBuf[:]
|
||||
for len(canonR) > 1 && canonR[0] == 0x00 && canonR[1]&0x80 == 0 {
|
||||
canonR = canonR[1:]
|
||||
}
|
||||
for len(canonS) > 1 && canonS[0] == 0x00 && canonS[1]&0x80 == 0 {
|
||||
canonS = canonS[1:]
|
||||
}
|
||||
// Total length of returned signature is 1 byte for each magic and length
|
||||
// (6 total), plus lengths of R and S.
|
||||
totalLen := 6 + len(canonR) + len(canonS)
|
||||
b := make([]byte, 0, totalLen)
|
||||
b = append(b, asn1SequenceID)
|
||||
b = append(b, byte(totalLen-2))
|
||||
b = append(b, asn1IntegerID)
|
||||
b = append(b, byte(len(canonR)))
|
||||
b = append(b, canonR...)
|
||||
b = append(b, asn1IntegerID)
|
||||
b = append(b, byte(len(canonS)))
|
||||
b = append(b, canonS...)
|
||||
return b
|
||||
}
|
||||
|
||||
// zeroArray32 zeroes the provided 32-byte buffer.
|
||||
func zeroArray32(b *[32]byte) {
|
||||
copy(b[:], zero32[:])
|
||||
}
|
||||
|
||||
// fieldToModNScalar converts a field value to scalar modulo the group order and
|
||||
// returns the scalar along with either 1 if it was reduced (aka it overflowed)
|
||||
// or 0 otherwise.
|
||||
//
|
||||
// Note that a bool is not used here because it is not possible in Go to convert
|
||||
// from a bool to numeric value in constant time and many constant-time
|
||||
// operations require a numeric value.
|
||||
func fieldToModNScalar(v *secp256k1.FieldVal) (secp256k1.ModNScalar, uint32) {
|
||||
var buf [32]byte
|
||||
v.PutBytes(&buf)
|
||||
var s secp256k1.ModNScalar
|
||||
overflow := s.SetBytes(&buf)
|
||||
zeroArray32(&buf)
|
||||
return s, overflow
|
||||
}
|
||||
|
||||
// modNScalarToField converts a scalar modulo the group order to a field value.
|
||||
func modNScalarToField(v *secp256k1.ModNScalar) secp256k1.FieldVal {
|
||||
var buf [32]byte
|
||||
v.PutBytes(&buf)
|
||||
var fv secp256k1.FieldVal
|
||||
fv.SetBytes(&buf)
|
||||
return fv
|
||||
}
|
||||
|
||||
// Verify returns whether the signature is valid for the provided hash
|
||||
// and secp256k1 public key.
|
||||
func (sig *Signature) Verify(hash []byte, pubKey *secp256k1.PublicKey) bool {
|
||||
// The algorithm for verifying an ECDSA signature is given as algorithm 4.30
|
||||
// in [GECC].
|
||||
//
|
||||
// The following is a paraphrased version for reference:
|
||||
//
|
||||
// G = curve generator
|
||||
// N = curve order
|
||||
// Q = public key
|
||||
// m = message
|
||||
// R, S = signature
|
||||
//
|
||||
// 1. Fail if R and S are not in [1, N-1]
|
||||
// 2. e = H(m)
|
||||
// 3. w = S^-1 mod N
|
||||
// 4. u1 = e * w mod N
|
||||
// u2 = R * w mod N
|
||||
// 5. X = u1G + u2Q
|
||||
// 6. Fail if X is the point at infinity
|
||||
// 7. x = X.x mod N (X.x is the x coordinate of X)
|
||||
// 8. Verified if x == R
|
||||
//
|
||||
// However, since all group operations are done internally in Jacobian
|
||||
// projective space, the algorithm is modified slightly here in order to
|
||||
// avoid an expensive inversion back into affine coordinates at step 7.
|
||||
// Credits to Greg Maxwell for originally suggesting this optimization.
|
||||
//
|
||||
// Ordinarily, step 7 involves converting the x coordinate to affine by
|
||||
// calculating x = x / z^2 (mod P) and then calculating the remainder as
|
||||
// x = x (mod N). Then step 8 compares it to R.
|
||||
//
|
||||
// Note that since R is the x coordinate mod N from a random point that was
|
||||
// originally mod P, and the cofactor of the secp256k1 curve is 1, there are
|
||||
// only two possible x coordinates that the original random point could have
|
||||
// been to produce R: x, where x < N, and x+N, where x+N < P.
|
||||
//
|
||||
// This implies that the signature is valid if either:
|
||||
// a) R == X.x / X.z^2 (mod P)
|
||||
// => R * X.z^2 == X.x (mod P)
|
||||
// --or--
|
||||
// b) R + N < P && R + N == X.x / X.z^2 (mod P)
|
||||
// => R + N < P && (R + N) * X.z^2 == X.x (mod P)
|
||||
//
|
||||
// Therefore the following modified algorithm is used:
|
||||
//
|
||||
// 1. Fail if R and S are not in [1, N-1]
|
||||
// 2. e = H(m)
|
||||
// 3. w = S^-1 mod N
|
||||
// 4. u1 = e * w mod N
|
||||
// u2 = R * w mod N
|
||||
// 5. X = u1G + u2Q
|
||||
// 6. Fail if X is the point at infinity
|
||||
// 7. z = (X.z)^2 mod P (X.z is the z coordinate of X)
|
||||
// 8. Verified if R * z == X.x (mod P)
|
||||
// 9. Fail if R + N >= P
|
||||
// 10. Verified if (R + N) * z == X.x (mod P)
|
||||
//
|
||||
// Step 1.
|
||||
//
|
||||
// Fail if R and S are not in [1, N-1].
|
||||
if sig.r.IsZero() || sig.s.IsZero() {
|
||||
return false
|
||||
}
|
||||
// Step 2.
|
||||
//
|
||||
// e = H(m)
|
||||
var e secp256k1.ModNScalar
|
||||
e.SetByteSlice(hash)
|
||||
// Step 3.
|
||||
//
|
||||
// w = S^-1 mod N
|
||||
w := new(secp256k1.ModNScalar).InverseValNonConst(&sig.s)
|
||||
// Step 4.
|
||||
//
|
||||
// u1 = e * w mod N
|
||||
// u2 = R * w mod N
|
||||
u1 := new(secp256k1.ModNScalar).Mul2(&e, w)
|
||||
u2 := new(secp256k1.ModNScalar).Mul2(&sig.r, w)
|
||||
// Step 5.
|
||||
//
|
||||
// X = u1G + u2Q
|
||||
var X, Q, u1G, u2Q secp256k1.JacobianPoint
|
||||
pubKey.AsJacobian(&Q)
|
||||
secp256k1.ScalarBaseMultNonConst(u1, &u1G)
|
||||
secp256k1.ScalarMultNonConst(u2, &Q, &u2Q)
|
||||
secp256k1.AddNonConst(&u1G, &u2Q, &X)
|
||||
// Step 6.
|
||||
//
|
||||
// Fail if X is the point at infinity
|
||||
if (X.X.IsZero() && X.Y.IsZero()) || X.Z.IsZero() {
|
||||
return false
|
||||
}
|
||||
// Step 7.
|
||||
//
|
||||
// z = (X.z)^2 mod P (X.z is the z coordinate of X)
|
||||
z := new(secp256k1.FieldVal).SquareVal(&X.Z)
|
||||
// Step 8.
|
||||
//
|
||||
// Verified if R * z == X.x (mod P)
|
||||
sigRModP := modNScalarToField(&sig.r)
|
||||
result := new(secp256k1.FieldVal).Mul2(&sigRModP, z).Normalize()
|
||||
if result.Equals(&X.X) {
|
||||
return true
|
||||
}
|
||||
// Step 9.
|
||||
//
|
||||
// Fail if R + N >= P
|
||||
if sigRModP.IsGtOrEqPrimeMinusOrder() {
|
||||
return false
|
||||
}
|
||||
// Step 10.
|
||||
//
|
||||
// Verified if (R + N) * z == X.x (mod P)
|
||||
sigRModP.Add(&orderAsFieldVal)
|
||||
result.Mul2(&sigRModP, z).Normalize()
|
||||
return result.Equals(&X.X)
|
||||
}
|
||||
|
||||
// IsEqual compares this Signature instance to the one passed, returning true if
|
||||
// both Signatures are equivalent. A signature is equivalent to another, if
|
||||
// they both have the same scalar value for R and S.
|
||||
func (sig *Signature) IsEqual(otherSig *Signature) bool {
|
||||
return sig.r.Equals(&otherSig.r) && sig.s.Equals(&otherSig.s)
|
||||
}
|
||||
|
||||
// ParseDERSignature parses a signature in the Distinguished Encoding Rules
|
||||
// (DER) format per section 10 of [ISO/IEC 8825-1] and enforces the following
|
||||
// additional restrictions specific to secp256k1:
|
||||
//
|
||||
// - The R and S values must be in the valid range for secp256k1 scalars:
|
||||
// - Negative values are rejected
|
||||
// - Zero is rejected
|
||||
// - Values greater than or equal to the secp256k1 group order are rejected
|
||||
func ParseDERSignature(sig []byte) (*Signature, error) {
|
||||
// The format of a DER encoded signature for secp256k1 is as follows:
|
||||
//
|
||||
// 0x30 <total length> 0x02 <length of R> <R> 0x02 <length of S> <S>
|
||||
// - 0x30 is the ASN.1 identifier for a sequence
|
||||
// - Total length is 1 byte and specifies length of all remaining data
|
||||
// - 0x02 is the ASN.1 identifier that specifies an integer follows
|
||||
// - Length of R is 1 byte and specifies how many bytes R occupies
|
||||
// - R is the arbitrary length big-endian encoded number which
|
||||
// represents the R value of the signature. DER encoding dictates
|
||||
// that the value must be encoded using the minimum possible number
|
||||
// of bytes. This implies the first byte can only be null if the
|
||||
// highest bit of the next byte is set in order to prevent it from
|
||||
// being interpreted as a negative number.
|
||||
// - 0x02 is once again the ASN.1 integer identifier
|
||||
// - Length of S is 1 byte and specifies how many bytes S occupies
|
||||
// - S is the arbitrary length big-endian encoded number which
|
||||
// represents the S value of the signature. The encoding rules are
|
||||
// identical as those for R.
|
||||
//
|
||||
// NOTE: The DER specification supports specifying lengths that can occupy
|
||||
// more than 1 byte, however, since this is specific to secp256k1
|
||||
// signatures, all lengths will be a single byte.
|
||||
const (
|
||||
// minSigLen is the minimum length of a DER encoded signature and is
|
||||
// when both R and S are 1 byte each.
|
||||
//
|
||||
// 0x30 + <1-byte> + 0x02 + 0x01 + <byte> + 0x2 + 0x01 + <byte>
|
||||
minSigLen = 8
|
||||
// maxSigLen is the maximum length of a DER encoded signature and is
|
||||
// when both R and S are 33 bytes each. It is 33 bytes because a
|
||||
// 256-bit integer requires 32 bytes and an additional leading null byte
|
||||
// might be required if the high bit is set in the value.
|
||||
//
|
||||
// 0x30 + <1-byte> + 0x02 + 0x21 + <33 bytes> + 0x2 + 0x21 + <33 bytes>
|
||||
maxSigLen = 72
|
||||
// sequenceOffset is the byte offset within the signature of the
|
||||
// expected ASN.1 sequence identifier.
|
||||
sequenceOffset = 0
|
||||
// dataLenOffset is the byte offset within the signature of the expected
|
||||
// total length of all remaining data in the signature.
|
||||
dataLenOffset = 1
|
||||
// rTypeOffset is the byte offset within the signature of the ASN.1
|
||||
// identifier for R and is expected to indicate an ASN.1 integer.
|
||||
rTypeOffset = 2
|
||||
// rLenOffset is the byte offset within the signature of the length of
|
||||
// R.
|
||||
rLenOffset = 3
|
||||
// rOffset is the byte offset within the signature of R.
|
||||
rOffset = 4
|
||||
)
|
||||
// The signature must adhere to the minimum and maximum allowed length.
|
||||
sigLen := len(sig)
|
||||
if sigLen < minSigLen {
|
||||
str := fmt.Sprintf("malformed signature: too short: %d < %d", sigLen,
|
||||
minSigLen)
|
||||
return nil, signatureError(ErrSigTooShort, str)
|
||||
}
|
||||
if sigLen > maxSigLen {
|
||||
str := fmt.Sprintf("malformed signature: too long: %d > %d", sigLen,
|
||||
maxSigLen)
|
||||
return nil, signatureError(ErrSigTooLong, str)
|
||||
}
|
||||
// The signature must start with the ASN.1 sequence identifier.
|
||||
if sig[sequenceOffset] != asn1SequenceID {
|
||||
str := fmt.Sprintf("malformed signature: format has wrong type: %#x",
|
||||
sig[sequenceOffset])
|
||||
return nil, signatureError(ErrSigInvalidSeqID, str)
|
||||
}
|
||||
// The signature must indicate the correct amount of data for all elements
|
||||
// related to R and S.
|
||||
if int(sig[dataLenOffset]) != sigLen-2 {
|
||||
str := fmt.Sprintf("malformed signature: bad length: %d != %d",
|
||||
sig[dataLenOffset], sigLen-2)
|
||||
return nil, signatureError(ErrSigInvalidDataLen, str)
|
||||
}
|
||||
// Calculate the offsets of the elements related to S and ensure S is inside
|
||||
// the signature.
|
||||
//
|
||||
// rLen specifies the length of the big-endian encoded number which
|
||||
// represents the R value of the signature.
|
||||
//
|
||||
// sTypeOffset is the offset of the ASN.1 identifier for S and, like its R
|
||||
// counterpart, is expected to indicate an ASN.1 integer.
|
||||
//
|
||||
// sLenOffset and sOffset are the byte offsets within the signature of the
|
||||
// length of S and S itself, respectively.
|
||||
rLen := int(sig[rLenOffset])
|
||||
sTypeOffset := rOffset + rLen
|
||||
sLenOffset := sTypeOffset + 1
|
||||
if sTypeOffset >= sigLen {
|
||||
str := "malformed signature: S type indicator missing"
|
||||
return nil, signatureError(ErrSigMissingSTypeID, str)
|
||||
}
|
||||
if sLenOffset >= sigLen {
|
||||
str := "malformed signature: S length missing"
|
||||
return nil, signatureError(ErrSigMissingSLen, str)
|
||||
}
|
||||
// The lengths of R and S must match the overall length of the signature.
|
||||
//
|
||||
// sLen specifies the length of the big-endian encoded number which
|
||||
// represents the S value of the signature.
|
||||
sOffset := sLenOffset + 1
|
||||
sLen := int(sig[sLenOffset])
|
||||
if sOffset+sLen != sigLen {
|
||||
str := "malformed signature: invalid S length"
|
||||
return nil, signatureError(ErrSigInvalidSLen, str)
|
||||
}
|
||||
// R elements must be ASN.1 integers.
|
||||
if sig[rTypeOffset] != asn1IntegerID {
|
||||
str := fmt.Sprintf("malformed signature: R integer marker: %#x != %#x",
|
||||
sig[rTypeOffset], asn1IntegerID)
|
||||
return nil, signatureError(ErrSigInvalidRIntID, str)
|
||||
}
|
||||
// Zero-length integers are not allowed for R.
|
||||
if rLen == 0 {
|
||||
str := "malformed signature: R length is zero"
|
||||
return nil, signatureError(ErrSigZeroRLen, str)
|
||||
}
|
||||
// R must not be negative.
|
||||
if sig[rOffset]&0x80 != 0 {
|
||||
str := "malformed signature: R is negative"
|
||||
return nil, signatureError(ErrSigNegativeR, str)
|
||||
}
|
||||
// Null bytes at the start of R are not allowed, unless R would otherwise be
|
||||
// interpreted as a negative number.
|
||||
if rLen > 1 && sig[rOffset] == 0x00 && sig[rOffset+1]&0x80 == 0 {
|
||||
str := "malformed signature: R value has too much padding"
|
||||
return nil, signatureError(ErrSigTooMuchRPadding, str)
|
||||
}
|
||||
// S elements must be ASN.1 integers.
|
||||
if sig[sTypeOffset] != asn1IntegerID {
|
||||
str := fmt.Sprintf("malformed signature: S integer marker: %#x != %#x",
|
||||
sig[sTypeOffset], asn1IntegerID)
|
||||
return nil, signatureError(ErrSigInvalidSIntID, str)
|
||||
}
|
||||
// Zero-length integers are not allowed for S.
|
||||
if sLen == 0 {
|
||||
str := "malformed signature: S length is zero"
|
||||
return nil, signatureError(ErrSigZeroSLen, str)
|
||||
}
|
||||
// S must not be negative.
|
||||
if sig[sOffset]&0x80 != 0 {
|
||||
str := "malformed signature: S is negative"
|
||||
return nil, signatureError(ErrSigNegativeS, str)
|
||||
}
|
||||
// Null bytes at the start of S are not allowed, unless S would otherwise be
|
||||
// interpreted as a negative number.
|
||||
if sLen > 1 && sig[sOffset] == 0x00 && sig[sOffset+1]&0x80 == 0 {
|
||||
str := "malformed signature: S value has too much padding"
|
||||
return nil, signatureError(ErrSigTooMuchSPadding, str)
|
||||
}
|
||||
// The signature is validly encoded per DER at this point, however, enforce
|
||||
// additional restrictions to ensure R and S are in the range [1, N-1] since
|
||||
// valid ECDSA signatures are required to be in that range per spec.
|
||||
//
|
||||
// Also note that while the overflow checks are required to make use of the
|
||||
// specialized mod N scalar type, rejecting zero here is not strictly
|
||||
// required because it is also checked when verifying the signature, but
|
||||
// there really isn't a good reason not to fail early here on signatures
|
||||
// that do not conform to the ECDSA spec.
|
||||
//
|
||||
// Strip leading zeroes from R.
|
||||
rBytes := sig[rOffset : rOffset+rLen]
|
||||
for len(rBytes) > 0 && rBytes[0] == 0x00 {
|
||||
rBytes = rBytes[1:]
|
||||
}
|
||||
// R must be in the range [1, N-1]. Notice the check for the maximum number
|
||||
// of bytes is required because SetByteSlice truncates as noted in its
|
||||
// comment so it could otherwise fail to detect the overflow.
|
||||
var r secp256k1.ModNScalar
|
||||
if len(rBytes) > 32 {
|
||||
str := "invalid signature: R is larger than 256 bits"
|
||||
return nil, signatureError(ErrSigRTooBig, str)
|
||||
}
|
||||
if overflow := r.SetByteSlice(rBytes); overflow {
|
||||
str := "invalid signature: R >= group order"
|
||||
return nil, signatureError(ErrSigRTooBig, str)
|
||||
}
|
||||
if r.IsZero() {
|
||||
str := "invalid signature: R is 0"
|
||||
return nil, signatureError(ErrSigRIsZero, str)
|
||||
}
|
||||
// Strip leading zeroes from S.
|
||||
sBytes := sig[sOffset : sOffset+sLen]
|
||||
for len(sBytes) > 0 && sBytes[0] == 0x00 {
|
||||
sBytes = sBytes[1:]
|
||||
}
|
||||
// S must be in the range [1, N-1]. Notice the check for the maximum number
|
||||
// of bytes is required because SetByteSlice truncates as noted in its
|
||||
// comment so it could otherwise fail to detect the overflow.
|
||||
var s secp256k1.ModNScalar
|
||||
if len(sBytes) > 32 {
|
||||
str := "invalid signature: S is larger than 256 bits"
|
||||
return nil, signatureError(ErrSigSTooBig, str)
|
||||
}
|
||||
if overflow := s.SetByteSlice(sBytes); overflow {
|
||||
str := "invalid signature: S >= group order"
|
||||
return nil, signatureError(ErrSigSTooBig, str)
|
||||
}
|
||||
if s.IsZero() {
|
||||
str := "invalid signature: S is 0"
|
||||
return nil, signatureError(ErrSigSIsZero, str)
|
||||
}
|
||||
// Create and return the signature.
|
||||
return NewSignature(&r, &s), nil
|
||||
}
|
||||
|
||||
// sign generates an ECDSA signature over the secp256k1 curve for the provided
|
||||
// hash (which should be the result of hashing a larger message) using the given
|
||||
// nonce and secret key and returns it along with an additional public key
|
||||
// recovery code and success indicator. Upon success, the produced signature is
|
||||
// deterministic (same message, nonce, and key yield the same signature) and
|
||||
// canonical in accordance with BIP0062.
|
||||
//
|
||||
// Note that signRFC6979 makes use of this function as it is the primary ECDSA
|
||||
// signing logic. It differs in that it accepts a nonce to use when signing and
|
||||
// may not successfully produce a valid signature for the given nonce. It is
|
||||
// primarily separated for testing purposes.
|
||||
func sign(secKey, nonce *secp256k1.ModNScalar, hash []byte) (*Signature, byte,
|
||||
bool) {
|
||||
// The algorithm for producing an ECDSA signature is given as algorithm 4.29
|
||||
// in [GECC].
|
||||
//
|
||||
// The following is a paraphrased version for reference:
|
||||
//
|
||||
// G = curve generator
|
||||
// N = curve order
|
||||
// d = secret key
|
||||
// m = message
|
||||
// r, s = signature
|
||||
//
|
||||
// 1. Select random nonce k in [1, N-1]
|
||||
// 2. Compute kG
|
||||
// 3. r = kG.x mod N (kG.x is the x coordinate of the point kG)
|
||||
// Repeat from step 1 if r = 0
|
||||
// 4. e = H(m)
|
||||
// 5. s = k^-1(e + dr) mod N
|
||||
// Repeat from step 1 if s = 0
|
||||
// 6. Return (r,s)
|
||||
//
|
||||
// This is slightly modified here to conform to RFC6979 and BIP 62 as
|
||||
// follows:
|
||||
//
|
||||
// A. Instead of selecting a random nonce in step 1, use RFC6979 to generate
|
||||
// a deterministic nonce in [1, N-1] parameterized by the secret key,
|
||||
// message being signed, and an iteration count for the repeat cases
|
||||
// B. Negate s calculated in step 5 if it is > N/2
|
||||
// This is done because both s and its negation are valid signatures
|
||||
// modulo the curve order N, so it forces a consistent choice to reduce
|
||||
// signature malleability
|
||||
//
|
||||
// NOTE: Step 1 is performed by the caller.
|
||||
//
|
||||
// Step 2.
|
||||
//
|
||||
// Compute kG
|
||||
//
|
||||
// Note that the point must be in affine coordinates.
|
||||
k := nonce
|
||||
var kG secp256k1.JacobianPoint
|
||||
secp256k1.ScalarBaseMultNonConst(k, &kG)
|
||||
kG.ToAffine()
|
||||
// Step 3.
|
||||
//
|
||||
// r = kG.x mod N
|
||||
// Repeat from step 1 if r = 0
|
||||
r, overflow := fieldToModNScalar(&kG.X)
|
||||
if r.IsZero() {
|
||||
return nil, 0, false
|
||||
}
|
||||
// Since the secp256k1 curve has a cofactor of 1, when recovering a
|
||||
// public key from an ECDSA signature over it, there are four possible
|
||||
// candidates corresponding to the following cases:
|
||||
//
|
||||
// 1) The X coord of the random point is < N and its Y coord even
|
||||
// 2) The X coord of the random point is < N and its Y coord is odd
|
||||
// 3) The X coord of the random point is >= N and its Y coord is even
|
||||
// 4) The X coord of the random point is >= N and its Y coord is odd
|
||||
//
|
||||
// Rather than forcing the recovery procedure to check all possible
|
||||
// cases, this creates a recovery code that uniquely identifies which of
|
||||
// the cases apply by making use of 2 bits. Bit 0 identifies the
|
||||
// oddness case and Bit 1 identifies the overflow case (aka when the X
|
||||
// coord >= N).
|
||||
//
|
||||
// It is also worth noting that making use of Hasse's theorem shows
|
||||
// there are around log_2((p-n)/p) ~= -127.65 ~= 1 in 2^127 points where
|
||||
// the X coordinate is >= N. It is not possible to calculate these
|
||||
// points since that would require breaking the ECDLP, but, in practice
|
||||
// this strongly implies with extremely high probability that there are
|
||||
// only a few actual points for which this case is true.
|
||||
pubKeyRecoveryCode := byte(overflow<<1) | byte(kG.Y.IsOddBit())
|
||||
// Step 4.
|
||||
//
|
||||
// e = H(m)
|
||||
//
|
||||
// Note that this actually sets e = H(m) mod N which is correct since
|
||||
// it is only used in step 5 which itself is mod N.
|
||||
var e secp256k1.ModNScalar
|
||||
e.SetByteSlice(hash)
|
||||
// Step 5 with modification B.
|
||||
//
|
||||
// s = k^-1(e + dr) mod N
|
||||
// Repeat from step 1 if s = 0
|
||||
// s = -s if s > N/2
|
||||
kinv := new(secp256k1.ModNScalar).InverseValNonConst(k)
|
||||
s := new(secp256k1.ModNScalar).Mul2(secKey, &r).Add(&e).Mul(kinv)
|
||||
if s.IsZero() {
|
||||
return nil, 0, false
|
||||
}
|
||||
if s.IsOverHalfOrder() {
|
||||
s.Negate()
|
||||
// Negating s corresponds to the random point that would have been
|
||||
// generated by -k (mod N), which necessarily has the opposite
|
||||
// oddness since N is prime, thus flip the pubkey recovery code
|
||||
// oddness bit accordingly.
|
||||
pubKeyRecoveryCode ^= 0x01
|
||||
}
|
||||
// Step 6.
|
||||
//
|
||||
// Return (r,s)
|
||||
return NewSignature(&r, s), pubKeyRecoveryCode, true
|
||||
}
|
||||
|
||||
// signRFC6979 generates a deterministic ECDSA signature according to RFC 6979
|
||||
// and BIP0062 and returns it along with an additional public key recovery code
|
||||
// for efficiently recovering the public key from the signature.
|
||||
func signRFC6979(secKey *secp256k1.SecretKey, hash []byte) (*Signature,
|
||||
byte) {
|
||||
// The algorithm for producing an ECDSA signature is given as algorithm 4.29
|
||||
// in [GECC].
|
||||
//
|
||||
// The following is a paraphrased version for reference:
|
||||
//
|
||||
// G = curve generator
|
||||
// N = curve order
|
||||
// d = secret key
|
||||
// m = message
|
||||
// r, s = signature
|
||||
//
|
||||
// 1. Select random nonce k in [1, N-1]
|
||||
// 2. Compute kG
|
||||
// 3. r = kG.x mod N (kG.x is the x coordinate of the point kG)
|
||||
// Repeat from step 1 if r = 0
|
||||
// 4. e = H(m)
|
||||
// 5. s = k^-1(e + dr) mod N
|
||||
// Repeat from step 1 if s = 0
|
||||
// 6. Return (r,s)
|
||||
//
|
||||
// This is slightly modified here to conform to RFC6979 and BIP 62 as
|
||||
// follows:
|
||||
//
|
||||
// A. Instead of selecting a random nonce in step 1, use RFC6979 to generate
|
||||
// a deterministic nonce in [1, N-1] parameterized by the secret key,
|
||||
// message being signed, and an iteration count for the repeat cases
|
||||
// B. Negate s calculated in step 5 if it is > N/2
|
||||
// This is done because both s and its negation are valid signatures
|
||||
// modulo the curve order N, so it forces a consistent choice to reduce
|
||||
// signature malleability
|
||||
secKeyScalar := &secKey.Key
|
||||
var secKeyBytes [32]byte
|
||||
secKeyScalar.PutBytes(&secKeyBytes)
|
||||
defer zeroArray32(&secKeyBytes)
|
||||
for iteration := uint32(0); ; iteration++ {
|
||||
// Step 1 with modification A.
|
||||
//
|
||||
// Generate a deterministic nonce in [1, N-1] parameterized by the
|
||||
// secret key, message being signed, and iteration count.
|
||||
k := secp256k1.NonceRFC6979(secKeyBytes[:], hash, nil, nil, iteration)
|
||||
// Steps 2-6.
|
||||
sig, pubKeyRecoveryCode, success := sign(secKeyScalar, k, hash)
|
||||
k.Zero()
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
return sig, pubKeyRecoveryCode
|
||||
}
|
||||
}
|
||||
|
||||
// Sign generates an ECDSA signature over the secp256k1 curve for the provided
|
||||
// hash (which should be the result of hashing a larger message) using the given
|
||||
// secret key. The produced signature is deterministic (same message and same
|
||||
// key yield the same signature) and canonical in accordance with RFC6979 and
|
||||
// BIP0062.
|
||||
func Sign(key *secp256k1.SecretKey, hash []byte) *Signature {
|
||||
signature, _ := signRFC6979(key, hash)
|
||||
return signature
|
||||
}
|
||||
|
||||
const (
|
||||
// compactSigSize is the size of a compact signature. It consists of a
|
||||
// compact signature recovery code byte followed by the R and S components
|
||||
// serialized as 32-byte big-endian values. 1+32*2 = 65.
|
||||
// for the R and S components. 1+32+32=65.
|
||||
compactSigSize = 65
|
||||
// compactSigMagicOffset is a value used when creating the compact signature
|
||||
// recovery code inherited from Bitcoin and has no meaning, but has been
|
||||
// retained for compatibility. For historical purposes, it was originally
|
||||
// picked to avoid a binary representation that would allow compact
|
||||
// signatures to be mistaken for other components.
|
||||
compactSigMagicOffset = 27
|
||||
// compactSigCompPubKey is a value used when creating the compact signature
|
||||
// recovery code to indicate the original public key was compressed.
|
||||
compactSigCompPubKey = 4
|
||||
// pubKeyRecoveryCodeOddnessBit specifies the bit that indicates the oddess
|
||||
// of the Y coordinate of the random point calculated when creating a
|
||||
// signature.
|
||||
pubKeyRecoveryCodeOddnessBit = 1 << 0
|
||||
// pubKeyRecoveryCodeOverflowBit specifies the bit that indicates the X
|
||||
// coordinate of the random point calculated when creating a signature was
|
||||
// >= N, where N is the order of the group.
|
||||
pubKeyRecoveryCodeOverflowBit = 1 << 1
|
||||
)
|
||||
|
||||
// SignCompact produces a compact ECDSA signature over the secp256k1 curve for
|
||||
// the provided hash (which should be the result of hashing a larger message)
|
||||
// using the given secret key. The isCompressedKey parameter specifies if the
|
||||
// produced signature should reference a compressed public key or not.
|
||||
//
|
||||
// Compact signature format:
|
||||
// <1-byte compact sig recovery code><32-byte R><32-byte S>
|
||||
//
|
||||
// The compact sig recovery code is the value 27 + public key recovery code + 4
|
||||
// if the compact signature was created with a compressed public key.
|
||||
func SignCompact(key *secp256k1.SecretKey, hash []byte,
|
||||
isCompressedKey bool) []byte {
|
||||
// Create the signature and associated pubkey recovery code and calculate
|
||||
// the compact signature recovery code.
|
||||
sig, pubKeyRecoveryCode := signRFC6979(key, hash)
|
||||
compactSigRecoveryCode := compactSigMagicOffset + pubKeyRecoveryCode
|
||||
if isCompressedKey {
|
||||
compactSigRecoveryCode += compactSigCompPubKey
|
||||
}
|
||||
// Output <compactSigRecoveryCode><32-byte R><32-byte S>.
|
||||
var b [compactSigSize]byte
|
||||
b[0] = compactSigRecoveryCode
|
||||
sig.r.PutBytesUnchecked(b[1:33])
|
||||
sig.s.PutBytesUnchecked(b[33:65])
|
||||
return b[:]
|
||||
}
|
||||
|
||||
// RecoverCompact attempts to recover the secp256k1 public key from the provided
|
||||
// compact signature and message hash. It first verifies the signature, and, if
|
||||
// the signature matches then the recovered public key will be returned as well
|
||||
// as a boolean indicating whether or not the original key was compressed.
|
||||
func RecoverCompact(signature, hash []byte) (*secp256k1.PublicKey, bool, error) {
|
||||
// The following is very loosely based on the information and algorithm that
|
||||
// describes recovering a public key from and ECDSA signature in section
|
||||
// 4.1.6 of [SEC1].
|
||||
//
|
||||
// Given the following parameters:
|
||||
//
|
||||
// G = curve generator
|
||||
// N = group order
|
||||
// P = field prime
|
||||
// Q = public key
|
||||
// m = message
|
||||
// e = hash of the message
|
||||
// r, s = signature
|
||||
// X = random point used when creating signature whose x coordinate is r
|
||||
//
|
||||
// The equation to recover a public key candidate from an ECDSA signature
|
||||
// is:
|
||||
// Q = r^-1(sX - eG).
|
||||
//
|
||||
// This can be verified by plugging it in for Q in the sig verification
|
||||
// equation:
|
||||
// X = s^-1(eG + rQ) (mod N)
|
||||
// => s^-1(eG + r(r^-1(sX - eG))) (mod N)
|
||||
// => s^-1(eG + sX - eG) (mod N)
|
||||
// => s^-1(sX) (mod N)
|
||||
// => X (mod N)
|
||||
//
|
||||
// However, note that since r is the x coordinate mod N from a random point
|
||||
// that was originally mod P, and the cofactor of the secp256k1 curve is 1,
|
||||
// there are four possible points that the original random point could have
|
||||
// been to produce r: (r,y), (r,-y), (r+N,y), and (r+N,-y). At least 2 of
|
||||
// those points will successfully verify, and all 4 will successfully verify
|
||||
// when the original x coordinate was in the range [N+1, P-1], but in any
|
||||
// case, only one of them corresponds to the original secret key used.
|
||||
//
|
||||
// The method described by section 4.1.6 of [SEC1] to determine which one is
|
||||
// the correct one involves calculating each possibility as a candidate
|
||||
// public key and comparing the candidate to the authentic public key. It
|
||||
// also hints that it is possible to generate the signature in a such a
|
||||
// way that only one of the candidate public keys is viable.
|
||||
//
|
||||
// A more efficient approach that is specific to the secp256k1 curve is used
|
||||
// here instead which is to produce a "pubkey recovery code" when signing
|
||||
// that uniquely identifies which of the 4 possibilities is correct for the
|
||||
// original random point and using that to recover the pubkey directly as
|
||||
// follows:
|
||||
//
|
||||
// 1. Fail if r and s are not in [1, N-1]
|
||||
// 2. Convert r to integer mod P
|
||||
// 3. If pubkey recovery code overflow bit is set:
|
||||
// 3.1 Fail if r + N >= P
|
||||
// 3.2 r = r + N (mod P)
|
||||
// 4. y = +sqrt(r^3 + 7) (mod P)
|
||||
// 4.1 Fail if y does not exist
|
||||
// 4.2 y = -y if needed to match pubkey recovery code oddness bit
|
||||
// 5. X = (r, y)
|
||||
// 6. e = H(m) mod N
|
||||
// 7. w = r^-1 mod N
|
||||
// 8. u1 = -(e * w) mod N
|
||||
// u2 = s * w mod N
|
||||
// 9. Q = u1G + u2X
|
||||
// 10. Fail if Q is the point at infinity
|
||||
//
|
||||
// A compact signature consists of a recovery byte followed by the R and
|
||||
// S components serialized as 32-byte big-endian values.
|
||||
if len(signature) != compactSigSize {
|
||||
str := fmt.Sprintf("malformed signature: wrong size: %d != %d",
|
||||
len(signature), compactSigSize)
|
||||
return nil, false, signatureError(ErrSigInvalidLen, str)
|
||||
}
|
||||
// Parse and validate the compact signature recovery code.
|
||||
const (
|
||||
minValidCode = compactSigMagicOffset
|
||||
maxValidCode = compactSigMagicOffset + compactSigCompPubKey + 3
|
||||
)
|
||||
sigRecoveryCode := signature[0]
|
||||
if sigRecoveryCode < minValidCode || sigRecoveryCode > maxValidCode {
|
||||
str := fmt.Sprintf("invalid signature: public key recovery code %d is "+
|
||||
"not in the valid range [%d, %d]", sigRecoveryCode, minValidCode,
|
||||
maxValidCode)
|
||||
return nil, false, signatureError(ErrSigInvalidRecoveryCode, str)
|
||||
}
|
||||
sigRecoveryCode -= compactSigMagicOffset
|
||||
wasCompressed := sigRecoveryCode&compactSigCompPubKey != 0
|
||||
pubKeyRecoveryCode := sigRecoveryCode & 3
|
||||
// Step 1.
|
||||
//
|
||||
// Parse and validate the R and S signature components.
|
||||
//
|
||||
// Fail if r and s are not in [1, N-1].
|
||||
var r, s secp256k1.ModNScalar
|
||||
if overflow := r.SetByteSlice(signature[1:33]); overflow {
|
||||
str := "invalid signature: R >= group order"
|
||||
return nil, false, signatureError(ErrSigRTooBig, str)
|
||||
}
|
||||
if r.IsZero() {
|
||||
str := "invalid signature: R is 0"
|
||||
return nil, false, signatureError(ErrSigRIsZero, str)
|
||||
}
|
||||
if overflow := s.SetByteSlice(signature[33:]); overflow {
|
||||
str := "invalid signature: S >= group order"
|
||||
return nil, false, signatureError(ErrSigSTooBig, str)
|
||||
}
|
||||
if s.IsZero() {
|
||||
str := "invalid signature: S is 0"
|
||||
return nil, false, signatureError(ErrSigSIsZero, str)
|
||||
}
|
||||
// Step 2.
|
||||
//
|
||||
// Convert r to integer mod P.
|
||||
fieldR := modNScalarToField(&r)
|
||||
// Step 3.
|
||||
//
|
||||
// If pubkey recovery code overflow bit is set:
|
||||
if pubKeyRecoveryCode&pubKeyRecoveryCodeOverflowBit != 0 {
|
||||
// Step 3.1.
|
||||
//
|
||||
// Fail if r + N >= P
|
||||
//
|
||||
// Either the signature or the recovery code must be invalid if the
|
||||
// recovery code overflow bit is set and adding N to the R component
|
||||
// would exceed the field prime since R originally came from the X
|
||||
// coordinate of a random point on the curve.
|
||||
if fieldR.IsGtOrEqPrimeMinusOrder() {
|
||||
str := "invalid signature: signature R + N >= P"
|
||||
return nil, false, signatureError(ErrSigOverflowsPrime, str)
|
||||
}
|
||||
// Step 3.2.
|
||||
//
|
||||
// r = r + N (mod P)
|
||||
fieldR.Add(&orderAsFieldVal)
|
||||
}
|
||||
// Step 4.
|
||||
//
|
||||
// y = +sqrt(r^3 + 7) (mod P)
|
||||
// Fail if y does not exist.
|
||||
// y = -y if needed to match pubkey recovery code oddness bit
|
||||
//
|
||||
// The signature must be invalid if the calculation fails because the X
|
||||
// coord originally came from a random point on the curve which means there
|
||||
// must be a Y coord that satisfies the equation for a valid signature.
|
||||
oddY := pubKeyRecoveryCode&pubKeyRecoveryCodeOddnessBit != 0
|
||||
var y secp256k1.FieldVal
|
||||
if valid := secp256k1.DecompressY(&fieldR, oddY, &y); !valid {
|
||||
str := "invalid signature: not for a valid curve point"
|
||||
return nil, false, signatureError(ErrPointNotOnCurve, str)
|
||||
}
|
||||
// Step 5.
|
||||
//
|
||||
// X = (r, y)
|
||||
var X secp256k1.JacobianPoint
|
||||
X.X.Set(fieldR.Normalize())
|
||||
X.Y.Set(y.Normalize())
|
||||
X.Z.SetInt(1)
|
||||
// Step 6.
|
||||
//
|
||||
// e = H(m) mod N
|
||||
var e secp256k1.ModNScalar
|
||||
e.SetByteSlice(hash)
|
||||
// Step 7.
|
||||
//
|
||||
// w = r^-1 mod N
|
||||
w := new(secp256k1.ModNScalar).InverseValNonConst(&r)
|
||||
// Step 8.
|
||||
//
|
||||
// u1 = -(e * w) mod N
|
||||
// u2 = s * w mod N
|
||||
u1 := new(secp256k1.ModNScalar).Mul2(&e, w).Negate()
|
||||
u2 := new(secp256k1.ModNScalar).Mul2(&s, w)
|
||||
// Step 9.
|
||||
//
|
||||
// Q = u1G + u2X
|
||||
var Q, u1G, u2X secp256k1.JacobianPoint
|
||||
secp256k1.ScalarBaseMultNonConst(u1, &u1G)
|
||||
secp256k1.ScalarMultNonConst(u2, &X, &u2X)
|
||||
secp256k1.AddNonConst(&u1G, &u2X, &Q)
|
||||
// Step 10.
|
||||
//
|
||||
// Fail if Q is the point at infinity.
|
||||
//
|
||||
// Either the signature or the pubkey recovery code must be invalid if the
|
||||
// recovered pubkey is the point at infinity.
|
||||
if (Q.X.IsZero() && Q.Y.IsZero()) || Q.Z.IsZero() {
|
||||
str := "invalid signature: recovered pubkey is the point at infinity"
|
||||
return nil, false, signatureError(ErrPointNotOnCurve, str)
|
||||
}
|
||||
// Notice that the public key is in affine coordinates.
|
||||
Q.ToAffine()
|
||||
pubKey := secp256k1.NewPublicKey(&Q.X, &Q.Y)
|
||||
return pubKey, wasCompressed, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user