diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4b82d51 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: rohenaz +custom: https://gobitcoinsv.com/?tab=tips&af=go-bitcoin \ No newline at end of file diff --git a/.github/IMAGES/github-share-image.png b/.github/IMAGES/github-share-image.png new file mode 100644 index 0000000..aacc035 Binary files /dev/null and b/.github/IMAGES/github-share-image.png differ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100755 index 0000000..460480d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve this project +labels: bug-p3 +assignees: mrz1836 + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100755 index 0000000..cb3fd96 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: idea +assignees: mrz1836 + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100755 index 0000000..c7f749a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,13 @@ +--- +name: Question +about: 'General template for a question ' +labels: question +assignees: mrz1836 + +--- + +**What's your question?** +A clear and concise question using references to specific regions of code if applicable. + +**Additional context** +Add any other context or information. diff --git a/.github/PULL_REQUEST_TEMPLATE/general.md b/.github/PULL_REQUEST_TEMPLATE/general.md new file mode 100755 index 0000000..4030f6f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/general.md @@ -0,0 +1,7 @@ +Fixes # + +## Proposed Changes + + - + - + - diff --git a/.gitignore b/.gitignore index 66fd13c..76760de 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# JetBeans +.idea diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..7580a49 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,27 @@ +# Make sure to check the documentation at http://goreleaser.com +# --------------------------- +# GENERAL +# --------------------------- +before: + hooks: + - make all +snapshot: + name_template: "{{ .Tag }}" +changelog: + sort: asc + filters: + exclude: + - '^.github:' + - '^test:' + +# --------------------------- +# BUILDER +# --------------------------- +build: + skip: true +# --------------------------- +# Github Release +# --------------------------- +release: + prerelease: true + name_template: "Release v{{.Version}}" \ No newline at end of file diff --git a/.make/Makefile.common b/.make/Makefile.common new file mode 100644 index 0000000..7010728 --- /dev/null +++ b/.make/Makefile.common @@ -0,0 +1,71 @@ +## Default repository domain name +ifndef GIT_DOMAIN + override GIT_DOMAIN=github.com +endif + +## Set if defined (alias variable for ease of use) +ifdef branch + override REPO_BRANCH=$(branch) + export REPO_BRANCH +endif + +## Do we have git available? +HAS_GIT := $(shell command -v git 2> /dev/null) + +ifdef HAS_GIT + ## Do we have a repo? + HAS_REPO := $(shell git rev-parse --is-inside-work-tree 2> /dev/null) + ifdef HAS_REPO + ## Automatically detect the repo owner and repo name (for local use with Git) + REPO_NAME=$(shell basename "$(shell git rev-parse --show-toplevel 2> /dev/null)") + REPO_OWNER=$(shell git config --get remote.origin.url | sed 's/git@$(GIT_DOMAIN)://g' | sed 's/\/$(REPO_NAME).git//g') + VERSION_SHORT=$(shell git describe --tags --always --abbrev=0) + export REPO_NAME, REPO_OWNER, VERSION_SHORT + endif +endif + +## Set the distribution folder +ifndef DISTRIBUTIONS_DIR + override DISTRIBUTIONS_DIR=./dist +endif +export DISTRIBUTIONS_DIR + +help: ## Show this help message + @egrep -h '^(.+)\:\ ##\ (.+)' ${MAKEFILE_LIST} | column -t -c 2 -s ':#' + +release:: ## Full production release (creates release in Github) + @test $(github_token) + @export GITHUB_TOKEN=$(github_token) && goreleaser --rm-dist + +release-test: ## Full production test release (everything except deploy) + @goreleaser --skip-publish --rm-dist + +release-snap: ## Test the full release (build binaries) + @goreleaser --snapshot --skip-publish --rm-dist + +replace-version: ## Replaces the version in HTML/JS (pre-deploy) + @test $(version) + @test "$(path)" + @find $(path) -name "*.html" -type f -exec sed -i '' -e "s/{{version}}/$(version)/g" {} \; + @find $(path) -name "*.js" -type f -exec sed -i '' -e "s/{{version}}/$(version)/g" {} \; + +tag: ## Generate a new tag and push (tag version=0.0.0) + @test $(version) + @git tag -a v$(version) -m "Pending full release..." + @git push origin v$(version) + @git fetch --tags -f + +tag-remove: ## Remove a tag if found (tag-remove version=0.0.0) + @test $(version) + @git tag -d v$(version) + @git push --delete origin v$(version) + @git fetch --tags + +tag-update: ## Update an existing tag to current commit (tag-update version=0.0.0) + @test $(version) + @git push --force origin HEAD:refs/tags/v$(version) + @git fetch --tags -f + +update-releaser: ## Update the goreleaser application + @brew update + @brew upgrade goreleaser diff --git a/.make/Makefile.go b/.make/Makefile.go new file mode 100644 index 0000000..86fdde3 --- /dev/null +++ b/.make/Makefile.go @@ -0,0 +1,80 @@ +## Default to the repo name if empty +ifndef BINARY_NAME + override BINARY_NAME=app +endif + +## Define the binary name +ifdef CUSTOM_BINARY_NAME + override BINARY_NAME=$(CUSTOM_BINARY_NAME) +endif + +## Set the binary release names +DARWIN=$(BINARY_NAME)-darwin +LINUX=$(BINARY_NAME)-linux +WINDOWS=$(BINARY_NAME)-windows.exe + +.PHONY: test lint install + +bench: ## Run all benchmarks in the Go application + @go test -bench=. -benchmem + +build-go: ## Build the Go application (locally) + @go build -o bin/$(BINARY_NAME) + +clean-mods: ## Remove all the Go mod cache + @go clean -modcache + +coverage: ## Shows the test coverage + @go test -coverprofile=coverage.out ./... && go tool cover -func=coverage.out + +godocs: ## Sync the latest tag with GoDocs + @test $(GIT_DOMAIN) + @test $(REPO_OWNER) + @test $(REPO_NAME) + @test $(VERSION_SHORT) + @curl https://proxy.golang.org/$(GIT_DOMAIN)/$(REPO_OWNER)/$(REPO_NAME)/@v/$(VERSION_SHORT).info + +install: ## Install the application + @go build -o $$GOPATH/bin/$(BINARY_NAME) + +install-go: ## Install the application (Using Native Go) + @go install $(GIT_DOMAIN)/$(REPO_OWNER)/$(REPO_NAME) + +lint: ## Run the Go lint application + @if [ "$(shell command -v golint)" = "" ]; then go get -u golang.org/x/lint/golint; fi + @golint + +test: ## Runs vet, lint and ALL tests + @$(MAKE) vet + @$(MAKE) lint + @go test ./... -v + +test-short: ## Runs vet, lint and tests (excludes integration tests) + @$(MAKE) vet + @$(MAKE) lint + @go test ./... -v -test.short + +test-travis: ## Runs all tests via Travis (also exports coverage) + @$(MAKE) vet + @$(MAKE) lint + @go test ./... -race -coverprofile=coverage.txt -covermode=atomic + +test-travis-short: ## Runs unit tests via Travis (also exports coverage) + @$(MAKE) vet + @$(MAKE) lint + @go test ./... -test.short -race -coverprofile=coverage.txt -covermode=atomic + +uninstall: ## Uninstall the application (and remove files) + @test $(BINARY_NAME) + @test $(GIT_DOMAIN) + @test $(REPO_OWNER) + @test $(REPO_NAME) + @go clean -i $(GIT_DOMAIN)/$(REPO_OWNER)/$(REPO_NAME) + @rm -rf $$GOPATH/src/$(GIT_DOMAIN)/$(REPO_OWNER)/$(REPO_NAME) + @rm -rf $$GOPATH/bin/$(BINARY_NAME) + +update: ## Update all project dependencies + @go get -u ./... && go mod tidy + +vet: ## Run the Go vet application + @go vet -v ./... \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..abd6f1c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +# Fast runner (trick from @y0ssar1an) (out-dated) +sudo: false + +# Language of deployment +language: go + +# Version +go: + - 1.15.x + +# Environment variables +env: + - GO111MODULE=on + +# Only clone the most recent commit +git: + depth: 1 + +# Notifications off +notifications: + email: false + +# Run all scripts +script: + - make test-travis + +# After build success +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..be4bcd0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Code of Merit + +1. The project creators, lead developers, core team, constitute +the managing members of the project and have final say in every decision +of the project, technical or otherwise, including overruling previous decisions. +There are no limitations to this decisional power. + +2. Contributions are an expected result of your membership on the project. +Don't expect others to do your work or help you with your work forever. + +3. All members have the same opportunities to seek any challenge they want +within the project. + +4. Authority or position in the project will be proportional +to the accrued contribution. Seniority must be earned. + +5. Software is evolutive: the better implementations must supersede lesser +implementations. Technical advantage is the primary evaluation metric. + +6. This is a space for technical prowess; topics outside of the project +will not be tolerated. + +7. Non technical conflicts will be discussed in a separate space. Disruption +of the project will not be allowed. + +8. Individual characteristics, including but not limited to, +body, sex, sexual preference, race, language, religion, nationality, +or political preferences are irrelevant in the scope of the project and +will not be taken into account concerning your value or that of your contribution +to the project. + +9. Discuss or debate the idea, not the person. + +10. There is no room for ambiguity: Ambiguity will be met with questioning; +further ambiguity will be met with silence. It is the responsibility +of the originator to provide requested context. + +11. If something is illegal outside the scope of the project, it is illegal +in the scope of the project. This Code of Merit does not take precedence over +governing law. + +12. This Code of Merit governs the technical procedures of the project not the +activities outside of it. + +13. Participation on the project equates to agreement of this Code of Merit. + +14. No objectives beyond the stated objectives of this project are relevant +to the project. Any intent to deviate the project from its original purpose +of existence will constitute grounds for remedial action which may include +expulsion from the project. + +This document is the Code of Merit (`http://code-of-merit.org`), version 1.0. \ No newline at end of file diff --git a/CODE_STANDARDS.md b/CODE_STANDARDS.md new file mode 100644 index 0000000..6d34d16 --- /dev/null +++ b/CODE_STANDARDS.md @@ -0,0 +1,37 @@ +# Code Standards + +This project uses the following code standards and specifications from: +- [effective go](https://golang.org/doc/effective_go.html) +- [go tests](https://golang.org/pkg/testing/) +- [go examples](https://golang.org/pkg/testing/#hdr-Examples) +- [go benchmarks](https://golang.org/pkg/testing/#hdr-Benchmarks) +- [gofmt](https://golang.org/cmd/gofmt/) +- [golint](https://github.com/golang/lint) +- [godoc](https://godoc.org/golang.org/x/tools/cmd/godoc) +- [vet](https://golang.org/cmd/vet/) +- [report card](https://goreportcard.com/) + +### *effective go* standards +View the [effective go](https://golang.org/doc/effective_go.html) standards documentation. + +### *golint* specifications +The package [golint](https://github.com/golang/lint) differs from [gofmt](https://golang.org/cmd/gofmt/). The package [gofmt](https://golang.org/cmd/gofmt/) formats Go source code, whereas [golint](https://github.com/golang/lint) prints out style mistakes. The package [golint](https://github.com/golang/lint) differs from [vet](https://golang.org/cmd/vet/). The package [vet](https://golang.org/cmd/vet/) is concerned with correctness, whereas [golint](https://github.com/golang/lint) is concerned with coding style. The package [golint](https://github.com/golang/lint) is in use at Google, and it seeks to match the accepted style of the open source [Go project](https://golang.org/). + +How to install [golint](https://github.com/golang/lint): +```shell script +go get -u golang.org/x/lint/golint +cd ../go-bitcoin +golint +``` + +### *go vet* specifications +[Vet](https://golang.org/cmd/vet/) examines Go source code and reports suspicious constructs. [Vet](https://golang.org/cmd/vet/) uses heuristics that do not guarantee all reports are genuine problems, but it can find errors not caught by the compilers. + +How to run [vet](https://golang.org/cmd/vet/): +```shell script +cd ../go-bitcoin +go vet -v +``` + +### *godoc* specifications +All code is written with documentation in mind. Follow the best practices with naming, examples and function descriptions. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7c5276b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# How to contribute + +Please send a GitHub Pull Request to with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). The more tests the merrier. Please follow the coding conventions (below) and make sure all of your commits are atomic (one feature per commit). + +## Testing + +All tests follow the standard Go testing pattern. +- [Go Tests](https://golang.org/pkg/testing/) +- [Go Examples](https://golang.org/pkg/testing/#hdr-Examples) +- [Go Benchmarks](https://golang.org/pkg/testing/#hdr-Benchmarks) + +## Coding conventions + +This project follows [effective Go standards](https://golang.org/doc/effective_go.html) and uses additional convention tools: +- [godoc](https://godoc.org/golang.org/x/tools/cmd/godoc) +- [golint](https://github.com/golang/lint) +- [vet](https://golang.org/cmd/vet/) +- [GoReportCard.com](https://goreportcard.com/report/github.com/rohenaz/go-bitcoin) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9709db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 @rohenaz + +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. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9316e93 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# Common makefile commands & variables between projects +include .make/Makefile.common + +# Common Golang makefile commands & variables between projects +include .make/Makefile.go + +## Not defined? Use default repo name which is the application +ifeq ($(REPO_NAME),) + REPO_NAME="go-bitcoin" +endif + +## Not defined? Use default repo owner +ifeq ($(REPO_OWNER),) + REPO_OWNER="rohenaz" +endif + +.PHONY: clean + +all: ## Runs multiple commands + @$(MAKE) test + +clean: ## Remove previous builds and any test cache data + @go clean -cache -testcache -i -r + @test $(DISTRIBUTIONS_DIR) + @if [ -d $(DISTRIBUTIONS_DIR) ]; then rm -r $(DISTRIBUTIONS_DIR); fi + +release:: ## Runs common.release then runs godocs + @$(MAKE) godocs \ No newline at end of file diff --git a/address.go b/address.go index ff3a229..d1b29cb 100644 --- a/address.go +++ b/address.go @@ -39,7 +39,7 @@ func (a *A25) EmbeddedChecksum() (c [4]byte) { } // Tmpl and Set58 are adapted from the C solution. -// Go has big integers but this techinique seems better. +// Go has big integers but this technique seems better. var tmpl = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") // Set58 takes a base58 encoded address and decodes it into the receiver. @@ -69,19 +69,22 @@ func (a *A25) Set58(s []byte) error { func (a *A25) ComputeChecksum() (c [4]byte) { copy(c[:], a.doubleSHA256()) return -} /* {{header|Go}} */ +} -// AddressFromPrivKey takes a private key string and returns a Bitcoin address -func AddressFromPrivKey(privKey string) string { - pubKey := PrivateKey(privKey).PubKey() - return Address(pubKey).EncodeAddress() +// AddressFromPrivateKey takes a private key string and returns a Bitcoin address +func AddressFromPrivateKey(privateKey string) (string, error) { + pubKey := PrivateKey(privateKey).PubKey() + address, err := Address(pubKey) + if err != nil { + return "", err + } + return address.EncodeAddress(), nil } // Address gets a bsvutil.LegacyAddressPubKeyHash -func Address(publicKey *bsvec.PublicKey) (address *bsvutil.LegacyAddressPubKeyHash) { +func Address(publicKey *bsvec.PublicKey) (*bsvutil.LegacyAddressPubKeyHash, error) { publicKeyHash := bsvutil.Hash160(publicKey.SerializeCompressed()) - address, _ = bsvutil.NewLegacyAddressPubKeyHash(publicKeyHash, &chaincfg.MainNetParams) - return + return bsvutil.NewLegacyAddressPubKeyHash(publicKeyHash, &chaincfg.MainNetParams) } // ValidA58 validates a base58 encoded bitcoin address. An address is valid @@ -89,7 +92,7 @@ func Address(publicKey *bsvec.PublicKey) (address *bsvutil.LegacyAddressPubKeyHa // and the checksum validates. Return value ok will be true for valid // addresses. If ok is false, the address is invalid and the error value // may indicate why. -func ValidA58(a58 []byte) (ok bool, err error) { +func ValidA58(a58 []byte) (bool, error) { var a A25 if err := a.Set58(a58); err != nil { return false, err diff --git a/address_test.go b/address_test.go index 08198bf..5b5225d 100644 --- a/address_test.go +++ b/address_test.go @@ -1,17 +1,60 @@ package bitcoin import ( + "fmt" "testing" ) -// Test address -const address = "1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2" - +// TestValidA58 will test the method ValidA58() func TestValidA58(t *testing.T) { - valid, err := ValidA58([]byte(address)) + t.Parallel() + + // Create the list of tests + var tests = []struct { + input string + expectedValid bool + expectedError bool + }{ + {"1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2", true, false}, + {"1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi", false, false}, + {"1KCEAmV", false, false}, + {"", false, false}, + {"0", false, true}, + } + + // Run tests + for _, test := range tests { + if valid, err := ValidA58([]byte(test.input)); err != nil && !test.expectedError { + t.Errorf("%s Failed: [%s] inputted and error not expected but got: %s", t.Name(), test.input, err.Error()) + } else if err == nil && test.expectedError { + t.Errorf("%s Failed: [%s] inputted and error was expected", t.Name(), test.input) + } else if valid && !test.expectedValid { + t.Errorf("%s Failed: [%s] inputted and was valid but should NOT be valid", t.Name(), test.input) + } else if !valid && test.expectedValid { + t.Errorf("%s Failed: [%s] inputted and was invalid but should be valid", t.Name(), test.input) + } + } +} + +// ExampleValidA58 example using ValidA58() +func ExampleValidA58() { + valid, err := ValidA58([]byte("1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2")) + if err != nil { + fmt.Printf("error occurred: %s", err.Error()) + return + } else if !valid { + fmt.Printf("address is not valid: %s", "1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2") + return + } else { + fmt.Printf("address is valid!") + } + // Output:address is valid! +} - if !valid { - t.Error("Failed to validate address", err) +// BenchmarkValidA58 benchmarks the method ValidA58() +func BenchmarkValidA58(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = ValidA58([]byte("1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2")) } } diff --git a/go.mod b/go.mod index 8214012..3b30741 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/rohenaz/go-bitcoin -go 1.14 +go 1.15 require ( github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 github.com/itchyny/base58-go v0.1.0 - github.com/piotrnar/gocoin v0.0.0-20200912093848-56d57d5b6be5 + github.com/piotrnar/gocoin v0.0.0-20200920172007-af9cf2799157 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a ) diff --git a/go.sum b/go.sum index 4f54f48..ae193bf 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,13 @@ github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 h1:2yTIV9u7H0BhRDGX github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173/go.mod h1:BZ1UcC9+tmcDEcdVXgpt13hMczwJxWzpAn68wNs7zRA= github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 h1:hFI8rT84FCA0FFy3cFrkW5Nz4FyNKlIdCvEvvTNySKg= github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9/go.mod h1:p44KuNKUH5BC8uX4ONEODaHUR4+ibC8todEAOGQEJAM= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/itchyny/base58-go v0.1.0 h1:zF5spLDo956exUAD17o+7GamZTRkXOZlqJjRciZwd1I= github.com/itchyny/base58-go v0.1.0/go.mod h1:SrMWPE3DFuJJp1M/RUhu4fccp/y9AlB8AL3o3duPToU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/piotrnar/gocoin v0.0.0-20200912093848-56d57d5b6be5 h1:7qMjDCz7USXw18S5feMCKjFtXTsv5yeK62TwFiP4nQE= -github.com/piotrnar/gocoin v0.0.0-20200912093848-56d57d5b6be5/go.mod h1:sW6i99ojgdRHcz53PCjyEeoTEDFh9dfP5iiEIiNfcaM= +github.com/piotrnar/gocoin v0.0.0-20200920172007-af9cf2799157 h1:evqZpI+lpcFQv7dEyquTJf5eyuuHJBtoRSq1d4nbUzk= +github.com/piotrnar/gocoin v0.0.0-20200920172007-af9cf2799157/go.mod h1:sW6i99ojgdRHcz53PCjyEeoTEDFh9dfP5iiEIiNfcaM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/privateKey.go b/private_key.go similarity index 100% rename from privateKey.go rename to private_key.go diff --git a/sign.go b/sign.go index 66a0fe6..8b03112 100644 --- a/sign.go +++ b/sign.go @@ -11,7 +11,7 @@ import ( func SignMessage(privKey string, message string) string { prefixBytes := []byte("Bitcoin Signed Message:\n") messageBytes := []byte(message) - bytes := []byte{} + var bytes []byte bytes = append(bytes, byte(len(prefixBytes))) bytes = append(bytes, prefixBytes...) bytes = append(bytes, byte(len(messageBytes))) diff --git a/sign_test.go b/sign_test.go index f43c351..a8eecb0 100644 --- a/sign_test.go +++ b/sign_test.go @@ -7,6 +7,7 @@ import ( // Identity Private Key const privKey = "E83385AF76B2B1997326B567461FB73DD9C27EAB9E1E86D26779F4650C5F2B75" +// TestSignMessage will test the method SignMessage() func TestSignMessage(t *testing.T) { // privKey string, message string, compress bool @@ -17,13 +18,14 @@ func TestSignMessage(t *testing.T) { } } +// TestVerifyMessage will test the method VerifyMessage() func TestVerifyMessage(t *testing.T) { var sig = "IBDscOd/Ov4yrd/YXantqajSAnW4fudpfr2KQy5GNo9pZybF12uNaal4KI822UpQLS/UJD+UK2SnNMn6Z3E4na8=" var address = "1FiyJnrgwBc3Ff83V1yRWAkmXBdGrDQnXQ" var data = "Testing!" - if !VerifyMessage(address, sig, data) { - t.Error("Failed to verify message") + if err := VerifyMessage(address, sig, data); err != nil { + t.Fatalf("failed to verify message: %s", err.Error()) } } diff --git a/util.go b/util.go index 52254d0..a3669b5 100644 --- a/util.go +++ b/util.go @@ -3,6 +3,7 @@ package bitcoin import "encoding/hex" // HexDecode returns a decoded hex string without handling errors +// todo: why ignore the error? (@mrz) func HexDecode(str string) []byte { b, _ := hex.DecodeString(str) return b diff --git a/verify.go b/verify.go index 9c86cf8..a79101d 100644 --- a/verify.go +++ b/verify.go @@ -12,9 +12,9 @@ import ( ) func sha256d(body []byte) []byte { - msghash1 := sha256.Sum256([]byte(body)) - msghash2 := sha256.Sum256(msghash1[:]) - return msghash2[:] + msgHash1 := sha256.Sum256(body) + msgHash2 := sha256.Sum256(msgHash1[:]) + return msgHash2[:] } const ( @@ -23,60 +23,56 @@ const ( ) // VerifyMessage verifies a string and address against the provided signature and assumes Bitcoin Signed Message encoding -func VerifyMessage(address, signature, data string) (ok bool) { - addrs, err := sigmestoaddr(signature, data) +func VerifyMessage(address, signature, data string) error { + addresses, err := sigMessageToAddress(signature, data) if err != nil { - return + return err } - for _, addr2 := range addrs { + for _, addr2 := range addresses { if address == addr2 { - ok = true - return + return nil } } - return + return fmt.Errorf("address: %s not found", address) } -func messagehash(message, header string) (msghash2 []byte, err error) { - hlen := len(header) - if hlen >= 0xfd { - err = fmt.Errorf("long header is not supported") - return - } - mlen := len(message) - if mlen >= 0xfd { - err = fmt.Errorf("long message is not supported") - return - } - bitcoinmsg := string([]byte{byte(hlen)}) - bitcoinmsg += header - bitcoinmsg += string([]byte{byte(mlen)}) - bitcoinmsg += message - msghash2 = sha256d([]byte(bitcoinmsg)) - return +// messageHash will compute a hash for the given message & header +func messageHash(message, header string) ([]byte, error) { + headerLength := len(header) + if headerLength >= 0xfd { + return nil, fmt.Errorf("long header is not supported") + } + messageLength := len(message) + if messageLength >= 0xfd { + return nil, fmt.Errorf("long message is not supported") + } + bitcoinMsg := string([]byte{byte(headerLength)}) + bitcoinMsg += header + bitcoinMsg += string([]byte{byte(messageLength)}) + bitcoinMsg += message + return sha256d([]byte(bitcoinMsg)), nil } -func parseSignature(signature string) (sig secp256k1.Signature, recid int, - err error) { - sigraw, err2 := base64.StdEncoding.DecodeString(signature) - if err2 != nil { - err = err2 +// parseSignature will parse the given signature +func parseSignature(signature string) (sig secp256k1.Signature, recID int, err error) { + var sigRaw []byte + if sigRaw, err = base64.StdEncoding.DecodeString(signature); err != nil { return } - r0 := sigraw[0] - 27 - recid = int(r0 & 3) + r0 := sigRaw[0] - 27 + recID = int(r0 & 3) compressed := (r0 & 4) == 1 if compressed { err = fmt.Errorf("compressed type is not supported") return } - sig.R.SetBytes(sigraw[1 : 1+32]) - sig.S.SetBytes(sigraw[1+32 : 1+32+32]) + sig.R.SetBytes(sigRaw[1 : 1+32]) + sig.S.SetBytes(sigRaw[1+32 : 1+32+32]) return } -func pubtoaddr(pubkeyXy2 secp256k1.XY, compressed bool, - magic []byte) (bcpy []byte) { +// pubKeyToAddress will convert a pubkey to an address +func pubKeyToAddress(pubkeyXy2 secp256k1.XY, compressed bool, magic []byte) (byteCopy []byte) { size := 65 if compressed { size = 33 @@ -91,23 +87,23 @@ func pubtoaddr(pubkeyXy2 secp256k1.XY, compressed bool, ripemd160H.Reset() ripemd160H.Write(pubHash1) pubHash2 := ripemd160H.Sum(nil) - bcpy = append(magic, pubHash2...) - hash2 := sha256d(bcpy) - bcpy = append(bcpy, hash2[0:4]...) + byteCopy = append(magic, pubHash2...) + hash2 := sha256d(byteCopy) + byteCopy = append(byteCopy, hash2[0:4]...) return } -func addrToStr(bcpy []byte) (s string, err error) { +// addressToString will convert a raw address to a string version +func addressToString(byteCopy []byte) (s string, err error) { z := new(big.Int) - z.SetBytes(bcpy) + z.SetBytes(byteCopy) enc := base58.BitcoinEncoding - var encdd []byte - encdd, err = enc.Encode([]byte(z.String())) - if err != nil { + var encodeResults []byte + if encodeResults, err = enc.Encode([]byte(z.String())); err != nil { return } - s = string(encdd) - for _, v := range bcpy { + s = string(encodeResults) + for _, v := range byteCopy { if v != 0 { break } @@ -120,23 +116,22 @@ func addrToStr(bcpy []byte) (s string, err error) { // And modified for local package. // License is: // https://github.com/piotrnar/gocoin/blob/master/lib/secp256k1/COPYING -func getBin(num *secp256k1.Number, le int) []byte { +func getBin(num *secp256k1.Number, le int) ([]byte, error) { bts := num.Bytes() if len(bts) > le { - panic("buffer too small") + return nil, fmt.Errorf("buffer too small") } if len(bts) == le { - return bts + return bts, nil } - return append(make([]byte, le-len(bts)), bts...) + return append(make([]byte, le-len(bts)), bts...), nil } // This function is copied from "piotrnar/gocoin/lib/secp256k1". // And modified for local package. // License is: // https://github.com/piotrnar/gocoin/blob/master/lib/secp256k1/COPYING -func recover(sig *secp256k1.Signature, pubkey *secp256k1.XY, - m *secp256k1.Number, recid int) (ret bool) { +func recoverSig(sig *secp256k1.Signature, pubkey *secp256k1.XY, m *secp256k1.Number, recID int) (bool, error) { var theCurveP secp256k1.Number theCurveP.SetBytes([]byte{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -147,18 +142,23 @@ func recover(sig *secp256k1.Signature, pubkey *secp256k1.XY, var xj, qj secp256k1.XYZ rx.Set(&sig.R.Int) - if (recid & 2) != 0 { + if (recID & 2) != 0 { rx.Add(&rx.Int, &secp256k1.TheCurve.Order.Int) if rx.Cmp(&theCurveP.Int) >= 0 { - return false + return false, nil // todo: is this actually an error? } } - fx.SetB32(getBin(&rx, 32)) + bin, err := getBin(&rx, 32) + if err != nil { + return false, err + } + + fx.SetB32(bin) - X.SetXO(&fx, (recid&1) != 0) + X.SetXO(&fx, (recID&1) != 0) if !X.IsValid() { - return false + return false, nil // todo: is this actually an error? } xj.SetXY(&X) @@ -175,39 +175,47 @@ func recover(sig *secp256k1.Signature, pubkey *secp256k1.XY, xj.ECmult(&qj, &u2, &u1) pubkey.SetXYZ(&qj) - return true + return true, nil } -func sigmestoaddr(signature, message string) (addrs []string, err error) { - msghash2, err2 := messagehash(message, hBSV) - if err2 != nil { - err = err2 - return +// sigMessageToAddress will convert a signature & message to a list of addresses +func sigMessageToAddress(signature, message string) ([]string, error) { + + // Get message hash + msgHash, err := messageHash(message, hBSV) + if err != nil { + return nil, err } - sig, recid, err2 := parseSignature(signature) - if err2 != nil { - err = err2 - return + + // Parse the signature + var sig secp256k1.Signature + var recID int + sig, recID, err = parseSignature(signature) + if err != nil { + return nil, err } var msg secp256k1.Number - msg.SetBytes(msghash2) + msg.SetBytes(msgHash) var pubkeyXy2 secp256k1.XY - ret2 := recover(&sig, &pubkeyXy2, &msg, recid) - if !ret2 { - err = fmt.Errorf("recover pubkey failed") - return + var ret bool + ret, err = recoverSig(&sig, &pubkeyXy2, &msg, recID) + if err != nil { + return nil, err + } else if !ret { + return nil, fmt.Errorf("recover pubkey failed") } - addrs = make([]string, 2) + addresses := make([]string, 2) for i, compressed := range []bool{true, false} { - bcpy := pubtoaddr(pubkeyXy2, compressed, []byte{byte(0)}) - s, err2 := addrToStr(bcpy) - if err2 != nil { - err = err2 - return + byteCopy := pubKeyToAddress(pubkeyXy2, compressed, []byte{byte(0)}) + + var addressString string + addressString, err = addressToString(byteCopy) + if err != nil { + return nil, err } - addrs[i] = s + addresses[i] = addressString } - return + return addresses, nil }