diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5da1bebc63..b015b3c175 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,35 +1,69 @@ name: Build and Test on: [push, pull_request] + +env: + # go needs absolute directories, using the $HOME variable doesn't work here. + GOCACHE: /home/runner/work/go/pkg/build + GOPATH: /home/runner/work/go + GO_VERSION: 1.17.5 + jobs: build: - # https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/ - permissions: - contents: read - name: Go CI + name: Build runs-on: ubuntu-latest - strategy: - matrix: - go: [1.16.8, 1.17.1] steps: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ${{ matrix.go }} + go-version: ${{ env.GO_VERSION }} + - name: Check out source uses: actions/checkout@v2 - - name: Install Linters - run: "curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.26.0" + - name: Build - env: - GO111MODULE: "on" - run: go build ./... + run: make build + + test-cover: + name: Unit coverage + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Check out source + uses: actions/checkout@v2 + - name: Test - env: - GO111MODULE: "on" - run: | - sh ./goclean.sh + run: make unit-cover - - name: Send coverage + - name: Send top-level coverage uses: shogo82148/actions-goveralls@v1 with: - path-to-profile: profile.cov + path-to-profile: coverage.txt + + - name: Send btcutil coverage + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: btcutil/coverage.txt + + - name: Send btcutil coverage for psbt package + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: btcutil/psbt/coverage.txt + + test-race: + name: Unit race + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Check out source + uses: actions/checkout@v2 + + - name: Test + run: make unit-race diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..5db0871ad1 --- /dev/null +++ b/Makefile @@ -0,0 +1,124 @@ +PKG := github.com/btcsuite/btcd + +LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint +GOACC_PKG := github.com/ory/go-acc +GOIMPORTS_PKG := golang.org/x/tools/cmd/goimports + +GO_BIN := ${GOPATH}/bin +LINT_BIN := $(GO_BIN)/golangci-lint +GOACC_BIN := $(GO_BIN)/go-acc + +LINT_COMMIT := v1.18.0 +GOACC_COMMIT := 80342ae2e0fcf265e99e76bcc4efd022c7c3811b + +DEPGET := cd /tmp && GO111MODULE=on go get -v +GOBUILD := GO111MODULE=on go build -v +GOINSTALL := GO111MODULE=on go install -v +DEV_TAGS := rpctest +GOTEST := GO111MODULE=on go test -v -tags=$(DEV_TAGS) + +GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*") + +RM := rm -f +CP := cp +MAKE := make +XARGS := xargs -L 1 + +# Linting uses a lot of memory, so keep it under control by limiting the number +# of workers if requested. +ifneq ($(workers),) +LINT_WORKERS = --concurrency=$(workers) +endif + +LINT = $(LINT_BIN) run -v $(LINT_WORKERS) + +GREEN := "\\033[0;32m" +NC := "\\033[0m" +define print + echo $(GREEN)$1$(NC) +endef + +default: build + +all: build check + +# ============ +# DEPENDENCIES +# ============ + +$(LINT_BIN): + @$(call print, "Fetching linter") + $(DEPGET) $(LINT_PKG)@$(LINT_COMMIT) + +$(GOACC_BIN): + @$(call print, "Fetching go-acc") + $(DEPGET) $(GOACC_PKG)@$(GOACC_COMMIT) + +goimports: + @$(call print, "Installing goimports.") + $(DEPGET) $(GOIMPORTS_PKG) + +# ============ +# INSTALLATION +# ============ + +build: + @$(call print, "Building all binaries") + $(GOBUILD) $(PKG) + $(GOBUILD) $(PKG)/cmd/btcctl + $(GOBUILD) $(PKG)/cmd/gencerts + $(GOBUILD) $(PKG)/cmd/findcheckpoint + $(GOBUILD) $(PKG)/cmd/addblock + +# ======= +# TESTING +# ======= + +check: unit + +unit: + @$(call print, "Running unit tests.") + $(GOTEST) ./... -test.timeout=20m + cd btcutil; $(GOTEST) ./... -test.timeout=20m + cd btcutil/psbt; $(GOTEST) ./... -test.timeout=20m + +unit-cover: $(GOACC_BIN) + @$(call print, "Running unit coverage tests.") + $(GOACC_BIN) ./... + cd btcutil; $(GOACC_BIN) ./... + cd btcutil/psbt; $(GOACC_BIN) ./... + +unit-race: + @$(call print, "Running unit race tests.") + env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./... + cd btcutil; env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./... + cd btcutil/psbt; env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m ./... + +# ========= +# UTILITIES +# ========= + +fmt: goimports + @$(call print, "Fixing imports.") + goimports -w $(GOFILES_NOVENDOR) + @$(call print, "Formatting source.") + gofmt -l -w -s $(GOFILES_NOVENDOR) + +lint: $(LINT_BIN) + @$(call print, "Linting source.") + $(LINT) + +clean: + @$(call print, "Cleaning source.$(NC)") + $(RM) coverage.txt btcutil/coverage.txt btcutil/psbt/coverage.txt + +.PHONY: all \ + default \ + build \ + check \ + unit \ + unit-cover \ + unit-race \ + fmt \ + lint \ + clean diff --git a/blockchain/accept.go b/blockchain/accept.go index f85d6558e8..44ccbf997a 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -8,7 +8,7 @@ import ( "fmt" "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // maybeAcceptBlock potentially accepts a block into the block chain and, if diff --git a/blockchain/bench_test.go b/blockchain/bench_test.go index 43d3152b37..eee4340bc8 100644 --- a/blockchain/bench_test.go +++ b/blockchain/bench_test.go @@ -7,7 +7,7 @@ package blockchain import ( "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // BenchmarkIsCoinBase performs a simple benchmark against the IsCoinBase diff --git a/blockchain/chain.go b/blockchain/chain.go index eea603ce8e..92bfb26876 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -16,7 +16,7 @@ import ( "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 7de323bc8d..34356326b9 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestHaveBlock tests the HaveBlock API to ensure proper functionality. diff --git a/blockchain/chainio.go b/blockchain/chainio.go index f40ba465e9..fa41254da6 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index af3b6d924e..dbfa9d146d 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // CheckpointConfirmations is the number of blocks before the end of the current diff --git a/blockchain/common_test.go b/blockchain/common_test.go index dd392a6669..8de699c416 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -20,7 +20,7 @@ import ( _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/example_test.go b/blockchain/example_test.go index 691d0927f2..7f15e59bc6 100644 --- a/blockchain/example_test.go +++ b/blockchain/example_test.go @@ -14,7 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" _ "github.com/btcsuite/btcd/database/ffldb" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // This example demonstrates how to create a new chain instance and use diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go index 3ae0d0eb51..3cc7c87b70 100644 --- a/blockchain/fullblocks_test.go +++ b/blockchain/fullblocks_test.go @@ -20,7 +20,7 @@ import ( _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index 82d3a036e9..9555116842 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -24,7 +24,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/indexers/addrindex.go b/blockchain/indexers/addrindex.go index 4a9bf4ec68..2f0e2eeba1 100644 --- a/blockchain/indexers/addrindex.go +++ b/blockchain/indexers/addrindex.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/indexers/blocklogger.go b/blockchain/indexers/blocklogger.go index 88d6f26998..3671c0162a 100644 --- a/blockchain/indexers/blocklogger.go +++ b/blockchain/indexers/blocklogger.go @@ -9,7 +9,7 @@ import ( "time" "github.com/btcsuite/btclog" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // blockProgressLogger provides periodic logging for other services in order diff --git a/blockchain/indexers/cfindex.go b/blockchain/indexers/cfindex.go index 8ea147282b..fa0ee3c0a6 100644 --- a/blockchain/indexers/cfindex.go +++ b/blockchain/indexers/cfindex.go @@ -12,9 +12,9 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/gcs" - "github.com/btcsuite/btcutil/gcs/builder" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs" + "github.com/btcsuite/btcd/btcutil/gcs/builder" ) const ( diff --git a/blockchain/indexers/common.go b/blockchain/indexers/common.go index a912bb4c70..07b2feca5d 100644 --- a/blockchain/indexers/common.go +++ b/blockchain/indexers/common.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var ( diff --git a/blockchain/indexers/manager.go b/blockchain/indexers/manager.go index bc0804f8bd..8c87ca0771 100644 --- a/blockchain/indexers/manager.go +++ b/blockchain/indexers/manager.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var ( diff --git a/blockchain/indexers/txindex.go b/blockchain/indexers/txindex.go index 00cfd00621..f1d734e06b 100644 --- a/blockchain/indexers/txindex.go +++ b/blockchain/indexers/txindex.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/merkle.go b/blockchain/merkle.go index 8f3f6b97ea..d7e567b283 100644 --- a/blockchain/merkle.go +++ b/blockchain/merkle.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/merkle_test.go b/blockchain/merkle_test.go index 275ffef37d..1a224586fa 100644 --- a/blockchain/merkle_test.go +++ b/blockchain/merkle_test.go @@ -7,7 +7,7 @@ package blockchain import ( "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestMerkle tests the BuildMerkleTreeStore API. diff --git a/blockchain/process.go b/blockchain/process.go index 6d2161bb95..c367b4ceff 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // BehaviorFlags is a bitmask defining tweaks to the normal behavior when diff --git a/blockchain/scriptval.go b/blockchain/scriptval.go index 8ba59a4214..8869e23f27 100644 --- a/blockchain/scriptval.go +++ b/blockchain/scriptval.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // txValidateItem holds a transaction along with which input to validate. diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index b85765814d..3398693225 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // txoFlags is a bitmask defining additional information and state for a diff --git a/blockchain/validate.go b/blockchain/validate.go index f41d54e6b1..575861e2d3 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index 9bf2ff42e8..d5d17781d3 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestSequenceLocksActive tests the SequenceLockActive function to ensure it diff --git a/blockchain/weight.go b/blockchain/weight.go index 6f6292a1b6..5a6f257be5 100644 --- a/blockchain/weight.go +++ b/blockchain/weight.go @@ -9,7 +9,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 59f18c74c8..8062d9d990 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // GetBlockHeaderVerboseResult models the data from the getblockheader command when diff --git a/btcjson/chainsvrresults_test.go b/btcjson/chainsvrresults_test.go index 72dcd8d7ec..aee514a9a4 100644 --- a/btcjson/chainsvrresults_test.go +++ b/btcjson/chainsvrresults_test.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" ) diff --git a/btcjson/walletsvrcmds.go b/btcjson/walletsvrcmds.go index e8ed62372b..1e061529d3 100644 --- a/btcjson/walletsvrcmds.go +++ b/btcjson/walletsvrcmds.go @@ -12,7 +12,7 @@ import ( "encoding/json" "fmt" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // AddMultisigAddressCmd defines the addmutisigaddress JSON-RPC command. diff --git a/btcjson/walletsvrcmds_test.go b/btcjson/walletsvrcmds_test.go index 9c68d260f6..396153ef04 100644 --- a/btcjson/walletsvrcmds_test.go +++ b/btcjson/walletsvrcmds_test.go @@ -12,7 +12,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestWalletSvrCmds tests all of the wallet server commands marshal and diff --git a/btcutil/LICENSE b/btcutil/LICENSE new file mode 100644 index 0000000000..3e7b16791f --- /dev/null +++ b/btcutil/LICENSE @@ -0,0 +1,16 @@ +ISC License + +Copyright (c) 2013-2017 The btcsuite developers +Copyright (c) 2016-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. diff --git a/btcutil/README.md b/btcutil/README.md new file mode 100644 index 0000000000..ad32607ca5 --- /dev/null +++ b/btcutil/README.md @@ -0,0 +1,49 @@ +btcutil +======= + +[![Build Status](https://github.com/btcsuite/btcd/btcutil/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/btcutil/actions) +[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/btcsuite/btcd/btcutil) + +Package btcutil provides bitcoin-specific convenience functions and types. +A comprehensive suite of tests is provided to ensure proper functionality. See +`test_coverage.txt` for the gocov coverage report. Alternatively, if you are +running a POSIX OS, you can run the `cov_report.sh` script for a real-time +report. + +This package was developed for btcd, an alternative full-node implementation of +bitcoin which is under active development by Conformal. Although it was +primarily written for btcd, this package has intentionally been designed so it +can be used as a standalone package for any projects needing the functionality +provided. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil +``` + +## GPG Verification Key + +All official release tags are signed by Conformal so users can ensure the code +has not been tampered with and is coming from the btcsuite developers. To +verify the signature perform the following: + +- Download the public key from the Conformal website at + https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt + +- Import the public key into your GPG keyring: + ```bash + gpg --import GIT-GPG-KEY-conformal.txt + ``` + +- Verify the release tag with the following command where `TAG_NAME` is a + placeholder for the specific tag: + ```bash + git tag -v TAG_NAME + ``` + +## License + +Package btcutil is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/address.go b/btcutil/address.go new file mode 100644 index 0000000000..7f7ccbbd7b --- /dev/null +++ b/btcutil/address.go @@ -0,0 +1,720 @@ +// 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 btcutil + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/btcutil/base58" + "github.com/btcsuite/btcd/btcutil/bech32" + "golang.org/x/crypto/ripemd160" +) + +// UnsupportedWitnessVerError describes an error where a segwit address being +// decoded has an unsupported witness version. +type UnsupportedWitnessVerError byte + +func (e UnsupportedWitnessVerError) Error() string { + return fmt.Sprintf("unsupported witness version: %#x", byte(e)) +} + +// UnsupportedWitnessProgLenError describes an error where a segwit address +// being decoded has an unsupported witness program length. +type UnsupportedWitnessProgLenError int + +func (e UnsupportedWitnessProgLenError) Error() string { + return fmt.Sprintf("unsupported witness program length: %d", int(e)) +} + +var ( + // ErrChecksumMismatch describes an error where decoding failed due + // to a bad checksum. + ErrChecksumMismatch = errors.New("checksum mismatch") + + // ErrUnknownAddressType describes an error where an address can not + // decoded as a specific address type due to the string encoding + // beginning with an identifier byte unknown to any standard or + // registered (via chaincfg.Register) network. + ErrUnknownAddressType = errors.New("unknown address type") + + // ErrAddressCollision describes an error where an address can not + // be uniquely determined as either a pay-to-pubkey-hash or + // pay-to-script-hash address since the leading identifier is used for + // describing both address kinds, but for different networks. Rather + // than assuming or defaulting to one or the other, this error is + // returned and the caller must decide how to decode the address. + ErrAddressCollision = errors.New("address collision") +) + +// encodeAddress returns a human-readable payment address given a ripemd160 hash +// and netID which encodes the bitcoin network and address type. It is used +// in both pay-to-pubkey-hash (P2PKH) and pay-to-script-hash (P2SH) address +// encoding. +func encodeAddress(hash160 []byte, netID byte) string { + // Format is 1 byte for a network and address class (i.e. P2PKH vs + // P2SH), 20 bytes for a RIPEMD160 hash, and 4 bytes of checksum. + return base58.CheckEncode(hash160[:ripemd160.Size], netID) +} + +// encodeSegWitAddress creates a bech32 (or bech32m for SegWit v1) encoded +// address string representation from witness version and witness program. +func encodeSegWitAddress(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) { + // Group the address bytes into 5 bit groups, as this is what is used to + // encode each character in the address string. + converted, err := bech32.ConvertBits(witnessProgram, 8, 5, true) + if err != nil { + return "", err + } + + // Concatenate the witness version and program, and encode the resulting + // bytes using bech32 encoding. + combined := make([]byte, len(converted)+1) + combined[0] = witnessVersion + copy(combined[1:], converted) + + var bech string + switch witnessVersion { + case 0: + bech, err = bech32.Encode(hrp, combined) + + case 1: + bech, err = bech32.EncodeM(hrp, combined) + + default: + return "", fmt.Errorf("unsupported witness version %d", + witnessVersion) + } + if err != nil { + return "", err + } + + // Check validity by decoding the created address. + version, program, err := decodeSegWitAddress(bech) + if err != nil { + return "", fmt.Errorf("invalid segwit address: %v", err) + } + + if version != witnessVersion || !bytes.Equal(program, witnessProgram) { + return "", fmt.Errorf("invalid segwit address") + } + + return bech, nil +} + +// Address is an interface type for any type of destination a transaction +// output may spend to. This includes pay-to-pubkey (P2PK), pay-to-pubkey-hash +// (P2PKH), and pay-to-script-hash (P2SH). Address is designed to be generic +// enough that other kinds of addresses may be added in the future without +// changing the decoding and encoding API. +type Address interface { + // String returns the string encoding of the transaction output + // destination. + // + // Please note that String differs subtly from EncodeAddress: String + // will return the value as a string without any conversion, while + // EncodeAddress may convert destination types (for example, + // converting pubkeys to P2PKH addresses) before encoding as a + // payment address string. + String() string + + // EncodeAddress returns the string encoding of the payment address + // associated with the Address value. See the comment on String + // for how this method differs from String. + EncodeAddress() string + + // ScriptAddress returns the raw bytes of the address to be used + // when inserting the address into a txout's script. + ScriptAddress() []byte + + // IsForNet returns whether or not the address is associated with the + // passed bitcoin network. + IsForNet(*chaincfg.Params) bool +} + +// DecodeAddress decodes the string encoding of an address and returns +// the Address if addr is a valid encoding for a known address type. +// +// The bitcoin network the address is associated with is extracted if possible. +// When the address does not encode the network, such as in the case of a raw +// public key, the address will be associated with the passed defaultNet. +func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) { + // Bech32 encoded segwit addresses start with a human-readable part + // (hrp) followed by '1'. For Bitcoin mainnet the hrp is "bc", and for + // testnet it is "tb". If the address string has a prefix that matches + // one of the prefixes for the known networks, we try to decode it as + // a segwit address. + oneIndex := strings.LastIndexByte(addr, '1') + if oneIndex > 1 { + prefix := addr[:oneIndex+1] + if chaincfg.IsBech32SegwitPrefix(prefix) { + witnessVer, witnessProg, err := decodeSegWitAddress(addr) + if err != nil { + return nil, err + } + + // We currently only support P2WPKH and P2WSH, which is + // witness version 0 and P2TR which is witness version + // 1. + if witnessVer != 0 && witnessVer != 1 { + return nil, UnsupportedWitnessVerError(witnessVer) + } + + // The HRP is everything before the found '1'. + hrp := prefix[:len(prefix)-1] + + switch len(witnessProg) { + case 20: + return newAddressWitnessPubKeyHash(hrp, witnessProg) + case 32: + if witnessVer == 1 { + return newAddressTaproot(hrp, witnessProg) + } + + return newAddressWitnessScriptHash(hrp, witnessProg) + default: + return nil, UnsupportedWitnessProgLenError(len(witnessProg)) + } + } + } + + // Serialized public keys are either 65 bytes (130 hex chars) if + // uncompressed/hybrid or 33 bytes (66 hex chars) if compressed. + if len(addr) == 130 || len(addr) == 66 { + serializedPubKey, err := hex.DecodeString(addr) + if err != nil { + return nil, err + } + return NewAddressPubKey(serializedPubKey, defaultNet) + } + + // Switch on decoded length to determine the type. + decoded, netID, err := base58.CheckDecode(addr) + if err != nil { + if err == base58.ErrChecksum { + return nil, ErrChecksumMismatch + } + return nil, errors.New("decoded address is of unknown format") + } + switch len(decoded) { + case ripemd160.Size: // P2PKH or P2SH + isP2PKH := netID == defaultNet.PubKeyHashAddrID + isP2SH := netID == defaultNet.ScriptHashAddrID + switch hash160 := decoded; { + case isP2PKH && isP2SH: + return nil, ErrAddressCollision + case isP2PKH: + return newAddressPubKeyHash(hash160, netID) + case isP2SH: + return newAddressScriptHashFromHash(hash160, netID) + default: + return nil, ErrUnknownAddressType + } + + default: + return nil, errors.New("decoded address is of unknown size") + } +} + +// decodeSegWitAddress parses a bech32 encoded segwit address string and +// returns the witness version and witness program byte representation. +func decodeSegWitAddress(address string) (byte, []byte, error) { + // Decode the bech32 encoded address. + _, data, bech32version, err := bech32.DecodeGeneric(address) + if err != nil { + return 0, nil, err + } + + // The first byte of the decoded address is the witness version, it must + // exist. + if len(data) < 1 { + return 0, nil, fmt.Errorf("no witness version") + } + + // ...and be <= 16. + version := data[0] + if version > 16 { + return 0, nil, fmt.Errorf("invalid witness version: %v", version) + } + + // The remaining characters of the address returned are grouped into + // words of 5 bits. In order to restore the original witness program + // bytes, we'll need to regroup into 8 bit words. + regrouped, err := bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return 0, nil, err + } + + // The regrouped data must be between 2 and 40 bytes. + if len(regrouped) < 2 || len(regrouped) > 40 { + return 0, nil, fmt.Errorf("invalid data length") + } + + // For witness version 0, address MUST be exactly 20 or 32 bytes. + if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 { + return 0, nil, fmt.Errorf("invalid data length for witness "+ + "version 0: %v", len(regrouped)) + } + + // For witness version 0, the bech32 encoding must be used. + if version == 0 && bech32version != bech32.Version0 { + return 0, nil, fmt.Errorf("invalid checksum expected bech32 " + + "encoding for address with witness version 0") + } + + // For witness version 1, the bech32m encoding must be used. + if version == 1 && bech32version != bech32.VersionM { + return 0, nil, fmt.Errorf("invalid checksum expected bech32m " + + "encoding for address with witness version 1") + } + + return version, regrouped, nil +} + +// AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH) +// transaction. +type AddressPubKeyHash struct { + hash [ripemd160.Size]byte + netID byte +} + +// NewAddressPubKeyHash returns a new AddressPubKeyHash. pkHash mustbe 20 +// bytes. +func NewAddressPubKeyHash(pkHash []byte, net *chaincfg.Params) (*AddressPubKeyHash, error) { + return newAddressPubKeyHash(pkHash, net.PubKeyHashAddrID) +} + +// newAddressPubKeyHash is the internal API to create a pubkey hash address +// with a known leading identifier byte for a network, rather than looking +// it up through its parameters. This is useful when creating a new address +// structure from a string encoding where the identifier byte is already +// known. +func newAddressPubKeyHash(pkHash []byte, netID byte) (*AddressPubKeyHash, error) { + // Check for a valid pubkey hash length. + if len(pkHash) != ripemd160.Size { + return nil, errors.New("pkHash must be 20 bytes") + } + + addr := &AddressPubKeyHash{netID: netID} + copy(addr.hash[:], pkHash) + return addr, nil +} + +// EncodeAddress returns the string encoding of a pay-to-pubkey-hash +// address. Part of the Address interface. +func (a *AddressPubKeyHash) EncodeAddress() string { + return encodeAddress(a.hash[:], a.netID) +} + +// ScriptAddress returns the bytes to be included in a txout script to pay +// to a pubkey hash. Part of the Address interface. +func (a *AddressPubKeyHash) ScriptAddress() []byte { + return a.hash[:] +} + +// IsForNet returns whether or not the pay-to-pubkey-hash address is associated +// with the passed bitcoin network. +func (a *AddressPubKeyHash) IsForNet(net *chaincfg.Params) bool { + return a.netID == net.PubKeyHashAddrID +} + +// String returns a human-readable string for the pay-to-pubkey-hash address. +// This is equivalent to calling EncodeAddress, but is provided so the type can +// be used as a fmt.Stringer. +func (a *AddressPubKeyHash) String() string { + return a.EncodeAddress() +} + +// Hash160 returns the underlying array of the pubkey hash. This can be useful +// when an array is more appropriate than a slice (for example, when used as map +// keys). +func (a *AddressPubKeyHash) Hash160() *[ripemd160.Size]byte { + return &a.hash +} + +// AddressScriptHash is an Address for a pay-to-script-hash (P2SH) +// transaction. +type AddressScriptHash struct { + hash [ripemd160.Size]byte + netID byte +} + +// NewAddressScriptHash returns a new AddressScriptHash. +func NewAddressScriptHash(serializedScript []byte, net *chaincfg.Params) (*AddressScriptHash, error) { + scriptHash := Hash160(serializedScript) + return newAddressScriptHashFromHash(scriptHash, net.ScriptHashAddrID) +} + +// NewAddressScriptHashFromHash returns a new AddressScriptHash. scriptHash +// must be 20 bytes. +func NewAddressScriptHashFromHash(scriptHash []byte, net *chaincfg.Params) (*AddressScriptHash, error) { + return newAddressScriptHashFromHash(scriptHash, net.ScriptHashAddrID) +} + +// newAddressScriptHashFromHash is the internal API to create a script hash +// address with a known leading identifier byte for a network, rather than +// looking it up through its parameters. This is useful when creating a new +// address structure from a string encoding where the identifier byte is already +// known. +func newAddressScriptHashFromHash(scriptHash []byte, netID byte) (*AddressScriptHash, error) { + // Check for a valid script hash length. + if len(scriptHash) != ripemd160.Size { + return nil, errors.New("scriptHash must be 20 bytes") + } + + addr := &AddressScriptHash{netID: netID} + copy(addr.hash[:], scriptHash) + return addr, nil +} + +// EncodeAddress returns the string encoding of a pay-to-script-hash +// address. Part of the Address interface. +func (a *AddressScriptHash) EncodeAddress() string { + return encodeAddress(a.hash[:], a.netID) +} + +// ScriptAddress returns the bytes to be included in a txout script to pay +// to a script hash. Part of the Address interface. +func (a *AddressScriptHash) ScriptAddress() []byte { + return a.hash[:] +} + +// IsForNet returns whether or not the pay-to-script-hash address is associated +// with the passed bitcoin network. +func (a *AddressScriptHash) IsForNet(net *chaincfg.Params) bool { + return a.netID == net.ScriptHashAddrID +} + +// String returns a human-readable string for the pay-to-script-hash address. +// This is equivalent to calling EncodeAddress, but is provided so the type can +// be used as a fmt.Stringer. +func (a *AddressScriptHash) String() string { + return a.EncodeAddress() +} + +// Hash160 returns the underlying array of the script hash. This can be useful +// when an array is more appropriate than a slice (for example, when used as map +// keys). +func (a *AddressScriptHash) Hash160() *[ripemd160.Size]byte { + return &a.hash +} + +// PubKeyFormat describes what format to use for a pay-to-pubkey address. +type PubKeyFormat int + +const ( + // PKFUncompressed indicates the pay-to-pubkey address format is an + // uncompressed public key. + PKFUncompressed PubKeyFormat = iota + + // PKFCompressed indicates the pay-to-pubkey address format is a + // compressed public key. + PKFCompressed + + // PKFHybrid indicates the pay-to-pubkey address format is a hybrid + // public key. + PKFHybrid +) + +// AddressPubKey is an Address for a pay-to-pubkey transaction. +type AddressPubKey struct { + pubKeyFormat PubKeyFormat + pubKey *btcec.PublicKey + pubKeyHashID byte +} + +// NewAddressPubKey returns a new AddressPubKey which represents a pay-to-pubkey +// address. The serializedPubKey parameter must be a valid pubkey and can be +// uncompressed, compressed, or hybrid. +func NewAddressPubKey(serializedPubKey []byte, net *chaincfg.Params) (*AddressPubKey, error) { + pubKey, err := btcec.ParsePubKey(serializedPubKey, btcec.S256()) + if err != nil { + return nil, err + } + + // Set the format of the pubkey. This probably should be returned + // from btcec, but do it here to avoid API churn. We already know the + // pubkey is valid since it parsed above, so it's safe to simply examine + // the leading byte to get the format. + pkFormat := PKFUncompressed + switch serializedPubKey[0] { + case 0x02, 0x03: + pkFormat = PKFCompressed + case 0x06, 0x07: + pkFormat = PKFHybrid + } + + return &AddressPubKey{ + pubKeyFormat: pkFormat, + pubKey: pubKey, + pubKeyHashID: net.PubKeyHashAddrID, + }, nil +} + +// serialize returns the serialization of the public key according to the +// format associated with the address. +func (a *AddressPubKey) serialize() []byte { + switch a.pubKeyFormat { + default: + fallthrough + case PKFUncompressed: + return a.pubKey.SerializeUncompressed() + + case PKFCompressed: + return a.pubKey.SerializeCompressed() + + case PKFHybrid: + return a.pubKey.SerializeHybrid() + } +} + +// EncodeAddress returns the string encoding of the public key as a +// pay-to-pubkey-hash. Note that the public key format (uncompressed, +// compressed, etc) will change the resulting address. This is expected since +// pay-to-pubkey-hash is a hash of the serialized public key which obviously +// differs with the format. At the time of this writing, most Bitcoin addresses +// are pay-to-pubkey-hash constructed from the uncompressed public key. +// +// Part of the Address interface. +func (a *AddressPubKey) EncodeAddress() string { + return encodeAddress(Hash160(a.serialize()), a.pubKeyHashID) +} + +// ScriptAddress returns the bytes to be included in a txout script to pay +// to a public key. Setting the public key format will affect the output of +// this function accordingly. Part of the Address interface. +func (a *AddressPubKey) ScriptAddress() []byte { + return a.serialize() +} + +// IsForNet returns whether or not the pay-to-pubkey address is associated +// with the passed bitcoin network. +func (a *AddressPubKey) IsForNet(net *chaincfg.Params) bool { + return a.pubKeyHashID == net.PubKeyHashAddrID +} + +// String returns the hex-encoded human-readable string for the pay-to-pubkey +// address. This is not the same as calling EncodeAddress. +func (a *AddressPubKey) String() string { + return hex.EncodeToString(a.serialize()) +} + +// Format returns the format (uncompressed, compressed, etc) of the +// pay-to-pubkey address. +func (a *AddressPubKey) Format() PubKeyFormat { + return a.pubKeyFormat +} + +// SetFormat sets the format (uncompressed, compressed, etc) of the +// pay-to-pubkey address. +func (a *AddressPubKey) SetFormat(pkFormat PubKeyFormat) { + a.pubKeyFormat = pkFormat +} + +// AddressPubKeyHash returns the pay-to-pubkey address converted to a +// pay-to-pubkey-hash address. Note that the public key format (uncompressed, +// compressed, etc) will change the resulting address. This is expected since +// pay-to-pubkey-hash is a hash of the serialized public key which obviously +// differs with the format. At the time of this writing, most Bitcoin addresses +// are pay-to-pubkey-hash constructed from the uncompressed public key. +func (a *AddressPubKey) AddressPubKeyHash() *AddressPubKeyHash { + addr := &AddressPubKeyHash{netID: a.pubKeyHashID} + copy(addr.hash[:], Hash160(a.serialize())) + return addr +} + +// PubKey returns the underlying public key for the address. +func (a *AddressPubKey) PubKey() *btcec.PublicKey { + return a.pubKey +} + +// AddressSegWit is the base address type for all SegWit addresses. +type AddressSegWit struct { + hrp string + witnessVersion byte + witnessProgram []byte +} + +// EncodeAddress returns the bech32 (or bech32m for SegWit v1) string encoding +// of an AddressSegWit. +// +// NOTE: This method is part of the Address interface. +func (a *AddressSegWit) EncodeAddress() string { + str, err := encodeSegWitAddress( + a.hrp, a.witnessVersion, a.witnessProgram[:], + ) + if err != nil { + return "" + } + return str +} + +// ScriptAddress returns the witness program for this address. +// +// NOTE: This method is part of the Address interface. +func (a *AddressSegWit) ScriptAddress() []byte { + return a.witnessProgram[:] +} + +// IsForNet returns whether the AddressSegWit is associated with the passed +// bitcoin network. +// +// NOTE: This method is part of the Address interface. +func (a *AddressSegWit) IsForNet(net *chaincfg.Params) bool { + return a.hrp == net.Bech32HRPSegwit +} + +// String returns a human-readable string for the AddressWitnessPubKeyHash. +// This is equivalent to calling EncodeAddress, but is provided so the type +// can be used as a fmt.Stringer. +// +// NOTE: This method is part of the Address interface. +func (a *AddressSegWit) String() string { + return a.EncodeAddress() +} + +// Hrp returns the human-readable part of the bech32 (or bech32m for SegWit v1) +// encoded AddressSegWit. +func (a *AddressSegWit) Hrp() string { + return a.hrp +} + +// WitnessVersion returns the witness version of the AddressSegWit. +func (a *AddressSegWit) WitnessVersion() byte { + return a.witnessVersion +} + +// WitnessProgram returns the witness program of the AddressSegWit. +func (a *AddressSegWit) WitnessProgram() []byte { + return a.witnessProgram[:] +} + +// AddressWitnessPubKeyHash is an Address for a pay-to-witness-pubkey-hash +// (P2WPKH) output. See BIP 173 for further details regarding native segregated +// witness address encoding: +// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +type AddressWitnessPubKeyHash struct { + AddressSegWit +} + +// NewAddressWitnessPubKeyHash returns a new AddressWitnessPubKeyHash. +func NewAddressWitnessPubKeyHash(witnessProg []byte, + net *chaincfg.Params) (*AddressWitnessPubKeyHash, error) { + + return newAddressWitnessPubKeyHash(net.Bech32HRPSegwit, witnessProg) +} + +// newAddressWitnessPubKeyHash is an internal helper function to create an +// AddressWitnessPubKeyHash with a known human-readable part, rather than +// looking it up through its parameters. +func newAddressWitnessPubKeyHash(hrp string, + witnessProg []byte) (*AddressWitnessPubKeyHash, error) { + + // Check for valid program length for witness version 0, which is 20 + // for P2WPKH. + if len(witnessProg) != 20 { + return nil, errors.New("witness program must be 20 " + + "bytes for p2wpkh") + } + + addr := &AddressWitnessPubKeyHash{ + AddressSegWit{ + hrp: strings.ToLower(hrp), + witnessVersion: 0x00, + witnessProgram: witnessProg, + }, + } + + return addr, nil +} + +// Hash160 returns the witness program of the AddressWitnessPubKeyHash as a +// byte array. +func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte { + var pubKeyHashWitnessProgram [20]byte + copy(pubKeyHashWitnessProgram[:], a.witnessProgram) + return &pubKeyHashWitnessProgram +} + +// AddressWitnessScriptHash is an Address for a pay-to-witness-script-hash +// (P2WSH) output. See BIP 173 for further details regarding native segregated +// witness address encoding: +// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +type AddressWitnessScriptHash struct { + AddressSegWit +} + +// NewAddressWitnessScriptHash returns a new AddressWitnessPubKeyHash. +func NewAddressWitnessScriptHash(witnessProg []byte, + net *chaincfg.Params) (*AddressWitnessScriptHash, error) { + + return newAddressWitnessScriptHash(net.Bech32HRPSegwit, witnessProg) +} + +// newAddressWitnessScriptHash is an internal helper function to create an +// AddressWitnessScriptHash with a known human-readable part, rather than +// looking it up through its parameters. +func newAddressWitnessScriptHash(hrp string, + witnessProg []byte) (*AddressWitnessScriptHash, error) { + + // Check for valid program length for witness version 0, which is 32 + // for P2WSH. + if len(witnessProg) != 32 { + return nil, errors.New("witness program must be 32 " + + "bytes for p2wsh") + } + + addr := &AddressWitnessScriptHash{ + AddressSegWit{ + hrp: strings.ToLower(hrp), + witnessVersion: 0x00, + witnessProgram: witnessProg, + }, + } + + return addr, nil +} + +// AddressTaproot is an Address for a pay-to-taproot (P2TR) output. See BIP 341 +// for further details. +type AddressTaproot struct { + AddressSegWit +} + +// NewAddressTaproot returns a new AddressTaproot. +func NewAddressTaproot(witnessProg []byte, + net *chaincfg.Params) (*AddressTaproot, error) { + + return newAddressTaproot(net.Bech32HRPSegwit, witnessProg) +} + +// newAddressWitnessScriptHash is an internal helper function to create an +// AddressWitnessScriptHash with a known human-readable part, rather than +// looking it up through its parameters. +func newAddressTaproot(hrp string, + witnessProg []byte) (*AddressTaproot, error) { + + // Check for valid program length for witness version 1, which is 32 + // for P2TR. + if len(witnessProg) != 32 { + return nil, errors.New("witness program must be 32 bytes for " + + "p2tr") + } + + addr := &AddressTaproot{ + AddressSegWit{ + hrp: strings.ToLower(hrp), + witnessVersion: 0x01, + witnessProgram: witnessProg, + }, + } + + return addr, nil +} diff --git a/btcutil/address_test.go b/btcutil/address_test.go new file mode 100644 index 0000000000..52472b7923 --- /dev/null +++ b/btcutil/address_test.go @@ -0,0 +1,1012 @@ +// 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 btcutil_test + +import ( + "bytes" + "encoding/hex" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "golang.org/x/crypto/ripemd160" +) + +type CustomParamStruct struct { + Net wire.BitcoinNet + PubKeyHashAddrID byte + ScriptHashAddrID byte + Bech32HRPSegwit string +} + +var CustomParams = CustomParamStruct{ + Net: 0xdbb6c0fb, // litecoin mainnet HD version bytes + PubKeyHashAddrID: 0x30, // starts with L + ScriptHashAddrID: 0x32, // starts with M + Bech32HRPSegwit: "ltc", // starts with ltc +} + +// We use this function to be able to test functionality in DecodeAddress for +// defaultNet addresses +func applyCustomParams(params chaincfg.Params, customParams CustomParamStruct) chaincfg.Params { + params.Net = customParams.Net + params.PubKeyHashAddrID = customParams.PubKeyHashAddrID + params.ScriptHashAddrID = customParams.ScriptHashAddrID + params.Bech32HRPSegwit = customParams.Bech32HRPSegwit + return params +} + +var customParams = applyCustomParams(chaincfg.MainNetParams, CustomParams) + +func TestAddresses(t *testing.T) { + tests := []struct { + name string + addr string + encoded string + valid bool + result btcutil.Address + f func() (btcutil.Address, error) + net *chaincfg.Params + }{ + // Positive P2PKH tests. + { + name: "mainnet p2pkh", + addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", + encoded: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", + valid: true, + result: btcutil.TstAddressPubKeyHash( + [ripemd160.Size]byte{ + 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, + 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84}, + chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, + 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84} + return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "mainnet p2pkh 2", + addr: "12MzCDwodF9G1e7jfwLXfR164RNtx4BRVG", + encoded: "12MzCDwodF9G1e7jfwLXfR164RNtx4BRVG", + valid: true, + result: btcutil.TstAddressPubKeyHash( + [ripemd160.Size]byte{ + 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4, + 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa}, + chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4, + 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa} + return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "litecoin mainnet p2pkh", + addr: "LM2WMpR1Rp6j3Sa59cMXMs1SPzj9eXpGc1", + encoded: "LM2WMpR1Rp6j3Sa59cMXMs1SPzj9eXpGc1", + valid: true, + result: btcutil.TstAddressPubKeyHash( + [ripemd160.Size]byte{ + 0x13, 0xc6, 0x0d, 0x8e, 0x68, 0xd7, 0x34, 0x9f, 0x5b, 0x4c, + 0xa3, 0x62, 0xc3, 0x95, 0x4b, 0x15, 0x04, 0x50, 0x61, 0xb1}, + CustomParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x13, 0xc6, 0x0d, 0x8e, 0x68, 0xd7, 0x34, 0x9f, 0x5b, 0x4c, + 0xa3, 0x62, 0xc3, 0x95, 0x4b, 0x15, 0x04, 0x50, 0x61, 0xb1} + return btcutil.NewAddressPubKeyHash(pkHash, &customParams) + }, + net: &customParams, + }, + { + name: "testnet p2pkh", + addr: "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", + encoded: "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", + valid: true, + result: btcutil.TstAddressPubKeyHash( + [ripemd160.Size]byte{ + 0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83, + 0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f}, + chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83, + 0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f} + return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + + // Negative P2PKH tests. + { + name: "p2pkh wrong hash length", + addr: "", + valid: false, + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x00, 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, + 0xf4, 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, + 0xaa} + return btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "p2pkh bad checksum", + addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gY", + valid: false, + net: &chaincfg.MainNetParams, + }, + + // Positive P2SH tests. + { + // Taken from transactions: + // output: 3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac + // input: 837dea37ddc8b1e3ce646f1a656e79bbd8cc7f558ac56a169626d649ebe2a3ba. + name: "mainnet p2sh", + addr: "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC", + encoded: "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC", + valid: true, + result: btcutil.TstAddressScriptHash( + [ripemd160.Size]byte{ + 0xf8, 0x15, 0xb0, 0x36, 0xd9, 0xbb, 0xbc, 0xe5, 0xe9, 0xf2, + 0xa0, 0x0a, 0xbd, 0x1b, 0xf3, 0xdc, 0x91, 0xe9, 0x55, 0x10}, + chaincfg.MainNetParams.ScriptHashAddrID), + f: func() (btcutil.Address, error) { + script := []byte{ + 0x52, 0x41, 0x04, 0x91, 0xbb, 0xa2, 0x51, 0x09, 0x12, 0xa5, + 0xbd, 0x37, 0xda, 0x1f, 0xb5, 0xb1, 0x67, 0x30, 0x10, 0xe4, + 0x3d, 0x2c, 0x6d, 0x81, 0x2c, 0x51, 0x4e, 0x91, 0xbf, 0xa9, + 0xf2, 0xeb, 0x12, 0x9e, 0x1c, 0x18, 0x33, 0x29, 0xdb, 0x55, + 0xbd, 0x86, 0x8e, 0x20, 0x9a, 0xac, 0x2f, 0xbc, 0x02, 0xcb, + 0x33, 0xd9, 0x8f, 0xe7, 0x4b, 0xf2, 0x3f, 0x0c, 0x23, 0x5d, + 0x61, 0x26, 0xb1, 0xd8, 0x33, 0x4f, 0x86, 0x41, 0x04, 0x86, + 0x5c, 0x40, 0x29, 0x3a, 0x68, 0x0c, 0xb9, 0xc0, 0x20, 0xe7, + 0xb1, 0xe1, 0x06, 0xd8, 0xc1, 0x91, 0x6d, 0x3c, 0xef, 0x99, + 0xaa, 0x43, 0x1a, 0x56, 0xd2, 0x53, 0xe6, 0x92, 0x56, 0xda, + 0xc0, 0x9e, 0xf1, 0x22, 0xb1, 0xa9, 0x86, 0x81, 0x8a, 0x7c, + 0xb6, 0x24, 0x53, 0x2f, 0x06, 0x2c, 0x1d, 0x1f, 0x87, 0x22, + 0x08, 0x48, 0x61, 0xc5, 0xc3, 0x29, 0x1c, 0xcf, 0xfe, 0xf4, + 0xec, 0x68, 0x74, 0x41, 0x04, 0x8d, 0x24, 0x55, 0xd2, 0x40, + 0x3e, 0x08, 0x70, 0x8f, 0xc1, 0xf5, 0x56, 0x00, 0x2f, 0x1b, + 0x6c, 0xd8, 0x3f, 0x99, 0x2d, 0x08, 0x50, 0x97, 0xf9, 0x97, + 0x4a, 0xb0, 0x8a, 0x28, 0x83, 0x8f, 0x07, 0x89, 0x6f, 0xba, + 0xb0, 0x8f, 0x39, 0x49, 0x5e, 0x15, 0xfa, 0x6f, 0xad, 0x6e, + 0xdb, 0xfb, 0x1e, 0x75, 0x4e, 0x35, 0xfa, 0x1c, 0x78, 0x44, + 0xc4, 0x1f, 0x32, 0x2a, 0x18, 0x63, 0xd4, 0x62, 0x13, 0x53, + 0xae} + return btcutil.NewAddressScriptHash(script, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "litecoin mainnet P2SH ", + addr: "MVcg9uEvtWuP5N6V48EHfEtbz48qR8TKZ9", + encoded: "MVcg9uEvtWuP5N6V48EHfEtbz48qR8TKZ9", + valid: true, + result: btcutil.TstAddressScriptHash( + [ripemd160.Size]byte{ + 0xee, 0x34, 0xac, 0x67, 0x6b, 0xda, 0xf6, 0xe3, 0x70, 0xc8, + 0xc8, 0x20, 0xb9, 0x48, 0xed, 0xfa, 0xd3, 0xa8, 0x73, 0xd8}, + CustomParams.ScriptHashAddrID), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0xEE, 0x34, 0xAC, 0x67, 0x6B, 0xDA, 0xF6, 0xE3, 0x70, 0xC8, + 0xC8, 0x20, 0xB9, 0x48, 0xED, 0xFA, 0xD3, 0xA8, 0x73, 0xD8} + return btcutil.NewAddressScriptHashFromHash(pkHash, &customParams) + }, + net: &customParams, + }, + { + // Taken from transactions: + // output: b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d + // input: (not yet redeemed at time test was written) + name: "mainnet p2sh 2", + addr: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", + encoded: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", + valid: true, + result: btcutil.TstAddressScriptHash( + [ripemd160.Size]byte{ + 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, + 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4}, + chaincfg.MainNetParams.ScriptHashAddrID), + f: func() (btcutil.Address, error) { + hash := []byte{ + 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, + 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4} + return btcutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + // Taken from bitcoind base58_keys_valid. + name: "testnet p2sh", + addr: "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", + encoded: "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", + valid: true, + result: btcutil.TstAddressScriptHash( + [ripemd160.Size]byte{ + 0xc5, 0x79, 0x34, 0x2c, 0x2c, 0x4c, 0x92, 0x20, 0x20, 0x5e, + 0x2c, 0xdc, 0x28, 0x56, 0x17, 0x04, 0x0c, 0x92, 0x4a, 0x0a}, + chaincfg.TestNet3Params.ScriptHashAddrID), + f: func() (btcutil.Address, error) { + hash := []byte{ + 0xc5, 0x79, 0x34, 0x2c, 0x2c, 0x4c, 0x92, 0x20, 0x20, 0x5e, + 0x2c, 0xdc, 0x28, 0x56, 0x17, 0x04, 0x0c, 0x92, 0x4a, 0x0a} + return btcutil.NewAddressScriptHashFromHash(hash, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + + // Negative P2SH tests. + { + name: "p2sh wrong hash length", + addr: "", + valid: false, + f: func() (btcutil.Address, error) { + hash := []byte{ + 0x00, 0xf8, 0x15, 0xb0, 0x36, 0xd9, 0xbb, 0xbc, 0xe5, 0xe9, + 0xf2, 0xa0, 0x0a, 0xbd, 0x1b, 0xf3, 0xdc, 0x91, 0xe9, 0x55, + 0x10} + return btcutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + + // Positive P2PK tests. + { + name: "mainnet p2pk compressed (0x02)", + addr: "02192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4", + encoded: "13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4}, + btcutil.PKFCompressed, chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "mainnet p2pk compressed (0x03)", + addr: "03b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65", + encoded: "15sHANNUBSh6nDp8XkDPmQcW6n3EFwmvE6", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65}, + btcutil.PKFCompressed, chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "mainnet p2pk uncompressed (0x04)", + addr: "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2" + + "e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3", + encoded: "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3}, + btcutil.PKFUncompressed, chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "mainnet p2pk hybrid (0x06)", + addr: "06192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4" + + "0d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453e", + encoded: "1Ja5rs7XBZnK88EuLVcFqYGMEbBitzchmX", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, + 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, + 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, + 0x44, 0xd3, 0x3f, 0x45, 0x3e}, + btcutil.PKFHybrid, chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, + 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, + 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, + 0x44, 0xd3, 0x3f, 0x45, 0x3e} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "mainnet p2pk hybrid (0x07)", + addr: "07b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65" + + "37a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7b", + encoded: "1ExqMmf6yMxcBMzHjbj41wbqYuqoX6uBLG", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, + 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, + 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, + 0x1e, 0x09, 0x08, 0xef, 0x7b}, + btcutil.PKFHybrid, chaincfg.MainNetParams.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, + 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, + 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, + 0x1e, 0x09, 0x08, 0xef, 0x7b} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "testnet p2pk compressed (0x02)", + addr: "02192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4", + encoded: "mhiDPVP2nJunaAgTjzWSHCYfAqxxrxzjmo", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4}, + btcutil.PKFCompressed, chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "testnet p2pk compressed (0x03)", + addr: "03b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65", + encoded: "mkPETRTSzU8MZLHkFKBmbKppxmdw9qT42t", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65}, + btcutil.PKFCompressed, chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "testnet p2pk uncompressed (0x04)", + addr: "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5" + + "cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3", + encoded: "mh8YhPYEAYs3E7EVyKtB5xrcfMExkkdEMF", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3}, + btcutil.PKFUncompressed, chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "testnet p2pk hybrid (0x06)", + addr: "06192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b" + + "40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453e", + encoded: "my639vCVzbDZuEiX44adfTUg6anRomZLEP", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, + 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, + 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, + 0x44, 0xd3, 0x3f, 0x45, 0x3e}, + btcutil.PKFHybrid, chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, + 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, + 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, + 0x44, 0xd3, 0x3f, 0x45, 0x3e} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "testnet p2pk hybrid (0x07)", + addr: "07b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6" + + "537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7b", + encoded: "muUnepk5nPPrxUTuTAhRqrpAQuSWS5fVii", + valid: true, + result: btcutil.TstAddressPubKey( + []byte{ + 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, + 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, + 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, + 0x1e, 0x09, 0x08, 0xef, 0x7b}, + btcutil.PKFHybrid, chaincfg.TestNet3Params.PubKeyHashAddrID), + f: func() (btcutil.Address, error) { + serializedPubKey := []byte{ + 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, + 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, + 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, + 0x1e, 0x09, 0x08, 0xef, 0x7b} + return btcutil.NewAddressPubKey(serializedPubKey, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + // Segwit address tests. + { + name: "segwit mainnet p2wpkh v0", + addr: "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + encoded: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", + valid: true, + result: btcutil.TstAddressWitnessPubKeyHash( + 0, + [20]byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6}, + chaincfg.MainNetParams.Bech32HRPSegwit), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6} + return btcutil.NewAddressWitnessPubKeyHash(pkHash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit mainnet p2wsh v0", + addr: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", + encoded: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", + valid: true, + result: btcutil.TstAddressWitnessScriptHash( + 0, + [32]byte{ + 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, + 0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, + 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1, + 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62}, + chaincfg.MainNetParams.Bech32HRPSegwit), + f: func() (btcutil.Address, error) { + scriptHash := []byte{ + 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, + 0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, + 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1, + 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62} + return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.MainNetParams) + }, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit testnet p2wpkh v0", + addr: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", + encoded: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", + valid: true, + result: btcutil.TstAddressWitnessPubKeyHash( + 0, + [20]byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6}, + chaincfg.TestNet3Params.Bech32HRPSegwit), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6} + return btcutil.NewAddressWitnessPubKeyHash(pkHash, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit testnet p2wsh v0", + addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + encoded: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + valid: true, + result: btcutil.TstAddressWitnessScriptHash( + 0, + [32]byte{ + 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, + 0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, + 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1, + 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62}, + chaincfg.TestNet3Params.Bech32HRPSegwit), + f: func() (btcutil.Address, error) { + scriptHash := []byte{ + 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, + 0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, + 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1, + 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62} + return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit testnet p2wsh witness v0", + addr: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + encoded: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + valid: true, + result: btcutil.TstAddressWitnessScriptHash( + 0, + [32]byte{ + 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, + 0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, + 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2, + 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33}, + chaincfg.TestNet3Params.Bech32HRPSegwit), + f: func() (btcutil.Address, error) { + scriptHash := []byte{ + 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, + 0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, + 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2, + 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33} + return btcutil.NewAddressWitnessScriptHash(scriptHash, &chaincfg.TestNet3Params) + }, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit litecoin mainnet p2wpkh v0", + addr: "LTC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KGMN4N9", + encoded: "ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9", + valid: true, + result: btcutil.TstAddressWitnessPubKeyHash( + 0, + [20]byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6}, + CustomParams.Bech32HRPSegwit, + ), + f: func() (btcutil.Address, error) { + pkHash := []byte{ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6} + return btcutil.NewAddressWitnessPubKeyHash(pkHash, &customParams) + }, + net: &customParams, + }, + + // P2TR address tests. + { + name: "segwit v1 mainnet p2tr", + addr: "bc1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqwr2d5c", + encoded: "bc1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqwr2d5c", + valid: true, + result: btcutil.TstAddressTaproot( + 1, [32]byte{ + 0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33, + 0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9, + 0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a, + 0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6, + }, chaincfg.MainNetParams.Bech32HRPSegwit, + ), + f: func() (btcutil.Address, error) { + scriptHash := []byte{ + 0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33, + 0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9, + 0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a, + 0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6, + } + return btcutil.NewAddressTaproot( + scriptHash, &chaincfg.MainNetParams, + ) + }, + net: &chaincfg.MainNetParams, + }, + + // Invalid bech32m tests. Source: + // https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki + { + name: "segwit v1 invalid human-readable part", + addr: "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 mainnet bech32 instead of bech32m", + addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 testnet bech32 instead of bech32m", + addr: "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit v1 mainnet bech32 instead of bech32m upper case", + addr: "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v0 mainnet bech32m instead of bech32", + addr: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 testnet bech32 instead of bech32m second test", + addr: "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit v1 mainnet bech32m invalid character in checksum", + addr: "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit mainnet witness v17", + addr: "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 mainnet bech32m invalid program length (1 byte)", + addr: "bc1pw5dgrnzv", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 mainnet bech32m invalid program length (41 bytes)", + addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 testnet bech32m mixed case", + addr: "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit v1 mainnet bech32m zero padding of more than 4 bits", + addr: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit v1 mainnet bech32m non-zero padding in 8-to-5-conversion", + addr: "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit v1 mainnet bech32m empty data section", + addr: "bc1gmk9yu", + valid: false, + net: &chaincfg.MainNetParams, + }, + + // Unsupported witness versions (version 0 and 1 only supported at this point) + { + name: "segwit mainnet witness v16", + addr: "BC1SW50QA3JX3S", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit mainnet witness v2", + addr: "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + valid: false, + net: &chaincfg.MainNetParams, + }, + // Invalid segwit addresses + { + name: "segwit invalid hrp", + addr: "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit invalid checksum", + addr: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit invalid witness version", + addr: "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit invalid program length", + addr: "bc1rw5uspcuh", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit invalid program length", + addr: "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit invalid program length for witness version 0 (per BIP141)", + addr: "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + valid: false, + net: &chaincfg.MainNetParams, + }, + { + name: "segwit mixed case", + addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit zero padding of more than 4 bits", + addr: "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", + valid: false, + net: &chaincfg.TestNet3Params, + }, + { + name: "segwit non-zero padding in 8-to-5 conversion", + addr: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + valid: false, + net: &chaincfg.TestNet3Params, + }, + } + + if err := chaincfg.Register(&customParams); err != nil { + panic(err) + } + + for _, test := range tests { + // Decode addr and compare error against valid. + decoded, err := btcutil.DecodeAddress(test.addr, test.net) + if (err == nil) != test.valid { + t.Errorf("%v: decoding test failed: %v", test.name, err) + return + } + + if err == nil { + // Ensure the stringer returns the same address as the + // original. + if decodedStringer, ok := decoded.(fmt.Stringer); ok { + addr := test.addr + + // For Segwit addresses the string representation + // will always be lower case, so in that case we + // convert the original to lower case first. + if strings.Contains(test.name, "segwit") { + addr = strings.ToLower(addr) + } + + if addr != decodedStringer.String() { + t.Errorf("%v: String on decoded value does not match expected value: %v != %v", + test.name, test.addr, decodedStringer.String()) + return + } + } + + // Encode again and compare against the original. + encoded := decoded.EncodeAddress() + if test.encoded != encoded { + t.Errorf("%v: decoding and encoding produced different addresses: %v != %v", + test.name, test.encoded, encoded) + return + } + + // Perform type-specific calculations. + var saddr []byte + switch d := decoded.(type) { + case *btcutil.AddressPubKeyHash: + saddr = btcutil.TstAddressSAddr(encoded) + + case *btcutil.AddressScriptHash: + saddr = btcutil.TstAddressSAddr(encoded) + + case *btcutil.AddressPubKey: + // Ignore the error here since the script + // address is checked below. + saddr, _ = hex.DecodeString(d.String()) + case *btcutil.AddressWitnessPubKeyHash: + saddr = btcutil.TstAddressSegwitSAddr(encoded) + case *btcutil.AddressWitnessScriptHash: + saddr = btcutil.TstAddressSegwitSAddr(encoded) + case *btcutil.AddressTaproot: + saddr = btcutil.TstAddressTaprootSAddr(encoded) + } + + // Check script address, as well as the Hash160 method for P2PKH and + // P2SH addresses. + if !bytes.Equal(saddr, decoded.ScriptAddress()) { + t.Errorf("%v: script addresses do not match:\n%x != \n%x", + test.name, saddr, decoded.ScriptAddress()) + return + } + switch a := decoded.(type) { + case *btcutil.AddressPubKeyHash: + if h := a.Hash160()[:]; !bytes.Equal(saddr, h) { + t.Errorf("%v: hashes do not match:\n%x != \n%x", + test.name, saddr, h) + return + } + + case *btcutil.AddressScriptHash: + if h := a.Hash160()[:]; !bytes.Equal(saddr, h) { + t.Errorf("%v: hashes do not match:\n%x != \n%x", + test.name, saddr, h) + return + } + + case *btcutil.AddressWitnessPubKeyHash: + if hrp := a.Hrp(); test.net.Bech32HRPSegwit != hrp { + t.Errorf("%v: hrps do not match:\n%x != \n%x", + test.name, test.net.Bech32HRPSegwit, hrp) + return + } + + expVer := test.result.(*btcutil.AddressWitnessPubKeyHash).WitnessVersion() + if v := a.WitnessVersion(); v != expVer { + t.Errorf("%v: witness versions do not match:\n%x != \n%x", + test.name, expVer, v) + return + } + + if p := a.WitnessProgram(); !bytes.Equal(saddr, p) { + t.Errorf("%v: witness programs do not match:\n%x != \n%x", + test.name, saddr, p) + return + } + + case *btcutil.AddressWitnessScriptHash: + if hrp := a.Hrp(); test.net.Bech32HRPSegwit != hrp { + t.Errorf("%v: hrps do not match:\n%x != \n%x", + test.name, test.net.Bech32HRPSegwit, hrp) + return + } + + expVer := test.result.(*btcutil.AddressWitnessScriptHash).WitnessVersion() + if v := a.WitnessVersion(); v != expVer { + t.Errorf("%v: witness versions do not match:\n%x != \n%x", + test.name, expVer, v) + return + } + + if p := a.WitnessProgram(); !bytes.Equal(saddr, p) { + t.Errorf("%v: witness programs do not match:\n%x != \n%x", + test.name, saddr, p) + return + } + } + + // Ensure the address is for the expected network. + if !decoded.IsForNet(test.net) { + t.Errorf("%v: calculated network does not match expected", + test.name) + return + } + } else { + // If there is an error, make sure we can print it + // correctly. + errStr := err.Error() + if errStr == "" { + t.Errorf("%v: error was non-nil but message is"+ + "empty: %v", test.name, err) + } + } + + if !test.valid { + // If address is invalid, but a creation function exists, + // verify that it returns a nil addr and non-nil error. + if test.f != nil { + _, err := test.f() + if err == nil { + t.Errorf("%v: address is invalid but creating new address succeeded", + test.name) + return + } + } + continue + } + + // Valid test, compare address created with f against expected result. + addr, err := test.f() + if err != nil { + t.Errorf("%v: address is valid but creating new address failed with error %v", + test.name, err) + return + } + + if !reflect.DeepEqual(addr, test.result) { + t.Errorf("%v: created address does not match expected result", + test.name) + return + } + } +} diff --git a/btcutil/amount.go b/btcutil/amount.go new file mode 100644 index 0000000000..71714153aa --- /dev/null +++ b/btcutil/amount.go @@ -0,0 +1,122 @@ +// 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 btcutil + +import ( + "errors" + "math" + "strconv" +) + +// AmountUnit describes a method of converting an Amount to something +// other than the base unit of a bitcoin. The value of the AmountUnit +// is the exponent component of the decadic multiple to convert from +// an amount in bitcoin to an amount counted in units. +type AmountUnit int + +// These constants define various units used when describing a bitcoin +// monetary amount. +const ( + AmountMegaBTC AmountUnit = 6 + AmountKiloBTC AmountUnit = 3 + AmountBTC AmountUnit = 0 + AmountMilliBTC AmountUnit = -3 + AmountMicroBTC AmountUnit = -6 + AmountSatoshi AmountUnit = -8 +) + +// String returns the unit as a string. For recognized units, the SI +// prefix is used, or "Satoshi" for the base unit. For all unrecognized +// units, "1eN BTC" is returned, where N is the AmountUnit. +func (u AmountUnit) String() string { + switch u { + case AmountMegaBTC: + return "MBTC" + case AmountKiloBTC: + return "kBTC" + case AmountBTC: + return "BTC" + case AmountMilliBTC: + return "mBTC" + case AmountMicroBTC: + return "μBTC" + case AmountSatoshi: + return "Satoshi" + default: + return "1e" + strconv.FormatInt(int64(u), 10) + " BTC" + } +} + +// Amount represents the base bitcoin monetary unit (colloquially referred +// to as a `Satoshi'). A single Amount is equal to 1e-8 of a bitcoin. +type Amount int64 + +// round converts a floating point number, which may or may not be representable +// as an integer, to the Amount integer type by rounding to the nearest integer. +// This is performed by adding or subtracting 0.5 depending on the sign, and +// relying on integer truncation to round the value to the nearest Amount. +func round(f float64) Amount { + if f < 0 { + return Amount(f - 0.5) + } + return Amount(f + 0.5) +} + +// NewAmount creates an Amount from a floating point value representing +// some value in bitcoin. NewAmount errors if f is NaN or +-Infinity, but +// does not check that the amount is within the total amount of bitcoin +// producible as f may not refer to an amount at a single moment in time. +// +// NewAmount is for specifically for converting BTC to Satoshi. +// For creating a new Amount with an int64 value which denotes a quantity of Satoshi, +// do a simple type conversion from type int64 to Amount. +// See GoDoc for example: http://godoc.org/github.com/btcsuite/btcd/btcutil#example-Amount +func NewAmount(f float64) (Amount, error) { + // The amount is only considered invalid if it cannot be represented + // as an integer type. This may happen if f is NaN or +-Infinity. + switch { + case math.IsNaN(f): + fallthrough + case math.IsInf(f, 1): + fallthrough + case math.IsInf(f, -1): + return 0, errors.New("invalid bitcoin amount") + } + + return round(f * SatoshiPerBitcoin), nil +} + +// ToUnit converts a monetary amount counted in bitcoin base units to a +// floating point value representing an amount of bitcoin. +func (a Amount) ToUnit(u AmountUnit) float64 { + return float64(a) / math.Pow10(int(u+8)) +} + +// ToBTC is the equivalent of calling ToUnit with AmountBTC. +func (a Amount) ToBTC() float64 { + return a.ToUnit(AmountBTC) +} + +// Format formats a monetary amount counted in bitcoin base units as a +// string for a given unit. The conversion will succeed for any unit, +// however, known units will be formated with an appended label describing +// the units with SI notation, or "Satoshi" for the base unit. +func (a Amount) Format(u AmountUnit) string { + units := " " + u.String() + return strconv.FormatFloat(a.ToUnit(u), 'f', -int(u+8), 64) + units +} + +// String is the equivalent of calling Format with AmountBTC. +func (a Amount) String() string { + return a.Format(AmountBTC) +} + +// MulF64 multiplies an Amount by a floating point value. While this is not +// an operation that must typically be done by a full node or wallet, it is +// useful for services that build on top of bitcoin (for example, calculating +// a fee by multiplying by a percentage). +func (a Amount) MulF64(f float64) Amount { + return round(float64(a) * f) +} diff --git a/btcutil/amount_test.go b/btcutil/amount_test.go new file mode 100644 index 0000000000..2b6c3f753d --- /dev/null +++ b/btcutil/amount_test.go @@ -0,0 +1,309 @@ +// 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 btcutil_test + +import ( + "math" + "testing" + + . "github.com/btcsuite/btcd/btcutil" +) + +func TestAmountCreation(t *testing.T) { + tests := []struct { + name string + amount float64 + valid bool + expected Amount + }{ + // Positive tests. + { + name: "zero", + amount: 0, + valid: true, + expected: 0, + }, + { + name: "max producible", + amount: 21e6, + valid: true, + expected: MaxSatoshi, + }, + { + name: "min producible", + amount: -21e6, + valid: true, + expected: -MaxSatoshi, + }, + { + name: "exceeds max producible", + amount: 21e6 + 1e-8, + valid: true, + expected: MaxSatoshi + 1, + }, + { + name: "exceeds min producible", + amount: -21e6 - 1e-8, + valid: true, + expected: -MaxSatoshi - 1, + }, + { + name: "one hundred", + amount: 100, + valid: true, + expected: 100 * SatoshiPerBitcoin, + }, + { + name: "fraction", + amount: 0.01234567, + valid: true, + expected: 1234567, + }, + { + name: "rounding up", + amount: 54.999999999999943157, + valid: true, + expected: 55 * SatoshiPerBitcoin, + }, + { + name: "rounding down", + amount: 55.000000000000056843, + valid: true, + expected: 55 * SatoshiPerBitcoin, + }, + + // Negative tests. + { + name: "not-a-number", + amount: math.NaN(), + valid: false, + }, + { + name: "-infinity", + amount: math.Inf(-1), + valid: false, + }, + { + name: "+infinity", + amount: math.Inf(1), + valid: false, + }, + } + + for _, test := range tests { + a, err := NewAmount(test.amount) + switch { + case test.valid && err != nil: + t.Errorf("%v: Positive test Amount creation failed with: %v", test.name, err) + continue + case !test.valid && err == nil: + t.Errorf("%v: Negative test Amount creation succeeded (value %v) when should fail", test.name, a) + continue + } + + if a != test.expected { + t.Errorf("%v: Created amount %v does not match expected %v", test.name, a, test.expected) + continue + } + } +} + +func TestAmountUnitConversions(t *testing.T) { + tests := []struct { + name string + amount Amount + unit AmountUnit + converted float64 + s string + }{ + { + name: "MBTC", + amount: MaxSatoshi, + unit: AmountMegaBTC, + converted: 21, + s: "21 MBTC", + }, + { + name: "kBTC", + amount: 44433322211100, + unit: AmountKiloBTC, + converted: 444.33322211100, + s: "444.333222111 kBTC", + }, + { + name: "BTC", + amount: 44433322211100, + unit: AmountBTC, + converted: 444333.22211100, + s: "444333.222111 BTC", + }, + { + name: "mBTC", + amount: 44433322211100, + unit: AmountMilliBTC, + converted: 444333222.11100, + s: "444333222.111 mBTC", + }, + { + + name: "μBTC", + amount: 44433322211100, + unit: AmountMicroBTC, + converted: 444333222111.00, + s: "444333222111 μBTC", + }, + { + + name: "satoshi", + amount: 44433322211100, + unit: AmountSatoshi, + converted: 44433322211100, + s: "44433322211100 Satoshi", + }, + { + + name: "non-standard unit", + amount: 44433322211100, + unit: AmountUnit(-1), + converted: 4443332.2211100, + s: "4443332.22111 1e-1 BTC", + }, + } + + for _, test := range tests { + f := test.amount.ToUnit(test.unit) + if f != test.converted { + t.Errorf("%v: converted value %v does not match expected %v", test.name, f, test.converted) + continue + } + + s := test.amount.Format(test.unit) + if s != test.s { + t.Errorf("%v: format '%v' does not match expected '%v'", test.name, s, test.s) + continue + } + + // Verify that Amount.ToBTC works as advertised. + f1 := test.amount.ToUnit(AmountBTC) + f2 := test.amount.ToBTC() + if f1 != f2 { + t.Errorf("%v: ToBTC does not match ToUnit(AmountBTC): %v != %v", test.name, f1, f2) + } + + // Verify that Amount.String works as advertised. + s1 := test.amount.Format(AmountBTC) + s2 := test.amount.String() + if s1 != s2 { + t.Errorf("%v: String does not match Format(AmountBitcoin): %v != %v", test.name, s1, s2) + } + } +} + +func TestAmountMulF64(t *testing.T) { + tests := []struct { + name string + amt Amount + mul float64 + res Amount + }{ + { + name: "Multiply 0.1 BTC by 2", + amt: 100e5, // 0.1 BTC + mul: 2, + res: 200e5, // 0.2 BTC + }, + { + name: "Multiply 0.2 BTC by 0.02", + amt: 200e5, // 0.2 BTC + mul: 1.02, + res: 204e5, // 0.204 BTC + }, + { + name: "Multiply 0.1 BTC by -2", + amt: 100e5, // 0.1 BTC + mul: -2, + res: -200e5, // -0.2 BTC + }, + { + name: "Multiply 0.2 BTC by -0.02", + amt: 200e5, // 0.2 BTC + mul: -1.02, + res: -204e5, // -0.204 BTC + }, + { + name: "Multiply -0.1 BTC by 2", + amt: -100e5, // -0.1 BTC + mul: 2, + res: -200e5, // -0.2 BTC + }, + { + name: "Multiply -0.2 BTC by 0.02", + amt: -200e5, // -0.2 BTC + mul: 1.02, + res: -204e5, // -0.204 BTC + }, + { + name: "Multiply -0.1 BTC by -2", + amt: -100e5, // -0.1 BTC + mul: -2, + res: 200e5, // 0.2 BTC + }, + { + name: "Multiply -0.2 BTC by -0.02", + amt: -200e5, // -0.2 BTC + mul: -1.02, + res: 204e5, // 0.204 BTC + }, + { + name: "Round down", + amt: 49, // 49 Satoshis + mul: 0.01, + res: 0, + }, + { + name: "Round up", + amt: 50, // 50 Satoshis + mul: 0.01, + res: 1, // 1 Satoshi + }, + { + name: "Multiply by 0.", + amt: 1e8, // 1 BTC + mul: 0, + res: 0, // 0 BTC + }, + { + name: "Multiply 1 by 0.5.", + amt: 1, // 1 Satoshi + mul: 0.5, + res: 1, // 1 Satoshi + }, + { + name: "Multiply 100 by 66%.", + amt: 100, // 100 Satoshis + mul: 0.66, + res: 66, // 66 Satoshis + }, + { + name: "Multiply 100 by 66.6%.", + amt: 100, // 100 Satoshis + mul: 0.666, + res: 67, // 67 Satoshis + }, + { + name: "Multiply 100 by 2/3.", + amt: 100, // 100 Satoshis + mul: 2.0 / 3, + res: 67, // 67 Satoshis + }, + } + + for _, test := range tests { + a := test.amt.MulF64(test.mul) + if a != test.res { + t.Errorf("%v: expected %v got %v", test.name, test.res, a) + } + } +} diff --git a/btcutil/appdata.go b/btcutil/appdata.go new file mode 100644 index 0000000000..e36cf7c4a4 --- /dev/null +++ b/btcutil/appdata.go @@ -0,0 +1,105 @@ +// 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 btcutil + +import ( + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + "unicode" +) + +// appDataDir returns an operating system specific directory to be used for +// storing application data for an application. See AppDataDir for more +// details. This unexported version takes an operating system argument +// primarily to enable the testing package to properly test the function by +// forcing an operating system that is not the currently one. +func appDataDir(goos, appName string, roaming bool) string { + if appName == "" || appName == "." { + return "." + } + + // The caller really shouldn't prepend the appName with a period, but + // if they do, handle it gracefully by trimming it. + appName = strings.TrimPrefix(appName, ".") + appNameUpper := string(unicode.ToUpper(rune(appName[0]))) + appName[1:] + appNameLower := string(unicode.ToLower(rune(appName[0]))) + appName[1:] + + // Get the OS specific home directory via the Go standard lib. + var homeDir string + usr, err := user.Current() + if err == nil { + homeDir = usr.HomeDir + } + + // Fall back to standard HOME environment variable that works + // for most POSIX OSes if the directory from the Go standard + // lib failed. + if err != nil || homeDir == "" { + homeDir = os.Getenv("HOME") + } + + switch goos { + // Attempt to use the LOCALAPPDATA or APPDATA environment variable on + // Windows. + case "windows": + // Windows XP and before didn't have a LOCALAPPDATA, so fallback + // to regular APPDATA when LOCALAPPDATA is not set. + appData := os.Getenv("LOCALAPPDATA") + if roaming || appData == "" { + appData = os.Getenv("APPDATA") + } + + if appData != "" { + return filepath.Join(appData, appNameUpper) + } + + case "darwin": + if homeDir != "" { + return filepath.Join(homeDir, "Library", + "Application Support", appNameUpper) + } + + case "plan9": + if homeDir != "" { + return filepath.Join(homeDir, appNameLower) + } + + default: + if homeDir != "" { + return filepath.Join(homeDir, "."+appNameLower) + } + } + + // Fall back to the current directory if all else fails. + return "." +} + +// AppDataDir returns an operating system specific directory to be used for +// storing application data for an application. +// +// The appName parameter is the name of the application the data directory is +// being requested for. This function will prepend a period to the appName for +// POSIX style operating systems since that is standard practice. An empty +// appName or one with a single dot is treated as requesting the current +// directory so only "." will be returned. Further, the first character +// of appName will be made lowercase for POSIX style operating systems and +// uppercase for Mac and Windows since that is standard practice. +// +// The roaming parameter only applies to Windows where it specifies the roaming +// application data profile (%APPDATA%) should be used instead of the local one +// (%LOCALAPPDATA%) that is used by default. +// +// Example results: +// dir := AppDataDir("myapp", false) +// POSIX (Linux/BSD): ~/.myapp +// Mac OS: $HOME/Library/Application Support/Myapp +// Windows: %LOCALAPPDATA%\Myapp +// Plan 9: $home/myapp +func AppDataDir(appName string, roaming bool) string { + return appDataDir(runtime.GOOS, appName, roaming) +} diff --git a/btcutil/appdata_test.go b/btcutil/appdata_test.go new file mode 100644 index 0000000000..061b1b5d80 --- /dev/null +++ b/btcutil/appdata_test.go @@ -0,0 +1,133 @@ +// 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 btcutil_test + +import ( + "os" + "os/user" + "path/filepath" + "runtime" + "testing" + "unicode" + + "github.com/btcsuite/btcd/btcutil" +) + +// TestAppDataDir tests the API for AppDataDir to ensure it gives expected +// results for various operating systems. +func TestAppDataDir(t *testing.T) { + // App name plus upper and lowercase variants. + appName := "myapp" + appNameUpper := string(unicode.ToUpper(rune(appName[0]))) + appName[1:] + appNameLower := string(unicode.ToLower(rune(appName[0]))) + appName[1:] + + // When we're on Windows, set the expected local and roaming directories + // per the environment vars. When we aren't on Windows, the function + // should return the current directory when forced to provide the + // Windows path since the environment variables won't exist. + winLocal := "." + winRoaming := "." + if runtime.GOOS == "windows" { + localAppData := os.Getenv("LOCALAPPDATA") + roamingAppData := os.Getenv("APPDATA") + if localAppData == "" { + localAppData = roamingAppData + } + winLocal = filepath.Join(localAppData, appNameUpper) + winRoaming = filepath.Join(roamingAppData, appNameUpper) + } + + // Get the home directory to use for testing expected results. + var homeDir string + usr, err := user.Current() + if err != nil { + t.Errorf("user.Current: %v", err) + return + } + homeDir = usr.HomeDir + + // Mac app data directory. + macAppData := filepath.Join(homeDir, "Library", "Application Support") + + tests := []struct { + goos string + appName string + roaming bool + want string + }{ + // Various combinations of application name casing, leading + // period, operating system, and roaming flags. + {"windows", appNameLower, false, winLocal}, + {"windows", appNameUpper, false, winLocal}, + {"windows", "." + appNameLower, false, winLocal}, + {"windows", "." + appNameUpper, false, winLocal}, + {"windows", appNameLower, true, winRoaming}, + {"windows", appNameUpper, true, winRoaming}, + {"windows", "." + appNameLower, true, winRoaming}, + {"windows", "." + appNameUpper, true, winRoaming}, + {"linux", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"linux", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"linux", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"linux", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"darwin", appNameLower, false, filepath.Join(macAppData, appNameUpper)}, + {"darwin", appNameUpper, false, filepath.Join(macAppData, appNameUpper)}, + {"darwin", "." + appNameLower, false, filepath.Join(macAppData, appNameUpper)}, + {"darwin", "." + appNameUpper, false, filepath.Join(macAppData, appNameUpper)}, + {"openbsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"openbsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"openbsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"openbsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"freebsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"freebsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"freebsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"freebsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"netbsd", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"netbsd", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"netbsd", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"netbsd", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"plan9", appNameLower, false, filepath.Join(homeDir, appNameLower)}, + {"plan9", appNameUpper, false, filepath.Join(homeDir, appNameLower)}, + {"plan9", "." + appNameLower, false, filepath.Join(homeDir, appNameLower)}, + {"plan9", "." + appNameUpper, false, filepath.Join(homeDir, appNameLower)}, + {"unrecognized", appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"unrecognized", appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + {"unrecognized", "." + appNameLower, false, filepath.Join(homeDir, "."+appNameLower)}, + {"unrecognized", "." + appNameUpper, false, filepath.Join(homeDir, "."+appNameLower)}, + + // No application name provided, so expect current directory. + {"windows", "", false, "."}, + {"windows", "", true, "."}, + {"linux", "", false, "."}, + {"darwin", "", false, "."}, + {"openbsd", "", false, "."}, + {"freebsd", "", false, "."}, + {"netbsd", "", false, "."}, + {"plan9", "", false, "."}, + {"unrecognized", "", false, "."}, + + // Single dot provided for application name, so expect current + // directory. + {"windows", ".", false, "."}, + {"windows", ".", true, "."}, + {"linux", ".", false, "."}, + {"darwin", ".", false, "."}, + {"openbsd", ".", false, "."}, + {"freebsd", ".", false, "."}, + {"netbsd", ".", false, "."}, + {"plan9", ".", false, "."}, + {"unrecognized", ".", false, "."}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + ret := btcutil.TstAppDataDir(test.goos, test.appName, test.roaming) + if ret != test.want { + t.Errorf("appDataDir #%d (%s) does not match - "+ + "expected got %s, want %s", i, test.goos, ret, + test.want) + continue + } + } +} diff --git a/btcutil/base58/README.md b/btcutil/base58/README.md new file mode 100644 index 0000000000..abdb78155d --- /dev/null +++ b/btcutil/base58/README.md @@ -0,0 +1,34 @@ +base58 +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcutil/base58) + +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. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/base58 +``` + +## Examples + +* [Decode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/base58#example-Decode) + Demonstrates how to decode modified base58 encoded data. +* [Encode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/base58#example-Encode) + Demonstrates how to encode data using the modified base58 encoding scheme. +* [CheckDecode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/base58#example-CheckDecode) + Demonstrates how to decode Base58Check encoded data. +* [CheckEncode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/base58#example-CheckEncode) + Demonstrates how to encode data using the Base58Check encoding scheme. + +## License + +Package base58 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/base58/alphabet.go b/btcutil/base58/alphabet.go new file mode 100644 index 0000000000..6bb39fef11 --- /dev/null +++ b/btcutil/base58/alphabet.go @@ -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 ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "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, +} diff --git a/btcutil/base58/base58.go b/btcutil/base58/base58.go new file mode 100644 index 0000000000..8ee5956718 --- /dev/null +++ b/btcutil/base58/base58.go @@ -0,0 +1,138 @@ +// 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] { + 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, alphabet[m%58]) + m /= 58 + } + } else { + m := mod.Int64() + for i := 0; i < 10; i++ { + answer = append(answer, alphabet[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) +} diff --git a/btcutil/base58/base58_test.go b/btcutil/base58/base58_test.go new file mode 100644 index 0000000000..b868d1d401 --- /dev/null +++ b/btcutil/base58/base58_test.go @@ -0,0 +1,101 @@ +// 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/btcsuite/btcd/btcutil/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", ""}, + {"!@#$%^&*()-_=+~`", ""}, +} + +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 + } + } +} diff --git a/btcutil/base58/base58bench_test.go b/btcutil/base58/base58bench_test.go new file mode 100644 index 0000000000..c7286e53c7 --- /dev/null +++ b/btcutil/base58/base58bench_test.go @@ -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/btcsuite/btcd/btcutil/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) + } +} diff --git a/btcutil/base58/base58check.go b/btcutil/base58/base58check.go new file mode 100644 index 0000000000..402c3233f4 --- /dev/null +++ b/btcutil/base58/base58check.go @@ -0,0 +1,52 @@ +// 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 ( + "crypto/sha256" + "errors" +) + +// 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 +} diff --git a/btcutil/base58/base58check_test.go b/btcutil/base58/base58check_test.go new file mode 100644 index 0000000000..e180b16391 --- /dev/null +++ b/btcutil/base58/base58check_test.go @@ -0,0 +1,69 @@ +// 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/btcsuite/btcd/btcutil/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") + } + } + +} diff --git a/btcutil/base58/cov_report.sh b/btcutil/base58/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/btcutil/base58/cov_report.sh @@ -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 diff --git a/btcutil/base58/doc.go b/btcutil/base58/doc.go new file mode 100644 index 0000000000..9a2c0e6e3d --- /dev/null +++ b/btcutil/base58/doc.go @@ -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 alphabet 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 alphabet 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 diff --git a/btcutil/base58/example_test.go b/btcutil/base58/example_test.go new file mode 100644 index 0000000000..babb35d3f4 --- /dev/null +++ b/btcutil/base58/example_test.go @@ -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/btcsuite/btcd/btcutil/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 +} diff --git a/btcutil/base58/genalphabet.go b/btcutil/base58/genalphabet.go new file mode 100644 index 0000000000..010cbee39e --- /dev/null +++ b/btcutil/base58/genalphabet.go @@ -0,0 +1,79 @@ +// 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. + +//+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 ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "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) +} diff --git a/btcutil/bech32/README.md b/btcutil/bech32/README.md new file mode 100644 index 0000000000..471cd50ee9 --- /dev/null +++ b/btcutil/bech32/README.md @@ -0,0 +1,29 @@ +bech32 +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://godoc.org/github.com/btcsuite/btcd/btcutil/bech32?status.png)](http://godoc.org/github.com/btcsuite/btcd/btcutil/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 github.com/btcsuite/btcd/btcutil/bech32 +``` + +## Examples + +* [Bech32 decode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/bech32#example-Bech32Decode) + Demonstrates how to decode a bech32 encoded string. +* [Bech32 encode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/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. diff --git a/btcutil/bech32/bech32.go b/btcutil/bech32/bech32.go new file mode 100644 index 0000000000..c1e00106e6 --- /dev/null +++ b/btcutil/bech32/bech32.go @@ -0,0 +1,442 @@ +// 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 ( + "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. +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 correspoding character in 'charset'. +func toBytes(chars string) ([]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 string, values, checksum []byte) int { + chk := 1 + + // Account for the high bits of the HRP in the checksum. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + hiBits := int(hrp[i]) >> 5 + chk = (chk&0x1ffffff)<<5 ^ hiBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= 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 := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + + // Account for the low bits of the HRP. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + loBits := int(hrp[i]) & 31 + chk = (chk&0x1ffffff)<<5 ^ loBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the values. + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= 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 := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } else { + // Checksum is provided during decoding, so use it. + for _, v := range checksum { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } + + return chk +} + +// 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 string, data []byte, bldr *strings.Builder, + 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 string, 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 string) (string, []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, 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, 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, VersionUnknown, ErrMixedCase{} + } + } + + // Bech32 standard uses only the lowercase for of strings for checksum + // calculation. + if hasUpper { + bech = strings.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 := strings.LastIndexByte(bech, '1') + if one < 1 || one+7 > len(bech) { + return "", 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, 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 strings.Builder + 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: actual, + } + return "", 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 string) (string, []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 string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", 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 string) (string, []byte, Version, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", 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 string, data []byte, + version Version) (string, error) { + + // The resulting bech32 string is the concatenation of the lowercase + // hrp, the separator 1, data and the 6-byte checksum. + hrp = strings.ToLower(hrp) + var bldr strings.Builder + bldr.Grow(len(hrp) + 1 + len(data) + 6) + bldr.WriteString(hrp) + bldr.WriteString("1") + + // Write the data part, using the bech32 charset. + for _, b := range data { + if int(b) >= len(charset) { + return "", ErrInvalidDataByte(b) + } + bldr.WriteByte(charset[b]) + } + + // Calculate and write the checksum of the data. + writeBech32Checksum(hrp, data, &bldr, version) + + return bldr.String(), 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 string, data []byte) (string, 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 string, data []byte) (string, 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 string, data []byte) (string, error) { + converted, err := ConvertBits(data, 8, 5, true) + if err != nil { + return "", 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 string) (string, []byte, error) { + hrp, data, err := Decode(bech) + if err != nil { + return "", nil, err + } + converted, err := ConvertBits(data, 5, 8, false) + if err != nil { + return "", nil, err + } + return hrp, converted, nil +} diff --git a/btcutil/bech32/bech32_test.go b/btcutil/bech32/bech32_test.go new file mode 100644 index 0000000000..1e04905a61 --- /dev/null +++ b/btcutil/bech32/bech32_test.go @@ -0,0 +1,691 @@ +// 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" + "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 := test.str + hrp, decoded, err := Decode(str) + if test.expectedError != err { + 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 encoded != strings.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 := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + 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 := 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 encoded != strings.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 := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + 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(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(test.hrp, convertedData) + if err != nil { + t.Errorf("%q: unexpected encode error: %v", test.name, err) + continue + } + if gotEncoded != 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(strings.ToUpper(test.encoded)) + if err != nil { + t.Errorf("%q: unexpected decode error: %v", test.name, err) + continue + } + wantHRP := strings.ToLower(test.hrp) + if gotHRP != 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(input) + if err == nil { + t.Fatalf("Test vector not appropriate") + } + + // Try and decode it. + hrp, data, err := DecodeNoLimit(input) + if err != nil { + t.Fatalf("Expected decoding of large string to work. Got error: %v", err) + } + + // Verify data for correctness. + if hrp != "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(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 gotHRP != 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(strings.ToUpper(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected uppercase HRP encode error: %v", test.name, + err) + } + wantEncoded := strings.ToLower(str) + if 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(strings.ToLower(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if 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 strings.Builder + for i, r := range test.hrp { + if i%2 == 0 { + mixedHRPBuilder.WriteString(strings.ToUpper(string(r))) + continue + } + mixedHRPBuilder.WriteRune(r) + } + gotEncoded, err = EncodeFromBase256(mixedHRPBuilder.String(), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if 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(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(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) + } + } +} diff --git a/btcutil/bech32/doc.go b/btcutil/bech32/doc.go new file mode 100644 index 0000000000..2d64fbe0bf --- /dev/null +++ b/btcutil/bech32/doc.go @@ -0,0 +1,15 @@ +// 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 diff --git a/btcutil/bech32/error.go b/btcutil/bech32/error.go new file mode 100644 index 0000000000..e8b1fe8c30 --- /dev/null +++ b/btcutil/bech32/error.go @@ -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 (e 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 (e 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 (e 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 (e ErrInvalidLength) Error() string { + return fmt.Sprintf("invalid bech32 string length %d", int(e)) +} + +// ErrInvalidCharacter is returned when the bech32 string has a character +// outside the range of the supported charset. +type ErrInvalidCharacter rune + +func (e ErrInvalidCharacter) Error() string { + return fmt.Sprintf("invalid character in string: '%c'", rune(e)) +} + +// ErrInvalidSeparatorIndex is returned when the separator character '1' is +// in an invalid position in the bech32 string. +type ErrInvalidSeparatorIndex int + +func (e ErrInvalidSeparatorIndex) Error() string { + return fmt.Sprintf("invalid separator index %d", int(e)) +} + +// ErrNonCharsetChar is returned when a character outside of the specific +// bech32 charset is used in the string. +type ErrNonCharsetChar rune + +func (e ErrNonCharsetChar) Error() string { + return fmt.Sprintf("invalid character not part of charset: %v", int(e)) +} + +// 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 (e ErrInvalidChecksum) Error() string { + return fmt.Sprintf("invalid checksum (expected (bech32=%v, "+ + "bech32m=%v), got %v)", e.Expected, e.ExpectedM, e.Actual) +} + +// ErrInvalidDataByte is returned when a byte outside the range required for +// conversion into a string was found. +type ErrInvalidDataByte byte + +func (e ErrInvalidDataByte) Error() string { + return fmt.Sprintf("invalid data byte: %v", byte(e)) +} diff --git a/btcutil/bech32/example_test.go b/btcutil/bech32/example_test.go new file mode 100644 index 0000000000..290baaed6c --- /dev/null +++ b/btcutil/bech32/example_test.go @@ -0,0 +1,49 @@ +// 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_test + +import ( + "encoding/hex" + "fmt" + + "github.com/btcsuite/btcd/btcutil/bech32" +) + +// This example demonstrates how to decode a bech32 encoded string. +func ExampleDecode() { + encoded := "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx" + hrp, decoded, err := bech32.Decode(encoded) + if err != nil { + fmt.Println("Error:", err) + } + + // Show the decoded data. + fmt.Println("Decoded human-readable part:", 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 := bech32.ConvertBits(data, 8, 5, true) + if err != nil { + fmt.Println("Error:", err) + } + encoded, err := bech32.Encode("customHrp!11111q", conv) + if err != nil { + fmt.Println("Error:", err) + } + + // Show the encoded data. + fmt.Println("Encoded Data:", encoded) + + // Output: + // Encoded Data: customhrp!11111q123jhxapqv3shgcgkxpuhe +} diff --git a/btcutil/bech32/version.go b/btcutil/bech32/version.go new file mode 100644 index 0000000000..147037db9a --- /dev/null +++ b/btcutil/bech32/version.go @@ -0,0 +1,43 @@ +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, +} diff --git a/btcutil/block.go b/btcutil/block.go new file mode 100644 index 0000000000..7d38abc4a0 --- /dev/null +++ b/btcutil/block.go @@ -0,0 +1,265 @@ +// 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 btcutil + +import ( + "bytes" + "fmt" + "io" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// OutOfRangeError describes an error due to accessing an element that is out +// of range. +type OutOfRangeError string + +// BlockHeightUnknown is the value returned for a block height that is unknown. +// This is typically because the block has not been inserted into the main chain +// yet. +const BlockHeightUnknown = int32(-1) + +// Error satisfies the error interface and prints human-readable errors. +func (e OutOfRangeError) Error() string { + return string(e) +} + +// Block defines a bitcoin block that provides easier and more efficient +// manipulation of raw blocks. It also memoizes hashes for the block and its +// transactions on their first access so subsequent accesses don't have to +// repeat the relatively expensive hashing operations. +type Block struct { + msgBlock *wire.MsgBlock // Underlying MsgBlock + serializedBlock []byte // Serialized bytes for the block + serializedBlockNoWitness []byte // Serialized bytes for block w/o witness data + blockHash *chainhash.Hash // Cached block hash + blockHeight int32 // Height in the main block chain + transactions []*Tx // Transactions + txnsGenerated bool // ALL wrapped transactions generated +} + +// MsgBlock returns the underlying wire.MsgBlock for the Block. +func (b *Block) MsgBlock() *wire.MsgBlock { + // Return the cached block. + return b.msgBlock +} + +// Bytes returns the serialized bytes for the Block. This is equivalent to +// calling Serialize on the underlying wire.MsgBlock, however it caches the +// result so subsequent calls are more efficient. +func (b *Block) Bytes() ([]byte, error) { + // Return the cached serialized bytes if it has already been generated. + if len(b.serializedBlock) != 0 { + return b.serializedBlock, nil + } + + // Serialize the MsgBlock. + w := bytes.NewBuffer(make([]byte, 0, b.msgBlock.SerializeSize())) + err := b.msgBlock.Serialize(w) + if err != nil { + return nil, err + } + serializedBlock := w.Bytes() + + // Cache the serialized bytes and return them. + b.serializedBlock = serializedBlock + return serializedBlock, nil +} + +// BytesNoWitness returns the serialized bytes for the block with transactions +// encoded without any witness data. +func (b *Block) BytesNoWitness() ([]byte, error) { + // Return the cached serialized bytes if it has already been generated. + if len(b.serializedBlockNoWitness) != 0 { + return b.serializedBlockNoWitness, nil + } + + // Serialize the MsgBlock. + var w bytes.Buffer + err := b.msgBlock.SerializeNoWitness(&w) + if err != nil { + return nil, err + } + serializedBlock := w.Bytes() + + // Cache the serialized bytes and return them. + b.serializedBlockNoWitness = serializedBlock + return serializedBlock, nil +} + +// Hash returns the block identifier hash for the Block. This is equivalent to +// calling BlockHash on the underlying wire.MsgBlock, however it caches the +// result so subsequent calls are more efficient. +func (b *Block) Hash() *chainhash.Hash { + // Return the cached block hash if it has already been generated. + if b.blockHash != nil { + return b.blockHash + } + + // Cache the block hash and return it. + hash := b.msgBlock.BlockHash() + b.blockHash = &hash + return &hash +} + +// Tx returns a wrapped transaction (btcutil.Tx) for the transaction at the +// specified index in the Block. The supplied index is 0 based. That is to +// say, the first transaction in the block is txNum 0. This is nearly +// equivalent to accessing the raw transaction (wire.MsgTx) from the +// underlying wire.MsgBlock, however the wrapped transaction has some helpful +// properties such as caching the hash so subsequent calls are more efficient. +func (b *Block) Tx(txNum int) (*Tx, error) { + // Ensure the requested transaction is in range. + numTx := uint64(len(b.msgBlock.Transactions)) + if txNum < 0 || uint64(txNum) >= numTx { + str := fmt.Sprintf("transaction index %d is out of range - max %d", + txNum, numTx-1) + return nil, OutOfRangeError(str) + } + + // Generate slice to hold all of the wrapped transactions if needed. + if len(b.transactions) == 0 { + b.transactions = make([]*Tx, numTx) + } + + // Return the wrapped transaction if it has already been generated. + if b.transactions[txNum] != nil { + return b.transactions[txNum], nil + } + + // Generate and cache the wrapped transaction and return it. + newTx := NewTx(b.msgBlock.Transactions[txNum]) + newTx.SetIndex(txNum) + b.transactions[txNum] = newTx + return newTx, nil +} + +// Transactions returns a slice of wrapped transactions (btcutil.Tx) for all +// transactions in the Block. This is nearly equivalent to accessing the raw +// transactions (wire.MsgTx) in the underlying wire.MsgBlock, however it +// instead provides easy access to wrapped versions (btcutil.Tx) of them. +func (b *Block) Transactions() []*Tx { + // Return transactions if they have ALL already been generated. This + // flag is necessary because the wrapped transactions are lazily + // generated in a sparse fashion. + if b.txnsGenerated { + return b.transactions + } + + // Generate slice to hold all of the wrapped transactions if needed. + if len(b.transactions) == 0 { + b.transactions = make([]*Tx, len(b.msgBlock.Transactions)) + } + + // Generate and cache the wrapped transactions for all that haven't + // already been done. + for i, tx := range b.transactions { + if tx == nil { + newTx := NewTx(b.msgBlock.Transactions[i]) + newTx.SetIndex(i) + b.transactions[i] = newTx + } + } + + b.txnsGenerated = true + return b.transactions +} + +// TxHash returns the hash for the requested transaction number in the Block. +// The supplied index is 0 based. That is to say, the first transaction in the +// block is txNum 0. This is equivalent to calling TxHash on the underlying +// wire.MsgTx, however it caches the result so subsequent calls are more +// efficient. +func (b *Block) TxHash(txNum int) (*chainhash.Hash, error) { + // Attempt to get a wrapped transaction for the specified index. It + // will be created lazily if needed or simply return the cached version + // if it has already been generated. + tx, err := b.Tx(txNum) + if err != nil { + return nil, err + } + + // Defer to the wrapped transaction which will return the cached hash if + // it has already been generated. + return tx.Hash(), nil +} + +// TxLoc returns the offsets and lengths of each transaction in a raw block. +// It is used to allow fast indexing into transactions within the raw byte +// stream. +func (b *Block) TxLoc() ([]wire.TxLoc, error) { + rawMsg, err := b.Bytes() + if err != nil { + return nil, err + } + rbuf := bytes.NewBuffer(rawMsg) + + var mblock wire.MsgBlock + txLocs, err := mblock.DeserializeTxLoc(rbuf) + if err != nil { + return nil, err + } + return txLocs, err +} + +// Height returns the saved height of the block in the block chain. This value +// will be BlockHeightUnknown if it hasn't already explicitly been set. +func (b *Block) Height() int32 { + return b.blockHeight +} + +// SetHeight sets the height of the block in the block chain. +func (b *Block) SetHeight(height int32) { + b.blockHeight = height +} + +// NewBlock returns a new instance of a bitcoin block given an underlying +// wire.MsgBlock. See Block. +func NewBlock(msgBlock *wire.MsgBlock) *Block { + return &Block{ + msgBlock: msgBlock, + blockHeight: BlockHeightUnknown, + } +} + +// NewBlockFromBytes returns a new instance of a bitcoin block given the +// serialized bytes. See Block. +func NewBlockFromBytes(serializedBlock []byte) (*Block, error) { + br := bytes.NewReader(serializedBlock) + b, err := NewBlockFromReader(br) + if err != nil { + return nil, err + } + b.serializedBlock = serializedBlock + return b, nil +} + +// NewBlockFromReader returns a new instance of a bitcoin block given a +// Reader to deserialize the block. See Block. +func NewBlockFromReader(r io.Reader) (*Block, error) { + // Deserialize the bytes into a MsgBlock. + var msgBlock wire.MsgBlock + err := msgBlock.Deserialize(r) + if err != nil { + return nil, err + } + + b := Block{ + msgBlock: &msgBlock, + blockHeight: BlockHeightUnknown, + } + return &b, nil +} + +// NewBlockFromBlockAndBytes returns a new instance of a bitcoin block given +// an underlying wire.MsgBlock and the serialized bytes for it. See Block. +func NewBlockFromBlockAndBytes(msgBlock *wire.MsgBlock, serializedBlock []byte) *Block { + return &Block{ + msgBlock: msgBlock, + serializedBlock: serializedBlock, + blockHeight: BlockHeightUnknown, + } +} diff --git a/btcutil/block_test.go b/btcutil/block_test.go new file mode 100644 index 0000000000..e24b9842f7 --- /dev/null +++ b/btcutil/block_test.go @@ -0,0 +1,556 @@ +// 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 btcutil_test + +import ( + "bytes" + "io" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "github.com/davecgh/go-spew/spew" +) + +// TestBlock tests the API for Block. +func TestBlock(t *testing.T) { + b := btcutil.NewBlock(&Block100000) + + // Ensure we get the same data back out. + if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) { + t.Errorf("MsgBlock: mismatched MsgBlock - got %v, want %v", + spew.Sdump(msgBlock), spew.Sdump(&Block100000)) + } + + // Ensure block height set and get work properly. + wantHeight := int32(100000) + b.SetHeight(wantHeight) + if gotHeight := b.Height(); gotHeight != wantHeight { + t.Errorf("Height: mismatched height - got %v, want %v", + gotHeight, wantHeight) + } + + // Hash for block 100,000. + wantHashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + wantHash, err := chainhash.NewHashFromStr(wantHashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Request the hash multiple times to test generation and caching. + for i := 0; i < 2; i++ { + hash := b.Hash() + if !hash.IsEqual(wantHash) { + t.Errorf("Hash #%d mismatched hash - got %v, want %v", + i, hash, wantHash) + } + } + + // Hashes for the transactions in Block100000. + wantTxHashes := []string{ + "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87", + "fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4", + "6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4", + "e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d", + } + + // Create a new block to nuke all cached data. + b = btcutil.NewBlock(&Block100000) + + // Request hash for all transactions one at a time via Tx. + for i, txHash := range wantTxHashes { + wantHash, err := chainhash.NewHashFromStr(txHash) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Request the hash multiple times to test generation and + // caching. + for j := 0; j < 2; j++ { + tx, err := b.Tx(i) + if err != nil { + t.Errorf("Tx #%d: %v", i, err) + continue + } + + hash := tx.Hash() + if !hash.IsEqual(wantHash) { + t.Errorf("Hash #%d mismatched hash - got %v, "+ + "want %v", j, hash, wantHash) + continue + } + } + } + + // Create a new block to nuke all cached data. + b = btcutil.NewBlock(&Block100000) + + // Request slice of all transactions multiple times to test generation + // and caching. + for i := 0; i < 2; i++ { + transactions := b.Transactions() + + // Ensure we get the expected number of transactions. + if len(transactions) != len(wantTxHashes) { + t.Errorf("Transactions #%d mismatched number of "+ + "transactions - got %d, want %d", i, + len(transactions), len(wantTxHashes)) + continue + } + + // Ensure all of the hashes match. + for j, tx := range transactions { + wantHash, err := chainhash.NewHashFromStr(wantTxHashes[j]) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + hash := tx.Hash() + if !hash.IsEqual(wantHash) { + t.Errorf("Transactions #%d mismatched hashes "+ + "- got %v, want %v", j, hash, wantHash) + continue + } + } + } + + // Serialize the test block. + var block100000Buf bytes.Buffer + err = Block100000.Serialize(&block100000Buf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + block100000Bytes := block100000Buf.Bytes() + + // Request serialized bytes multiple times to test generation and + // caching. + for i := 0; i < 2; i++ { + serializedBytes, err := b.Bytes() + if err != nil { + t.Errorf("Bytes: %v", err) + continue + } + if !bytes.Equal(serializedBytes, block100000Bytes) { + t.Errorf("Bytes #%d wrong bytes - got %v, want %v", i, + spew.Sdump(serializedBytes), + spew.Sdump(block100000Bytes)) + continue + } + } + + // Transaction offsets and length for the transaction in Block100000. + wantTxLocs := []wire.TxLoc{ + {TxStart: 81, TxLen: 144}, + {TxStart: 225, TxLen: 259}, + {TxStart: 484, TxLen: 257}, + {TxStart: 741, TxLen: 225}, + } + + // Ensure the transaction location information is accurate. + txLocs, err := b.TxLoc() + if err != nil { + t.Errorf("TxLoc: %v", err) + return + } + if !reflect.DeepEqual(txLocs, wantTxLocs) { + t.Errorf("TxLoc: mismatched transaction location information "+ + "- got %v, want %v", spew.Sdump(txLocs), + spew.Sdump(wantTxLocs)) + } +} + +// TestNewBlockFromBytes tests creation of a Block from serialized bytes. +func TestNewBlockFromBytes(t *testing.T) { + // Serialize the test block. + var block100000Buf bytes.Buffer + err := Block100000.Serialize(&block100000Buf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + block100000Bytes := block100000Buf.Bytes() + + // Create a new block from the serialized bytes. + b, err := btcutil.NewBlockFromBytes(block100000Bytes) + if err != nil { + t.Errorf("NewBlockFromBytes: %v", err) + return + } + + // Ensure we get the same data back out. + serializedBytes, err := b.Bytes() + if err != nil { + t.Errorf("Bytes: %v", err) + return + } + if !bytes.Equal(serializedBytes, block100000Bytes) { + t.Errorf("Bytes: wrong bytes - got %v, want %v", + spew.Sdump(serializedBytes), + spew.Sdump(block100000Bytes)) + } + + // Ensure the generated MsgBlock is correct. + if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) { + t.Errorf("MsgBlock: mismatched MsgBlock - got %v, want %v", + spew.Sdump(msgBlock), spew.Sdump(&Block100000)) + } +} + +// TestNewBlockFromBlockAndBytes tests creation of a Block from a MsgBlock and +// raw bytes. +func TestNewBlockFromBlockAndBytes(t *testing.T) { + // Serialize the test block. + var block100000Buf bytes.Buffer + err := Block100000.Serialize(&block100000Buf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + block100000Bytes := block100000Buf.Bytes() + + // Create a new block from the serialized bytes. + b := btcutil.NewBlockFromBlockAndBytes(&Block100000, block100000Bytes) + + // Ensure we get the same data back out. + serializedBytes, err := b.Bytes() + if err != nil { + t.Errorf("Bytes: %v", err) + return + } + if !bytes.Equal(serializedBytes, block100000Bytes) { + t.Errorf("Bytes: wrong bytes - got %v, want %v", + spew.Sdump(serializedBytes), + spew.Sdump(block100000Bytes)) + } + if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) { + t.Errorf("MsgBlock: mismatched MsgBlock - got %v, want %v", + spew.Sdump(msgBlock), spew.Sdump(&Block100000)) + } +} + +// TestBlockErrors tests the error paths for the Block API. +func TestBlockErrors(t *testing.T) { + // Ensure out of range errors are as expected. + wantErr := "transaction index -1 is out of range - max 3" + testErr := btcutil.OutOfRangeError(wantErr) + if testErr.Error() != wantErr { + t.Errorf("OutOfRangeError: wrong error - got %v, want %v", + testErr.Error(), wantErr) + } + + // Serialize the test block. + var block100000Buf bytes.Buffer + err := Block100000.Serialize(&block100000Buf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + block100000Bytes := block100000Buf.Bytes() + + // Create a new block from the serialized bytes. + b, err := btcutil.NewBlockFromBytes(block100000Bytes) + if err != nil { + t.Errorf("NewBlockFromBytes: %v", err) + return + } + + // Truncate the block byte buffer to force errors. + shortBytes := block100000Bytes[:80] + _, err = btcutil.NewBlockFromBytes(shortBytes) + if err != io.EOF { + t.Errorf("NewBlockFromBytes: did not get expected error - "+ + "got %v, want %v", err, io.EOF) + } + + // Ensure TxHash returns expected error on invalid indices. + _, err = b.TxHash(-1) + if _, ok := err.(btcutil.OutOfRangeError); !ok { + t.Errorf("TxHash: wrong error - got: %v <%T>, "+ + "want: <%T>", err, err, btcutil.OutOfRangeError("")) + } + _, err = b.TxHash(len(Block100000.Transactions)) + if _, ok := err.(btcutil.OutOfRangeError); !ok { + t.Errorf("TxHash: wrong error - got: %v <%T>, "+ + "want: <%T>", err, err, btcutil.OutOfRangeError("")) + } + + // Ensure Tx returns expected error on invalid indices. + _, err = b.Tx(-1) + if _, ok := err.(btcutil.OutOfRangeError); !ok { + t.Errorf("Tx: wrong error - got: %v <%T>, "+ + "want: <%T>", err, err, btcutil.OutOfRangeError("")) + } + _, err = b.Tx(len(Block100000.Transactions)) + if _, ok := err.(btcutil.OutOfRangeError); !ok { + t.Errorf("Tx: wrong error - got: %v <%T>, "+ + "want: <%T>", err, err, btcutil.OutOfRangeError("")) + } + + // Ensure TxLoc returns expected error with short byte buffer. + // This makes use of the test package only function, SetBlockBytes, to + // inject a short byte buffer. + b.SetBlockBytes(shortBytes) + _, err = b.TxLoc() + if err != io.EOF { + t.Errorf("TxLoc: did not get expected error - "+ + "got %v, want %v", err, io.EOF) + } +} + +// Block100000 defines block 100,000 of the block chain. It is used to +// test Block operations. +var Block100000 = wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: chainhash.Hash([32]byte{ // Make go vet happy. + 0x50, 0x12, 0x01, 0x19, 0x17, 0x2a, 0x61, 0x04, + 0x21, 0xa6, 0xc3, 0x01, 0x1d, 0xd3, 0x30, 0xd9, + 0xdf, 0x07, 0xb6, 0x36, 0x16, 0xc2, 0xcc, 0x1f, + 0x1c, 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + }), // 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250 + MerkleRoot: chainhash.Hash([32]byte{ // Make go vet happy. + 0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0, + 0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22, + 0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85, + 0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3, + }), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766 + Timestamp: time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC + Bits: 0x1b04864c, // 453281356 + Nonce: 0x10572b0f, // 274148111 + }, + Transactions: []*wire.MsgTx{ + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, + }, + Sequence: 0xffffffff, + Witness: [][]byte{ + {0x04, 0x31}, + {0x01, 0x43}, + }, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x12a05f200, // 5000000000 + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, + 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, + 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, + 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, + }), // 87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03 + Index: 0, + }, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, + 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, + 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, + 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, + 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, + 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, + 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, + 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, + 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, + 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, + 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, + 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, + 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, + 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, + 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, + 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, + 0xd3, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x2123e300, // 556000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x108e20f00, // 4444000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, + 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, + 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, + 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, + }), // cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3 + Index: 1, + }, + SignatureScript: []byte{ + 0x47, // OP_DATA_71 + 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, + 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, + 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, + 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, + 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, + 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, + 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, + 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, + 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, + 0x41, // OP_DATA_65 + 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, + 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, + 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, + 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, + 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, + 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, + 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, + 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, + 0x0f, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0xf4240, // 1000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x11d260c0, // 299000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73, + 0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac, + 0x3b, 0x24, 0x0c, 0x84, 0xb9, 0x17, 0xa3, 0x90, + 0x9b, 0xa1, 0xc4, 0x3d, 0xed, 0x5f, 0x51, 0xf4, + }), // f4515fed3dc4a19b90a317b9840c243bac26114cf637522373a7d486b372600b + Index: 0, + }, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xbb, 0x1a, 0xd2, + 0x6d, 0xf9, 0x30, 0xa5, 0x1c, 0xce, 0x11, 0x0c, + 0xf4, 0x4f, 0x7a, 0x48, 0xc3, 0xc5, 0x61, 0xfd, + 0x97, 0x75, 0x00, 0xb1, 0xae, 0x5d, 0x6b, 0x6f, + 0xd1, 0x3d, 0x0b, 0x3f, 0x4a, 0x02, 0x21, 0x00, + 0xc5, 0xb4, 0x29, 0x51, 0xac, 0xed, 0xff, 0x14, + 0xab, 0xba, 0x27, 0x36, 0xfd, 0x57, 0x4b, 0xdb, + 0x46, 0x5f, 0x3e, 0x6f, 0x8d, 0xa1, 0x2e, 0x2c, + 0x53, 0x03, 0x95, 0x4a, 0xca, 0x7f, 0x78, 0xf3, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xa7, 0x13, 0x5b, 0xfe, 0x82, 0x4c, 0x97, + 0xec, 0xc0, 0x1e, 0xc7, 0xd7, 0xe3, 0x36, 0x18, + 0x5c, 0x81, 0xe2, 0xaa, 0x2c, 0x41, 0xab, 0x17, + 0x54, 0x07, 0xc0, 0x94, 0x84, 0xce, 0x96, 0x94, + 0xb4, 0x49, 0x53, 0xfc, 0xb7, 0x51, 0x20, 0x65, + 0x64, 0xa9, 0xc2, 0x4d, 0xd0, 0x94, 0xd4, 0x2f, + 0xdb, 0xfd, 0xd5, 0xaa, 0xd3, 0xe0, 0x63, 0xce, + 0x6a, 0xf4, 0xcf, 0xaa, 0xea, 0x4e, 0xa1, 0x4f, + 0xbb, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0xf4240, // 1000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x39, 0xaa, 0x3d, 0x56, 0x9e, 0x06, 0xa1, 0xd7, + 0x92, 0x6d, 0xc4, 0xbe, 0x11, 0x93, 0xc9, 0x9b, + 0xf2, 0xeb, 0x9e, 0xe0, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + }, +} diff --git a/btcutil/bloom/README.md b/btcutil/bloom/README.md new file mode 100644 index 0000000000..4a5c937f88 --- /dev/null +++ b/btcutil/bloom/README.md @@ -0,0 +1,30 @@ +bloom +===== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcutil/bloom) + +Package bloom provides an API for dealing with bitcoin-specific bloom filters. + +A comprehensive suite of tests is provided to ensure proper functionality. See +`test_coverage.txt` for the gocov coverage report. Alternatively, if you are +running a POSIX OS, you can run the `cov_report.sh` script for a real-time +report. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/bloom +``` + +## Examples + +* [NewFilter Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/bloom#example-NewFilter) + Demonstrates how to create a new bloom filter, add a transaction hash to it, + and check if the filter matches the transaction. + +## License + +Package bloom is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/bloom/cov_report.sh b/btcutil/bloom/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/btcutil/bloom/cov_report.sh @@ -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 diff --git a/btcutil/bloom/example_test.go b/btcutil/bloom/example_test.go new file mode 100644 index 0000000000..bcd5d0190b --- /dev/null +++ b/btcutil/bloom/example_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2014-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bloom_test + +import ( + "fmt" + "math/rand" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil/bloom" +) + +// This example demonstrates how to create a new bloom filter, add a transaction +// hash to it, and check if the filter matches the transaction. +func ExampleNewFilter() { + rand.Seed(time.Now().UnixNano()) + tweak := rand.Uint32() + + // Create a new bloom filter intended to hold 10 elements with a 0.01% + // false positive rate and does not include any automatic update + // functionality when transactions are matched. + filter := bloom.NewFilter(10, tweak, 0.0001, wire.BloomUpdateNone) + + // Create a transaction hash and add it to the filter. This particular + // trasaction is the first transaction in block 310,000 of the main + // bitcoin block chain. + txHashStr := "fd611c56ca0d378cdcd16244b45c2ba9588da3adac367c4ef43e808b280b8a45" + txHash, err := chainhash.NewHashFromStr(txHashStr) + if err != nil { + fmt.Println(err) + return + } + filter.AddHash(txHash) + + // Show that the filter matches. + matches := filter.Matches(txHash[:]) + fmt.Println("Filter Matches?:", matches) + + // Output: + // Filter Matches?: true +} diff --git a/btcutil/bloom/filter.go b/btcutil/bloom/filter.go new file mode 100644 index 0000000000..8c4527ea29 --- /dev/null +++ b/btcutil/bloom/filter.go @@ -0,0 +1,354 @@ +// Copyright (c) 2014-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bloom + +import ( + "encoding/binary" + "math" + "sync" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" +) + +// ln2Squared is simply the square of the natural log of 2. +const ln2Squared = math.Ln2 * math.Ln2 + +// minUint32 is a convenience function to return the minimum value of the two +// passed uint32 values. +func minUint32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + +// Filter defines a bitcoin bloom filter that provides easy manipulation of raw +// filter data. +type Filter struct { + mtx sync.Mutex + msgFilterLoad *wire.MsgFilterLoad +} + +// NewFilter creates a new bloom filter instance, mainly to be used by SPV +// clients. The tweak parameter is a random value added to the seed value. +// The false positive rate is the probability of a false positive where 1.0 is +// "match everything" and zero is unachievable. Thus, providing any false +// positive rates less than 0 or greater than 1 will be adjusted to the valid +// range. +// +// For more information on what values to use for both elements and fprate, +// see https://en.wikipedia.org/wiki/Bloom_filter. +func NewFilter(elements, tweak uint32, fprate float64, flags wire.BloomUpdateType) *Filter { + // Massage the false positive rate to sane values. + if fprate > 1.0 { + fprate = 1.0 + } + if fprate < 1e-9 { + fprate = 1e-9 + } + + // Calculate the size of the filter in bytes for the given number of + // elements and false positive rate. + // + // Equivalent to m = -(n*ln(p) / ln(2)^2), where m is in bits. + // Then clamp it to the maximum filter size and convert to bytes. + dataLen := uint32(-1 * float64(elements) * math.Log(fprate) / ln2Squared) + dataLen = minUint32(dataLen, wire.MaxFilterLoadFilterSize*8) / 8 + + // Calculate the number of hash functions based on the size of the + // filter calculated above and the number of elements. + // + // Equivalent to k = (m/n) * ln(2) + // Then clamp it to the maximum allowed hash funcs. + hashFuncs := uint32(float64(dataLen*8) / float64(elements) * math.Ln2) + hashFuncs = minUint32(hashFuncs, wire.MaxFilterLoadHashFuncs) + + data := make([]byte, dataLen) + msg := wire.NewMsgFilterLoad(data, hashFuncs, tweak, flags) + + return &Filter{ + msgFilterLoad: msg, + } +} + +// LoadFilter creates a new Filter instance with the given underlying +// wire.MsgFilterLoad. +func LoadFilter(filter *wire.MsgFilterLoad) *Filter { + return &Filter{ + msgFilterLoad: filter, + } +} + +// IsLoaded returns true if a filter is loaded, otherwise false. +// +// This function is safe for concurrent access. +func (bf *Filter) IsLoaded() bool { + bf.mtx.Lock() + loaded := bf.msgFilterLoad != nil + bf.mtx.Unlock() + return loaded +} + +// Reload loads a new filter replacing any existing filter. +// +// This function is safe for concurrent access. +func (bf *Filter) Reload(filter *wire.MsgFilterLoad) { + bf.mtx.Lock() + bf.msgFilterLoad = filter + bf.mtx.Unlock() +} + +// Unload unloads the bloom filter. +// +// This function is safe for concurrent access. +func (bf *Filter) Unload() { + bf.mtx.Lock() + bf.msgFilterLoad = nil + bf.mtx.Unlock() +} + +// hash returns the bit offset in the bloom filter which corresponds to the +// passed data for the given independent hash function number. +func (bf *Filter) hash(hashNum uint32, data []byte) uint32 { + // bitcoind: 0xfba4c795 chosen as it guarantees a reasonable bit + // difference between hashNum values. + // + // Note that << 3 is equivalent to multiplying by 8, but is faster. + // Thus the returned hash is brought into range of the number of bits + // the filter has and returned. + mm := MurmurHash3(hashNum*0xfba4c795+bf.msgFilterLoad.Tweak, data) + return mm % (uint32(len(bf.msgFilterLoad.Filter)) << 3) +} + +// matches returns true if the bloom filter might contain the passed data and +// false if it definitely does not. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) matches(data []byte) bool { + if bf.msgFilterLoad == nil { + return false + } + + // The bloom filter does not contain the data if any of the bit offsets + // which result from hashing the data using each independent hash + // function are not set. The shifts and masks below are a faster + // equivalent of: + // arrayIndex := idx / 8 (idx >> 3) + // bitOffset := idx % 8 (idx & 7) + /// if filter[arrayIndex] & 1<>3]&(1<<(idx&7)) == 0 { + return false + } + } + return true +} + +// Matches returns true if the bloom filter might contain the passed data and +// false if it definitely does not. +// +// This function is safe for concurrent access. +func (bf *Filter) Matches(data []byte) bool { + bf.mtx.Lock() + match := bf.matches(data) + bf.mtx.Unlock() + return match +} + +// matchesOutPoint returns true if the bloom filter might contain the passed +// outpoint and false if it definitely does not. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) matchesOutPoint(outpoint *wire.OutPoint) bool { + // Serialize + var buf [chainhash.HashSize + 4]byte + copy(buf[:], outpoint.Hash[:]) + binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index) + + return bf.matches(buf[:]) +} + +// MatchesOutPoint returns true if the bloom filter might contain the passed +// outpoint and false if it definitely does not. +// +// This function is safe for concurrent access. +func (bf *Filter) MatchesOutPoint(outpoint *wire.OutPoint) bool { + bf.mtx.Lock() + match := bf.matchesOutPoint(outpoint) + bf.mtx.Unlock() + return match +} + +// add adds the passed byte slice to the bloom filter. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) add(data []byte) { + if bf.msgFilterLoad == nil { + return + } + + // Adding data to a bloom filter consists of setting all of the bit + // offsets which result from hashing the data using each independent + // hash function. The shifts and masks below are a faster equivalent + // of: + // arrayIndex := idx / 8 (idx >> 3) + // bitOffset := idx % 8 (idx & 7) + /// filter[arrayIndex] |= 1<>3] |= (1 << (7 & idx)) + } +} + +// Add adds the passed byte slice to the bloom filter. +// +// This function is safe for concurrent access. +func (bf *Filter) Add(data []byte) { + bf.mtx.Lock() + bf.add(data) + bf.mtx.Unlock() +} + +// AddHash adds the passed chainhash.Hash to the Filter. +// +// This function is safe for concurrent access. +func (bf *Filter) AddHash(hash *chainhash.Hash) { + bf.mtx.Lock() + bf.add(hash[:]) + bf.mtx.Unlock() +} + +// addOutPoint adds the passed transaction outpoint to the bloom filter. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) addOutPoint(outpoint *wire.OutPoint) { + // Serialize + var buf [chainhash.HashSize + 4]byte + copy(buf[:], outpoint.Hash[:]) + binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index) + + bf.add(buf[:]) +} + +// AddOutPoint adds the passed transaction outpoint to the bloom filter. +// +// This function is safe for concurrent access. +func (bf *Filter) AddOutPoint(outpoint *wire.OutPoint) { + bf.mtx.Lock() + bf.addOutPoint(outpoint) + bf.mtx.Unlock() +} + +// maybeAddOutpoint potentially adds the passed outpoint to the bloom filter +// depending on the bloom update flags and the type of the passed public key +// script. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) maybeAddOutpoint(pkScript []byte, outHash *chainhash.Hash, outIdx uint32) { + switch bf.msgFilterLoad.Flags { + case wire.BloomUpdateAll: + outpoint := wire.NewOutPoint(outHash, outIdx) + bf.addOutPoint(outpoint) + case wire.BloomUpdateP2PubkeyOnly: + class := txscript.GetScriptClass(pkScript) + if class == txscript.PubKeyTy || class == txscript.MultiSigTy { + outpoint := wire.NewOutPoint(outHash, outIdx) + bf.addOutPoint(outpoint) + } + } +} + +// matchTxAndUpdate returns true if the bloom filter matches data within the +// passed transaction, otherwise false is returned. If the filter does match +// the passed transaction, it will also update the filter depending on the bloom +// update flags set via the loaded filter if needed. +// +// This function MUST be called with the filter lock held. +func (bf *Filter) matchTxAndUpdate(tx *btcutil.Tx) bool { + // Check if the filter matches the hash of the transaction. + // This is useful for finding transactions when they appear in a block. + matched := bf.matches(tx.Hash()[:]) + + // Check if the filter matches any data elements in the public key + // scripts of any of the outputs. When it does, add the outpoint that + // matched so transactions which spend from the matched transaction are + // also included in the filter. This removes the burden of updating the + // filter for this scenario from the client. It is also more efficient + // on the network since it avoids the need for another filteradd message + // from the client and avoids some potential races that could otherwise + // occur. + for i, txOut := range tx.MsgTx().TxOut { + pushedData, err := txscript.PushedData(txOut.PkScript) + if err != nil { + continue + } + + for _, data := range pushedData { + if !bf.matches(data) { + continue + } + + matched = true + bf.maybeAddOutpoint(txOut.PkScript, tx.Hash(), uint32(i)) + break + } + } + + // Nothing more to do if a match has already been made. + if matched { + return true + } + + // At this point, the transaction and none of the data elements in the + // public key scripts of its outputs matched. + + // Check if the filter matches any outpoints this transaction spends or + // any data elements in the signature scripts of any of the inputs. + for _, txin := range tx.MsgTx().TxIn { + if bf.matchesOutPoint(&txin.PreviousOutPoint) { + return true + } + + pushedData, err := txscript.PushedData(txin.SignatureScript) + if err != nil { + continue + } + for _, data := range pushedData { + if bf.matches(data) { + return true + } + } + } + + return false +} + +// MatchTxAndUpdate returns true if the bloom filter matches data within the +// passed transaction, otherwise false is returned. If the filter does match +// the passed transaction, it will also update the filter depending on the bloom +// update flags set via the loaded filter if needed. +// +// This function is safe for concurrent access. +func (bf *Filter) MatchTxAndUpdate(tx *btcutil.Tx) bool { + bf.mtx.Lock() + match := bf.matchTxAndUpdate(tx) + bf.mtx.Unlock() + return match +} + +// MsgFilterLoad returns the underlying wire.MsgFilterLoad for the bloom +// filter. +// +// This function is safe for concurrent access. +func (bf *Filter) MsgFilterLoad() *wire.MsgFilterLoad { + bf.mtx.Lock() + msg := bf.msgFilterLoad + bf.mtx.Unlock() + return msg +} diff --git a/btcutil/bloom/filter_test.go b/btcutil/bloom/filter_test.go new file mode 100644 index 0000000000..1811dbf57e --- /dev/null +++ b/btcutil/bloom/filter_test.go @@ -0,0 +1,660 @@ +// 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 bloom_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bloom" +) + +// TestFilterLarge ensures a maximum sized filter can be created. +func TestFilterLarge(t *testing.T) { + f := bloom.NewFilter(100000000, 0, 0.01, wire.BloomUpdateNone) + if len(f.MsgFilterLoad().Filter) > wire.MaxFilterLoadFilterSize { + t.Errorf("TestFilterLarge test failed: %d > %d", + len(f.MsgFilterLoad().Filter), wire.MaxFilterLoadFilterSize) + } +} + +// TestFilterLoad ensures loading and unloading of a filter pass. +func TestFilterLoad(t *testing.T) { + merkle := wire.MsgFilterLoad{} + + f := bloom.LoadFilter(&merkle) + if !f.IsLoaded() { + t.Errorf("TestFilterLoad IsLoaded test failed: want %v got %v", + true, !f.IsLoaded()) + return + } + f.Unload() + if f.IsLoaded() { + t.Errorf("TestFilterLoad IsLoaded test failed: want %v got %v", + f.IsLoaded(), false) + return + } +} + +// TestFilterInsert ensures inserting data into the filter causes that data +// to be matched and the resulting serialized MsgFilterLoad is the expected +// value. +func TestFilterInsert(t *testing.T) { + var tests = []struct { + hex string + insert bool + }{ + {"99108ad8ed9bb6274d3980bab5a85c048f0950c8", true}, + {"19108ad8ed9bb6274d3980bab5a85c048f0950c8", false}, + {"b5a2c786d9ef4658287ced5914b37a1b4aa32eee", true}, + {"b9300670b4c5366e95b2699e8b18bc75e5f729c5", true}, + } + + f := bloom.NewFilter(3, 0, 0.01, wire.BloomUpdateAll) + + for i, test := range tests { + data, err := hex.DecodeString(test.hex) + if err != nil { + t.Errorf("TestFilterInsert DecodeString failed: %v\n", err) + return + } + if test.insert { + f.Add(data) + } + + result := f.Matches(data) + if test.insert != result { + t.Errorf("TestFilterInsert Matches test #%d failure: got %v want %v\n", + i, result, test.insert) + return + } + } + + want, err := hex.DecodeString("03614e9b050000000000000001") + if err != nil { + t.Errorf("TestFilterInsert DecodeString failed: %v\n", err) + return + } + + got := bytes.NewBuffer(nil) + err = f.MsgFilterLoad().BtcEncode(got, wire.ProtocolVersion, wire.LatestEncoding) + if err != nil { + t.Errorf("TestFilterInsert BtcDecode failed: %v\n", err) + return + } + + if !bytes.Equal(got.Bytes(), want) { + t.Errorf("TestFilterInsert failure: got %v want %v\n", + got.Bytes(), want) + return + } +} + +// TestFilterFPRange checks that new filters made with out of range +// false positive targets result in either max or min false positive rates. +func TestFilterFPRange(t *testing.T) { + tests := []struct { + name string + hash string + want string + filter *bloom.Filter + }{ + { + name: "fprates > 1 should be clipped at 1", + hash: "02981fa052f0481dbc5868f4fc2166035a10f27a03cfd2de67326471df5bc041", + want: "00000000000000000001", + filter: bloom.NewFilter(1, 0, 20.9999999769, wire.BloomUpdateAll), + }, + { + name: "fprates less than 1e-9 should be clipped at min", + hash: "02981fa052f0481dbc5868f4fc2166035a10f27a03cfd2de67326471df5bc041", + want: "0566d97a91a91b0000000000000001", + filter: bloom.NewFilter(1, 0, 0, wire.BloomUpdateAll), + }, + { + name: "negative fprates should be clipped at min", + hash: "02981fa052f0481dbc5868f4fc2166035a10f27a03cfd2de67326471df5bc041", + want: "0566d97a91a91b0000000000000001", + filter: bloom.NewFilter(1, 0, -1, wire.BloomUpdateAll), + }, + } + + for _, test := range tests { + // Convert test input to appropriate types. + hash, err := chainhash.NewHashFromStr(test.hash) + if err != nil { + t.Errorf("NewHashFromStr unexpected error: %v", err) + continue + } + want, err := hex.DecodeString(test.want) + if err != nil { + t.Errorf("DecodeString unexpected error: %v\n", err) + continue + } + + // Add the test hash to the bloom filter and ensure the + // filter serializes to the expected bytes. + f := test.filter + f.AddHash(hash) + got := bytes.NewBuffer(nil) + err = f.MsgFilterLoad().BtcEncode(got, wire.ProtocolVersion, wire.LatestEncoding) + if err != nil { + t.Errorf("BtcDecode unexpected error: %v\n", err) + continue + } + if !bytes.Equal(got.Bytes(), want) { + t.Errorf("serialized filter mismatch: got %x want %x\n", + got.Bytes(), want) + continue + } + } +} + +// TestFilterInsert ensures inserting data into the filter with a tweak causes +// that data to be matched and the resulting serialized MsgFilterLoad is the +// expected value. +func TestFilterInsertWithTweak(t *testing.T) { + var tests = []struct { + hex string + insert bool + }{ + {"99108ad8ed9bb6274d3980bab5a85c048f0950c8", true}, + {"19108ad8ed9bb6274d3980bab5a85c048f0950c8", false}, + {"b5a2c786d9ef4658287ced5914b37a1b4aa32eee", true}, + {"b9300670b4c5366e95b2699e8b18bc75e5f729c5", true}, + } + + f := bloom.NewFilter(3, 2147483649, 0.01, wire.BloomUpdateAll) + + for i, test := range tests { + data, err := hex.DecodeString(test.hex) + if err != nil { + t.Errorf("TestFilterInsertWithTweak DecodeString failed: %v\n", err) + return + } + if test.insert { + f.Add(data) + } + + result := f.Matches(data) + if test.insert != result { + t.Errorf("TestFilterInsertWithTweak Matches test #%d failure: got %v want %v\n", + i, result, test.insert) + return + } + } + + want, err := hex.DecodeString("03ce4299050000000100008001") + if err != nil { + t.Errorf("TestFilterInsertWithTweak DecodeString failed: %v\n", err) + return + } + got := bytes.NewBuffer(nil) + err = f.MsgFilterLoad().BtcEncode(got, wire.ProtocolVersion, wire.LatestEncoding) + if err != nil { + t.Errorf("TestFilterInsertWithTweak BtcDecode failed: %v\n", err) + return + } + + if !bytes.Equal(got.Bytes(), want) { + t.Errorf("TestFilterInsertWithTweak failure: got %v want %v\n", + got.Bytes(), want) + return + } +} + +// TestFilterInsertKey ensures inserting public keys and addresses works as +// expected. +func TestFilterInsertKey(t *testing.T) { + secret := "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C" + + wif, err := btcutil.DecodeWIF(secret) + if err != nil { + t.Errorf("TestFilterInsertKey DecodeWIF failed: %v", err) + return + } + + f := bloom.NewFilter(2, 0, 0.001, wire.BloomUpdateAll) + f.Add(wif.SerializePubKey()) + f.Add(btcutil.Hash160(wif.SerializePubKey())) + + want, err := hex.DecodeString("038fc16b080000000000000001") + if err != nil { + t.Errorf("TestFilterInsertWithTweak DecodeString failed: %v\n", err) + return + } + got := bytes.NewBuffer(nil) + err = f.MsgFilterLoad().BtcEncode(got, wire.ProtocolVersion, wire.LatestEncoding) + if err != nil { + t.Errorf("TestFilterInsertWithTweak BtcDecode failed: %v\n", err) + return + } + + if !bytes.Equal(got.Bytes(), want) { + t.Errorf("TestFilterInsertWithTweak failure: got %v want %v\n", + got.Bytes(), want) + return + } +} + +func TestFilterBloomMatch(t *testing.T) { + str := "01000000010b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e" + + "88607d722c190000000008b4830450220070aca44506c5cef3a16ed519d7" + + "c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d2" + + "7d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a0141046d11fee" + + "51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5e" + + "eef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c33" + + "9ffffffff021bff3d11000000001976a91404943fdd508053c75000106d3" + + "bc6e2754dbcff1988ac2f15de00000000001976a914a266436d296554760" + + "8b9e15d9032a7b9d64fa43188ac00000000" + strBytes, err := hex.DecodeString(str) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failure: %v", err) + return + } + tx, err := btcutil.NewTxFromBytes(strBytes) + if err != nil { + t.Errorf("TestFilterBloomMatch NewTxFromBytes failure: %v", err) + return + } + spendingTxBytes := []byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x6b, 0xff, 0x7f, + 0xcd, 0x4f, 0x85, 0x65, 0xef, 0x40, 0x6d, 0xd5, 0xd6, + 0x3d, 0x4f, 0xf9, 0x4f, 0x31, 0x8f, 0xe8, 0x20, 0x27, + 0xfd, 0x4d, 0xc4, 0x51, 0xb0, 0x44, 0x74, 0x01, 0x9f, + 0x74, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x49, 0x30, + 0x46, 0x02, 0x21, 0x00, 0xda, 0x0d, 0xc6, 0xae, 0xce, + 0xfe, 0x1e, 0x06, 0xef, 0xdf, 0x05, 0x77, 0x37, 0x57, + 0xde, 0xb1, 0x68, 0x82, 0x09, 0x30, 0xe3, 0xb0, 0xd0, + 0x3f, 0x46, 0xf5, 0xfc, 0xf1, 0x50, 0xbf, 0x99, 0x0c, + 0x02, 0x21, 0x00, 0xd2, 0x5b, 0x5c, 0x87, 0x04, 0x00, + 0x76, 0xe4, 0xf2, 0x53, 0xf8, 0x26, 0x2e, 0x76, 0x3e, + 0x2d, 0xd5, 0x1e, 0x7f, 0xf0, 0xbe, 0x15, 0x77, 0x27, + 0xc4, 0xbc, 0x42, 0x80, 0x7f, 0x17, 0xbd, 0x39, 0x01, + 0x41, 0x04, 0xe6, 0xc2, 0x6e, 0xf6, 0x7d, 0xc6, 0x10, + 0xd2, 0xcd, 0x19, 0x24, 0x84, 0x78, 0x9a, 0x6c, 0xf9, + 0xae, 0xa9, 0x93, 0x0b, 0x94, 0x4b, 0x7e, 0x2d, 0xb5, + 0x34, 0x2b, 0x9d, 0x9e, 0x5b, 0x9f, 0xf7, 0x9a, 0xff, + 0x9a, 0x2e, 0xe1, 0x97, 0x8d, 0xd7, 0xfd, 0x01, 0xdf, + 0xc5, 0x22, 0xee, 0x02, 0x28, 0x3d, 0x3b, 0x06, 0xa9, + 0xd0, 0x3a, 0xcf, 0x80, 0x96, 0x96, 0x8d, 0x7d, 0xbb, + 0x0f, 0x91, 0x78, 0xff, 0xff, 0xff, 0xff, 0x02, 0x8b, + 0xa7, 0x94, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, + 0xa9, 0x14, 0xba, 0xde, 0xec, 0xfd, 0xef, 0x05, 0x07, + 0x24, 0x7f, 0xc8, 0xf7, 0x42, 0x41, 0xd7, 0x3b, 0xc0, + 0x39, 0x97, 0x2d, 0x7b, 0x88, 0xac, 0x40, 0x94, 0xa8, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, + 0xc1, 0x09, 0x32, 0x48, 0x3f, 0xec, 0x93, 0xed, 0x51, + 0xf5, 0xfe, 0x95, 0xe7, 0x25, 0x59, 0xf2, 0xcc, 0x70, + 0x43, 0xf9, 0x88, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00} + + spendingTx, err := btcutil.NewTxFromBytes(spendingTxBytes) + if err != nil { + t.Errorf("TestFilterBloomMatch NewTxFromBytes failure: %v", err) + return + } + + f := bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr := "b4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b" + hash, err := chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch NewHashFromStr failed: %v\n", err) + return + } + f.AddHash(hash) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match hash %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "6bff7fcd4f8565ef406dd5d63d4ff94f318fe82027fd4dc451b04474019f74b4" + hashBytes, err := hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match hash %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "30450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065" + + "f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643" + + "ac4cb7cb3c462aced7f14711a01" + hashBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match input signature %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95" + + "c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe" + + "76036c339" + hashBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match input pubkey %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "04943fdd508053c75000106d3bc6e2754dbcff19" + hashBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match output address %s", inputStr) + } + if !f.MatchTxAndUpdate(spendingTx) { + t.Errorf("TestFilterBloomMatch spendingTx didn't match output address %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "a266436d2965547608b9e15d9032a7b9d64fa431" + hashBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match output address %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "90c122d70786e899529d71dbeba91ba216982fb6ba58f3bdaab65e73b7e9260b" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch NewHashFromStr failed: %v\n", err) + return + } + outpoint := wire.NewOutPoint(hash, 0) + f.AddOutPoint(outpoint) + if !f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch didn't match outpoint %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "00000009e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch NewHashFromStr failed: %v\n", err) + return + } + f.AddHash(hash) + if f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch matched hash %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "0000006d2965547608b9e15d9032a7b9d64fa431" + hashBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch DecodeString failed: %v\n", err) + return + } + f.Add(hashBytes) + if f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch matched address %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "90c122d70786e899529d71dbeba91ba216982fb6ba58f3bdaab65e73b7e9260b" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch NewHashFromStr failed: %v\n", err) + return + } + outpoint = wire.NewOutPoint(hash, 1) + f.AddOutPoint(outpoint) + if f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch matched outpoint %s", inputStr) + } + + f = bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + inputStr = "000000d70786e899529d71dbeba91ba216982fb6ba58f3bdaab65e73b7e9260b" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterBloomMatch NewHashFromStr failed: %v\n", err) + return + } + outpoint = wire.NewOutPoint(hash, 0) + f.AddOutPoint(outpoint) + if f.MatchTxAndUpdate(tx) { + t.Errorf("TestFilterBloomMatch matched outpoint %s", inputStr) + } +} + +func TestFilterInsertUpdateNone(t *testing.T) { + f := bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateNone) + + // Add the generation pubkey + inputStr := "04eaafc2314def4ca98ac970241bcab022b9c1e1f4ea423a20f134c" + + "876f2c01ec0f0dd5b2e86e7168cefe0d81113c3807420ce13ad1357231a" + + "2252247d97a46a91" + inputBytes, err := hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterInsertUpdateNone DecodeString failed: %v", err) + return + } + f.Add(inputBytes) + + // Add the output address for the 4th transaction + inputStr = "b6efd80d99179f4f4ff6f4dd0a007d018c385d21" + inputBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterInsertUpdateNone DecodeString failed: %v", err) + return + } + f.Add(inputBytes) + + inputStr = "147caa76786596590baa4e98f5d9f48b86c7765e489f7a6ff3360fe5c674360b" + hash, err := chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterInsertUpdateNone NewHashFromStr failed: %v", err) + return + } + outpoint := wire.NewOutPoint(hash, 0) + + if f.MatchesOutPoint(outpoint) { + t.Errorf("TestFilterInsertUpdateNone matched outpoint %s", inputStr) + return + } + + inputStr = "02981fa052f0481dbc5868f4fc2166035a10f27a03cfd2de67326471df5bc041" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestFilterInsertUpdateNone NewHashFromStr failed: %v", err) + return + } + outpoint = wire.NewOutPoint(hash, 0) + + if f.MatchesOutPoint(outpoint) { + t.Errorf("TestFilterInsertUpdateNone matched outpoint %s", inputStr) + return + } +} + +func TestFilterInsertP2PubKeyOnly(t *testing.T) { + blockStr := "0100000082bb869cf3a793432a66e826e05a6fc37469f8efb7421dc" + + "880670100000000007f16c5962e8bd963659c793ce370d95f093bc7e367" + + "117b3c30c1f8fdd0d9728776381b4d4c86041b554b85290701000000010" + + "00000000000000000000000000000000000000000000000000000000000" + + "0000ffffffff07044c86041b0136ffffffff0100f2052a0100000043410" + + "4eaafc2314def4ca98ac970241bcab022b9c1e1f4ea423a20f134c876f2" + + "c01ec0f0dd5b2e86e7168cefe0d81113c3807420ce13ad1357231a22522" + + "47d97a46a91ac000000000100000001bcad20a6a29827d1424f08989255" + + "120bf7f3e9e3cdaaa6bb31b0737fe048724300000000494830450220356" + + "e834b046cadc0f8ebb5a8a017b02de59c86305403dad52cd77b55af062e" + + "a10221009253cd6c119d4729b77c978e1e2aa19f5ea6e0e52b3f16e32fa" + + "608cd5bab753901ffffffff02008d380c010000001976a9142b4b8072ec" + + "bba129b6453c63e129e643207249ca88ac0065cd1d000000001976a9141" + + "b8dd13b994bcfc787b32aeadf58ccb3615cbd5488ac0000000001000000" + + "03fdacf9b3eb077412e7a968d2e4f11b9a9dee312d666187ed77ee7d26a" + + "f16cb0b000000008c493046022100ea1608e70911ca0de5af51ba57ad23" + + "b9a51db8d28f82c53563c56a05c20f5a87022100a8bdc8b4a8acc8634c6" + + "b420410150775eb7f2474f5615f7fccd65af30f310fbf01410465fdf49e" + + "29b06b9a1582287b6279014f834edc317695d125ef623c1cc3aaece245b" + + "d69fcad7508666e9c74a49dc9056d5fc14338ef38118dc4afae5fe2c585" + + "caffffffff309e1913634ecb50f3c4f83e96e70b2df071b497b8973a3e7" + + "5429df397b5af83000000004948304502202bdb79c596a9ffc24e96f438" + + "6199aba386e9bc7b6071516e2b51dda942b3a1ed022100c53a857e76b72" + + "4fc14d45311eac5019650d415c3abb5428f3aae16d8e69bec2301ffffff" + + "ff2089e33491695080c9edc18a428f7d834db5b6d372df13ce2b1b0e0cb" + + "cb1e6c10000000049483045022100d4ce67c5896ee251c810ac1ff9cecc" + + "d328b497c8f553ab6e08431e7d40bad6b5022033119c0c2b7d792d31f11" + + "87779c7bd95aefd93d90a715586d73801d9b47471c601ffffffff010071" + + "4460030000001976a914c7b55141d097ea5df7a0ed330cf794376e53ec8" + + "d88ac0000000001000000045bf0e214aa4069a3e792ecee1e1bf0c1d397" + + "cde8dd08138f4b72a00681743447000000008b48304502200c45de8c4f3" + + "e2c1821f2fc878cba97b1e6f8807d94930713aa1c86a67b9bf1e4022100" + + "8581abfef2e30f957815fc89978423746b2086375ca8ecf359c85c2a5b7" + + "c88ad01410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf64852" + + "61c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270efb1d" + + "3ae37079b794a92d7ec95ffffffffd669f7d7958d40fc59d2253d88e0f2" + + "48e29b599c80bbcec344a83dda5f9aa72c000000008a473044022078124" + + "c8beeaa825f9e0b30bff96e564dd859432f2d0cb3b72d3d5d93d38d7e93" + + "0220691d233b6c0f995be5acb03d70a7f7a65b6bc9bdd426260f38a1346" + + "669507a3601410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf6" + + "485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270e" + + "fb1d3ae37079b794a92d7ec95fffffffff878af0d93f5229a68166cf051" + + "fd372bb7a537232946e0a46f53636b4dafdaa4000000008c49304602210" + + "0c717d1714551663f69c3c5759bdbb3a0fcd3fab023abc0e522fe6440de" + + "35d8290221008d9cbe25bffc44af2b18e81c58eb37293fd7fe1c2e7b46f" + + "c37ee8c96c50ab1e201410462bb73f76ca0994fcb8b4271e6fb7561f5c0" + + "f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f" + + "4d87270efb1d3ae37079b794a92d7ec95ffffffff27f2b668859cd7f2f8" + + "94aa0fd2d9e60963bcd07c88973f425f999b8cbfd7a1e2000000008c493" + + "046022100e00847147cbf517bcc2f502f3ddc6d284358d102ed20d47a8a" + + "a788a62f0db780022100d17b2d6fa84dcaf1c95d88d7e7c30385aecf415" + + "588d749afd3ec81f6022cecd701410462bb73f76ca0994fcb8b4271e6fb" + + "7561f5c0f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018" + + "ffd6238f4d87270efb1d3ae37079b794a92d7ec95ffffffff0100c817a8" + + "040000001976a914b6efd80d99179f4f4ff6f4dd0a007d018c385d2188a" + + "c000000000100000001834537b2f1ce8ef9373a258e10545ce5a50b758d" + + "f616cd4356e0032554ebd3c4000000008b483045022100e68f422dd7c34" + + "fdce11eeb4509ddae38201773dd62f284e8aa9d96f85099d0b002202243" + + "bd399ff96b649a0fad05fa759d6a882f0af8c90cf7632c2840c29070aec" + + "20141045e58067e815c2f464c6a2a15f987758374203895710c2d452442" + + "e28496ff38ba8f5fd901dc20e29e88477167fe4fc299bf818fd0d9e1632" + + "d467b2a3d9503b1aaffffffff0280d7e636030000001976a914f34c3e10" + + "eb387efe872acb614c89e78bfca7815d88ac404b4c00000000001976a91" + + "4a84e272933aaf87e1715d7786c51dfaeb5b65a6f88ac00000000010000" + + "000143ac81c8e6f6ef307dfe17f3d906d999e23e0189fda838c5510d850" + + "927e03ae7000000008c4930460221009c87c344760a64cb8ae6685a3eec" + + "2c1ac1bed5b88c87de51acd0e124f266c16602210082d07c037359c3a25" + + "7b5c63ebd90f5a5edf97b2ac1c434b08ca998839f346dd40141040ba7e5" + + "21fa7946d12edbb1d1e95a15c34bd4398195e86433c92b431cd315f455f" + + "e30032ede69cad9d1e1ed6c3c4ec0dbfced53438c625462afb792dcb098" + + "544bffffffff0240420f00000000001976a9144676d1b820d63ec272f19" + + "00d59d43bc6463d96f888ac40420f00000000001976a914648d04341d00" + + "d7968b3405c034adc38d4d8fb9bd88ac00000000010000000248cc91750" + + "1ea5c55f4a8d2009c0567c40cfe037c2e71af017d0a452ff705e3f10000" + + "00008b483045022100bf5fdc86dc5f08a5d5c8e43a8c9d5b1ed8c65562e" + + "280007b52b133021acd9acc02205e325d613e555f772802bf413d36ba80" + + "7892ed1a690a77811d3033b3de226e0a01410429fa713b124484cb2bd7b" + + "5557b2c0b9df7b2b1fee61825eadc5ae6c37a9920d38bfccdc7dc3cb0c4" + + "7d7b173dbc9db8d37db0a33ae487982c59c6f8606e9d1791ffffffff41e" + + "d70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d0" + + "68000000008b4830450221008513ad65187b903aed1102d1d0c47688127" + + "658c51106753fed0151ce9c16b80902201432b9ebcb87bd04ceb2de6603" + + "5fbbaf4bf8b00d1cfe41f1a1f7338f9ad79d210141049d4cf80125bf50b" + + "e1709f718c07ad15d0fc612b7da1f5570dddc35f2a352f0f27c978b0682" + + "0edca9ef982c35fda2d255afba340068c5035552368bc7200c1488fffff" + + "fff0100093d00000000001976a9148edb68822f1ad580b043c7b3df2e40" + + "0f8699eb4888ac00000000" + blockBytes, err := hex.DecodeString(blockStr) + if err != nil { + t.Errorf("TestFilterInsertP2PubKeyOnly DecodeString failed: %v", err) + return + } + block, err := btcutil.NewBlockFromBytes(blockBytes) + if err != nil { + t.Errorf("TestFilterInsertP2PubKeyOnly NewBlockFromBytes failed: %v", err) + return + } + + f := bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateP2PubkeyOnly) + + // Generation pubkey + inputStr := "04eaafc2314def4ca98ac970241bcab022b9c1e1f4ea423a20f134c" + + "876f2c01ec0f0dd5b2e86e7168cefe0d81113c3807420ce13ad1357231a" + + "2252247d97a46a91" + inputBytes, err := hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterInsertP2PubKeyOnly DecodeString failed: %v", err) + return + } + f.Add(inputBytes) + + // Output address of 4th transaction + inputStr = "b6efd80d99179f4f4ff6f4dd0a007d018c385d21" + inputBytes, err = hex.DecodeString(inputStr) + if err != nil { + t.Errorf("TestFilterInsertP2PubKeyOnly DecodeString failed: %v", err) + return + } + f.Add(inputBytes) + + // Ignore return value -- this is just used to update the filter. + _, _ = bloom.NewMerkleBlock(block, f) + + // We should match the generation pubkey + inputStr = "147caa76786596590baa4e98f5d9f48b86c7765e489f7a6ff3360fe5c674360b" + hash, err := chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestMerkleBlockP2PubKeyOnly NewHashFromStr failed: %v", err) + return + } + outpoint := wire.NewOutPoint(hash, 0) + if !f.MatchesOutPoint(outpoint) { + t.Errorf("TestMerkleBlockP2PubKeyOnly didn't match the generation "+ + "outpoint %s", inputStr) + return + } + + // We should not match the 4th transaction, which is not p2pk + inputStr = "02981fa052f0481dbc5868f4fc2166035a10f27a03cfd2de67326471df5bc041" + hash, err = chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestMerkleBlockP2PubKeyOnly NewHashFromStr failed: %v", err) + return + } + outpoint = wire.NewOutPoint(hash, 0) + if f.MatchesOutPoint(outpoint) { + t.Errorf("TestMerkleBlockP2PubKeyOnly matched outpoint %s", inputStr) + return + } +} + +func TestFilterReload(t *testing.T) { + f := bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + + bFilter := bloom.LoadFilter(f.MsgFilterLoad()) + if bFilter.MsgFilterLoad() == nil { + t.Errorf("TestFilterReload LoadFilter test failed") + return + } + bFilter.Reload(nil) + + if bFilter.MsgFilterLoad() != nil { + t.Errorf("TestFilterReload Reload test failed") + } +} diff --git a/btcutil/bloom/merkleblock.go b/btcutil/bloom/merkleblock.go new file mode 100644 index 0000000000..101a8f9194 --- /dev/null +++ b/btcutil/bloom/merkleblock.go @@ -0,0 +1,125 @@ +// 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 bloom + +import ( + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" +) + +// merkleBlock is used to house intermediate information needed to generate a +// wire.MsgMerkleBlock according to a filter. +type merkleBlock struct { + numTx uint32 + allHashes []*chainhash.Hash + finalHashes []*chainhash.Hash + matchedBits []byte + bits []byte +} + +// calcTreeWidth calculates and returns the the number of nodes (width) or a +// merkle tree at the given depth-first height. +func (m *merkleBlock) calcTreeWidth(height uint32) uint32 { + return (m.numTx + (1 << height) - 1) >> height +} + +// calcHash returns the hash for a sub-tree given a depth-first height and +// node position. +func (m *merkleBlock) calcHash(height, pos uint32) *chainhash.Hash { + if height == 0 { + return m.allHashes[pos] + } + + var right *chainhash.Hash + left := m.calcHash(height-1, pos*2) + if pos*2+1 < m.calcTreeWidth(height-1) { + right = m.calcHash(height-1, pos*2+1) + } else { + right = left + } + return blockchain.HashMerkleBranches(left, right) +} + +// traverseAndBuild builds a partial merkle tree using a recursive depth-first +// approach. As it calculates the hashes, it also saves whether or not each +// node is a parent node and a list of final hashes to be included in the +// merkle block. +func (m *merkleBlock) traverseAndBuild(height, pos uint32) { + // Determine whether this node is a parent of a matched node. + var isParent byte + for i := pos << height; i < (pos+1)< 1 { + height++ + } + + // Build the depth-first partial merkle tree. + mBlock.traverseAndBuild(height, 0) + + // Create and return the merkle block. + msgMerkleBlock := wire.MsgMerkleBlock{ + Header: block.MsgBlock().Header, + Transactions: mBlock.numTx, + Hashes: make([]*chainhash.Hash, 0, len(mBlock.finalHashes)), + Flags: make([]byte, (len(mBlock.bits)+7)/8), + } + for _, hash := range mBlock.finalHashes { + _ = msgMerkleBlock.AddTxHash(hash) + } + for i := uint32(0); i < uint32(len(mBlock.bits)); i++ { + msgMerkleBlock.Flags[i/8] |= mBlock.bits[i] << (i % 8) + } + return &msgMerkleBlock, matchedIndices +} diff --git a/btcutil/bloom/merkleblock_test.go b/btcutil/bloom/merkleblock_test.go new file mode 100644 index 0000000000..15e21a4bb7 --- /dev/null +++ b/btcutil/bloom/merkleblock_test.go @@ -0,0 +1,74 @@ +// 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 bloom_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bloom" +) + +func TestMerkleBlock3(t *testing.T) { + blockStr := "0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b" + + "4b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdc" + + "c96b2c3ff60abe184f196367291b4d4c86041b8fa45d630101000000010" + + "00000000000000000000000000000000000000000000000000000000000" + + "0000ffffffff08044c86041b020a02ffffffff0100f2052a01000000434" + + "104ecd3229b0571c3be876feaac0442a9f13c5a572742927af1dc623353" + + "ecf8c202225f64868137a18cdd85cbbb4c74fbccfd4f49639cf1bdc94a5" + + "672bb15ad5d4cac00000000" + blockBytes, err := hex.DecodeString(blockStr) + if err != nil { + t.Errorf("TestMerkleBlock3 DecodeString failed: %v", err) + return + } + blk, err := btcutil.NewBlockFromBytes(blockBytes) + if err != nil { + t.Errorf("TestMerkleBlock3 NewBlockFromBytes failed: %v", err) + return + } + + f := bloom.NewFilter(10, 0, 0.000001, wire.BloomUpdateAll) + + inputStr := "63194f18be0af63f2c6bc9dc0f777cbefed3d9415c4af83f3ee3a3d669c00cb5" + hash, err := chainhash.NewHashFromStr(inputStr) + if err != nil { + t.Errorf("TestMerkleBlock3 NewHashFromStr failed: %v", err) + return + } + + f.AddHash(hash) + + mBlock, _ := bloom.NewMerkleBlock(blk, f) + + wantStr := "0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b4" + + "b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc" + + "96b2c3ff60abe184f196367291b4d4c86041b8fa45d630100000001b50c" + + "c069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f196" + + "30101" + want, err := hex.DecodeString(wantStr) + if err != nil { + t.Errorf("TestMerkleBlock3 DecodeString failed: %v", err) + return + } + + got := bytes.NewBuffer(nil) + err = mBlock.BtcEncode(got, wire.ProtocolVersion, wire.LatestEncoding) + if err != nil { + t.Errorf("TestMerkleBlock3 BtcEncode failed: %v", err) + return + } + + if !bytes.Equal(want, got.Bytes()) { + t.Errorf("TestMerkleBlock3 failed merkle block comparison: "+ + "got %v want %v", got.Bytes(), want) + return + } +} diff --git a/btcutil/bloom/murmurhash3.go b/btcutil/bloom/murmurhash3.go new file mode 100644 index 0000000000..6bb562ecba --- /dev/null +++ b/btcutil/bloom/murmurhash3.go @@ -0,0 +1,72 @@ +// 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 bloom + +import ( + "encoding/binary" +) + +// The following constants are used by the MurmurHash3 algorithm. +const ( + murmurC1 = 0xcc9e2d51 + murmurC2 = 0x1b873593 + murmurR1 = 15 + murmurR2 = 13 + murmurM = 5 + murmurN = 0xe6546b64 +) + +// MurmurHash3 implements a non-cryptographic hash function using the +// MurmurHash3 algorithm. This implementation yields a 32-bit hash value which +// is suitable for general hash-based lookups. The seed can be used to +// effectively randomize the hash function. This makes it ideal for use in +// bloom filters which need multiple independent hash functions. +func MurmurHash3(seed uint32, data []byte) uint32 { + dataLen := uint32(len(data)) + hash := seed + k := uint32(0) + numBlocks := dataLen / 4 + + // Calculate the hash in 4-byte chunks. + for i := uint32(0); i < numBlocks; i++ { + k = binary.LittleEndian.Uint32(data[i*4:]) + k *= murmurC1 + k = (k << murmurR1) | (k >> (32 - murmurR1)) + k *= murmurC2 + + hash ^= k + hash = (hash << murmurR2) | (hash >> (32 - murmurR2)) + hash = hash*murmurM + murmurN + } + + // Handle remaining bytes. + tailIdx := numBlocks * 4 + k = 0 + + switch dataLen & 3 { + case 3: + k ^= uint32(data[tailIdx+2]) << 16 + fallthrough + case 2: + k ^= uint32(data[tailIdx+1]) << 8 + fallthrough + case 1: + k ^= uint32(data[tailIdx]) + k *= murmurC1 + k = (k << murmurR1) | (k >> (32 - murmurR1)) + k *= murmurC2 + hash ^= k + } + + // Finalization. + hash ^= dataLen + hash ^= hash >> 16 + hash *= 0x85ebca6b + hash ^= hash >> 13 + hash *= 0xc2b2ae35 + hash ^= hash >> 16 + + return hash +} diff --git a/btcutil/bloom/murmurhash3_test.go b/btcutil/bloom/murmurhash3_test.go new file mode 100644 index 0000000000..9c102d5f8c --- /dev/null +++ b/btcutil/bloom/murmurhash3_test.go @@ -0,0 +1,45 @@ +// 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 bloom_test + +import ( + "testing" + + "github.com/btcsuite/btcd/btcutil/bloom" +) + +// TestMurmurHash3 ensure the MurmurHash3 function produces the correct hash +// when given various seeds and data. +func TestMurmurHash3(t *testing.T) { + var tests = []struct { + seed uint32 + data []byte + out uint32 + }{ + {0x00000000, []byte{}, 0x00000000}, + {0xfba4c795, []byte{}, 0x6a396f08}, + {0xffffffff, []byte{}, 0x81f16f39}, + {0x00000000, []byte{0x00}, 0x514e28b7}, + {0xfba4c795, []byte{0x00}, 0xea3f0b17}, + {0x00000000, []byte{0xff}, 0xfd6cf10d}, + {0x00000000, []byte{0x00, 0x11}, 0x16c6b7ab}, + {0x00000000, []byte{0x00, 0x11, 0x22}, 0x8eb51c3d}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33}, 0xb4471bf8}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33, 0x44}, 0xe2301fa8}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, 0xfc2e4a15}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, 0xb074502c}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}, 0x8034d2a0}, + {0x00000000, []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, 0xb4698def}, + } + + for i, test := range tests { + result := bloom.MurmurHash3(test.seed, test.data) + if result != test.out { + t.Errorf("MurmurHash3 test #%d failed: got %v want %v\n", + i, result, test.out) + continue + } + } +} diff --git a/btcutil/bloom/test_coverage.txt b/btcutil/bloom/test_coverage.txt new file mode 100644 index 0000000000..e503f26feb --- /dev/null +++ b/btcutil/bloom/test_coverage.txt @@ -0,0 +1,28 @@ + +github.com/conformal/btcutil/bloom/murmurhash3.go MurmurHash3 100.00% (31/31) +github.com/conformal/btcutil/bloom/merkleblock.go NewMerkleBlock 100.00% (19/19) +github.com/conformal/btcutil/bloom/merkleblock.go merkleBlock.traverseAndBuild 100.00% (10/10) +github.com/conformal/btcutil/bloom/merkleblock.go merkleBlock.calcHash 100.00% (8/8) +github.com/conformal/btcutil/bloom/filter.go Filter.maybeAddOutpoint 100.00% (7/7) +github.com/conformal/btcutil/bloom/filter.go Filter.addOutPoint 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.IsLoaded 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.MsgFilterLoad 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.matchesOutPoint 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.MatchesOutPoint 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.MatchTxAndUpdate 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.Matches 100.00% (4/4) +github.com/conformal/btcutil/bloom/filter.go Filter.Add 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go Filter.Reload 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go Filter.Unload 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go Filter.AddShaHash 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go Filter.AddOutPoint 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go minUint32 100.00% (3/3) +github.com/conformal/btcutil/bloom/filter.go Filter.hash 100.00% (2/2) +github.com/conformal/btcutil/bloom/merkleblock.go merkleBlock.calcTreeWidth 100.00% (1/1) +github.com/conformal/btcutil/bloom/filter.go LoadFilter 100.00% (1/1) +github.com/conformal/btcutil/bloom/filter.go Filter.matchTxAndUpdate 91.30% (21/23) +github.com/conformal/btcutil/bloom/filter.go Filter.matches 85.71% (6/7) +github.com/conformal/btcutil/bloom/filter.go NewFilter 81.82% (9/11) +github.com/conformal/btcutil/bloom/filter.go Filter.add 80.00% (4/5) +github.com/conformal/btcutil/bloom ---------------------------- 96.49% (165/171) + diff --git a/btcutil/certgen.go b/btcutil/certgen.go new file mode 100644 index 0000000000..52e2d9cc95 --- /dev/null +++ b/btcutil/certgen.go @@ -0,0 +1,144 @@ +// 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 btcutil + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + _ "crypto/sha512" // Needed for RegisterHash in init + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "os" + "time" +) + +// NewTLSCertPair returns a new PEM-encoded x.509 certificate pair +// based on a 521-bit ECDSA private key. The machine's local interface +// addresses and all variants of IPv4 and IPv6 localhost are included as +// valid IP addresses. +func NewTLSCertPair(organization string, validUntil time.Time, extraHosts []string) (cert, key []byte, err error) { + now := time.Now() + if validUntil.Before(now) { + return nil, nil, errors.New("validUntil would create an already-expired certificate") + } + + priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return nil, nil, err + } + + // end of ASN.1 time + endOfTime := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC) + if validUntil.After(endOfTime) { + validUntil = endOfTime + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate serial number: %s", err) + } + + host, err := os.Hostname() + if err != nil { + return nil, nil, err + } + + ipAddresses := []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + dnsNames := []string{host} + if host != "localhost" { + dnsNames = append(dnsNames, "localhost") + } + + addIP := func(ipAddr net.IP) { + for _, ip := range ipAddresses { + if ip.Equal(ipAddr) { + return + } + } + ipAddresses = append(ipAddresses, ipAddr) + } + addHost := func(host string) { + for _, dnsName := range dnsNames { + if host == dnsName { + return + } + } + dnsNames = append(dnsNames, host) + } + + addrs, err := interfaceAddrs() + if err != nil { + return nil, nil, err + } + for _, a := range addrs { + ipAddr, _, err := net.ParseCIDR(a.String()) + if err == nil { + addIP(ipAddr) + } + } + + for _, hostStr := range extraHosts { + host, _, err := net.SplitHostPort(hostStr) + if err != nil { + host = hostStr + } + if ip := net.ParseIP(host); ip != nil { + addIP(ip) + } else { + addHost(host) + } + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{organization}, + CommonName: host, + }, + NotBefore: now.Add(-time.Hour * 24), + NotAfter: validUntil, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | + x509.KeyUsageCertSign, + IsCA: true, // so can sign self. + BasicConstraintsValid: true, + + DNSNames: dnsNames, + IPAddresses: ipAddresses, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, + &template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, fmt.Errorf("failed to create certificate: %v", err) + } + + certBuf := &bytes.Buffer{} + err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode certificate: %v", err) + } + + keybytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal private key: %v", err) + } + + keyBuf := &bytes.Buffer{} + err = pem.Encode(keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes}) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode private key: %v", err) + } + + return certBuf.Bytes(), keyBuf.Bytes(), nil +} diff --git a/btcutil/certgen_test.go b/btcutil/certgen_test.go new file mode 100644 index 0000000000..81aeea5d6a --- /dev/null +++ b/btcutil/certgen_test.go @@ -0,0 +1,123 @@ +// 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 btcutil_test + +import ( + "crypto/x509" + "encoding/pem" + "net" + "testing" + "time" + + "github.com/btcsuite/btcd/btcutil" + //"github.com/davecgh/go-spew/spew" +) + +// TestNewTLSCertPair ensures the NewTLSCertPair function works as expected. +func TestNewTLSCertPair(t *testing.T) { + // Certs don't support sub-second precision, so truncate it now to + // ensure the checks later don't fail due to nanosecond precision + // differences. + validUntil := time.Unix(time.Now().Add(10*365*24*time.Hour).Unix(), 0) + org := "test autogenerated cert" + extraHosts := []string{"testtlscert.bogus", "localhost", "127.0.0.1"} + cert, key, err := btcutil.NewTLSCertPair(org, validUntil, extraHosts) + if err != nil { + t.Fatalf("failed with unexpected error: %v", err) + } + + // Ensure the PEM-encoded cert that is returned can be decoded. + pemCert, _ := pem.Decode(cert) + if pemCert == nil { + t.Fatalf("pem.Decode was unable to decode the certificate") + } + + // Ensure the PEM-encoded key that is returned can be decoded. + pemKey, _ := pem.Decode(key) + if pemCert == nil { + t.Fatalf("pem.Decode was unable to decode the key") + } + + // Ensure the DER-encoded key bytes can be successfully parsed. + _, err = x509.ParseECPrivateKey(pemKey.Bytes) + if err != nil { + t.Fatalf("failed with unexpected error: %v", err) + } + + // Ensure the DER-encoded cert bytes can be successfully into an X.509 + // certificate. + x509Cert, err := x509.ParseCertificate(pemCert.Bytes) + if err != nil { + t.Fatalf("failed with unexpected error: %v", err) + } + + // Ensure the specified organization is correct. + x509Orgs := x509Cert.Subject.Organization + if len(x509Orgs) == 0 || x509Orgs[0] != org { + x509Org := "" + if len(x509Orgs) > 0 { + x509Org = x509Orgs[0] + } + t.Fatalf("generated cert organization field mismatch, got "+ + "'%v', want '%v'", x509Org, org) + } + + // Ensure the specified valid until value is correct. + if !x509Cert.NotAfter.Equal(validUntil) { + t.Fatalf("generated cert valid until field mismatch, got %v, "+ + "want %v", x509Cert.NotAfter, validUntil) + } + + // Ensure the specified extra hosts are present. + for _, host := range extraHosts { + if err := x509Cert.VerifyHostname(host); err != nil { + t.Fatalf("failed to verify extra host '%s'", host) + } + } + + // Ensure that the Common Name is also the first SAN DNS name. + cn := x509Cert.Subject.CommonName + san0 := x509Cert.DNSNames[0] + if cn != san0 { + t.Errorf("common name %s does not match first SAN %s", cn, san0) + } + + // Ensure there are no duplicate hosts or IPs. + hostCounts := make(map[string]int) + for _, host := range x509Cert.DNSNames { + hostCounts[host]++ + } + ipCounts := make(map[string]int) + for _, ip := range x509Cert.IPAddresses { + ipCounts[string(ip)]++ + } + for host, count := range hostCounts { + if count != 1 { + t.Errorf("host %s appears %d times in certificate", host, count) + } + } + for ipStr, count := range ipCounts { + if count != 1 { + t.Errorf("ip %s appears %d times in certificate", net.IP(ipStr), count) + } + } + + // Ensure the cert can be use for the intended purposes. + if !x509Cert.IsCA { + t.Fatal("generated cert is not a certificate authority") + } + if x509Cert.KeyUsage&x509.KeyUsageKeyEncipherment == 0 { + t.Fatal("generated cert can't be used for key encipherment") + } + if x509Cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 { + t.Fatal("generated cert can't be used for digital signatures") + } + if x509Cert.KeyUsage&x509.KeyUsageCertSign == 0 { + t.Fatal("generated cert can't be used for signing other certs") + } + if !x509Cert.BasicConstraintsValid { + t.Fatal("generated cert does not have valid basic constraints") + } +} diff --git a/btcutil/coinset/README.md b/btcutil/coinset/README.md new file mode 100644 index 0000000000..4fc239ad5e --- /dev/null +++ b/btcutil/coinset/README.md @@ -0,0 +1,71 @@ +coinset +======= + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcutil/coinset) + +Package coinset provides bitcoin-specific convenience functions for selecting +from and managing sets of unspent transaction outpoints (UTXOs). + +A comprehensive suite of tests is provided to ensure proper functionality. See +`test_coverage.txt` for the gocov coverage report. Alternatively, if you are +running a POSIX OS, you can run the `cov_report.sh` script for a real-time +report. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/coinset +``` + +## Usage + +Each unspent transaction outpoint is represented by the Coin interface. An +example of a concrete type that implements Coin is coinset.SimpleCoin. + +The typical use case for this library is for creating raw bitcoin transactions +given a set of Coins that may be spent by the user, for example as below: + +```Go +var unspentCoins = []coinset.Coin{ ... } +``` + +When the user needs to spend a certain amount, they will need to select a +subset of these coins which contain at least that value. CoinSelector is +an interface that represents types that implement coin selection algos, +subject to various criteria. There are a few examples of CoinSelector's: + +- MinIndexCoinSelector + +- MinNumberCoinSelector + +- MaxValueAgeCoinSelector + +- MinPriorityCoinSelector + +For example, if the user wishes to maximize the probability that their +transaction is mined quickly, they could use the MaxValueAgeCoinSelector to +select high priority coins, then also attach a relatively high fee. + +```Go +selector := &coinset.MaxValueAgeCoinSelector{ + MaxInputs: 10, + MinAmountChange: 10000, +} +selectedCoins, err := selector.CoinSelect(targetAmount + bigFee, unspentCoins) +if err != nil { + return err +} +msgTx := coinset.NewMsgTxWithInputCoins(selectedCoins) +... + +``` + +The user can then create the msgTx.TxOut's as required, then sign the +transaction and transmit it to the network. + +## License + +Package coinset is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/coinset/coins.go b/btcutil/coinset/coins.go new file mode 100644 index 0000000000..9d813418b6 --- /dev/null +++ b/btcutil/coinset/coins.go @@ -0,0 +1,396 @@ +// Copyright (c) 2014-2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package coinset + +import ( + "container/list" + "errors" + "sort" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" +) + +// Coin represents a spendable transaction outpoint +type Coin interface { + Hash() *chainhash.Hash + Index() uint32 + Value() btcutil.Amount + PkScript() []byte + NumConfs() int64 + ValueAge() int64 +} + +// Coins represents a set of Coins +type Coins interface { + Coins() []Coin +} + +// CoinSet is a utility struct for the modifications of a set of +// Coins that implements the Coins interface. To create a CoinSet, +// you must call NewCoinSet with nil for an empty set or a slice of +// coins as the initial contents. +// +// It is important to note that the all the Coins being added or removed +// from a CoinSet must have a constant ValueAge() during the use of +// the CoinSet, otherwise the cached values will be incorrect. +type CoinSet struct { + coinList *list.List + totalValue btcutil.Amount + totalValueAge int64 +} + +// Ensure that CoinSet is a Coins +var _ Coins = NewCoinSet(nil) + +// NewCoinSet creates a CoinSet containing the coins provided. +// To create an empty CoinSet, you may pass null as the coins input parameter. +func NewCoinSet(coins []Coin) *CoinSet { + newCoinSet := &CoinSet{ + coinList: list.New(), + totalValue: 0, + totalValueAge: 0, + } + for _, coin := range coins { + newCoinSet.PushCoin(coin) + } + return newCoinSet +} + +// Coins returns a new slice of the coins contained in the set. +func (cs *CoinSet) Coins() []Coin { + coins := make([]Coin, cs.coinList.Len()) + for i, e := 0, cs.coinList.Front(); e != nil; i, e = i+1, e.Next() { + coins[i] = e.Value.(Coin) + } + return coins +} + +// TotalValue returns the total value of the coins in the set. +func (cs *CoinSet) TotalValue() (value btcutil.Amount) { + return cs.totalValue +} + +// TotalValueAge returns the total value * number of confirmations +// of the coins in the set. +func (cs *CoinSet) TotalValueAge() (valueAge int64) { + return cs.totalValueAge +} + +// Num returns the number of coins in the set +func (cs *CoinSet) Num() int { + return cs.coinList.Len() +} + +// PushCoin adds a coin to the end of the list and updates +// the cached value amounts. +func (cs *CoinSet) PushCoin(c Coin) { + cs.coinList.PushBack(c) + cs.totalValue += c.Value() + cs.totalValueAge += c.ValueAge() +} + +// PopCoin removes the last coin on the list and returns it. +func (cs *CoinSet) PopCoin() Coin { + back := cs.coinList.Back() + if back == nil { + return nil + } + return cs.removeElement(back) +} + +// ShiftCoin removes the first coin on the list and returns it. +func (cs *CoinSet) ShiftCoin() Coin { + front := cs.coinList.Front() + if front == nil { + return nil + } + return cs.removeElement(front) +} + +// removeElement updates the cached value amounts in the CoinSet, +// removes the element from the list, then returns the Coin that +// was removed to the caller. +func (cs *CoinSet) removeElement(e *list.Element) Coin { + c := e.Value.(Coin) + cs.coinList.Remove(e) + cs.totalValue -= c.Value() + cs.totalValueAge -= c.ValueAge() + return c +} + +// NewMsgTxWithInputCoins takes the coins in the CoinSet and makes them +// the inputs to a new wire.MsgTx which is returned. +func NewMsgTxWithInputCoins(txVersion int32, inputCoins Coins) *wire.MsgTx { + msgTx := wire.NewMsgTx(txVersion) + coins := inputCoins.Coins() + msgTx.TxIn = make([]*wire.TxIn, len(coins)) + for i, coin := range coins { + msgTx.TxIn[i] = &wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: *coin.Hash(), + Index: coin.Index(), + }, + SignatureScript: nil, + Sequence: wire.MaxTxInSequenceNum, + } + } + return msgTx +} + +var ( + // ErrCoinsNoSelectionAvailable is returned when a CoinSelector believes there is no + // possible combination of coins which can meet the requirements provided to the selector. + ErrCoinsNoSelectionAvailable = errors.New("no coin selection possible") +) + +// satisfiesTargetValue checks that the totalValue is either exactly the targetValue +// or is greater than the targetValue by at least the minChange amount. +func satisfiesTargetValue(targetValue, minChange, totalValue btcutil.Amount) bool { + return (totalValue == targetValue || totalValue >= targetValue+minChange) +} + +// CoinSelector is an interface that wraps the CoinSelect method. +// +// CoinSelect will attempt to select a subset of the coins which has at +// least the targetValue amount. CoinSelect is not guaranteed to return a +// selection of coins even if the total value of coins given is greater +// than the target value. +// +// The exact choice of coins in the subset will be implementation specific. +// +// It is important to note that the Coins being used as inputs need to have +// a constant ValueAge() during the execution of CoinSelect. +type CoinSelector interface { + CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) +} + +// MinIndexCoinSelector is a CoinSelector that attempts to construct a +// selection of coins whose total value is at least targetValue and prefers +// any number of lower indexes (as in the ordered array) over higher ones. +type MinIndexCoinSelector struct { + MaxInputs int + MinChangeAmount btcutil.Amount +} + +// CoinSelect will attempt to select coins using the algorithm described +// in the MinIndexCoinSelector struct. +func (s MinIndexCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) { + cs := NewCoinSet(nil) + for n := 0; n < len(coins) && n < s.MaxInputs; n++ { + cs.PushCoin(coins[n]) + if satisfiesTargetValue(targetValue, s.MinChangeAmount, cs.TotalValue()) { + return cs, nil + } + } + return nil, ErrCoinsNoSelectionAvailable +} + +// MinNumberCoinSelector is a CoinSelector that attempts to construct +// a selection of coins whose total value is at least targetValue +// that uses as few of the inputs as possible. +type MinNumberCoinSelector struct { + MaxInputs int + MinChangeAmount btcutil.Amount +} + +// CoinSelect will attempt to select coins using the algorithm described +// in the MinNumberCoinSelector struct. +func (s MinNumberCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) { + sortedCoins := make([]Coin, 0, len(coins)) + sortedCoins = append(sortedCoins, coins...) + sort.Sort(sort.Reverse(byAmount(sortedCoins))) + + return MinIndexCoinSelector(s).CoinSelect(targetValue, sortedCoins) +} + +// MaxValueAgeCoinSelector is a CoinSelector that attempts to construct +// a selection of coins whose total value is at least targetValue +// that has as much input value-age as possible. +// +// This would be useful in the case where you want to maximize +// likelihood of the inclusion of your transaction in the next mined +// block. +type MaxValueAgeCoinSelector struct { + MaxInputs int + MinChangeAmount btcutil.Amount +} + +// CoinSelect will attempt to select coins using the algorithm described +// in the MaxValueAgeCoinSelector struct. +func (s MaxValueAgeCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) { + sortedCoins := make([]Coin, 0, len(coins)) + sortedCoins = append(sortedCoins, coins...) + sort.Sort(sort.Reverse(byValueAge(sortedCoins))) + + return MinIndexCoinSelector(s).CoinSelect(targetValue, sortedCoins) +} + +// MinPriorityCoinSelector is a CoinSelector that attempts to construct +// a selection of coins whose total value is at least targetValue and +// whose average value-age per input is greater than MinAvgValueAgePerInput. +// If there is change, it must exceed MinChangeAmount to be a valid selection. +// +// When possible, MinPriorityCoinSelector will attempt to reduce the average +// input priority over the threshold, but no guarantees will be made as to +// minimality of the selection. The selection below is almost certainly +// suboptimal. +// +type MinPriorityCoinSelector struct { + MaxInputs int + MinChangeAmount btcutil.Amount + MinAvgValueAgePerInput int64 +} + +// CoinSelect will attempt to select coins using the algorithm described +// in the MinPriorityCoinSelector struct. +func (s MinPriorityCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) { + possibleCoins := make([]Coin, 0, len(coins)) + possibleCoins = append(possibleCoins, coins...) + + sort.Sort(byValueAge(possibleCoins)) + + // find the first coin with sufficient valueAge + cutoffIndex := -1 + for i := 0; i < len(possibleCoins); i++ { + if possibleCoins[i].ValueAge() >= s.MinAvgValueAgePerInput { + cutoffIndex = i + break + } + } + if cutoffIndex < 0 { + return nil, ErrCoinsNoSelectionAvailable + } + + // create sets of input coins that will obey minimum average valueAge + for i := cutoffIndex; i < len(possibleCoins); i++ { + possibleHighCoins := possibleCoins[cutoffIndex : i+1] + + // choose a set of high-enough valueAge coins + highSelect, err := (&MinNumberCoinSelector{ + MaxInputs: s.MaxInputs, + MinChangeAmount: s.MinChangeAmount, + }).CoinSelect(targetValue, possibleHighCoins) + + if err != nil { + // attempt to add available low priority to make a solution + + for numLow := 1; numLow <= cutoffIndex && numLow+(i-cutoffIndex) <= s.MaxInputs; numLow++ { + allHigh := NewCoinSet(possibleCoins[cutoffIndex : i+1]) + newTargetValue := targetValue - allHigh.TotalValue() + newMaxInputs := allHigh.Num() + numLow + if newMaxInputs > numLow { + newMaxInputs = numLow + } + newMinAvgValueAge := ((s.MinAvgValueAgePerInput * int64(allHigh.Num()+numLow)) - allHigh.TotalValueAge()) / int64(numLow) + + // find the minimum priority that can be added to set + lowSelect, err := (&MinPriorityCoinSelector{ + MaxInputs: newMaxInputs, + MinChangeAmount: s.MinChangeAmount, + MinAvgValueAgePerInput: newMinAvgValueAge, + }).CoinSelect(newTargetValue, possibleCoins[0:cutoffIndex]) + + if err != nil { + continue + } + + for _, coin := range lowSelect.Coins() { + allHigh.PushCoin(coin) + } + + return allHigh, nil + } + // oh well, couldn't fix, try to add more high priority to the set. + } else { + extendedCoins := NewCoinSet(highSelect.Coins()) + + // attempt to lower priority towards target with lowest ones first + for n := 0; n < cutoffIndex; n++ { + if extendedCoins.Num() >= s.MaxInputs { + break + } + if possibleCoins[n].ValueAge() == 0 { + continue + } + + extendedCoins.PushCoin(possibleCoins[n]) + if extendedCoins.TotalValueAge()/int64(extendedCoins.Num()) < s.MinAvgValueAgePerInput { + extendedCoins.PopCoin() + continue + } + } + return extendedCoins, nil + } + } + + return nil, ErrCoinsNoSelectionAvailable +} + +type byValueAge []Coin + +func (a byValueAge) Len() int { return len(a) } +func (a byValueAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byValueAge) Less(i, j int) bool { return a[i].ValueAge() < a[j].ValueAge() } + +type byAmount []Coin + +func (a byAmount) Len() int { return len(a) } +func (a byAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byAmount) Less(i, j int) bool { return a[i].Value() < a[j].Value() } + +// SimpleCoin defines a concrete instance of Coin that is backed by a +// btcutil.Tx, a specific outpoint index, and the number of confirmations +// that transaction has had. +type SimpleCoin struct { + Tx *btcutil.Tx + TxIndex uint32 + TxNumConfs int64 +} + +// Ensure that SimpleCoin is a Coin +var _ Coin = &SimpleCoin{} + +// Hash returns the hash value of the transaction on which the Coin is an output +func (c *SimpleCoin) Hash() *chainhash.Hash { + return c.Tx.Hash() +} + +// Index returns the index of the output on the transaction which the Coin represents +func (c *SimpleCoin) Index() uint32 { + return c.TxIndex +} + +// txOut returns the TxOut of the transaction the Coin represents +func (c *SimpleCoin) txOut() *wire.TxOut { + return c.Tx.MsgTx().TxOut[c.TxIndex] +} + +// Value returns the value of the Coin +func (c *SimpleCoin) Value() btcutil.Amount { + return btcutil.Amount(c.txOut().Value) +} + +// PkScript returns the outpoint script of the Coin. +// +// This can be used to determine what type of script the Coin uses +// and extract standard addresses if possible using +// txscript.ExtractPkScriptAddrs for example. +func (c *SimpleCoin) PkScript() []byte { + return c.txOut().PkScript +} + +// NumConfs returns the number of confirmations that the transaction the Coin references +// has had. +func (c *SimpleCoin) NumConfs() int64 { + return c.TxNumConfs +} + +// ValueAge returns the product of the value and the number of confirmations. This is +// used as an input to calculate the priority of the transaction. +func (c *SimpleCoin) ValueAge() int64 { + return c.TxNumConfs * int64(c.Value()) +} diff --git a/btcutil/coinset/coins_test.go b/btcutil/coinset/coins_test.go new file mode 100644 index 0000000000..874dc6c6d6 --- /dev/null +++ b/btcutil/coinset/coins_test.go @@ -0,0 +1,260 @@ +// Copyright (c) 2014-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package coinset_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/coinset" +) + +type TestCoin struct { + TxHash *chainhash.Hash + TxIndex uint32 + TxValue btcutil.Amount + TxNumConfs int64 +} + +func (c *TestCoin) Hash() *chainhash.Hash { return c.TxHash } +func (c *TestCoin) Index() uint32 { return c.TxIndex } +func (c *TestCoin) Value() btcutil.Amount { return c.TxValue } +func (c *TestCoin) PkScript() []byte { return nil } +func (c *TestCoin) NumConfs() int64 { return c.TxNumConfs } +func (c *TestCoin) ValueAge() int64 { return int64(c.TxValue) * c.TxNumConfs } + +func NewCoin(index int64, value btcutil.Amount, numConfs int64) coinset.Coin { + h := sha256.New() + _, _ = h.Write([]byte(fmt.Sprintf("%d", index))) + hash, _ := chainhash.NewHash(h.Sum(nil)) + c := &TestCoin{ + TxHash: hash, + TxIndex: 0, + TxValue: value, + TxNumConfs: numConfs, + } + return coinset.Coin(c) +} + +type coinSelectTest struct { + selector coinset.CoinSelector + inputCoins []coinset.Coin + targetValue btcutil.Amount + expectedCoins []coinset.Coin + expectedError error +} + +func testCoinSelector(tests []coinSelectTest, t *testing.T) { + for testIndex, test := range tests { + cs, err := test.selector.CoinSelect(test.targetValue, test.inputCoins) + if err != test.expectedError { + t.Errorf("[%d] expected a different error: got=%v, expected=%v", testIndex, err, test.expectedError) + continue + } + if test.expectedCoins != nil { + if cs == nil { + t.Errorf("[%d] expected non-nil coinset", testIndex) + continue + } + coins := cs.Coins() + if len(coins) != len(test.expectedCoins) { + t.Errorf("[%d] expected different number of coins: got=%d, expected=%d", testIndex, len(coins), len(test.expectedCoins)) + continue + } + for n := 0; n < len(test.expectedCoins); n++ { + if coins[n] != test.expectedCoins[n] { + t.Errorf("[%d] expected different coins at coin index %d: got=%#v, expected=%#v", testIndex, n, coins[n], test.expectedCoins[n]) + continue + } + } + coinSet := coinset.NewCoinSet(coins) + if coinSet.TotalValue() < test.targetValue { + t.Errorf("[%d] targetValue not satistifed", testIndex) + continue + } + } + } +} + +var coins = []coinset.Coin{ + NewCoin(1, 100000000, 1), + NewCoin(2, 10000000, 20), + NewCoin(3, 50000000, 0), + NewCoin(4, 25000000, 6), +} + +func TestCoinSet(t *testing.T) { + cs := coinset.NewCoinSet(nil) + if cs.PopCoin() != nil { + t.Error("Expected popCoin of empty to be nil") + } + if cs.ShiftCoin() != nil { + t.Error("Expected shiftCoin of empty to be nil") + } + + cs.PushCoin(coins[0]) + cs.PushCoin(coins[1]) + cs.PushCoin(coins[2]) + if cs.PopCoin() != coins[2] { + t.Error("Expected third coin") + } + if cs.ShiftCoin() != coins[0] { + t.Error("Expected first coin") + } + + mtx := coinset.NewMsgTxWithInputCoins(wire.TxVersion, cs) + if len(mtx.TxIn) != 1 { + t.Errorf("Expected only 1 TxIn, got %d", len(mtx.TxIn)) + } + op := mtx.TxIn[0].PreviousOutPoint + if !op.Hash.IsEqual(coins[1].Hash()) || op.Index != coins[1].Index() { + t.Errorf("Expected the second coin to be added as input to mtx") + } +} + +var minIndexSelectors = []coinset.MinIndexCoinSelector{ + {MaxInputs: 10, MinChangeAmount: 10000}, + {MaxInputs: 2, MinChangeAmount: 10000}, +} + +var minIndexTests = []coinSelectTest{ + {minIndexSelectors[0], coins, coins[0].Value() - minIndexSelectors[0].MinChangeAmount, []coinset.Coin{coins[0]}, nil}, + {minIndexSelectors[0], coins, coins[0].Value() - minIndexSelectors[0].MinChangeAmount + 1, []coinset.Coin{coins[0], coins[1]}, nil}, + {minIndexSelectors[0], coins, 100000000, []coinset.Coin{coins[0]}, nil}, + {minIndexSelectors[0], coins, 110000000, []coinset.Coin{coins[0], coins[1]}, nil}, + {minIndexSelectors[0], coins, 140000000, []coinset.Coin{coins[0], coins[1], coins[2]}, nil}, + {minIndexSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minIndexSelectors[1], coins, 10000000, []coinset.Coin{coins[0]}, nil}, + {minIndexSelectors[1], coins, 110000000, []coinset.Coin{coins[0], coins[1]}, nil}, + {minIndexSelectors[1], coins, 140000000, nil, coinset.ErrCoinsNoSelectionAvailable}, +} + +func TestMinIndexSelector(t *testing.T) { + testCoinSelector(minIndexTests, t) +} + +var minNumberSelectors = []coinset.MinNumberCoinSelector{ + {MaxInputs: 10, MinChangeAmount: 10000}, + {MaxInputs: 2, MinChangeAmount: 10000}, +} + +var minNumberTests = []coinSelectTest{ + {minNumberSelectors[0], coins, coins[0].Value() - minNumberSelectors[0].MinChangeAmount, []coinset.Coin{coins[0]}, nil}, + {minNumberSelectors[0], coins, coins[0].Value() - minNumberSelectors[0].MinChangeAmount + 1, []coinset.Coin{coins[0], coins[2]}, nil}, + {minNumberSelectors[0], coins, 100000000, []coinset.Coin{coins[0]}, nil}, + {minNumberSelectors[0], coins, 110000000, []coinset.Coin{coins[0], coins[2]}, nil}, + {minNumberSelectors[0], coins, 160000000, []coinset.Coin{coins[0], coins[2], coins[3]}, nil}, + {minNumberSelectors[0], coins, 184990000, []coinset.Coin{coins[0], coins[2], coins[3], coins[1]}, nil}, + {minNumberSelectors[0], coins, 184990001, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minNumberSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minNumberSelectors[1], coins, 10000000, []coinset.Coin{coins[0]}, nil}, + {minNumberSelectors[1], coins, 110000000, []coinset.Coin{coins[0], coins[2]}, nil}, + {minNumberSelectors[1], coins, 140000000, []coinset.Coin{coins[0], coins[2]}, nil}, +} + +func TestMinNumberSelector(t *testing.T) { + testCoinSelector(minNumberTests, t) +} + +var maxValueAgeSelectors = []coinset.MaxValueAgeCoinSelector{ + {MaxInputs: 10, MinChangeAmount: 10000}, + {MaxInputs: 2, MinChangeAmount: 10000}, +} + +var maxValueAgeTests = []coinSelectTest{ + {maxValueAgeSelectors[0], coins, 100000, []coinset.Coin{coins[1]}, nil}, + {maxValueAgeSelectors[0], coins, 10000000, []coinset.Coin{coins[1]}, nil}, + {maxValueAgeSelectors[0], coins, 10000001, []coinset.Coin{coins[1], coins[3]}, nil}, + {maxValueAgeSelectors[0], coins, 35000000, []coinset.Coin{coins[1], coins[3]}, nil}, + {maxValueAgeSelectors[0], coins, 135000000, []coinset.Coin{coins[1], coins[3], coins[0]}, nil}, + {maxValueAgeSelectors[0], coins, 185000000, []coinset.Coin{coins[1], coins[3], coins[0], coins[2]}, nil}, + {maxValueAgeSelectors[0], coins, 200000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {maxValueAgeSelectors[1], coins, 40000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {maxValueAgeSelectors[1], coins, 35000000, []coinset.Coin{coins[1], coins[3]}, nil}, + {maxValueAgeSelectors[1], coins, 34990001, nil, coinset.ErrCoinsNoSelectionAvailable}, +} + +func TestMaxValueAgeSelector(t *testing.T) { + testCoinSelector(maxValueAgeTests, t) +} + +var minPrioritySelectors = []coinset.MinPriorityCoinSelector{ + {MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 100000000}, + {MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 200000000}, + {MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 150000000}, + {MaxInputs: 03, MinChangeAmount: 10000, MinAvgValueAgePerInput: 150000000}, + {MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 1000000000}, + {MaxInputs: 10, MinChangeAmount: 10000, MinAvgValueAgePerInput: 175000000}, + {MaxInputs: 02, MinChangeAmount: 10000, MinAvgValueAgePerInput: 125000000}, +} + +var connectedCoins = []coinset.Coin{coins[0], coins[1], coins[3]} + +var minPriorityTests = []coinSelectTest{ + {minPrioritySelectors[0], connectedCoins, 100000000, []coinset.Coin{coins[0]}, nil}, + {minPrioritySelectors[0], connectedCoins, 125000000, []coinset.Coin{coins[0], coins[3]}, nil}, + {minPrioritySelectors[0], connectedCoins, 135000000, []coinset.Coin{coins[0], coins[3], coins[1]}, nil}, + {minPrioritySelectors[0], connectedCoins, 140000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minPrioritySelectors[1], connectedCoins, 100000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minPrioritySelectors[1], connectedCoins, 10000000, []coinset.Coin{coins[1]}, nil}, + {minPrioritySelectors[1], connectedCoins, 100000000, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minPrioritySelectors[2], connectedCoins, 11000000, []coinset.Coin{coins[3]}, nil}, + {minPrioritySelectors[2], connectedCoins, 25000001, []coinset.Coin{coins[3], coins[1]}, nil}, + {minPrioritySelectors[3], connectedCoins, 25000001, []coinset.Coin{coins[3], coins[1], coins[0]}, nil}, + {minPrioritySelectors[3], connectedCoins, 100000000, []coinset.Coin{coins[3], coins[1], coins[0]}, nil}, + {minPrioritySelectors[3], []coinset.Coin{coins[1], coins[2]}, 10000000, []coinset.Coin{coins[1]}, nil}, + {minPrioritySelectors[4], connectedCoins, 1, nil, coinset.ErrCoinsNoSelectionAvailable}, + {minPrioritySelectors[5], connectedCoins, 20000000, []coinset.Coin{coins[1], coins[3]}, nil}, + {minPrioritySelectors[6], connectedCoins, 25000000, []coinset.Coin{coins[3], coins[0]}, nil}, +} + +func TestMinPrioritySelector(t *testing.T) { + testCoinSelector(minPriorityTests, t) +} + +var ( + // should be two outpoints, with 1st one having 0.035BTC value. + testSimpleCoinNumConfs = int64(1) + testSimpleCoinTxHash = "9b5965c86de51d5dc824e179a05cf232db78c80ae86ca9d7cb2a655b5e19c1e2" + testSimpleCoinTxHex = "0100000001a214a110f79e4abe073865ea5b3745c6e82c913bad44be70652804a5e4003b0a010000008c493046022100edd18a69664efa57264be207100c203e6cade1888cbb88a0ad748548256bb2f0022100f1027dc2e6c7f248d78af1dd90027b5b7d8ec563bb62aa85d4e74d6376f3868c0141048f3757b65ed301abd1b0e8942d1ab5b50594d3314cff0299f300c696376a0a9bf72e74710a8af7a5372d4af4bb519e2701a094ef48c8e48e3b65b28502452dceffffffff02e0673500000000001976a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ace86ef102000000001976a914ac3f995655e81b875b38b64351d6f896ddbfc68588ac00000000" + testSimpleCoinTxValue0 = btcutil.Amount(3500000) + testSimpleCoinTxValueAge0 = int64(testSimpleCoinTxValue0) * testSimpleCoinNumConfs + testSimpleCoinTxPkScript0Hex = "76a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ac" + testSimpleCoinTxPkScript0Bytes, _ = hex.DecodeString(testSimpleCoinTxPkScript0Hex) + testSimpleCoinTxBytes, _ = hex.DecodeString(testSimpleCoinTxHex) + testSimpleCoinTx, _ = btcutil.NewTxFromBytes(testSimpleCoinTxBytes) + testSimpleCoin = &coinset.SimpleCoin{ + Tx: testSimpleCoinTx, + TxIndex: 0, + TxNumConfs: testSimpleCoinNumConfs, + } +) + +func TestSimpleCoin(t *testing.T) { + if testSimpleCoin.Hash().String() != testSimpleCoinTxHash { + t.Error("Different value for tx hash than expected") + } + if testSimpleCoin.Index() != 0 { + t.Error("Different value for index of outpoint than expected") + } + if testSimpleCoin.Value() != testSimpleCoinTxValue0 { + t.Error("Different value of coin value than expected") + } + if !bytes.Equal(testSimpleCoin.PkScript(), testSimpleCoinTxPkScript0Bytes) { + t.Error("Different value of coin pkScript than expected") + } + if testSimpleCoin.NumConfs() != 1 { + t.Error("Differet value of num confs than expected") + } + if testSimpleCoin.ValueAge() != testSimpleCoinTxValueAge0 { + t.Error("Different value of coin value * age than expected") + } +} diff --git a/btcutil/coinset/cov_report.sh b/btcutil/coinset/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/btcutil/coinset/cov_report.sh @@ -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 diff --git a/btcutil/coinset/test_coverage.txt b/btcutil/coinset/test_coverage.txt new file mode 100644 index 0000000000..0e3e15d9f7 --- /dev/null +++ b/btcutil/coinset/test_coverage.txt @@ -0,0 +1,31 @@ + +github.com/conformal/btcutil/coinset/coins.go MinPriorityCoinSelector.CoinSelect 100.00% (39/39) +github.com/conformal/btcutil/coinset/coins.go NewMsgTxWithInputCoins 100.00% (6/6) +github.com/conformal/btcutil/coinset/coins.go MinIndexCoinSelector.CoinSelect 100.00% (6/6) +github.com/conformal/btcutil/coinset/coins.go CoinSet.removeElement 100.00% (5/5) +github.com/conformal/btcutil/coinset/coins.go NewCoinSet 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go CoinSet.Coins 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go CoinSet.PopCoin 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go CoinSet.ShiftCoin 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go MinNumberCoinSelector.CoinSelect 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go MaxValueAgeCoinSelector.CoinSelect 100.00% (4/4) +github.com/conformal/btcutil/coinset/coins.go CoinSet.PushCoin 100.00% (3/3) +github.com/conformal/btcutil/coinset/coins.go CoinSet.TotalValueAge 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.NumConfs 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.ValueAge 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go CoinSet.TotalValue 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byValueAge.Len 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byValueAge.Swap 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byValueAge.Less 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byAmount.Len 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byAmount.Swap 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go byAmount.Less 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Hash 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Index 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.txOut 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.Value 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go SimpleCoin.PkScript 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go CoinSet.Num 100.00% (1/1) +github.com/conformal/btcutil/coinset/coins.go satisfiesTargetValue 100.00% (1/1) +github.com/conformal/btcutil/coinset ---------------------------------- 100.00% (100/100) + diff --git a/btcutil/const.go b/btcutil/const.go new file mode 100644 index 0000000000..c73946056f --- /dev/null +++ b/btcutil/const.go @@ -0,0 +1,16 @@ +// 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 btcutil + +const ( + // SatoshiPerBitcent is the number of satoshi in one bitcoin cent. + SatoshiPerBitcent = 1e6 + + // SatoshiPerBitcoin is the number of satoshi in one bitcoin (1 BTC). + SatoshiPerBitcoin = 1e8 + + // MaxSatoshi is the maximum transaction amount allowed in satoshi. + MaxSatoshi = 21e6 * SatoshiPerBitcoin +) diff --git a/btcutil/cov_report.sh b/btcutil/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/btcutil/cov_report.sh @@ -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 diff --git a/btcutil/doc.go b/btcutil/doc.go new file mode 100644 index 0000000000..36cda1c782 --- /dev/null +++ b/btcutil/doc.go @@ -0,0 +1,46 @@ +// 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 btcutil provides bitcoin-specific convenience functions and types. + +Block Overview + +A Block defines a bitcoin block that provides easier and more efficient +manipulation of raw wire protocol blocks. It also memoizes hashes for the +block and its transactions on their first access so subsequent accesses don't +have to repeat the relatively expensive hashing operations. + +Tx Overview + +A Tx defines a bitcoin transaction that provides more efficient manipulation of +raw wire protocol transactions. It memoizes the hash for the transaction on its +first access so subsequent accesses don't have to repeat the relatively +expensive hashing operations. + +Address Overview + +The Address interface provides an abstraction for a Bitcoin address. While the +most common type is a pay-to-pubkey-hash, Bitcoin already supports others and +may well support more in the future. This package currently provides +implementations for the pay-to-pubkey, pay-to-pubkey-hash, and +pay-to-script-hash address types. + +To decode/encode an address: + + // NOTE: The default network is only used for address types which do not + // already contain that information. At this time, that is only + // pay-to-pubkey addresses. + addrString := "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962" + + "e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d57" + + "8a4c702b6bf11d5f" + defaultNet := &chaincfg.MainNetParams + addr, err := btcutil.DecodeAddress(addrString, defaultNet) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(addr.EncodeAddress()) +*/ +package btcutil diff --git a/btcutil/example_test.go b/btcutil/example_test.go new file mode 100644 index 0000000000..6b62fdd44f --- /dev/null +++ b/btcutil/example_test.go @@ -0,0 +1,76 @@ +package btcutil_test + +import ( + "fmt" + "math" + + "github.com/btcsuite/btcd/btcutil" +) + +func ExampleAmount() { + + a := btcutil.Amount(0) + fmt.Println("Zero Satoshi:", a) + + a = btcutil.Amount(1e8) + fmt.Println("100,000,000 Satoshis:", a) + + a = btcutil.Amount(1e5) + fmt.Println("100,000 Satoshis:", a) + // Output: + // Zero Satoshi: 0 BTC + // 100,000,000 Satoshis: 1 BTC + // 100,000 Satoshis: 0.001 BTC +} + +func ExampleNewAmount() { + amountOne, err := btcutil.NewAmount(1) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(amountOne) //Output 1 + + amountFraction, err := btcutil.NewAmount(0.01234567) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(amountFraction) //Output 2 + + amountZero, err := btcutil.NewAmount(0) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(amountZero) //Output 3 + + amountNaN, err := btcutil.NewAmount(math.NaN()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(amountNaN) //Output 4 + + // Output: 1 BTC + // 0.01234567 BTC + // 0 BTC + // invalid bitcoin amount +} + +func ExampleAmount_unitConversions() { + amount := btcutil.Amount(44433322211100) + + fmt.Println("Satoshi to kBTC:", amount.Format(btcutil.AmountKiloBTC)) + fmt.Println("Satoshi to BTC:", amount) + fmt.Println("Satoshi to MilliBTC:", amount.Format(btcutil.AmountMilliBTC)) + fmt.Println("Satoshi to MicroBTC:", amount.Format(btcutil.AmountMicroBTC)) + fmt.Println("Satoshi to Satoshi:", amount.Format(btcutil.AmountSatoshi)) + + // Output: + // Satoshi to kBTC: 444.333222111 kBTC + // Satoshi to BTC: 444333.222111 BTC + // Satoshi to MilliBTC: 444333222.111 mBTC + // Satoshi to MicroBTC: 444333222111 μBTC + // Satoshi to Satoshi: 44433322211100 Satoshi +} diff --git a/btcutil/gcs/README.md b/btcutil/gcs/README.md new file mode 100644 index 0000000000..ee0c120401 --- /dev/null +++ b/btcutil/gcs/README.md @@ -0,0 +1,24 @@ +gcs +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://godoc.org/github.com/btcsuite/btcd/btcutil/gcs?status.png)] +(http://godoc.org/github.com/btcsuite/btcd/btcutil/gcs) + +Package gcs provides an API for building and using a Golomb-coded set filter +similar to that described [here](http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters). + +A comprehensive suite of tests is provided to ensure proper functionality. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/gcs +``` + +## License + +Package gcs is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/gcs/builder/builder.go b/btcutil/gcs/builder/builder.go new file mode 100644 index 0000000000..6f15ec7a68 --- /dev/null +++ b/btcutil/gcs/builder/builder.go @@ -0,0 +1,371 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package builder + +import ( + "crypto/rand" + "fmt" + "math" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil/gcs" +) + +const ( + // DefaultP is the default collision probability (2^-19) + DefaultP = 19 + + // DefaultM is the default value used for the hash range. + DefaultM uint64 = 784931 +) + +// GCSBuilder is a utility class that makes building GCS filters convenient. +type GCSBuilder struct { + p uint8 + + m uint64 + + key [gcs.KeySize]byte + + // data is a set of entries represented as strings. This is done to + // deduplicate items as they are added. + data map[string]struct{} + err error +} + +// RandomKey is a utility function that returns a cryptographically random +// [gcs.KeySize]byte usable as a key for a GCS filter. +func RandomKey() ([gcs.KeySize]byte, error) { + var key [gcs.KeySize]byte + + // Read a byte slice from rand.Reader. + randKey := make([]byte, gcs.KeySize) + _, err := rand.Read(randKey) + + // This shouldn't happen unless the user is on a system that doesn't + // have a system CSPRNG. OK to panic in this case. + if err != nil { + return key, err + } + + // Copy the byte slice to a [gcs.KeySize]byte array and return it. + copy(key[:], randKey) + return key, nil +} + +// DeriveKey is a utility function that derives a key from a chainhash.Hash by +// truncating the bytes of the hash to the appopriate key size. +func DeriveKey(keyHash *chainhash.Hash) [gcs.KeySize]byte { + var key [gcs.KeySize]byte + copy(key[:], keyHash.CloneBytes()) + return key +} + +// Key retrieves the key with which the builder will build a filter. This is +// useful if the builder is created with a random initial key. +func (b *GCSBuilder) Key() ([gcs.KeySize]byte, error) { + // Do nothing if the builder's errored out. + if b.err != nil { + return [gcs.KeySize]byte{}, b.err + } + + return b.key, nil +} + +// SetKey sets the key with which the builder will build a filter to the passed +// [gcs.KeySize]byte. +func (b *GCSBuilder) SetKey(key [gcs.KeySize]byte) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + copy(b.key[:], key[:]) + return b +} + +// SetKeyFromHash sets the key with which the builder will build a filter to a +// key derived from the passed chainhash.Hash using DeriveKey(). +func (b *GCSBuilder) SetKeyFromHash(keyHash *chainhash.Hash) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + return b.SetKey(DeriveKey(keyHash)) +} + +// SetP sets the filter's probability after calling Builder(). +func (b *GCSBuilder) SetP(p uint8) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + // Basic sanity check. + if p > 32 { + b.err = gcs.ErrPTooBig + return b + } + + b.p = p + return b +} + +// SetM sets the filter's modulous value after calling Builder(). +func (b *GCSBuilder) SetM(m uint64) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + // Basic sanity check. + if m > uint64(math.MaxUint32) { + b.err = gcs.ErrPTooBig + return b + } + + b.m = m + return b +} + +// Preallocate sets the estimated filter size after calling Builder() to reduce +// the probability of memory reallocations. If the builder has already had data +// added to it, Preallocate has no effect. +func (b *GCSBuilder) Preallocate(n uint32) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + if b.data == nil { + b.data = make(map[string]struct{}, n) + } + + return b +} + +// AddEntry adds a []byte to the list of entries to be included in the GCS +// filter when it's built. +func (b *GCSBuilder) AddEntry(data []byte) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + b.data[string(data)] = struct{}{} + return b +} + +// AddEntries adds all the []byte entries in a [][]byte to the list of entries +// to be included in the GCS filter when it's built. +func (b *GCSBuilder) AddEntries(data [][]byte) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + for _, entry := range data { + b.AddEntry(entry) + } + return b +} + +// AddHash adds a chainhash.Hash to the list of entries to be included in the +// GCS filter when it's built. +func (b *GCSBuilder) AddHash(hash *chainhash.Hash) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + return b.AddEntry(hash.CloneBytes()) +} + +// AddWitness adds each item of the passed filter stack to the filter, and then +// adds each item as a script. +func (b *GCSBuilder) AddWitness(witness wire.TxWitness) *GCSBuilder { + // Do nothing if the builder's already errored out. + if b.err != nil { + return b + } + + return b.AddEntries(witness) +} + +// Build returns a function which builds a GCS filter with the given parameters +// and data. +func (b *GCSBuilder) Build() (*gcs.Filter, error) { + // Do nothing if the builder's already errored out. + if b.err != nil { + return nil, b.err + } + + // We'll ensure that all the parmaters we need to actually build the + // filter properly are set. + if b.p == 0 { + return nil, fmt.Errorf("p value is not set, cannot build") + } + if b.m == 0 { + return nil, fmt.Errorf("m value is not set, cannot build") + } + + dataSlice := make([][]byte, 0, len(b.data)) + for item := range b.data { + dataSlice = append(dataSlice, []byte(item)) + } + + return gcs.BuildGCSFilter(b.p, b.m, b.key, dataSlice) +} + +// WithKeyPNM creates a GCSBuilder with specified key and the passed +// probability, modulus and estimated filter size. +func WithKeyPNM(key [gcs.KeySize]byte, p uint8, n uint32, m uint64) *GCSBuilder { + b := GCSBuilder{} + return b.SetKey(key).SetP(p).SetM(m).Preallocate(n) +} + +// WithKeyPM creates a GCSBuilder with specified key and the passed +// probability. Estimated filter size is set to zero, which means more +// reallocations are done when building the filter. +func WithKeyPM(key [gcs.KeySize]byte, p uint8, m uint64) *GCSBuilder { + return WithKeyPNM(key, p, 0, m) +} + +// WithKey creates a GCSBuilder with specified key. Probability is set to 19 +// (2^-19 collision probability). Estimated filter size is set to zero, which +// means more reallocations are done when building the filter. +func WithKey(key [gcs.KeySize]byte) *GCSBuilder { + return WithKeyPNM(key, DefaultP, 0, DefaultM) +} + +// WithKeyHashPNM creates a GCSBuilder with key derived from the specified +// chainhash.Hash and the passed probability and estimated filter size. +func WithKeyHashPNM(keyHash *chainhash.Hash, p uint8, n uint32, + m uint64) *GCSBuilder { + + return WithKeyPNM(DeriveKey(keyHash), p, n, m) +} + +// WithKeyHashPM creates a GCSBuilder with key derived from the specified +// chainhash.Hash and the passed probability. Estimated filter size is set to +// zero, which means more reallocations are done when building the filter. +func WithKeyHashPM(keyHash *chainhash.Hash, p uint8, m uint64) *GCSBuilder { + return WithKeyHashPNM(keyHash, p, 0, m) +} + +// WithKeyHash creates a GCSBuilder with key derived from the specified +// chainhash.Hash. Probability is set to 20 (2^-20 collision probability). +// Estimated filter size is set to zero, which means more reallocations are +// done when building the filter. +func WithKeyHash(keyHash *chainhash.Hash) *GCSBuilder { + return WithKeyHashPNM(keyHash, DefaultP, 0, DefaultM) +} + +// WithRandomKeyPNM creates a GCSBuilder with a cryptographically random key and +// the passed probability and estimated filter size. +func WithRandomKeyPNM(p uint8, n uint32, m uint64) *GCSBuilder { + key, err := RandomKey() + if err != nil { + b := GCSBuilder{err: err} + return &b + } + return WithKeyPNM(key, p, n, m) +} + +// WithRandomKeyPM creates a GCSBuilder with a cryptographically random key and +// the passed probability. Estimated filter size is set to zero, which means +// more reallocations are done when building the filter. +func WithRandomKeyPM(p uint8, m uint64) *GCSBuilder { + return WithRandomKeyPNM(p, 0, m) +} + +// WithRandomKey creates a GCSBuilder with a cryptographically random key. +// Probability is set to 20 (2^-20 collision probability). Estimated filter +// size is set to zero, which means more reallocations are done when +// building the filter. +func WithRandomKey() *GCSBuilder { + return WithRandomKeyPNM(DefaultP, 0, DefaultM) +} + +// BuildBasicFilter builds a basic GCS filter from a block. A basic GCS filter +// will contain all the previous output scripts spent by inputs within a block, +// as well as the data pushes within all the outputs created within a block. +func BuildBasicFilter(block *wire.MsgBlock, prevOutScripts [][]byte) (*gcs.Filter, error) { + blockHash := block.BlockHash() + b := WithKeyHash(&blockHash) + + // If the filter had an issue with the specified key, then we force it + // to bubble up here by calling the Key() function. + _, err := b.Key() + if err != nil { + return nil, err + } + + // In order to build a basic filter, we'll range over the entire block, + // adding each whole script itself. + for _, tx := range block.Transactions { + // For each output in a transaction, we'll add each of the + // individual data pushes within the script. + for _, txOut := range tx.TxOut { + if len(txOut.PkScript) == 0 { + continue + } + + // In order to allow the filters to later be committed + // to within an OP_RETURN output, we ignore all + // OP_RETURNs to avoid a circular dependency. + if txOut.PkScript[0] == txscript.OP_RETURN { + continue + } + + b.AddEntry(txOut.PkScript) + } + } + + // In the second pass, we'll also add all the prevOutScripts + // individually as elements. + for _, prevScript := range prevOutScripts { + if len(prevScript) == 0 { + continue + } + + b.AddEntry(prevScript) + } + + return b.Build() +} + +// GetFilterHash returns the double-SHA256 of the filter. +func GetFilterHash(filter *gcs.Filter) (chainhash.Hash, error) { + filterData, err := filter.NBytes() + if err != nil { + return chainhash.Hash{}, err + } + + return chainhash.DoubleHashH(filterData), nil +} + +// MakeHeaderForFilter makes a filter chain header for a filter, given the +// filter and the previous filter chain header. +func MakeHeaderForFilter(filter *gcs.Filter, prevHeader chainhash.Hash) (chainhash.Hash, error) { + filterTip := make([]byte, 2*chainhash.HashSize) + filterHash, err := GetFilterHash(filter) + if err != nil { + return chainhash.Hash{}, err + } + + // In the buffer we created above we'll compute hash || prevHash as an + // intermediate value. + copy(filterTip, filterHash[:]) + copy(filterTip[chainhash.HashSize:], prevHeader[:]) + + // The final filter hash is the double-sha256 of the hash computed + // above. + return chainhash.DoubleHashH(filterTip), nil +} diff --git a/btcutil/gcs/builder/builder_test.go b/btcutil/gcs/builder/builder_test.go new file mode 100644 index 0000000000..59768380b1 --- /dev/null +++ b/btcutil/gcs/builder/builder_test.go @@ -0,0 +1,281 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package builder_test + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs" + "github.com/btcsuite/btcd/btcutil/gcs/builder" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +var ( + // List of values for building a filter + contents = [][]byte{ + []byte("Alex"), + []byte("Bob"), + []byte("Charlie"), + []byte("Dick"), + []byte("Ed"), + []byte("Frank"), + []byte("George"), + []byte("Harry"), + []byte("Ilya"), + []byte("John"), + []byte("Kevin"), + []byte("Larry"), + []byte("Michael"), + []byte("Nate"), + []byte("Owen"), + []byte("Paul"), + []byte("Quentin"), + } + + testKey = [16]byte{0x4c, 0xb1, 0xab, 0x12, 0x57, 0x62, 0x1e, 0x41, + 0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15} + + testHash = "000000000000000000496d7ff9bd2c96154a8d64260e8b3b411e625712abb14c" + + testAddr = "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v" + + witness = [][]byte{ + {0x4c, 0xb1, 0xab, 0x12, 0x57, 0x62, 0x1e, 0x41, + 0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15, + 0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15, + 0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15}, + + {0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6, + 0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81, + 0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9, + 0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98, + 0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98, + 0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98}, + } +) + +// TestUseBlockHash tests using a block hash as a filter key. +func TestUseBlockHash(t *testing.T) { + // Block hash #448710, pretty high difficulty. + hash, err := chainhash.NewHashFromStr(testHash) + if err != nil { + t.Fatalf("Hash from string failed: %s", err.Error()) + } + + // wire.OutPoint + outPoint := wire.OutPoint{ + Hash: *hash, + Index: 4321, + } + + // btcutil.Address + addr, err := btcutil.DecodeAddress(testAddr, &chaincfg.MainNetParams) + if err != nil { + t.Fatalf("Address decode failed: %s", err.Error()) + } + addrBytes, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160).AddData(addr.ScriptAddress()). + AddOp(txscript.OP_EQUAL).Script() + if err != nil { + t.Fatalf("Address script build failed: %s", err.Error()) + } + + // Create a GCSBuilder with a key hash and check that the key is derived + // correctly, then test it. + b := builder.WithKeyHash(hash) + key, err := b.Key() + if err != nil { + t.Fatalf("Builder instantiation with key hash failed: %s", + err.Error()) + } + if key != testKey { + t.Fatalf("Key not derived correctly from key hash:\n%s\n%s", + hex.EncodeToString(key[:]), + hex.EncodeToString(testKey[:])) + } + BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a key hash and non-default P and test it. + b = builder.WithKeyHashPM(hash, 30, 90) + BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a random key, set the key from a hash + // manually, check that the key is correct, and test it. + b = builder.WithRandomKey() + b.SetKeyFromHash(hash) + key, err = b.Key() + if err != nil { + t.Fatalf("Builder instantiation with known key failed: %s", + err.Error()) + } + if key != testKey { + t.Fatalf("Key not copied correctly from known key:\n%s\n%s", + hex.EncodeToString(key[:]), + hex.EncodeToString(testKey[:])) + } + BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a random key and test it. + b = builder.WithRandomKey() + key1, err := b.Key() + if err != nil { + t.Fatalf("Builder instantiation with random key failed: %s", + err.Error()) + } + t.Logf("Random Key 1: %s", hex.EncodeToString(key1[:])) + BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a random key and non-default P and test it. + b = builder.WithRandomKeyPM(30, 90) + key2, err := b.Key() + if err != nil { + t.Fatalf("Builder instantiation with random key failed: %s", + err.Error()) + } + t.Logf("Random Key 2: %s", hex.EncodeToString(key2[:])) + if key2 == key1 { + t.Fatalf("Random keys are the same!") + } + BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a known key and test it. + b = builder.WithKey(testKey) + key, err = b.Key() + if err != nil { + t.Fatalf("Builder instantiation with known key failed: %s", + err.Error()) + } + if key != testKey { + t.Fatalf("Key not copied correctly from known key:\n%s\n%s", + hex.EncodeToString(key[:]), + hex.EncodeToString(testKey[:])) + } + BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a known key and non-default P and test it. + b = builder.WithKeyPM(testKey, 30, 90) + key, err = b.Key() + if err != nil { + t.Fatalf("Builder instantiation with known key failed: %s", + err.Error()) + } + if key != testKey { + t.Fatalf("Key not copied correctly from known key:\n%s\n%s", + hex.EncodeToString(key[:]), + hex.EncodeToString(testKey[:])) + } + BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t) + + // Create a GCSBuilder with a known key and too-high P and ensure error + // works throughout all functions that use it. + b = builder.WithRandomKeyPM(33, 99).SetKeyFromHash(hash).SetKey(testKey) + b.SetP(30).AddEntry(hash.CloneBytes()).AddEntries(contents). + AddHash(hash).AddEntry(addrBytes) + _, err = b.Key() + if err != gcs.ErrPTooBig { + t.Fatalf("No error on P too big!") + } + _, err = b.Build() + if err != gcs.ErrPTooBig { + t.Fatalf("No error on P too big!") + } +} + +func BuilderTest(b *builder.GCSBuilder, hash *chainhash.Hash, p uint8, + outPoint wire.OutPoint, addrBytes []byte, witness wire.TxWitness, + t *testing.T) { + + key, err := b.Key() + if err != nil { + t.Fatalf("Builder instantiation with key hash failed: %s", + err.Error()) + } + + // Build a filter and test matches. + b.AddEntries(contents) + f, err := b.Build() + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } + if f.P() != p { + t.Fatalf("Filter built with wrong probability") + } + match, err := f.Match(key, []byte("Nate")) + if err != nil { + t.Fatalf("Filter match failed: %s", err) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + match, err = f.Match(key, []byte("weks")) + if err != nil { + t.Fatalf("Filter match failed: %s", err) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", + builder.DefaultP) + } + + // Add a hash, build a filter, and test matches + b.AddHash(hash) + f, err = b.Build() + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } + match, err = f.Match(key, hash.CloneBytes()) + if err != nil { + t.Fatalf("Filter match failed: %s", err) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + + // Add a script, build a filter, and test matches + b.AddEntry(addrBytes) + f, err = b.Build() + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } + match, err = f.MatchAny(key, [][]byte{addrBytes}) + if err != nil { + t.Fatalf("Filter match any failed: %s", err) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + + // Add a routine witness stack, build a filter, and test that it + // matches. + b.AddWitness(witness) + f, err = b.Build() + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } + match, err = f.MatchAny(key, witness) + if err != nil { + t.Fatalf("Filter match any failed: %s", err) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + + // Check that adding duplicate items does not increase filter size. + originalSize := f.N() + b.AddEntry(addrBytes) + b.AddWitness(witness) + f, err = b.Build() + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } + if f.N() != originalSize { + t.Fatal("Filter size increased with duplicate items") + } +} diff --git a/btcutil/gcs/doc.go b/btcutil/gcs/doc.go new file mode 100644 index 0000000000..780fd76631 --- /dev/null +++ b/btcutil/gcs/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016-2017 The btcsuite developers +// Copyright (c) 2016-2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package gcs provides an API for building and using a Golomb-coded set filter. + +Golomb-Coded Set + +A Golomb-coded set is a probabilistic data structure used similarly to a Bloom +filter. A filter uses constant-size overhead plus on average n+2 bits per +item added to the filter, where 2^-n is the desired false positive (collision) +probability. + +GCS use in Bitcoin + +GCS filters are a proposed mechanism for storing and transmitting per-block +filters in Bitcoin. The usage is intended to be the inverse of Bloom filters: +a full node would send an SPV node the GCS filter for a block, which the SPV +node would check against its list of relevant items. The suggested collision +probability for Bitcoin use is 2^-20. +*/ +package gcs diff --git a/btcutil/gcs/gcs.go b/btcutil/gcs/gcs.go new file mode 100644 index 0000000000..fbffb06131 --- /dev/null +++ b/btcutil/gcs/gcs.go @@ -0,0 +1,541 @@ +// Copyright (c) 2016-2017 The btcsuite developers +// Copyright (c) 2016-2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package gcs + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/aead/siphash" + "github.com/btcsuite/btcd/wire" + "github.com/kkdai/bstream" +) + +// Inspired by https://github.com/rasky/gcs + +var ( + // ErrNTooBig signifies that the filter can't handle N items. + ErrNTooBig = fmt.Errorf("N is too big to fit in uint32") + + // ErrPTooBig signifies that the filter can't handle `1/2**P` + // collision probability. + ErrPTooBig = fmt.Errorf("P is too big to fit in uint32") +) + +const ( + // KeySize is the size of the byte array required for key material for + // the SipHash keyed hash function. + KeySize = 16 + + // varIntProtoVer is the protocol version to use for serializing N as a + // VarInt. + varIntProtoVer uint32 = 0 +) + +// fastReduction calculates a mapping that's more ore less equivalent to: x mod +// N. However, instead of using a mod operation, which using a non-power of two +// will lead to slowness on many processors due to unnecessary division, we +// instead use a "multiply-and-shift" trick which eliminates all divisions, +// described in: +// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +// +// * v * N >> log_2(N) +// +// In our case, using 64-bit integers, log_2 is 64. As most processors don't +// support 128-bit arithmetic natively, we'll be super portable and unfold the +// operation into several operations with 64-bit arithmetic. As inputs, we the +// number to reduce, and our modulus N divided into its high 32-bits and lower +// 32-bits. +func fastReduction(v, nHi, nLo uint64) uint64 { + // First, we'll spit the item we need to reduce into its higher and + // lower bits. + vhi := v >> 32 + vlo := uint64(uint32(v)) + + // Then, we distribute multiplication over each part. + vnphi := vhi * nHi + vnpmid := vhi * nLo + npvmid := nHi * vlo + vnplo := vlo * nLo + + // We calculate the carry bit. + carry := (uint64(uint32(vnpmid)) + uint64(uint32(npvmid)) + + (vnplo >> 32)) >> 32 + + // Last, we add the high bits, the middle bits, and the carry. + v = vnphi + (vnpmid >> 32) + (npvmid >> 32) + carry + + return v +} + +// Filter describes an immutable filter that can be built from a set of data +// elements, serialized, deserialized, and queried in a thread-safe manner. The +// serialized form is compressed as a Golomb Coded Set (GCS), but does not +// include N or P to allow the user to encode the metadata separately if +// necessary. The hash function used is SipHash, a keyed function; the key used +// in building the filter is required in order to match filter values and is +// not included in the serialized form. +type Filter struct { + n uint32 + p uint8 + modulusNP uint64 + + filterData []byte +} + +// BuildGCSFilter builds a new GCS filter with the collision probability of +// `1/(2**P)`, key `key`, and including every `[]byte` in `data` as a member of +// the set. +func BuildGCSFilter(P uint8, M uint64, key [KeySize]byte, data [][]byte) (*Filter, error) { // nolint:gocritic + // Some initial parameter checks: make sure we have data from which to + // build the filter, and make sure our parameters will fit the hash + // function we're using. + if uint64(len(data)) >= (1 << 32) { + return nil, ErrNTooBig + } + if P > 32 { + return nil, ErrPTooBig + } + + // Create the filter object and insert metadata. + f := Filter{ + n: uint32(len(data)), + p: P, + } + + // First we'll compute the value of m, which is the modulus we use + // within our finite field. We want to compute: mScalar * 2^P. We use + // math.Round in order to round the value up, rather than down. + f.modulusNP = uint64(f.n) * M + + // Shortcut if the filter is empty. + if f.n == 0 { + return &f, nil + } + + // Build the filter. + values := make([]uint64, 0, len(data)) + b := bstream.NewBStreamWriter(0) + + // Insert the hash (fast-ranged over a space of N*P) of each data + // element into a slice and sort the slice. This can be greatly + // optimized with native 128-bit multiplication, but we're going to be + // fully portable for now. + // + // First, we cache the high and low bits of modulusNP for the + // multiplication of 2 64-bit integers into a 128-bit integer. + nphi := f.modulusNP >> 32 + nplo := uint64(uint32(f.modulusNP)) + for _, d := range data { + // For each datum, we assign the initial hash to a uint64. + v := siphash.Sum64(d, &key) + + v = fastReduction(v, nphi, nplo) + values = append(values, v) + } + sort.Slice(values, func(i, j int) bool { return values[i] < values[j] }) + + // Write the sorted list of values into the filter bitstream, + // compressing it using Golomb coding. + var value, lastValue, remainder uint64 + for _, v := range values { + // Calculate the difference between this value and the last, + // modulo P. + remainder = (v - lastValue) & ((uint64(1) << f.p) - 1) + + // Calculate the difference between this value and the last, + // divided by P. + value = (v - lastValue - remainder) >> f.p + lastValue = v + + // Write the P multiple into the bitstream in unary; the + // average should be around 1 (2 bits - 0b10). + for value > 0 { + b.WriteBit(true) + value-- + } + b.WriteBit(false) + + // Write the remainder as a big-endian integer with enough bits + // to represent the appropriate collision probability. + b.WriteBits(remainder, int(f.p)) + } + + // Copy the bitstream into the filter object and return the object. + f.filterData = b.Bytes() + + return &f, nil +} + +// FromBytes deserializes a GCS filter from a known N, P, and serialized filter +// as returned by Bytes(). +func FromBytes(N uint32, P uint8, M uint64, d []byte) (*Filter, error) { // nolint:gocritic + // Basic sanity check. + if P > 32 { + return nil, ErrPTooBig + } + + // Create the filter object and insert metadata. + f := &Filter{ + n: N, + p: P, + } + + // First we'll compute the value of m, which is the modulus we use + // within our finite field. We want to compute: mScalar * 2^P. We use + // math.Round in order to round the value up, rather than down. + f.modulusNP = uint64(f.n) * M + + // Copy the filter. + f.filterData = make([]byte, len(d)) + copy(f.filterData, d) + + return f, nil +} + +// FromNBytes deserializes a GCS filter from a known P, and serialized N and +// filter as returned by NBytes(). +func FromNBytes(P uint8, M uint64, d []byte) (*Filter, error) { // nolint:gocritic + buffer := bytes.NewBuffer(d) + N, err := wire.ReadVarInt(buffer, varIntProtoVer) + if err != nil { + return nil, err + } + if N >= (1 << 32) { + return nil, ErrNTooBig + } + return FromBytes(uint32(N), P, M, buffer.Bytes()) +} + +// Bytes returns the serialized format of the GCS filter, which does not +// include N or P (returned by separate methods) or the key used by SipHash. +func (f *Filter) Bytes() ([]byte, error) { + filterData := make([]byte, len(f.filterData)) + copy(filterData, f.filterData) + return filterData, nil +} + +// NBytes returns the serialized format of the GCS filter with N, which does +// not include P (returned by a separate method) or the key used by SipHash. +func (f *Filter) NBytes() ([]byte, error) { + var buffer bytes.Buffer + buffer.Grow(wire.VarIntSerializeSize(uint64(f.n)) + len(f.filterData)) + + err := wire.WriteVarInt(&buffer, varIntProtoVer, uint64(f.n)) + if err != nil { + return nil, err + } + + _, err = buffer.Write(f.filterData) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} + +// PBytes returns the serialized format of the GCS filter with P, which does +// not include N (returned by a separate method) or the key used by SipHash. +func (f *Filter) PBytes() ([]byte, error) { + filterData := make([]byte, len(f.filterData)+1) + filterData[0] = f.p + copy(filterData[1:], f.filterData) + return filterData, nil +} + +// NPBytes returns the serialized format of the GCS filter with N and P, which +// does not include the key used by SipHash. +func (f *Filter) NPBytes() ([]byte, error) { + var buffer bytes.Buffer + buffer.Grow(wire.VarIntSerializeSize(uint64(f.n)) + 1 + len(f.filterData)) + + err := wire.WriteVarInt(&buffer, varIntProtoVer, uint64(f.n)) + if err != nil { + return nil, err + } + + err = buffer.WriteByte(f.p) + if err != nil { + return nil, err + } + + _, err = buffer.Write(f.filterData) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} + +// P returns the filter's collision probability as a negative power of 2 (that +// is, a collision probability of `1/2**20` is represented as 20). +func (f *Filter) P() uint8 { + return f.p +} + +// N returns the size of the data set used to build the filter. +func (f *Filter) N() uint32 { + return f.n +} + +// Match checks whether a []byte value is likely (within collision probability) +// to be a member of the set represented by the filter. +func (f *Filter) Match(key [KeySize]byte, data []byte) (bool, error) { + // Create a filter bitstream. + filterData, err := f.Bytes() + if err != nil { + return false, err + } + + b := bstream.NewBStreamReader(filterData) + + // We take the high and low bits of modulusNP for the multiplication + // of 2 64-bit integers into a 128-bit integer. + nphi := f.modulusNP >> 32 + nplo := uint64(uint32(f.modulusNP)) + + // Then we hash our search term with the same parameters as the filter. + term := siphash.Sum64(data, &key) + term = fastReduction(term, nphi, nplo) + + // Go through the search filter and look for the desired value. + var value uint64 + for i := uint32(0); i < f.N(); i++ { + // Read the difference between previous and new value from + // bitstream. + delta, err := f.readFullUint64(b) + if err != nil { + if err == io.EOF { + return false, nil + } + return false, err + } + + // Add the delta to the previous value. + value += delta + switch { + + // The current value matches our query term, success. + case value == term: + return true, nil + + // The current value is greater than our query term, thus no + // future decoded value can match because the values + // monotonically increase. + case value > term: + return false, nil + } + } + + // All values were decoded and none produced a successful match. This + // indicates that the items in the filter were all smaller than our + // target. + return false, nil +} + +// MatchAny returns checks whether any []byte value is likely (within collision +// probability) to be a member of the set represented by the filter faster than +// calling Match() for each value individually. +func (f *Filter) MatchAny(key [KeySize]byte, data [][]byte) (bool, error) { + // TODO(conner): add real heuristics to query optimization + switch { + + case len(data) >= int(f.N()/2): + return f.HashMatchAny(key, data) + + default: + return f.ZipMatchAny(key, data) + } +} + +// ZipMatchAny returns checks whether any []byte value is likely (within +// collision probability) to be a member of the set represented by the filter +// faster than calling Match() for each value individually. +// +// NOTE: This method should outperform HashMatchAny when the number of query +// entries is smaller than the number of filter entries. +func (f *Filter) ZipMatchAny(key [KeySize]byte, data [][]byte) (bool, error) { + // Basic anity check. + if len(data) == 0 { + return false, nil + } + + // Create a filter bitstream. + filterData, err := f.Bytes() + if err != nil { + return false, err + } + + b := bstream.NewBStreamReader(filterData) + + // Create an uncompressed filter of the search values. + values := make([]uint64, 0, len(data)) + + // First, we cache the high and low bits of modulusNP for the + // multiplication of 2 64-bit integers into a 128-bit integer. + nphi := f.modulusNP >> 32 + nplo := uint64(uint32(f.modulusNP)) + for _, d := range data { + // For each datum, we assign the initial hash to a uint64. + v := siphash.Sum64(d, &key) + + // We'll then reduce the value down to the range of our + // modulus. + v = fastReduction(v, nphi, nplo) + values = append(values, v) + } + sort.Slice(values, func(i, j int) bool { return values[i] < values[j] }) + + querySize := len(values) + + // Zip down the filters, comparing values until we either run out of + // values to compare in one of the filters or we reach a matching + // value. + var ( + value uint64 + queryIndex int + ) +out: + for i := uint32(0); i < f.N(); i++ { + // Advance filter we're searching or return false if we're at + // the end because nothing matched. + delta, err := f.readFullUint64(b) + if err != nil { + if err == io.EOF { + return false, nil + } + return false, err + } + value += delta + + for { + switch { + + // All query items have been exhausted and we haven't + // had a match, therefore there are no matches. + case queryIndex == querySize: + return false, nil + + // The current item in the query matches the decoded + // value, success. + case values[queryIndex] == value: + return true, nil + + // The current item in the query is greater than the + // current decoded value, continue to decode the next + // delta and try again. + case values[queryIndex] > value: + continue out + } + + queryIndex++ + } + } + + // All items in the filter were decoded and none produced a successful + // match. + return false, nil +} + +// HashMatchAny returns checks whether any []byte value is likely (within +// collision probability) to be a member of the set represented by the filter +// faster than calling Match() for each value individually. +// +// NOTE: This method should outperform MatchAny if the number of query entries +// approaches the number of filter entries, len(data) >= f.N(). +func (f *Filter) HashMatchAny(key [KeySize]byte, data [][]byte) (bool, error) { + // Basic sanity check. + if len(data) == 0 { + return false, nil + } + + // Create a filter bitstream. + filterData, err := f.Bytes() + if err != nil { + return false, err + } + + b := bstream.NewBStreamReader(filterData) + + var ( + values = make(map[uint32]struct{}, f.N()) + lastValue uint64 + ) + + // First, decompress the filter and construct an index of the keys + // contained within the filter. Index construction terminates after all + // values have been read from the bitstream. + for { + // Read the next diff value from the filter, add it to the + // last value, and set the new value in the index. + value, err := f.readFullUint64(b) + if err == nil { + lastValue += value + values[uint32(lastValue)] = struct{}{} + continue + } else if err == io.EOF { + break + } + + return false, err + } + + // We cache the high and low bits of modulusNP for the multiplication of + // 2 64-bit integers into a 128-bit integer. + nphi := f.modulusNP >> 32 + nplo := uint64(uint32(f.modulusNP)) + + // Finally, run through the provided data items, querying the index to + // determine if the filter contains any elements of interest. + for _, d := range data { + // For each datum, we assign the initial hash to + // a uint64. + v := siphash.Sum64(d, &key) + + // We'll then reduce the value down to the range + // of our modulus. + v = fastReduction(v, nphi, nplo) + + if _, ok := values[uint32(v)]; !ok { + continue + } + + return true, nil + } + + return false, nil +} + +// readFullUint64 reads a value represented by the sum of a unary multiple of +// the filter's P modulus (`2**P`) and a big-endian P-bit remainder. +func (f *Filter) readFullUint64(b *bstream.BStream) (uint64, error) { + var quotient uint64 + + // Count the 1s until we reach a 0. + c, err := b.ReadBit() + if err != nil { + return 0, err + } + for c { + quotient++ + c, err = b.ReadBit() + if err != nil { + return 0, err + } + } + + // Read P bits. + remainder, err := b.ReadBits(int(f.p)) + if err != nil { + return 0, err + } + + // Add the multiple and the remainder. + v := (quotient << f.p) + remainder + return v, nil +} diff --git a/btcutil/gcs/gcs_test.go b/btcutil/gcs/gcs_test.go new file mode 100644 index 0000000000..8ae49f0507 --- /dev/null +++ b/btcutil/gcs/gcs_test.go @@ -0,0 +1,368 @@ +// Copyright (c) 2016-2017 The btcsuite developers +// Copyright (c) 2016-2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package gcs_test + +import ( + "bytes" + "encoding/binary" + "math/rand" + "testing" + + "github.com/btcsuite/btcd/btcutil/gcs" +) + +var ( + // No need to allocate an err variable in every test + err error + + // Collision probability for the tests (1/2**19) + P = uint8(19) + + // Modulus value for the tests. + M uint64 = 784931 + + // Filters are conserved between tests but we must define with an + // interface which functions we're testing because the gcsFilter type + // isn't exported + filter, filter2, filter3 *gcs.Filter + + // We need to use the same key for building and querying the filters + key [gcs.KeySize]byte + + // List of values for building a filter + contents = [][]byte{ + []byte("Alex"), + []byte("Bob"), + []byte("Charlie"), + []byte("Dick"), + []byte("Ed"), + []byte("Frank"), + []byte("George"), + []byte("Harry"), + []byte("Ilya"), + []byte("John"), + []byte("Kevin"), + []byte("Larry"), + []byte("Michael"), + []byte("Nate"), + []byte("Owen"), + []byte("Paul"), + []byte("Quentin"), + } + + // List of values for querying a filter using MatchAny() + contents2 = [][]byte{ + []byte("Alice"), + []byte("Betty"), + []byte("Charmaine"), + []byte("Donna"), + []byte("Edith"), + []byte("Faina"), + []byte("Georgia"), + []byte("Hannah"), + []byte("Ilsbeth"), + []byte("Jennifer"), + []byte("Kayla"), + []byte("Lena"), + []byte("Michelle"), + []byte("Natalie"), + []byte("Ophelia"), + []byte("Peggy"), + []byte("Queenie"), + } +) + +// TestGCSFilterBuild builds a test filter with a randomized key. For Bitcoin +// use, deterministic filter generation is desired. Therefore, a key that's +// derived deterministically would be required. +func TestGCSFilterBuild(t *testing.T) { + for i := 0; i < gcs.KeySize; i += 4 { + binary.BigEndian.PutUint32(key[i:], rand.Uint32()) + } + filter, err = gcs.BuildGCSFilter(P, M, key, contents) + if err != nil { + t.Fatalf("Filter build failed: %s", err.Error()) + } +} + +// TestGCSMatchZeroHash ensures that Match and MatchAny properly match an item +// if it's hash after the reduction is zero. This is accomplished by brute +// forcing a specific target whose hash is zero given a certain (P, M, key, +// len(elements)) combination. In this case, P and M are the default, key was +// chosen randomly, and len(elements) is 13. The target 4-byte value of 16060032 +// is the first such 32-bit value, thus we use the number 0-11 as the other +// elements in the filter since we know they won't collide. We test both the +// positive and negative cases, when the zero hash item is in the filter and +// when it is excluded. In the negative case, the 32-bit value of 12 is added to +// the filter instead of the target. +func TestGCSMatchZeroHash(t *testing.T) { + t.Run("include zero", func(t *testing.T) { + testGCSMatchZeroHash(t, true) + }) + t.Run("exclude zero", func(t *testing.T) { + testGCSMatchZeroHash(t, false) + }) +} + +func testGCSMatchZeroHash(t *testing.T, includeZeroHash bool) { + key := [gcs.KeySize]byte{ + 0x25, 0x28, 0x0d, 0x25, 0x26, 0xe1, 0xd3, 0xc7, + 0xa5, 0x71, 0x85, 0x34, 0x92, 0xa5, 0x7e, 0x68, + } + + // Construct the target data to match, whose hash is zero after applying + // the reduction with the parameters in the test. + target := make([]byte, 4) + binary.BigEndian.PutUint32(target, 16060032) + + // Construct the set of 13 items including the target, using the 32-bit + // values of 0 through 11 as the first 12 items. We known none of these + // hash to zero since the brute force ended well beyond them. + elements := make([][]byte, 0, 13) + for i := 0; i < 12; i++ { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, uint32(i)) + elements = append(elements, data) + } + + // If the filter should include the zero hash element, add the target + // which we know hashes to zero. Otherwise add 32-bit value of 12 which + // we know does not hash to zero. + if includeZeroHash { + elements = append(elements, target) + } else { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, 12) + elements = append(elements, data) + } + + filter, err := gcs.BuildGCSFilter(P, M, key, elements) + if err != nil { + t.Fatalf("unable to build filter: %v", err) + } + + match, err := filter.Match(key, target) + if err != nil { + t.Fatalf("unable to match: %v", err) + } + + // We should only get a match iff the target was included. + if match != includeZeroHash { + t.Fatalf("expected match from Match: %t, got %t", + includeZeroHash, match) + } + + match, err = filter.MatchAny(key, [][]byte{target}) + if err != nil { + t.Fatalf("unable to match any: %v", err) + } + + // We should only get a match iff the target was included. + if match != includeZeroHash { + t.Fatalf("expected match from MatchAny: %t, got %t", + includeZeroHash, match) + } +} + +// TestGCSFilterCopy deserializes and serializes a filter to create a copy. +func TestGCSFilterCopy(t *testing.T) { + serialized2, err := filter.Bytes() + if err != nil { + t.Fatalf("Filter Bytes() failed: %v", err) + } + filter2, err = gcs.FromBytes(filter.N(), P, M, serialized2) + if err != nil { + t.Fatalf("Filter copy failed: %s", err.Error()) + } + serialized3, err := filter.NBytes() + if err != nil { + t.Fatalf("Filter NBytes() failed: %v", err) + } + filter3, err = gcs.FromNBytes(filter.P(), M, serialized3) + if err != nil { + t.Fatalf("Filter copy failed: %s", err.Error()) + } +} + +// TestGCSFilterMetadata checks that the filter metadata is built and copied +// correctly. +func TestGCSFilterMetadata(t *testing.T) { + if filter.P() != P { + t.Fatal("P not correctly stored in filter metadata") + } + if filter.N() != uint32(len(contents)) { + t.Fatal("N not correctly stored in filter metadata") + } + if filter.P() != filter2.P() { + t.Fatal("P doesn't match between copied filters") + } + if filter.P() != filter3.P() { + t.Fatal("P doesn't match between copied filters") + } + if filter.N() != filter2.N() { + t.Fatal("N doesn't match between copied filters") + } + if filter.N() != filter3.N() { + t.Fatal("N doesn't match between copied filters") + } + serialized, err := filter.Bytes() + if err != nil { + t.Fatalf("Filter Bytes() failed: %v", err) + } + serialized2, err := filter2.Bytes() + if err != nil { + t.Fatalf("Filter Bytes() failed: %v", err) + } + if !bytes.Equal(serialized, serialized2) { + t.Fatal("Bytes don't match between copied filters") + } + serialized3, err := filter3.Bytes() + if err != nil { + t.Fatalf("Filter Bytes() failed: %v", err) + } + if !bytes.Equal(serialized, serialized3) { + t.Fatal("Bytes don't match between copied filters") + } + serialized4, err := filter3.Bytes() + if err != nil { + t.Fatalf("Filter Bytes() failed: %v", err) + } + if !bytes.Equal(serialized, serialized4) { + t.Fatal("Bytes don't match between copied filters") + } +} + +// TestGCSFilterMatch checks that both the built and copied filters match +// correctly, logging any false positives without failing on them. +func TestGCSFilterMatch(t *testing.T) { + match, err := filter.Match(key, []byte("Nate")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + match, err = filter2.Match(key, []byte("Nate")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + match, err = filter.Match(key, []byte("Quentin")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + match, err = filter2.Match(key, []byte("Quentin")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match when it should have!") + } + match, err = filter.Match(key, []byte("Nates")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } + match, err = filter2.Match(key, []byte("Nates")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } + match, err = filter.Match(key, []byte("Quentins")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } + match, err = filter2.Match(key, []byte("Quentins")) + if err != nil { + t.Fatalf("Filter match failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } +} + +// AnyMatcher is the function signature of our matching algorithms. +type AnyMatcher func(key [gcs.KeySize]byte, data [][]byte) (bool, error) + +// TestGCSFilterMatchAnySuite checks that all of our matching algorithms +// properly match a list correctly when using built or copied filters, logging +// any false positives without failing on them. +func TestGCSFilterMatchAnySuite(t *testing.T) { + funcs := []struct { + name string + matchAny func(*gcs.Filter) AnyMatcher + }{ + { + "default", + func(f *gcs.Filter) AnyMatcher { + return f.MatchAny + }, + }, + { + "hash", + func(f *gcs.Filter) AnyMatcher { + return f.HashMatchAny + }, + }, + { + "zip", + func(f *gcs.Filter) AnyMatcher { + return f.ZipMatchAny + }, + }, + } + + for _, test := range funcs { + test := test + + t.Run(test.name, func(t *testing.T) { + contentsCopy := make([][]byte, len(contents2)) + copy(contentsCopy, contents2) + + match, err := test.matchAny(filter)(key, contentsCopy) + if err != nil { + t.Fatalf("Filter match any failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } + match, err = test.matchAny(filter2)(key, contentsCopy) + if err != nil { + t.Fatalf("Filter match any failed: %s", err.Error()) + } + if match { + t.Logf("False positive match, should be 1 in 2**%d!", P) + } + contentsCopy = append(contentsCopy, []byte("Nate")) + match, err = test.matchAny(filter)(key, contentsCopy) + if err != nil { + t.Fatalf("Filter match any failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match any when it should have!") + } + match, err = test.matchAny(filter2)(key, contentsCopy) + if err != nil { + t.Fatalf("Filter match any failed: %s", err.Error()) + } + if !match { + t.Fatal("Filter didn't match any when it should have!") + } + }) + } +} diff --git a/btcutil/gcs/gcsbench_test.go b/btcutil/gcs/gcsbench_test.go new file mode 100644 index 0000000000..14125a16c0 --- /dev/null +++ b/btcutil/gcs/gcsbench_test.go @@ -0,0 +1,237 @@ +// Copyright (c) 2016-2017 The btcsuite developers +// Copyright (c) 2016-2017 The Lightning Network Developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package gcs_test + +import ( + "encoding/binary" + "math/rand" + "testing" + + "github.com/btcsuite/btcd/btcutil/gcs" +) + +func genRandFilterElements(numElements uint) ([][]byte, error) { + testContents := make([][]byte, numElements) + for i := range testContents { + randElem := make([]byte, 32) + if _, err := rand.Read(randElem); err != nil { + return nil, err + } + testContents[i] = randElem + } + + return testContents, nil +} + +var ( + generatedFilter *gcs.Filter +) + +// BenchmarkGCSFilterBuild benchmarks building a filter. +func BenchmarkGCSFilterBuild50000(b *testing.B) { + var testKey [gcs.KeySize]byte + for i := 0; i < gcs.KeySize; i += 4 { + binary.BigEndian.PutUint32(testKey[i:], rand.Uint32()) + } + + randFilterElems, genErr := genRandFilterElements(50000) + if err != nil { + b.Fatalf("unable to generate random item: %v", genErr) + } + + b.ReportAllocs() + b.ResetTimer() + + var localFilter *gcs.Filter + for i := 0; i < b.N; i++ { + localFilter, err = gcs.BuildGCSFilter( + P, M, key, randFilterElems, + ) + if err != nil { + b.Fatalf("unable to generate filter: %v", err) + } + } + generatedFilter = localFilter +} + +// BenchmarkGCSFilterBuild benchmarks building a filter. +func BenchmarkGCSFilterBuild100000(b *testing.B) { + var testKey [gcs.KeySize]byte + for i := 0; i < gcs.KeySize; i += 4 { + binary.BigEndian.PutUint32(testKey[i:], rand.Uint32()) + } + + randFilterElems, genErr := genRandFilterElements(100000) + if err != nil { + b.Fatalf("unable to generate random item: %v", genErr) + } + + b.ReportAllocs() + b.ResetTimer() + + var localFilter *gcs.Filter + for i := 0; i < b.N; i++ { + localFilter, err = gcs.BuildGCSFilter( + P, M, key, randFilterElems, + ) + if err != nil { + b.Fatalf("unable to generate filter: %v", err) + } + } + generatedFilter = localFilter +} + +var ( + match bool +) + +// BenchmarkGCSFilterMatch benchmarks querying a filter for a single value. +func BenchmarkGCSFilterMatch(b *testing.B) { + filter, err := gcs.BuildGCSFilter(P, M, key, contents) + if err != nil { + b.Fatalf("Failed to build filter") + } + + b.ReportAllocs() + b.ResetTimer() + + var localMatch bool + for i := 0; i < b.N; i++ { + _, err = filter.Match(key, []byte("Nate")) + if err != nil { + b.Fatalf("unable to match filter: %v", err) + } + + localMatch, err = filter.Match(key, []byte("Nates")) + if err != nil { + b.Fatalf("unable to match filter: %v", err) + } + } + match = localMatch +} + +var ( + randElems100, _ = genRandFilterElements(100) + randElems1000, _ = genRandFilterElements(1000) + randElems10000, _ = genRandFilterElements(10000) + randElems100000, _ = genRandFilterElements(100000) + randElems1000000, _ = genRandFilterElements(1000000) + randElems10000000, _ = genRandFilterElements(10000000) + + filterElems1000, _ = genRandFilterElements(1000) + filter1000, _ = gcs.BuildGCSFilter(P, M, key, filterElems1000) + filterElems5000, _ = genRandFilterElements(5000) + filter5000, _ = gcs.BuildGCSFilter(P, M, key, filterElems5000) + filterElems10000, _ = genRandFilterElements(10000) + filter10000, _ = gcs.BuildGCSFilter(P, M, key, filterElems10000) +) + +// matchAnyBenchmarks contains combinations of random filters and queries used +// to measure performance of various MatchAny implementations. +var matchAnyBenchmarks = []struct { + name string + query [][]byte + filter *gcs.Filter +}{ + {"q100-f1K", randElems100, filter1000}, + {"q1K-f1K", randElems1000, filter1000}, + {"q10K-f1K", randElems10000, filter1000}, + {"q100K-f1K", randElems100000, filter1000}, + {"q1M-f1K", randElems1000000, filter1000}, + {"q10M-f1K", randElems10000000, filter1000}, + + {"q100-f5K", randElems100, filter5000}, + {"q1K-f5K", randElems1000, filter5000}, + {"q10K-f5K", randElems10000, filter5000}, + {"q100K-f5K", randElems100000, filter5000}, + {"q1M-f5K", randElems1000000, filter5000}, + {"q10M-f5K", randElems10000000, filter5000}, + + {"q100-f10K", randElems100, filter10000}, + {"q1K-f10K", randElems1000, filter10000}, + {"q10K-f10K", randElems10000, filter10000}, + {"q100K-f10K", randElems100000, filter10000}, + {"q1M-f10K", randElems1000000, filter10000}, + {"q10M-f10K", randElems10000000, filter10000}, +} + +// BenchmarkGCSFilterMatchAny benchmarks the sort-and-zip MatchAny impl. +func BenchmarkGCSFilterZipMatchAny(b *testing.B) { + for _, test := range matchAnyBenchmarks { + test := test + + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + + var ( + localMatch bool + err error + ) + + for i := 0; i < b.N; i++ { + localMatch, err = test.filter.ZipMatchAny( + key, test.query, + ) + if err != nil { + b.Fatalf("unable to match filter: %v", err) + } + } + match = localMatch + }) + } +} + +// BenchmarkGCSFilterMatchAny benchmarks the hash-join MatchAny impl. +func BenchmarkGCSFilterHashMatchAny(b *testing.B) { + for _, test := range matchAnyBenchmarks { + test := test + + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + + var ( + localMatch bool + err error + ) + + for i := 0; i < b.N; i++ { + localMatch, err = test.filter.HashMatchAny( + key, test.query, + ) + if err != nil { + b.Fatalf("unable to match filter: %v", err) + } + } + match = localMatch + }) + } +} + +// BenchmarkGCSFilterMatchAny benchmarks the hybrid MatchAny impl. +func BenchmarkGCSFilterMatchAny(b *testing.B) { + for _, test := range matchAnyBenchmarks { + test := test + + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + + var ( + localMatch bool + err error + ) + + for i := 0; i < b.N; i++ { + localMatch, err = test.filter.MatchAny( + key, test.query, + ) + if err != nil { + b.Fatalf("unable to match filter: %v", err) + } + } + match = localMatch + }) + } +} diff --git a/btcutil/go.mod b/btcutil/go.mod new file mode 100644 index 0000000000..71014ebed5 --- /dev/null +++ b/btcutil/go.mod @@ -0,0 +1,11 @@ +module github.com/btcsuite/btcd/btcutil + +go 1.16 + +require ( + github.com/aead/siphash v1.0.1 + github.com/btcsuite/btcd v0.20.1-beta + github.com/davecgh/go-spew v1.1.1 + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 +) diff --git a/btcutil/go.sum b/btcutil/go.sum new file mode 100644 index 0000000000..c51f84dd2d --- /dev/null +++ b/btcutil/go.sum @@ -0,0 +1,85 @@ +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/btcutil/hash160.go b/btcutil/hash160.go new file mode 100644 index 0000000000..fa26e09bf9 --- /dev/null +++ b/btcutil/hash160.go @@ -0,0 +1,23 @@ +// 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 btcutil + +import ( + "crypto/sha256" + "hash" + + "golang.org/x/crypto/ripemd160" +) + +// Calculate the hash of hasher over buf. +func calcHash(buf []byte, hasher hash.Hash) []byte { + _, _ = hasher.Write(buf) + return hasher.Sum(nil) +} + +// Hash160 calculates the hash ripemd160(sha256(b)). +func Hash160(buf []byte) []byte { + return calcHash(calcHash(buf, sha256.New()), ripemd160.New()) +} diff --git a/btcutil/hdkeychain/README.md b/btcutil/hdkeychain/README.md new file mode 100644 index 0000000000..eaf57d7c58 --- /dev/null +++ b/btcutil/hdkeychain/README.md @@ -0,0 +1,59 @@ +hdkeychain +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcutil/hdkeychain) + +Package hdkeychain provides an API for bitcoin hierarchical deterministic +extended keys (BIP0032). + +A comprehensive suite of tests is provided to ensure proper functionality. See +`test_coverage.txt` for the gocov coverage report. Alternatively, if you are +running a POSIX OS, you can run the `cov_report.sh` script for a real-time +report. + +## Feature Overview + +- Full BIP0032 implementation +- Single type for private and public extended keys +- Convenient cryptograpically secure seed generation +- Simple creation of master nodes +- Support for multi-layer derivation +- Easy serialization and deserialization for both private and public extended + keys +- Support for custom networks by registering them with chaincfg +- Obtaining the underlying EC pubkeys, EC privkeys, and associated bitcoin + addresses ties in seamlessly with existing btcec and btcutil types which + provide powerful tools for working with them to do things like sign + transations and generate payment scripts +- Uses the btcec package which is highly optimized for secp256k1 +- Code examples including: + - Generating a cryptographically secure random seed and deriving a + master node from it + - Default HD wallet layout as described by BIP0032 + - Audits use case as described by BIP0032 +- Comprehensive test coverage including the BIP0032 test vectors +- Benchmarks + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/hdkeychain +``` + +## Examples + +* [NewMaster Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/hdkeychain#example-NewMaster) + Demonstrates how to generate a cryptographically random seed then use it to + create a new master node (extended key). +* [Default Wallet Layout Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/hdkeychain#example-package--DefaultWalletLayout) + Demonstrates the default hierarchical deterministic wallet layout as described + in BIP0032. +* [Audits Use Case Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/hdkeychain#example-package--Audits) + Demonstrates the audits use case in BIP0032. + +## License + +Package hdkeychain is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/hdkeychain/bench_test.go b/btcutil/hdkeychain/bench_test.go new file mode 100644 index 0000000000..285ef3e8d6 --- /dev/null +++ b/btcutil/hdkeychain/bench_test.go @@ -0,0 +1,84 @@ +// 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 hdkeychain_test + +import ( + "testing" + + "github.com/btcsuite/btcd/btcutil/hdkeychain" +) + +// bip0032MasterPriv1 is the master private extended key from the first set of +// test vectors in BIP0032. +const bip0032MasterPriv1 = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbP" + + "y6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + +// BenchmarkDeriveHardened benchmarks how long it takes to derive a hardened +// child from a master private extended key. +func BenchmarkDeriveHardened(b *testing.B) { + b.StopTimer() + masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) + if err != nil { + b.Errorf("Failed to decode master seed: %v", err) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, _ = masterKey.Derive(hdkeychain.HardenedKeyStart) + } +} + +// BenchmarkDeriveNormal benchmarks how long it takes to derive a normal +// (non-hardened) child from a master private extended key. +func BenchmarkDeriveNormal(b *testing.B) { + b.StopTimer() + masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) + if err != nil { + b.Errorf("Failed to decode master seed: %v", err) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, _ = masterKey.Derive(0) + } +} + +// BenchmarkPrivToPub benchmarks how long it takes to convert a private extended +// key to a public extended key. +func BenchmarkPrivToPub(b *testing.B) { + b.StopTimer() + masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) + if err != nil { + b.Errorf("Failed to decode master seed: %v", err) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, _ = masterKey.Neuter() + } +} + +// BenchmarkDeserialize benchmarks how long it takes to deserialize a private +// extended key. +func BenchmarkDeserialize(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = hdkeychain.NewKeyFromString(bip0032MasterPriv1) + } +} + +// BenchmarkSerialize benchmarks how long it takes to serialize a private +// extended key. +func BenchmarkSerialize(b *testing.B) { + b.StopTimer() + masterKey, err := hdkeychain.NewKeyFromString(bip0032MasterPriv1) + if err != nil { + b.Errorf("Failed to decode master seed: %v", err) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + _ = masterKey.String() + } +} diff --git a/btcutil/hdkeychain/cov_report.sh b/btcutil/hdkeychain/cov_report.sh new file mode 100644 index 0000000000..307f05b76c --- /dev/null +++ b/btcutil/hdkeychain/cov_report.sh @@ -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 diff --git a/btcutil/hdkeychain/doc.go b/btcutil/hdkeychain/doc.go new file mode 100644 index 0000000000..dcf74f6b51 --- /dev/null +++ b/btcutil/hdkeychain/doc.go @@ -0,0 +1,84 @@ +// 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 hdkeychain provides an API for bitcoin hierarchical deterministic +extended keys (BIP0032). + +Overview + +The ability to implement hierarchical deterministic wallets depends on the +ability to create and derive hierarchical deterministic extended keys. + +At a high level, this package provides support for those hierarchical +deterministic extended keys by providing an ExtendedKey type and supporting +functions. Each extended key can either be a private or public extended key +which itself is capable of deriving a child extended key. + +Determining the Extended Key Type + +Whether an extended key is a private or public extended key can be determined +with the IsPrivate function. + +Transaction Signing Keys and Payment Addresses + +In order to create and sign transactions, or provide others with addresses to +send funds to, the underlying key and address material must be accessible. This +package provides the ECPubKey, ECPrivKey, and Address functions for this +purpose. + +The Master Node + +As previously mentioned, the extended keys are hierarchical meaning they are +used to form a tree. The root of that tree is called the master node and this +package provides the NewMaster function to create it from a cryptographically +random seed. The GenerateSeed function is provided as a convenient way to +create a random seed for use with the NewMaster function. + +Deriving Children + +Once you have created a tree root (or have deserialized an extended key as +discussed later), the child extended keys can be derived by using the Derive +function. The Derive function supports deriving both normal (non-hardened) and +hardened child extended keys. In order to derive a hardened extended key, use +the HardenedKeyStart constant + the hardened key number as the index to the +Derive function. This provides the ability to cascade the keys into a tree and +hence generate the hierarchical deterministic key chains. + +Normal vs Hardened Derived Extended Keys + +A private extended key can be used to derive both hardened and non-hardened +(normal) child private and public extended keys. A public extended key can only +be used to derive non-hardened child public extended keys. As enumerated in +BIP0032 "knowledge of the extended public key plus any non-hardened private key +descending from it is equivalent to knowing the extended private key (and thus +every private and public key descending from it). This means that extended +public keys must be treated more carefully than regular public keys. It is also +the reason for the existence of hardened keys, and why they are used for the +account level in the tree. This way, a leak of an account-specific (or below) +private key never risks compromising the master or other accounts." + +Neutering a Private Extended Key + +A private extended key can be converted to a new instance of the corresponding +public extended key with the Neuter function. The original extended key is not +modified. A public extended key is still capable of deriving non-hardened child +public extended keys. + +Serializing and Deserializing Extended Keys + +Extended keys are serialized and deserialized with the String and +NewKeyFromString functions. The serialized key is a Base58-encoded string which +looks like the following: + public key: xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw + private key: xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7 + +Network + +Extended keys are much like normal Bitcoin addresses in that they have version +bytes which tie them to a specific network. The SetNet and IsForNet functions +are provided to set and determinine which network an extended key is associated +with. +*/ +package hdkeychain diff --git a/btcutil/hdkeychain/example_test.go b/btcutil/hdkeychain/example_test.go new file mode 100644 index 0000000000..7489d387a7 --- /dev/null +++ b/btcutil/hdkeychain/example_test.go @@ -0,0 +1,182 @@ +// 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 hdkeychain_test + +import ( + "fmt" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/btcutil/hdkeychain" +) + +// This example demonstrates how to generate a cryptographically random seed +// then use it to create a new master node (extended key). +func ExampleNewMaster() { + // Generate a random seed at the recommended length. + seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) + if err != nil { + fmt.Println(err) + return + } + + // Generate a new master node using the seed. + key, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + fmt.Println(err) + return + } + + // Show that the generated master node extended key is private. + fmt.Println("Private Extended Key?:", key.IsPrivate()) + + // Output: + // Private Extended Key?: true +} + +// This example demonstrates the default hierarchical deterministic wallet +// layout as described in BIP0032. +func Example_defaultWalletLayout() { + // The default wallet layout described in BIP0032 is: + // + // Each account is composed of two keypair chains: an internal and an + // external one. The external keychain is used to generate new public + // addresses, while the internal keychain is used for all other + // operations (change addresses, generation addresses, ..., anything + // that doesn't need to be communicated). + // + // * m/iH/0/k + // corresponds to the k'th keypair of the external chain of account + // number i of the HDW derived from master m. + // * m/iH/1/k + // corresponds to the k'th keypair of the internal chain of account + // number i of the HDW derived from master m. + + // Ordinarily this would either be read from some encrypted source + // and be decrypted or generated as the NewMaster example shows, but + // for the purposes of this example, the private extended key for the + // master node is being hard coded here. + master := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jP" + + "PqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + + // Start by getting an extended key instance for the master node. + // This gives the path: + // m + masterKey, err := hdkeychain.NewKeyFromString(master) + if err != nil { + fmt.Println(err) + return + } + + // Derive the extended key for account 0. This gives the path: + // m/0H + acct0, err := masterKey.Derive(hdkeychain.HardenedKeyStart + 0) + if err != nil { + fmt.Println(err) + return + } + + // Derive the extended key for the account 0 external chain. This + // gives the path: + // m/0H/0 + acct0Ext, err := acct0.Derive(0) + if err != nil { + fmt.Println(err) + return + } + + // Derive the extended key for the account 0 internal chain. This gives + // the path: + // m/0H/1 + acct0Int, err := acct0.Derive(1) + if err != nil { + fmt.Println(err) + return + } + + // At this point, acct0Ext and acct0Int are ready to derive the keys for + // the external and internal wallet chains. + + // Derive the 10th extended key for the account 0 external chain. This + // gives the path: + // m/0H/0/10 + acct0Ext10, err := acct0Ext.Derive(10) + if err != nil { + fmt.Println(err) + return + } + + // Derive the 1st extended key for the account 0 internal chain. This + // gives the path: + // m/0H/1/0 + acct0Int0, err := acct0Int.Derive(0) + if err != nil { + fmt.Println(err) + return + } + + // Get and show the address associated with the extended keys for the + // main bitcoin network. + acct0ExtAddr, err := acct0Ext10.Address(&chaincfg.MainNetParams) + if err != nil { + fmt.Println(err) + return + } + acct0IntAddr, err := acct0Int0.Address(&chaincfg.MainNetParams) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Account 0 External Address 10:", acct0ExtAddr) + fmt.Println("Account 0 Internal Address 0:", acct0IntAddr) + + // Output: + // Account 0 External Address 10: 1HVccubUT8iKTapMJ5AnNA4sLRN27xzQ4F + // Account 0 Internal Address 0: 1J5rebbkQaunJTUoNVREDbeB49DqMNFFXk +} + +// This example demonstrates the audits use case in BIP0032. +func Example_audits() { + // The audits use case described in BIP0032 is: + // + // In case an auditor needs full access to the list of incoming and + // outgoing payments, one can share all account public extended keys. + // This will allow the auditor to see all transactions from and to the + // wallet, in all accounts, but not a single secret key. + // + // * N(m/*) + // corresponds to the neutered master extended key (also called + // the master public extended key) + + // Ordinarily this would either be read from some encrypted source + // and be decrypted or generated as the NewMaster example shows, but + // for the purposes of this example, the private extended key for the + // master node is being hard coded here. + master := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jP" + + "PqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + + // Start by getting an extended key instance for the master node. + // This gives the path: + // m + masterKey, err := hdkeychain.NewKeyFromString(master) + if err != nil { + fmt.Println(err) + return + } + + // Neuter the master key to generate a master public extended key. This + // gives the path: + // N(m/*) + masterPubKey, err := masterKey.Neuter() + if err != nil { + fmt.Println(err) + return + } + + // Share the master public extended key with the auditor. + fmt.Println("Audit key N(m/*):", masterPubKey) + + // Output: + // Audit key N(m/*): xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 +} diff --git a/btcutil/hdkeychain/extendedkey.go b/btcutil/hdkeychain/extendedkey.go new file mode 100644 index 0000000000..4145c5c23e --- /dev/null +++ b/btcutil/hdkeychain/extendedkey.go @@ -0,0 +1,706 @@ +// Copyright (c) 2014-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package hdkeychain + +// References: +// [BIP32]: BIP0032 - Hierarchical Deterministic Wallets +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + +import ( + "bytes" + "crypto/hmac" + "crypto/rand" + "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/base58" +) + +const ( + // RecommendedSeedLen is the recommended length in bytes for a seed + // to a master node. + RecommendedSeedLen = 32 // 256 bits + + // HardenedKeyStart is the index at which a hardened key starts. Each + // extended key has 2^31 normal child keys and 2^31 hardened child keys. + // Thus the range for normal child keys is [0, 2^31 - 1] and the range + // for hardened child keys is [2^31, 2^32 - 1]. + HardenedKeyStart = 0x80000000 // 2^31 + + // MinSeedBytes is the minimum number of bytes allowed for a seed to + // a master node. + MinSeedBytes = 16 // 128 bits + + // MaxSeedBytes is the maximum number of bytes allowed for a seed to + // a master node. + MaxSeedBytes = 64 // 512 bits + + // serializedKeyLen is the length of a serialized public or private + // extended key. It consists of 4 bytes version, 1 byte depth, 4 bytes + // fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes + // public/private key data. + serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes + + // maxUint8 is the max positive integer which can be serialized in a uint8 + maxUint8 = 1<<8 - 1 +) + +var ( + // ErrDeriveHardFromPublic describes an error in which the caller + // attempted to derive a hardened extended key from a public key. + ErrDeriveHardFromPublic = errors.New("cannot derive a hardened key " + + "from a public key") + + // ErrDeriveBeyondMaxDepth describes an error in which the caller + // has attempted to derive more than 255 keys from a root key. + ErrDeriveBeyondMaxDepth = errors.New("cannot derive a key with more than " + + "255 indices in its path") + + // ErrNotPrivExtKey describes an error in which the caller attempted + // to extract a private key from a public extended key. + ErrNotPrivExtKey = errors.New("unable to create private keys from a " + + "public extended key") + + // ErrInvalidChild describes an error in which the child at a specific + // index is invalid due to the derived key falling outside of the valid + // range for secp256k1 private keys. This error indicates the caller + // should simply ignore the invalid child extended key at this index and + // increment to the next index. + ErrInvalidChild = errors.New("the extended key at this index is invalid") + + // ErrUnusableSeed describes an error in which the provided seed is not + // usable due to the derived key falling outside of the valid range for + // secp256k1 private keys. This error indicates the caller must choose + // another seed. + ErrUnusableSeed = errors.New("unusable seed") + + // ErrInvalidSeedLen describes an error in which the provided seed or + // seed length is not in the allowed range. + ErrInvalidSeedLen = fmt.Errorf("seed length must be between %d and %d "+ + "bits", MinSeedBytes*8, MaxSeedBytes*8) + + // ErrBadChecksum describes an error in which the checksum encoded with + // a serialized extended key does not match the calculated value. + ErrBadChecksum = errors.New("bad extended key checksum") + + // ErrInvalidKeyLen describes an error in which the provided serialized + // key is not the expected length. + ErrInvalidKeyLen = errors.New("the provided serialized extended key " + + "length is invalid") +) + +// masterKey is the master key used along with a random seed used to generate +// the master node in the hierarchical tree. +var masterKey = []byte("Bitcoin seed") + +// ExtendedKey houses all the information needed to support a hierarchical +// deterministic extended key. See the package overview documentation for +// more details on how to use extended keys. +type ExtendedKey struct { + key []byte // This will be the pubkey for extended pub keys + pubKey []byte // This will only be set for extended priv keys + chainCode []byte + depth uint8 + parentFP []byte + childNum uint32 + version []byte + isPrivate bool +} + +// NewExtendedKey returns a new instance of an extended key with the given +// fields. No error checking is performed here as it's only intended to be a +// convenience method used to create a populated struct. This function should +// only be used by applications that need to create custom ExtendedKeys. All +// other applications should just use NewMaster, Derive, or Neuter. +func NewExtendedKey(version, key, chainCode, parentFP []byte, depth uint8, + childNum uint32, isPrivate bool) *ExtendedKey { + + // NOTE: The pubKey field is intentionally left nil so it is only + // computed and memoized as required. + return &ExtendedKey{ + key: key, + chainCode: chainCode, + depth: depth, + parentFP: parentFP, + childNum: childNum, + version: version, + isPrivate: isPrivate, + } +} + +// pubKeyBytes returns bytes for the serialized compressed public key associated +// with this extended key in an efficient manner including memoization as +// necessary. +// +// When the extended key is already a public key, the key is simply returned as +// is since it's already in the correct form. However, when the extended key is +// a private key, the public key will be calculated and memoized so future +// accesses can simply return the cached result. +func (k *ExtendedKey) pubKeyBytes() []byte { + // Just return the key if it's already an extended public key. + if !k.isPrivate { + return k.key + } + + // This is a private extended key, so calculate and memoize the public + // key if needed. + if len(k.pubKey) == 0 { + pkx, pky := btcec.S256().ScalarBaseMult(k.key) + pubKey := btcec.PublicKey{Curve: btcec.S256(), X: pkx, Y: pky} + k.pubKey = pubKey.SerializeCompressed() + } + + return k.pubKey +} + +// IsPrivate returns whether or not the extended key is a private extended key. +// +// A private extended key can be used to derive both hardened and non-hardened +// child private and public extended keys. A public extended key can only be +// used to derive non-hardened child public extended keys. +func (k *ExtendedKey) IsPrivate() bool { + return k.isPrivate +} + +// Depth returns the current derivation level with respect to the root. +// +// The root key has depth zero, and the field has a maximum of 255 due to +// how depth is serialized. +func (k *ExtendedKey) Depth() uint8 { + return k.depth +} + +// Version returns the extended key's hardened derivation version. This can be +// used to identify the extended key's type. +func (k *ExtendedKey) Version() []byte { + return k.version +} + +// ParentFingerprint returns a fingerprint of the parent extended key from which +// this one was derived. +func (k *ExtendedKey) ParentFingerprint() uint32 { + return binary.BigEndian.Uint32(k.parentFP) +} + +// ChainCode returns the chain code part of this extended key. +// +// It is identical for both public and private extended keys. +func (k *ExtendedKey) ChainCode() []byte { + return append([]byte{}, k.chainCode...) +} + +// Derive returns a derived child extended key at the given index. +// +// IMPORTANT: if you were previously using the Child method, this method is incompatible. +// The Child method had a BIP-32 standard compatibility issue. You have to check whether +// any hardened derivations in your derivation path are affected by this issue, via the +// IsAffectedByIssue172 method and migrate the wallet if so. This method does conform +// to the standard. If you need the old behavior, use DeriveNonStandard. +// +// When this extended key is a private extended key (as determined by the IsPrivate +// function), a private extended key will be derived. Otherwise, the derived +// extended key will be also be a public extended key. +// +// When the index is greater to or equal than the HardenedKeyStart constant, the +// derived extended key will be a hardened extended key. It is only possible to +// derive a hardened extended key from a private extended key. Consequently, +// this function will return ErrDeriveHardFromPublic if a hardened child +// extended key is requested from a public extended key. +// +// A hardened extended key is useful since, as previously mentioned, it requires +// a parent private extended key to derive. In other words, normal child +// extended public keys can be derived from a parent public extended key (no +// knowledge of the parent private key) whereas hardened extended keys may not +// be. +// +// NOTE: There is an extremely small chance (< 1 in 2^127) the specific child +// index does not derive to a usable child. The ErrInvalidChild error will be +// returned if this should occur, and the caller is expected to ignore the +// invalid child and simply increment to the next index. +func (k *ExtendedKey) Derive(i uint32) (*ExtendedKey, error) { + // Prevent derivation of children beyond the max allowed depth. + if k.depth == maxUint8 { + return nil, ErrDeriveBeyondMaxDepth + } + + // There are four scenarios that could happen here: + // 1) Private extended key -> Hardened child private extended key + // 2) Private extended key -> Non-hardened child private extended key + // 3) Public extended key -> Non-hardened child public extended key + // 4) Public extended key -> Hardened child public extended key (INVALID!) + + // Case #4 is invalid, so error out early. + // A hardened child extended key may not be created from a public + // extended key. + isChildHardened := i >= HardenedKeyStart + if !k.isPrivate && isChildHardened { + return nil, ErrDeriveHardFromPublic + } + + // The data used to derive the child key depends on whether or not the + // child is hardened per [BIP32]. + // + // For hardened children: + // 0x00 || ser256(parentKey) || ser32(i) + // + // For normal children: + // serP(parentPubKey) || ser32(i) + keyLen := 33 + data := make([]byte, keyLen+4) + if isChildHardened { + // Case #1. + // When the child is a hardened child, the key is known to be a + // private key due to the above early return. Pad it with a + // leading zero as required by [BIP32] for deriving the child. + // Additionally, right align it if it's shorter than 32 bytes. + offset := 33 - len(k.key) + copy(data[offset:], k.key) + } else { + // Case #2 or #3. + // This is either a public or private extended key, but in + // either case, the data which is used to derive the child key + // starts with the secp256k1 compressed public key bytes. + copy(data, k.pubKeyBytes()) + } + binary.BigEndian.PutUint32(data[keyLen:], i) + + // Take the HMAC-SHA512 of the current key's chain code and the derived + // data: + // I = HMAC-SHA512(Key = chainCode, Data = data) + hmac512 := hmac.New(sha512.New, k.chainCode) + _, _ = hmac512.Write(data) + ilr := hmac512.Sum(nil) + + // Split "I" into two 32-byte sequences Il and Ir where: + // Il = intermediate key used to derive the child + // Ir = child chain code + il := ilr[:len(ilr)/2] + childChainCode := ilr[len(ilr)/2:] + + // Both derived public or private keys rely on treating the left 32-byte + // sequence calculated above (Il) as a 256-bit integer that must be + // within the valid range for a secp256k1 private key. There is a small + // chance (< 1 in 2^127) this condition will not hold, and in that case, + // a child extended key can't be created for this index and the caller + // should simply increment to the next index. + ilNum := new(big.Int).SetBytes(il) + if ilNum.Cmp(btcec.S256().N) >= 0 || ilNum.Sign() == 0 { + return nil, ErrInvalidChild + } + + // The algorithm used to derive the child key depends on whether or not + // a private or public child is being derived. + // + // For private children: + // childKey = parse256(Il) + parentKey + // + // For public children: + // childKey = serP(point(parse256(Il)) + parentKey) + var isPrivate bool + var childKey []byte + if k.isPrivate { + // Case #1 or #2. + // Add the parent private key to the intermediate private key to + // derive the final child key. + // + // childKey = parse256(Il) + parenKey + keyNum := new(big.Int).SetBytes(k.key) + ilNum.Add(ilNum, keyNum) + ilNum.Mod(ilNum, btcec.S256().N) + childKey = ilNum.Bytes() + isPrivate = true + } else { + // Case #3. + // Calculate the corresponding intermediate public key for + // intermediate private key. + ilx, ily := btcec.S256().ScalarBaseMult(il) + if ilx.Sign() == 0 || ily.Sign() == 0 { + return nil, ErrInvalidChild + } + + // Convert the serialized compressed parent public key into X + // and Y coordinates so it can be added to the intermediate + // public key. + pubKey, err := btcec.ParsePubKey(k.key, btcec.S256()) + if err != nil { + return nil, err + } + + // Add the intermediate public key to the parent public key to + // derive the final child key. + // + // childKey = serP(point(parse256(Il)) + parentKey) + childX, childY := btcec.S256().Add(ilx, ily, pubKey.X, pubKey.Y) + pk := btcec.PublicKey{Curve: btcec.S256(), X: childX, Y: childY} + childKey = pk.SerializeCompressed() + } + + // The fingerprint of the parent for the derived child is the first 4 + // bytes of the RIPEMD160(SHA256(parentPubKey)). + parentFP := btcutil.Hash160(k.pubKeyBytes())[:4] + return NewExtendedKey(k.version, childKey, childChainCode, parentFP, + k.depth+1, i, isPrivate), nil +} + +// Returns true if this key was affected by the BIP-32 issue in the Child +// method (since renamed to DeriveNonStandard). +func (k *ExtendedKey) IsAffectedByIssue172() bool { + return len(k.key) < 32 +} + +// Deprecated: This is a non-standard derivation that is affected by issue #172. +// 1-of-256 hardened derivations will be wrong. See note in the Derive method +// and IsAffectedByIssue172. +func (k *ExtendedKey) DeriveNonStandard(i uint32) (*ExtendedKey, error) { + if k.depth == maxUint8 { + return nil, ErrDeriveBeyondMaxDepth + } + + isChildHardened := i >= HardenedKeyStart + if !k.isPrivate && isChildHardened { + return nil, ErrDeriveHardFromPublic + } + + keyLen := 33 + data := make([]byte, keyLen+4) + if isChildHardened { + copy(data[1:], k.key) + } else { + copy(data, k.pubKeyBytes()) + } + binary.BigEndian.PutUint32(data[keyLen:], i) + + hmac512 := hmac.New(sha512.New, k.chainCode) + _, _ = hmac512.Write(data) + ilr := hmac512.Sum(nil) + + il := ilr[:len(ilr)/2] + childChainCode := ilr[len(ilr)/2:] + + ilNum := new(big.Int).SetBytes(il) + if ilNum.Cmp(btcec.S256().N) >= 0 || ilNum.Sign() == 0 { + return nil, ErrInvalidChild + } + + var isPrivate bool + var childKey []byte + if k.isPrivate { + keyNum := new(big.Int).SetBytes(k.key) + ilNum.Add(ilNum, keyNum) + ilNum.Mod(ilNum, btcec.S256().N) + childKey = ilNum.Bytes() + isPrivate = true + } else { + ilx, ily := btcec.S256().ScalarBaseMult(il) + if ilx.Sign() == 0 || ily.Sign() == 0 { + return nil, ErrInvalidChild + } + + pubKey, err := btcec.ParsePubKey(k.key, btcec.S256()) + if err != nil { + return nil, err + } + + childX, childY := btcec.S256().Add(ilx, ily, pubKey.X, pubKey.Y) + pk := btcec.PublicKey{Curve: btcec.S256(), X: childX, Y: childY} + childKey = pk.SerializeCompressed() + } + + parentFP := btcutil.Hash160(k.pubKeyBytes())[:4] + return NewExtendedKey(k.version, childKey, childChainCode, parentFP, + k.depth+1, i, isPrivate), nil +} + +// ChildNum returns the index at which the child extended key was derived. +// +// Extended keys with ChildNum value between 0 and 2^31-1 are normal child +// keys, and those with a value between 2^31 and 2^32-1 are hardened keys. +func (k *ExtendedKey) ChildIndex() uint32 { + return k.childNum +} + +// Neuter returns a new extended public key from this extended private key. The +// same extended key will be returned unaltered if it is already an extended +// public key. +// +// As the name implies, an extended public key does not have access to the +// private key, so it is not capable of signing transactions or deriving +// child extended private keys. However, it is capable of deriving further +// child extended public keys. +func (k *ExtendedKey) Neuter() (*ExtendedKey, error) { + // Already an extended public key. + if !k.isPrivate { + return k, nil + } + + // Get the associated public extended key version bytes. + version, err := chaincfg.HDPrivateKeyToPublicKeyID(k.version) + if err != nil { + return nil, err + } + + // Convert it to an extended public key. The key for the new extended + // key will simply be the pubkey of the current extended private key. + // + // This is the function N((k,c)) -> (K, c) from [BIP32]. + return NewExtendedKey(version, k.pubKeyBytes(), k.chainCode, k.parentFP, + k.depth, k.childNum, false), nil +} + +// CloneWithVersion returns a new extended key cloned from this extended key, +// but using the provided HD version bytes. The version must be a private HD +// key ID for an extended private key, and a public HD key ID for an extended +// public key. +// +// This method creates a new copy and therefore does not mutate the original +// extended key instance. +// +// Unlike Neuter(), this does NOT convert an extended private key to an +// extended public key. It is particularly useful for converting between +// standard BIP0032 extended keys (serializable to xprv/xpub) and keys based +// on the SLIP132 standard (serializable to yprv/ypub, zprv/zpub, etc.). +// +// References: +// [SLIP132]: SLIP-0132 - Registered HD version bytes for BIP-0032 +// https://github.com/satoshilabs/slips/blob/master/slip-0132.md +func (k *ExtendedKey) CloneWithVersion(version []byte) (*ExtendedKey, error) { + if len(version) != 4 { + // TODO: The semantically correct error to return here is + // ErrInvalidHDKeyID (introduced in btcsuite/btcd#1617). Update the + // error type once available in a stable btcd / chaincfg release. + return nil, chaincfg.ErrUnknownHDKeyID + } + + // Initialize a new extended key instance with the same fields as the + // current extended private/public key and the provided HD version bytes. + return NewExtendedKey(version, k.key, k.chainCode, k.parentFP, + k.depth, k.childNum, k.isPrivate), nil +} + +// ECPubKey converts the extended key to a btcec public key and returns it. +func (k *ExtendedKey) ECPubKey() (*btcec.PublicKey, error) { + return btcec.ParsePubKey(k.pubKeyBytes(), btcec.S256()) +} + +// ECPrivKey converts the extended key to a btcec private key and returns it. +// As you might imagine this is only possible if the extended key is a private +// extended key (as determined by the IsPrivate function). The ErrNotPrivExtKey +// error will be returned if this function is called on a public extended key. +func (k *ExtendedKey) ECPrivKey() (*btcec.PrivateKey, error) { + if !k.isPrivate { + return nil, ErrNotPrivExtKey + } + + privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), k.key) + return privKey, nil +} + +// Address converts the extended key to a standard bitcoin pay-to-pubkey-hash +// address for the passed network. +func (k *ExtendedKey) Address(net *chaincfg.Params) (*btcutil.AddressPubKeyHash, error) { + pkHash := btcutil.Hash160(k.pubKeyBytes()) + return btcutil.NewAddressPubKeyHash(pkHash, net) +} + +// paddedAppend appends the src byte slice to dst, returning the new slice. +// If the length of the source is smaller than the passed size, leading zero +// bytes are appended to the dst slice before appending src. +func paddedAppend(size uint, dst, src []byte) []byte { + for i := 0; i < int(size)-len(src); i++ { + dst = append(dst, 0) + } + return append(dst, src...) +} + +// String returns the extended key as a human-readable base58-encoded string. +func (k *ExtendedKey) String() string { + if len(k.key) == 0 { + return "zeroed extended key" + } + + var childNumBytes [4]byte + binary.BigEndian.PutUint32(childNumBytes[:], k.childNum) + + // The serialized format is: + // version (4) || depth (1) || parent fingerprint (4)) || + // child num (4) || chain code (32) || key data (33) || checksum (4) + serializedBytes := make([]byte, 0, serializedKeyLen+4) + serializedBytes = append(serializedBytes, k.version...) + serializedBytes = append(serializedBytes, k.depth) + serializedBytes = append(serializedBytes, k.parentFP...) + serializedBytes = append(serializedBytes, childNumBytes[:]...) + serializedBytes = append(serializedBytes, k.chainCode...) + if k.isPrivate { + serializedBytes = append(serializedBytes, 0x00) + serializedBytes = paddedAppend(32, serializedBytes, k.key) + } else { + serializedBytes = append(serializedBytes, k.pubKeyBytes()...) + } + + checkSum := chainhash.DoubleHashB(serializedBytes)[:4] + serializedBytes = append(serializedBytes, checkSum...) + return base58.Encode(serializedBytes) +} + +// IsForNet returns whether or not the extended key is associated with the +// passed bitcoin network. +func (k *ExtendedKey) IsForNet(net *chaincfg.Params) bool { + return bytes.Equal(k.version, net.HDPrivateKeyID[:]) || + bytes.Equal(k.version, net.HDPublicKeyID[:]) +} + +// SetNet associates the extended key, and any child keys yet to be derived from +// it, with the passed network. +func (k *ExtendedKey) SetNet(net *chaincfg.Params) { + if k.isPrivate { + k.version = net.HDPrivateKeyID[:] + } else { + k.version = net.HDPublicKeyID[:] + } +} + +// zero sets all bytes in the passed slice to zero. This is used to +// explicitly clear private key material from memory. +func zero(b []byte) { + lenb := len(b) + for i := 0; i < lenb; i++ { + b[i] = 0 + } +} + +// Zero manually clears all fields and bytes in the extended key. This can be +// used to explicitly clear key material from memory for enhanced security +// against memory scraping. This function only clears this particular key and +// not any children that have already been derived. +func (k *ExtendedKey) Zero() { + zero(k.key) + zero(k.pubKey) + zero(k.chainCode) + zero(k.parentFP) + k.version = nil + k.key = nil + k.depth = 0 + k.childNum = 0 + k.isPrivate = false +} + +// NewMaster creates a new master node for use in creating a hierarchical +// deterministic key chain. The seed must be between 128 and 512 bits and +// should be generated by a cryptographically secure random generation source. +// +// NOTE: There is an extremely small chance (< 1 in 2^127) the provided seed +// will derive to an unusable secret key. The ErrUnusable error will be +// returned if this should occur, so the caller must check for it and generate a +// new seed accordingly. +func NewMaster(seed []byte, net *chaincfg.Params) (*ExtendedKey, error) { + // Per [BIP32], the seed must be in range [MinSeedBytes, MaxSeedBytes]. + if len(seed) < MinSeedBytes || len(seed) > MaxSeedBytes { + return nil, ErrInvalidSeedLen + } + + // First take the HMAC-SHA512 of the master key and the seed data: + // I = HMAC-SHA512(Key = "Bitcoin seed", Data = S) + hmac512 := hmac.New(sha512.New, masterKey) + _, _ = hmac512.Write(seed) + lr := hmac512.Sum(nil) + + // Split "I" into two 32-byte sequences Il and Ir where: + // Il = master secret key + // Ir = master chain code + secretKey := lr[:len(lr)/2] + chainCode := lr[len(lr)/2:] + + // Ensure the key in usable. + secretKeyNum := new(big.Int).SetBytes(secretKey) + if secretKeyNum.Cmp(btcec.S256().N) >= 0 || secretKeyNum.Sign() == 0 { + return nil, ErrUnusableSeed + } + + parentFP := []byte{0x00, 0x00, 0x00, 0x00} + return NewExtendedKey(net.HDPrivateKeyID[:], secretKey, chainCode, + parentFP, 0, 0, true), nil +} + +// NewKeyFromString returns a new extended key instance from a base58-encoded +// extended key. +func NewKeyFromString(key string) (*ExtendedKey, error) { + // The base58-decoded extended key must consist of a serialized payload + // plus an additional 4 bytes for the checksum. + decoded := base58.Decode(key) + if len(decoded) != serializedKeyLen+4 { + return nil, ErrInvalidKeyLen + } + + // The serialized format is: + // version (4) || depth (1) || parent fingerprint (4)) || + // child num (4) || chain code (32) || key data (33) || checksum (4) + + // Split the payload and checksum up and ensure the checksum matches. + payload := decoded[:len(decoded)-4] + checkSum := decoded[len(decoded)-4:] + expectedCheckSum := chainhash.DoubleHashB(payload)[:4] + if !bytes.Equal(checkSum, expectedCheckSum) { + return nil, ErrBadChecksum + } + + // Deserialize each of the payload fields. + version := payload[:4] + depth := payload[4:5][0] + parentFP := payload[5:9] + childNum := binary.BigEndian.Uint32(payload[9:13]) + chainCode := payload[13:45] + keyData := payload[45:78] + + // The key data is a private key if it starts with 0x00. Serialized + // compressed pubkeys either start with 0x02 or 0x03. + isPrivate := keyData[0] == 0x00 + if isPrivate { + // Ensure the private key is valid. It must be within the range + // of the order of the secp256k1 curve and not be 0. + keyData = keyData[1:] + keyNum := new(big.Int).SetBytes(keyData) + if keyNum.Cmp(btcec.S256().N) >= 0 || keyNum.Sign() == 0 { + return nil, ErrUnusableSeed + } + } else { + // Ensure the public key parses correctly and is actually on the + // secp256k1 curve. + _, err := btcec.ParsePubKey(keyData, btcec.S256()) + if err != nil { + return nil, err + } + } + + return NewExtendedKey(version, keyData, chainCode, parentFP, depth, + childNum, isPrivate), nil +} + +// GenerateSeed returns a cryptographically secure random seed that can be used +// as the input for the NewMaster function to generate a new master node. +// +// The length is in bytes and it must be between 16 and 64 (128 to 512 bits). +// The recommended length is 32 (256 bits) as defined by the RecommendedSeedLen +// constant. +func GenerateSeed(length uint8) ([]byte, error) { + // Per [BIP32], the seed must be in range [MinSeedBytes, MaxSeedBytes]. + if length < MinSeedBytes || length > MaxSeedBytes { + return nil, ErrInvalidSeedLen + } + + buf := make([]byte, length) + _, err := rand.Read(buf) + if err != nil { + return nil, err + } + + return buf, nil +} diff --git a/btcutil/hdkeychain/extendedkey_test.go b/btcutil/hdkeychain/extendedkey_test.go new file mode 100644 index 0000000000..dd6deb514e --- /dev/null +++ b/btcutil/hdkeychain/extendedkey_test.go @@ -0,0 +1,1209 @@ +// Copyright (c) 2014-2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package hdkeychain + +// References: +// [BIP32]: BIP0032 - Hierarchical Deterministic Wallets +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "errors" + "math" + "reflect" + "testing" + + "github.com/btcsuite/btcd/chaincfg" +) + +// TestBIP0032Vectors tests the vectors provided by [BIP32] to ensure the +// derivation works as intended. +func TestBIP0032Vectors(t *testing.T) { + // The master seeds for each of the two test vectors in [BIP32]. + testVec1MasterHex := "000102030405060708090a0b0c0d0e0f" + testVec2MasterHex := "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" + testVec3MasterHex := "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" + hkStart := uint32(0x80000000) + + tests := []struct { + name string + master string + path []uint32 + wantPub string + wantPriv string + net *chaincfg.Params + }{ + // Test vector 1 + { + name: "test vector 1 chain m", + master: testVec1MasterHex, + path: []uint32{}, + wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 1 chain m/0H", + master: testVec1MasterHex, + path: []uint32{hkStart}, + wantPub: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", + wantPriv: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 1 chain m/0H/1", + master: testVec1MasterHex, + path: []uint32{hkStart, 1}, + wantPub: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", + wantPriv: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 1 chain m/0H/1/2H", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2}, + wantPub: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", + wantPriv: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 1 chain m/0H/1/2H/2", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2, 2}, + wantPub: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", + wantPriv: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 1 chain m/0H/1/2H/2/1000000000", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, + wantPub: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", + wantPriv: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", + net: &chaincfg.MainNetParams, + }, + + // Test vector 2 + { + name: "test vector 2 chain m", + master: testVec2MasterHex, + path: []uint32{}, + wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 2 chain m/0", + master: testVec2MasterHex, + path: []uint32{0}, + wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", + wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 2 chain m/0/2147483647H", + master: testVec2MasterHex, + path: []uint32{0, hkStart + 2147483647}, + wantPub: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", + wantPriv: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 2 chain m/0/2147483647H/1", + master: testVec2MasterHex, + path: []uint32{0, hkStart + 2147483647, 1}, + wantPub: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", + wantPriv: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 2 chain m/0/2147483647H/1/2147483646H", + master: testVec2MasterHex, + path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646}, + wantPub: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", + wantPriv: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 2 chain m/0/2147483647H/1/2147483646H/2", + master: testVec2MasterHex, + path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646, 2}, + wantPub: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", + wantPriv: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + net: &chaincfg.MainNetParams, + }, + + // Test vector 3 + { + name: "test vector 3 chain m", + master: testVec3MasterHex, + path: []uint32{}, + wantPub: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", + wantPriv: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", + net: &chaincfg.MainNetParams, + }, + { + name: "test vector 3 chain m/0H", + master: testVec3MasterHex, + path: []uint32{hkStart}, + wantPub: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", + wantPriv: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", + net: &chaincfg.MainNetParams, + }, + + // Test vector 1 - Testnet + { + name: "test vector 1 chain m - testnet", + master: testVec1MasterHex, + path: []uint32{}, + wantPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", + wantPriv: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", + net: &chaincfg.TestNet3Params, + }, + { + name: "test vector 1 chain m/0H - testnet", + master: testVec1MasterHex, + path: []uint32{hkStart}, + wantPub: "tpubD8eQVK4Kdxg3gHrF62jGP7dKVCoYiEB8dFSpuTawkL5YxTus5j5pf83vaKnii4bc6v2NVEy81P2gYrJczYne3QNNwMTS53p5uzDyHvnw2jm", + wantPriv: "tprv8bxNLu25VazNnppTCP4fyhyCvBHcYtzE3wr3cwYeL4HA7yf6TLGEUdS4QC1vLT63TkjRssqJe4CvGNEC8DzW5AoPUw56D1Ayg6HY4oy8QZ9", + net: &chaincfg.TestNet3Params, + }, + { + name: "test vector 1 chain m/0H/1 - testnet", + master: testVec1MasterHex, + path: []uint32{hkStart, 1}, + wantPub: "tpubDApXh6cD2fZ7WjtgpHd8yrWyYaneiFuRZa7fVjMkgxsmC1QzoXW8cgx9zQFJ81Jx4deRGfRE7yXA9A3STsxXj4CKEZJHYgpMYikkas9DBTP", + wantPriv: "tprv8e8VYgZxtHsSdGrtvdxYaSrryZGiYviWzGWtDDKTGh5NMXAEB8gYSCLHpFCywNs5uqV7ghRjimALQJkRFZnUrLHpzi2pGkwqLtbubgWuQ8q", + net: &chaincfg.TestNet3Params, + }, + { + name: "test vector 1 chain m/0H/1/2H - testnet", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2}, + wantPub: "tpubDDRojdS4jYQXNugn4t2WLrZ7mjfAyoVQu7MLk4eurqFCbrc7cHLZX8W5YRS8ZskGR9k9t3PqVv68bVBjAyW4nWM9pTGRddt3GQftg6MVQsm", + wantPriv: "tprv8gjmbDPpbAirVSezBEMuwSu1Ci9EpUJWKokZTYccSZSomNMLytWyLdtDNHRbucNaRJWWHANf9AzEdWVAqahfyRjVMKbNRhBmxAM8EJr7R15", + net: &chaincfg.TestNet3Params, + }, + { + name: "test vector 1 chain m/0H/1/2H/2 - testnet", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2, 2}, + wantPub: "tpubDFfCa4Z1v25WTPAVm9EbEMiRrYwucPocLbEe12BPBGooxxEUg42vihy1DkRWyftztTsL23snYezF9uXjGGwGW6pQjEpcTpmsH6ajpf4CVPn", + wantPriv: "tprv8iyAReWmmePqZv8hsVZzpx4KHXRyT4chmHdriW95m11R8Tyi3fDLYDM93bq4NGn1V6eCu5cE3zSQ6hPd31F2ApKXkZgTyn1V78pHjkq1V2v", + net: &chaincfg.TestNet3Params, + }, + { + name: "test vector 1 chain m/0H/1/2H/2/1000000000 - testnet", + master: testVec1MasterHex, + path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000}, + wantPub: "tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF", + wantPriv: "tprv8kgvuL81tmn36Fv9z38j8f4K5m1HGZRjZY2QxnXDy5PuqbP6a5TzoKWCgTcGHBu66W3TgSbAu2yX6sPza5FkHmy564Sh6gmCPUNeUt4yj2x", + net: &chaincfg.TestNet3Params, + }, + } + +tests: + for i, test := range tests { + masterSeed, err := hex.DecodeString(test.master) + if err != nil { + t.Errorf("DecodeString #%d (%s): unexpected error: %v", + i, test.name, err) + continue + } + + extKey, err := NewMaster(masterSeed, test.net) + if err != nil { + t.Errorf("NewMaster #%d (%s): unexpected error when "+ + "creating new master key: %v", i, test.name, + err) + continue + } + + for _, childNum := range test.path { + var err error + extKey, err = extKey.Derive(childNum) + if err != nil { + t.Errorf("err: %v", err) + continue tests + } + } + + if extKey.Depth() != uint8(len(test.path)) { + t.Errorf("Depth of key %d should match fixture path: %v", + extKey.Depth(), len(test.path)) + continue + } + + privStr := extKey.String() + if privStr != test.wantPriv { + t.Errorf("Serialize #%d (%s): mismatched serialized "+ + "private extended key -- got: %s, want: %s", i, + test.name, privStr, test.wantPriv) + continue + } + + pubKey, err := extKey.Neuter() + if err != nil { + t.Errorf("Neuter #%d (%s): unexpected error: %v ", i, + test.name, err) + continue + } + + // Neutering a second time should have no effect. + pubKey, err = pubKey.Neuter() + if err != nil { + t.Errorf("Neuter #%d (%s): unexpected error: %v", i, + test.name, err) + return + } + + pubStr := pubKey.String() + if pubStr != test.wantPub { + t.Errorf("Neuter #%d (%s): mismatched serialized "+ + "public extended key -- got: %s, want: %s", i, + test.name, pubStr, test.wantPub) + continue + } + } +} + +// TestPrivateDerivation tests several vectors which derive private keys from +// other private keys works as intended. +func TestPrivateDerivation(t *testing.T) { + // The private extended keys for test vectors in [BIP32]. + testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" + + tests := []struct { + name string + master string + path []uint32 + wantPriv string + }{ + // Test vector 1 + { + name: "test vector 1 chain m", + master: testVec1MasterPrivKey, + path: []uint32{}, + wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + }, + { + name: "test vector 1 chain m/0", + master: testVec1MasterPrivKey, + path: []uint32{0}, + wantPriv: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R", + }, + { + name: "test vector 1 chain m/0/1", + master: testVec1MasterPrivKey, + path: []uint32{0, 1}, + wantPriv: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G", + }, + { + name: "test vector 1 chain m/0/1/2", + master: testVec1MasterPrivKey, + path: []uint32{0, 1, 2}, + wantPriv: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi", + }, + { + name: "test vector 1 chain m/0/1/2/2", + master: testVec1MasterPrivKey, + path: []uint32{0, 1, 2, 2}, + wantPriv: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh", + }, + { + name: "test vector 1 chain m/0/1/2/2/1000000000", + master: testVec1MasterPrivKey, + path: []uint32{0, 1, 2, 2, 1000000000}, + wantPriv: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz", + }, + + // Test vector 2 + { + name: "test vector 2 chain m", + master: testVec2MasterPrivKey, + path: []uint32{}, + wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + }, + { + name: "test vector 2 chain m/0", + master: testVec2MasterPrivKey, + path: []uint32{0}, + wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + }, + { + name: "test vector 2 chain m/0/2147483647", + master: testVec2MasterPrivKey, + path: []uint32{0, 2147483647}, + wantPriv: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6", + }, + { + name: "test vector 2 chain m/0/2147483647/1", + master: testVec2MasterPrivKey, + path: []uint32{0, 2147483647, 1}, + wantPriv: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm", + }, + { + name: "test vector 2 chain m/0/2147483647/1/2147483646", + master: testVec2MasterPrivKey, + path: []uint32{0, 2147483647, 1, 2147483646}, + wantPriv: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp", + }, + { + name: "test vector 2 chain m/0/2147483647/1/2147483646/2", + master: testVec2MasterPrivKey, + path: []uint32{0, 2147483647, 1, 2147483646, 2}, + wantPriv: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam", + }, + + // Custom tests to trigger specific conditions. + { + // Seed 000000000000000000000000000000da. + name: "Derived privkey with zero high byte m/0", + master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz", + path: []uint32{0}, + wantPriv: "xprv9uC5JqtViMmgcAMUxcsBCBFA7oYCNs4bozPbyvLfddjHou4rMiGEHipz94xNaPb1e4f18TRoPXfiXx4C3cDAcADqxCSRSSWLvMBRWPctSN9", + }, + } + +tests: + for i, test := range tests { + extKey, err := NewKeyFromString(test.master) + if err != nil { + t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ + "creating extended key: %v", i, test.name, + err) + continue + } + + for _, childNum := range test.path { + var err error + extKey, err = extKey.Derive(childNum) + if err != nil { + t.Errorf("err: %v", err) + continue tests + } + } + + privStr := extKey.String() + if privStr != test.wantPriv { + t.Errorf("Derive #%d (%s): mismatched serialized "+ + "private extended key -- got: %s, want: %s", i, + test.name, privStr, test.wantPriv) + continue + } + } +} + +// TestPublicDerivation tests several vectors which derive public keys from +// other public keys works as intended. +func TestPublicDerivation(t *testing.T) { + // The public extended keys for test vectors in [BIP32]. + testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" + testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" + + tests := []struct { + name string + master string + path []uint32 + wantPub string + }{ + // Test vector 1 + { + name: "test vector 1 chain m", + master: testVec1MasterPubKey, + path: []uint32{}, + wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + }, + { + name: "test vector 1 chain m/0", + master: testVec1MasterPubKey, + path: []uint32{0}, + wantPub: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1", + }, + { + name: "test vector 1 chain m/0/1", + master: testVec1MasterPubKey, + path: []uint32{0, 1}, + wantPub: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr", + }, + { + name: "test vector 1 chain m/0/1/2", + master: testVec1MasterPubKey, + path: []uint32{0, 1, 2}, + wantPub: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv", + }, + { + name: "test vector 1 chain m/0/1/2/2", + master: testVec1MasterPubKey, + path: []uint32{0, 1, 2, 2}, + wantPub: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp", + }, + { + name: "test vector 1 chain m/0/1/2/2/1000000000", + master: testVec1MasterPubKey, + path: []uint32{0, 1, 2, 2, 1000000000}, + wantPub: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9", + }, + + // Test vector 2 + { + name: "test vector 2 chain m", + master: testVec2MasterPubKey, + path: []uint32{}, + wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + }, + { + name: "test vector 2 chain m/0", + master: testVec2MasterPubKey, + path: []uint32{0}, + wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", + }, + { + name: "test vector 2 chain m/0/2147483647", + master: testVec2MasterPubKey, + path: []uint32{0, 2147483647}, + wantPub: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD", + }, + { + name: "test vector 2 chain m/0/2147483647/1", + master: testVec2MasterPubKey, + path: []uint32{0, 2147483647, 1}, + wantPub: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b", + }, + { + name: "test vector 2 chain m/0/2147483647/1/2147483646", + master: testVec2MasterPubKey, + path: []uint32{0, 2147483647, 1, 2147483646}, + wantPub: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta", + }, + { + name: "test vector 2 chain m/0/2147483647/1/2147483646/2", + master: testVec2MasterPubKey, + path: []uint32{0, 2147483647, 1, 2147483646, 2}, + wantPub: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK", + }, + } + +tests: + for i, test := range tests { + extKey, err := NewKeyFromString(test.master) + if err != nil { + t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ + "creating extended key: %v", i, test.name, + err) + continue + } + + for _, childNum := range test.path { + var err error + extKey, err = extKey.Derive(childNum) + if err != nil { + t.Errorf("err: %v", err) + continue tests + } + } + + pubStr := extKey.String() + if pubStr != test.wantPub { + t.Errorf("Derive #%d (%s): mismatched serialized "+ + "public extended key -- got: %s, want: %s", i, + test.name, pubStr, test.wantPub) + continue + } + } +} + +// TestGenerateSeed ensures the GenerateSeed function works as intended. +func TestGenerateSeed(t *testing.T) { + wantErr := errors.New("seed length must be between 128 and 512 bits") + + tests := []struct { + name string + length uint8 + err error + }{ + // Test various valid lengths. + {name: "16 bytes", length: 16}, + {name: "17 bytes", length: 17}, + {name: "20 bytes", length: 20}, + {name: "32 bytes", length: 32}, + {name: "64 bytes", length: 64}, + + // Test invalid lengths. + {name: "15 bytes", length: 15, err: wantErr}, + {name: "65 bytes", length: 65, err: wantErr}, + } + + for i, test := range tests { + seed, err := GenerateSeed(test.length) + if !reflect.DeepEqual(err, test.err) { + t.Errorf("GenerateSeed #%d (%s): unexpected error -- "+ + "want %v, got %v", i, test.name, test.err, err) + continue + } + + if test.err == nil && len(seed) != int(test.length) { + t.Errorf("GenerateSeed #%d (%s): length mismatch -- "+ + "got %d, want %d", i, test.name, len(seed), + test.length) + continue + } + } +} + +// TestExtendedKeyAPI ensures the API on the ExtendedKey type works as intended. +func TestExtendedKeyAPI(t *testing.T) { + tests := []struct { + name string + extKey string + isPrivate bool + parentFP uint32 + chainCode []byte + childNum uint32 + privKey string + privKeyErr error + pubKey string + address string + }{ + { + name: "test vector 1 master node private", + extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + isPrivate: true, + parentFP: 0, + chainCode: []byte{135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73, 222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8}, + childNum: 0, + privKey: "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", + pubKey: "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", + address: "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma", + }, + { + name: "test vector 1 chain m/0H/1/2H public", + extKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", + isPrivate: false, + parentFP: 3203769081, + chainCode: []byte{4, 70, 107, 156, 200, 225, 97, 233, 102, 64, 156, 165, 41, 134, 197, 132, 240, 126, 157, 200, 31, 115, 93, 182, 131, 195, 255, 110, 199, 177, 80, 63}, + childNum: 2147483650, + privKeyErr: ErrNotPrivExtKey, + pubKey: "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", + address: "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x", + }, + } + + for i, test := range tests { + key, err := NewKeyFromString(test.extKey) + if err != nil { + t.Errorf("NewKeyFromString #%d (%s): unexpected "+ + "error: %v", i, test.name, err) + continue + } + + if key.IsPrivate() != test.isPrivate { + t.Errorf("IsPrivate #%d (%s): mismatched key type -- "+ + "want private %v, got private %v", i, test.name, + test.isPrivate, key.IsPrivate()) + continue + } + + parentFP := key.ParentFingerprint() + if parentFP != test.parentFP { + t.Errorf("ParentFingerprint #%d (%s): mismatched "+ + "parent fingerprint -- want %d, got %d", i, + test.name, test.parentFP, parentFP) + continue + } + + chainCode := key.ChainCode() + if !bytes.Equal(chainCode, test.chainCode) { + t.Errorf("ChainCode #%d (%s): want %v, got %v", i, + test.name, test.chainCode, chainCode) + continue + } + + childIndex := key.ChildIndex() + if childIndex != test.childNum { + t.Errorf("ChildIndex #%d (%s): want %d, got %d", i, + test.name, test.childNum, childIndex) + continue + } + + serializedKey := key.String() + if serializedKey != test.extKey { + t.Errorf("String #%d (%s): mismatched serialized key "+ + "-- want %s, got %s", i, test.name, test.extKey, + serializedKey) + continue + } + + privKey, err := key.ECPrivKey() + if !reflect.DeepEqual(err, test.privKeyErr) { + t.Errorf("ECPrivKey #%d (%s): mismatched error: want "+ + "%v, got %v", i, test.name, test.privKeyErr, err) + continue + } + if test.privKeyErr == nil { + privKeyStr := hex.EncodeToString(privKey.Serialize()) + if privKeyStr != test.privKey { + t.Errorf("ECPrivKey #%d (%s): mismatched "+ + "private key -- want %s, got %s", i, + test.name, test.privKey, privKeyStr) + continue + } + } + + pubKey, err := key.ECPubKey() + if err != nil { + t.Errorf("ECPubKey #%d (%s): unexpected error: %v", i, + test.name, err) + continue + } + pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed()) + if pubKeyStr != test.pubKey { + t.Errorf("ECPubKey #%d (%s): mismatched public key -- "+ + "want %s, got %s", i, test.name, test.pubKey, + pubKeyStr) + continue + } + + addr, err := key.Address(&chaincfg.MainNetParams) + if err != nil { + t.Errorf("Address #%d (%s): unexpected error: %v", i, + test.name, err) + continue + } + if addr.EncodeAddress() != test.address { + t.Errorf("Address #%d (%s): mismatched address -- want "+ + "%s, got %s", i, test.name, test.address, + addr.EncodeAddress()) + continue + } + } +} + +// TestNet ensures the network related APIs work as intended. +func TestNet(t *testing.T) { + tests := []struct { + name string + key string + origNet *chaincfg.Params + newNet *chaincfg.Params + newPriv string + newPub string + isPrivate bool + }{ + // Private extended keys. + { + name: "mainnet -> simnet", + key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + origNet: &chaincfg.MainNetParams, + newNet: &chaincfg.SimNetParams, + newPriv: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", + newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", + isPrivate: true, + }, + { + name: "simnet -> mainnet", + key: "sprv8Erh3X3hFeKunvVdAGQQtambRPapECWiTDtvsTGdyrhzhbYgnSZajRRWbihzvq4AM4ivm6uso31VfKaukwJJUs3GYihXP8ebhMb3F2AHu3P", + origNet: &chaincfg.SimNetParams, + newNet: &chaincfg.MainNetParams, + newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + isPrivate: true, + }, + { + name: "mainnet -> regtest", + key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + origNet: &chaincfg.MainNetParams, + newNet: &chaincfg.RegressionNetParams, + newPriv: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", + newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", + isPrivate: true, + }, + { + name: "regtest -> mainnet", + key: "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", + origNet: &chaincfg.RegressionNetParams, + newNet: &chaincfg.MainNetParams, + newPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + isPrivate: true, + }, + + // Public extended keys. + { + name: "mainnet -> simnet", + key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + origNet: &chaincfg.MainNetParams, + newNet: &chaincfg.SimNetParams, + newPub: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", + isPrivate: false, + }, + { + name: "simnet -> mainnet", + key: "spub4Tr3T2ab61tD1Qa6GHwRFiiKyRRJdfEZpSpXfqgFYCEyaPsqKysqHDjzSzMJSiUEGbcsG3w2SLMoTqn44B8x6u3MLRRkYfACTUBnHK79THk", + origNet: &chaincfg.SimNetParams, + newNet: &chaincfg.MainNetParams, + newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + isPrivate: false, + }, + { + name: "mainnet -> regtest", + key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + origNet: &chaincfg.MainNetParams, + newNet: &chaincfg.RegressionNetParams, + newPub: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", + isPrivate: false, + }, + { + name: "regtest -> mainnet", + key: "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp", + origNet: &chaincfg.RegressionNetParams, + newNet: &chaincfg.MainNetParams, + newPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + isPrivate: false, + }, + } + + for i, test := range tests { + extKey, err := NewKeyFromString(test.key) + if err != nil { + t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ + "creating extended key: %v", i, test.name, + err) + continue + } + + if !extKey.IsForNet(test.origNet) { + t.Errorf("IsForNet #%d (%s): key is not for expected "+ + "network %v", i, test.name, test.origNet.Name) + continue + } + + extKey.SetNet(test.newNet) + if !extKey.IsForNet(test.newNet) { + t.Errorf("SetNet/IsForNet #%d (%s): key is not for "+ + "expected network %v", i, test.name, + test.newNet.Name) + continue + } + + if test.isPrivate { + privStr := extKey.String() + if privStr != test.newPriv { + t.Errorf("Serialize #%d (%s): mismatched serialized "+ + "private extended key -- got: %s, want: %s", i, + test.name, privStr, test.newPriv) + continue + } + + extKey, err = extKey.Neuter() + if err != nil { + t.Errorf("Neuter #%d (%s): unexpected error: %v ", i, + test.name, err) + continue + } + } + + pubStr := extKey.String() + if pubStr != test.newPub { + t.Errorf("Neuter #%d (%s): mismatched serialized "+ + "public extended key -- got: %s, want: %s", i, + test.name, pubStr, test.newPub) + continue + } + } +} + +// TestErrors performs some negative tests for various invalid cases to ensure +// the errors are handled properly. +func TestErrors(t *testing.T) { + // Should get an error when seed has too few bytes. + net := &chaincfg.MainNetParams + _, err := NewMaster(bytes.Repeat([]byte{0x00}, 15), net) + if err != ErrInvalidSeedLen { + t.Fatalf("NewMaster: mismatched error -- got: %v, want: %v", + err, ErrInvalidSeedLen) + } + + // Should get an error when seed has too many bytes. + _, err = NewMaster(bytes.Repeat([]byte{0x00}, 65), net) + if err != ErrInvalidSeedLen { + t.Fatalf("NewMaster: mismatched error -- got: %v, want: %v", + err, ErrInvalidSeedLen) + } + + // Generate a new key and neuter it to a public extended key. + seed, err := GenerateSeed(RecommendedSeedLen) + if err != nil { + t.Fatalf("GenerateSeed: unexpected error: %v", err) + } + extKey, err := NewMaster(seed, net) + if err != nil { + t.Fatalf("NewMaster: unexpected error: %v", err) + } + pubKey, err := extKey.Neuter() + if err != nil { + t.Fatalf("Neuter: unexpected error: %v", err) + } + + // Deriving a hardened child extended key should fail from a public key. + _, err = pubKey.Derive(HardenedKeyStart) + if err != ErrDeriveHardFromPublic { + t.Fatalf("Derive: mismatched error -- got: %v, want: %v", + err, ErrDeriveHardFromPublic) + } + + // NewKeyFromString failure tests. + tests := []struct { + name string + key string + err error + neuter bool + neuterErr error + }{ + { + name: "invalid key length", + key: "xpub1234", + err: ErrInvalidKeyLen, + }, + { + name: "bad checksum", + key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EBygr15", + err: ErrBadChecksum, + }, + { + name: "pubkey not on curve", + key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ1hr9Rwbk95YadvBkQXxzHBSngB8ndpW6QH7zhhsXZ2jHyZqPjk", + err: errors.New("invalid square root"), + }, + { + name: "unsupported version", + key: "xbad4LfUL9eKmA66w2GJdVMqhvDmYGJpTGjWRAtjHqoUY17sGaymoMV9Cm3ocn9Ud6Hh2vLFVC7KSKCRVVrqc6dsEdsTjRV1WUmkK85YEUujAPX", + err: nil, + neuter: true, + neuterErr: chaincfg.ErrUnknownHDKeyID, + }, + } + + for i, test := range tests { + extKey, err := NewKeyFromString(test.key) + if !reflect.DeepEqual(err, test.err) { + t.Errorf("NewKeyFromString #%d (%s): mismatched error "+ + "-- got: %v, want: %v", i, test.name, err, + test.err) + continue + } + + if test.neuter { + _, err := extKey.Neuter() + if !reflect.DeepEqual(err, test.neuterErr) { + t.Errorf("Neuter #%d (%s): mismatched error "+ + "-- got: %v, want: %v", i, test.name, + err, test.neuterErr) + continue + } + } + } +} + +// TestZero ensures that zeroing an extended key works as intended. +func TestZero(t *testing.T) { + tests := []struct { + name string + master string + extKey string + net *chaincfg.Params + }{ + // Test vector 1 + { + name: "test vector 1 chain m", + master: "000102030405060708090a0b0c0d0e0f", + extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + net: &chaincfg.MainNetParams, + }, + + // Test vector 2 + { + name: "test vector 2 chain m", + master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + extKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + net: &chaincfg.MainNetParams, + }, + } + + // Use a closure to test that a key is zeroed since the tests create + // keys in different ways and need to test the same things multiple + // times. + testZeroed := func(i int, testName string, key *ExtendedKey) bool { + // Zeroing a key should result in it no longer being private + if key.IsPrivate() { + t.Errorf("IsPrivate #%d (%s): mismatched key type -- "+ + "want private %v, got private %v", i, testName, + false, key.IsPrivate()) + return false + } + + parentFP := key.ParentFingerprint() + if parentFP != 0 { + t.Errorf("ParentFingerprint #%d (%s): mismatched "+ + "parent fingerprint -- want %d, got %d", i, + testName, 0, parentFP) + return false + } + + wantKey := "zeroed extended key" + serializedKey := key.String() + if serializedKey != wantKey { + t.Errorf("String #%d (%s): mismatched serialized key "+ + "-- want %s, got %s", i, testName, wantKey, + serializedKey) + return false + } + + wantErr := ErrNotPrivExtKey + _, err := key.ECPrivKey() + if !reflect.DeepEqual(err, wantErr) { + t.Errorf("ECPrivKey #%d (%s): mismatched error: want "+ + "%v, got %v", i, testName, wantErr, err) + return false + } + + wantErr = errors.New("pubkey string is empty") + _, err = key.ECPubKey() + if !reflect.DeepEqual(err, wantErr) { + t.Errorf("ECPubKey #%d (%s): mismatched error: want "+ + "%v, got %v", i, testName, wantErr, err) + return false + } + + wantAddr := "1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E" + addr, err := key.Address(&chaincfg.MainNetParams) + if err != nil { + t.Errorf("Address #%d (%s): unexpected error: %v", i, + testName, err) + return false + } + if addr.EncodeAddress() != wantAddr { + t.Errorf("Address #%d (%s): mismatched address -- want "+ + "%s, got %s", i, testName, wantAddr, + addr.EncodeAddress()) + return false + } + + return true + } + + for i, test := range tests { + // Create new key from seed and get the neutered version. + masterSeed, err := hex.DecodeString(test.master) + if err != nil { + t.Errorf("DecodeString #%d (%s): unexpected error: %v", + i, test.name, err) + continue + } + key, err := NewMaster(masterSeed, test.net) + if err != nil { + t.Errorf("NewMaster #%d (%s): unexpected error when "+ + "creating new master key: %v", i, test.name, + err) + continue + } + neuteredKey, err := key.Neuter() + if err != nil { + t.Errorf("Neuter #%d (%s): unexpected error: %v", i, + test.name, err) + continue + } + + // Ensure both non-neutered and neutered keys are zeroed + // properly. + key.Zero() + if !testZeroed(i, test.name+" from seed not neutered", key) { + continue + } + neuteredKey.Zero() + if !testZeroed(i, test.name+" from seed neutered", key) { + continue + } + + // Deserialize key and get the neutered version. + key, err = NewKeyFromString(test.extKey) + if err != nil { + t.Errorf("NewKeyFromString #%d (%s): unexpected "+ + "error: %v", i, test.name, err) + continue + } + neuteredKey, err = key.Neuter() + if err != nil { + t.Errorf("Neuter #%d (%s): unexpected error: %v", i, + test.name, err) + continue + } + + // Ensure both non-neutered and neutered keys are zeroed + // properly. + key.Zero() + if !testZeroed(i, test.name+" deserialized not neutered", key) { + continue + } + neuteredKey.Zero() + if !testZeroed(i, test.name+" deserialized neutered", key) { + continue + } + } +} + +// TestMaximumDepth ensures that attempting to retrieve a child key when already +// at the maximum depth is not allowed. The serialization of a BIP32 key uses +// uint8 to encode the depth. This implicitly bounds the depth of the tree to +// 255 derivations. Here we test that an error is returned after 'max uint8'. +func TestMaximumDepth(t *testing.T) { + net := &chaincfg.MainNetParams + extKey, err := NewMaster([]byte(`abcd1234abcd1234abcd1234abcd1234`), net) + if err != nil { + t.Fatalf("NewMaster: unexpected error: %v", err) + } + + for i := uint8(0); i < math.MaxUint8; i++ { + if extKey.Depth() != i { + t.Fatalf("extendedkey depth %d should match expected value %d", + extKey.Depth(), i) + } + newKey, err := extKey.Derive(1) + if err != nil { + t.Fatalf("Derive: unexpected error: %v", err) + } + extKey = newKey + } + + noKey, err := extKey.Derive(1) + if err != ErrDeriveBeyondMaxDepth { + t.Fatalf("Derive: mismatched error: want %v, got %v", + ErrDeriveBeyondMaxDepth, err) + } + if noKey != nil { + t.Fatal("Derive: deriving 256th key should not succeed") + } +} + +// TestCloneWithVersion ensures proper conversion between standard and SLIP132 +// extended keys. +// +// The following tool was used for generating the tests: +// https://jlopp.github.io/xpub-converter +func TestCloneWithVersion(t *testing.T) { + tests := []struct { + name string + key string + version []byte + want string + wantErr error + }{ + { + name: "test xpub to zpub", + key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + version: []byte{0x04, 0xb2, 0x47, 0x46}, + want: "zpub6jftahH18ngZxUuv6oSniLNrBCSSE1B4EEU59bwTCEt8x6aS6b2mdfLxbS4QS53g85SWWP6wexqeer516433gYpZQoJie2tcMYdJ1SYYYAL", + }, + { + name: "test zpub to xpub", + key: "zpub6jftahH18ngZxUuv6oSniLNrBCSSE1B4EEU59bwTCEt8x6aS6b2mdfLxbS4QS53g85SWWP6wexqeer516433gYpZQoJie2tcMYdJ1SYYYAL", + version: []byte{0x04, 0x88, 0xb2, 0x1e}, + want: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + }, + { + name: "test xprv to zprv", + key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + version: []byte{0x04, 0xb2, 0x43, 0x0c}, + want: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", + }, + { + name: "test zprv to xprv", + key: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", + version: []byte{0x04, 0x88, 0xad, 0xe4}, + want: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + }, + { + name: "test invalid key id", + key: "zprvAWgYBBk7JR8GjzqSzmunMCS7dAbwpYTCs1YUMDXqduMA5JFHZ3iX5s2UkAR6vBdcCYYa1S5o1fVLrKsrnpCQ4WpUd6aVUWP1bS2Yy5DoaKv", + version: []byte{0x4B, 0x1D}, + wantErr: chaincfg.ErrUnknownHDKeyID, + }, + } + + for i, test := range tests { + extKey, err := NewKeyFromString(test.key) + if err != nil { + panic(err) // This is never expected to fail. + } + + got, err := extKey.CloneWithVersion(test.version) + if !reflect.DeepEqual(err, test.wantErr) { + t.Errorf("CloneWithVersion #%d (%s): unexpected error -- "+ + "want %v, got %v", i, test.name, test.wantErr, err) + continue + } + + if test.wantErr == nil { + if k := got.String(); k != test.want { + t.Errorf("CloneWithVersion #%d (%s): "+ + "got %s, want %s", i, test.name, k, test.want) + continue + } + } + } +} + +// TestLeadingZero ensures that deriving children from keys with a leading zero byte is done according +// to the BIP-32 standard and that the legacy method generates a backwards-compatible result. +func TestLeadingZero(t *testing.T) { + // The 400th seed results in a m/0' public key with a leading zero, allowing us to test + // the desired behavior. + ii := 399 + seed := make([]byte, 32) + binary.BigEndian.PutUint32(seed[28:], uint32(ii)) + masterKey, err := NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + t.Fatalf("hdkeychain.NewMaster failed: %v", err) + } + child0, err := masterKey.Derive(0 + HardenedKeyStart) + if err != nil { + t.Fatalf("masterKey.Derive failed: %v", err) + } + if !child0.IsAffectedByIssue172() { + t.Fatal("expected child0 to be affected by issue 172") + } + child1, err := child0.Derive(0 + HardenedKeyStart) + if err != nil { + t.Fatalf("child0.Derive failed: %v", err) + } + if child1.IsAffectedByIssue172() { + t.Fatal("did not expect child1 to be affected by issue 172") + } + + child1nonstandard, err := child0.DeriveNonStandard(0 + HardenedKeyStart) + if err != nil { + t.Fatalf("child0.DeriveNonStandard failed: %v", err) + } + + // This is the correct result based on BIP32 + if hex.EncodeToString(child1.key) != "a9b6b30a5b90b56ed48728c73af1d8a7ef1e9cc372ec21afcc1d9bdf269b0988" { + t.Error("incorrect standard BIP32 derivation") + } + + if hex.EncodeToString(child1nonstandard.key) != "ea46d8f58eb863a2d371a938396af8b0babe85c01920f59a8044412e70e837ee" { + t.Error("incorrect btcutil backwards compatible BIP32-like derivation") + } + + if !child0.IsAffectedByIssue172() { + t.Error("child 0 should be affected by issue 172") + } + + if child1.IsAffectedByIssue172() { + t.Error("child 1 should not be affected by issue 172") + } +} diff --git a/btcutil/hdkeychain/test_coverage.txt b/btcutil/hdkeychain/test_coverage.txt new file mode 100644 index 0000000000..c0bc7ef035 --- /dev/null +++ b/btcutil/hdkeychain/test_coverage.txt @@ -0,0 +1,20 @@ + +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.String 100.00% (18/18) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.Zero 100.00% (9/9) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.pubKeyBytes 100.00% (7/7) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.Neuter 100.00% (6/6) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.ECPrivKey 100.00% (4/4) +github.com/conformal/btcutil/hdkeychain/extendedkey.go zero 100.00% (3/3) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.SetNet 100.00% (3/3) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.Address 100.00% (2/2) +github.com/conformal/btcutil/hdkeychain/extendedkey.go newExtendedKey 100.00% (1/1) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.IsPrivate 100.00% (1/1) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.ParentFingerprint 100.00% (1/1) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.ECPubKey 100.00% (1/1) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.IsForNet 100.00% (1/1) +github.com/conformal/btcutil/hdkeychain/extendedkey.go NewKeyFromString 95.83% (23/24) +github.com/conformal/btcutil/hdkeychain/extendedkey.go ExtendedKey.Child 91.67% (33/36) +github.com/conformal/btcutil/hdkeychain/extendedkey.go NewMaster 91.67% (11/12) +github.com/conformal/btcutil/hdkeychain/extendedkey.go GenerateSeed 85.71% (6/7) +github.com/conformal/btcutil/hdkeychain ----------------------------- 95.59% (130/136) + diff --git a/btcutil/internal_test.go b/btcutil/internal_test.go new file mode 100644 index 0000000000..6d1efa0199 --- /dev/null +++ b/btcutil/internal_test.go @@ -0,0 +1,147 @@ +// 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. + +/* +This test file is part of the btcutil package rather than than the +btcutil_test package so it can bridge access to the internals to properly test +cases which are either not possible or can't reliably be tested via the public +interface. The functions are only exported while the tests are being run. +*/ + +package btcutil + +import ( + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcutil/base58" + "github.com/btcsuite/btcd/btcutil/bech32" + "golang.org/x/crypto/ripemd160" +) + +// SetBlockBytes sets the internal serialized block byte buffer to the passed +// buffer. It is used to inject errors and is only available to the test +// package. +func (b *Block) SetBlockBytes(buf []byte) { + b.serializedBlock = buf +} + +// TstAppDataDir makes the internal appDataDir function available to the test +// package. +func TstAppDataDir(goos, appName string, roaming bool) string { + return appDataDir(goos, appName, roaming) +} + +// TstAddressPubKeyHash makes an AddressPubKeyHash, setting the +// unexported fields with the parameters hash and netID. +func TstAddressPubKeyHash(hash [ripemd160.Size]byte, + netID byte) *AddressPubKeyHash { + + return &AddressPubKeyHash{ + hash: hash, + netID: netID, + } +} + +// TstAddressScriptHash makes an AddressScriptHash, setting the +// unexported fields with the parameters hash and netID. +func TstAddressScriptHash(hash [ripemd160.Size]byte, + netID byte) *AddressScriptHash { + + return &AddressScriptHash{ + hash: hash, + netID: netID, + } +} + +// TstAddressWitnessPubKeyHash creates an AddressWitnessPubKeyHash, initiating +// the fields as given. +func TstAddressWitnessPubKeyHash(version byte, program [20]byte, + hrp string) *AddressWitnessPubKeyHash { + + return &AddressWitnessPubKeyHash{ + AddressSegWit{ + hrp: hrp, + witnessVersion: version, + witnessProgram: program[:], + }, + } +} + +// TstAddressWitnessScriptHash creates an AddressWitnessScriptHash, initiating +// the fields as given. +func TstAddressWitnessScriptHash(version byte, program [32]byte, + hrp string) *AddressWitnessScriptHash { + + return &AddressWitnessScriptHash{ + AddressSegWit{ + hrp: hrp, + witnessVersion: version, + witnessProgram: program[:], + }, + } +} + +// TstAddressTaproot creates an AddressTaproot, initiating the fields as given. +func TstAddressTaproot(version byte, program [32]byte, + hrp string) *AddressTaproot { + + return &AddressTaproot{ + AddressSegWit{ + hrp: hrp, + witnessVersion: version, + witnessProgram: program[:], + }, + } +} + +// TstAddressPubKey makes an AddressPubKey, setting the unexported fields with +// the parameters. +func TstAddressPubKey(serializedPubKey []byte, pubKeyFormat PubKeyFormat, + netID byte) *AddressPubKey { + + pubKey, _ := btcec.ParsePubKey(serializedPubKey, btcec.S256()) + return &AddressPubKey{ + pubKeyFormat: pubKeyFormat, + pubKey: pubKey, + pubKeyHashID: netID, + } +} + +// TstAddressSAddr returns the expected script address bytes for +// P2PKH and P2SH bitcoin addresses. +func TstAddressSAddr(addr string) []byte { + decoded := base58.Decode(addr) + return decoded[1 : 1+ripemd160.Size] +} + +// TstAddressSegwitSAddr returns the expected witness program bytes for +// bech32 encoded P2WPKH and P2WSH bitcoin addresses. +func TstAddressSegwitSAddr(addr string) []byte { + _, data, err := bech32.Decode(addr) + if err != nil { + return []byte{} + } + + // First byte is version, rest is base 32 encoded data. + data, err = bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return []byte{} + } + return data +} + +// TstAddressTaprootSAddr returns the expected witness program bytes for a +// bech32m encoded P2TR bitcoin address. +func TstAddressTaprootSAddr(addr string) []byte { + _, data, err := bech32.Decode(addr) + if err != nil { + return []byte{} + } + + // First byte is version, rest is base 32 encoded data. + data, err = bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return []byte{} + } + return data +} diff --git a/btcutil/net.go b/btcutil/net.go new file mode 100644 index 0000000000..bf11733c64 --- /dev/null +++ b/btcutil/net.go @@ -0,0 +1,18 @@ +// 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. + +// +build !appengine + +package btcutil + +import ( + "net" +) + +// interfaceAddrs returns a list of the system's network interface addresses. +// It is wrapped here so that we can substitute it for other functions when +// building for systems that do not allow access to net.InterfaceAddrs(). +func interfaceAddrs() ([]net.Addr, error) { + return net.InterfaceAddrs() +} diff --git a/btcutil/net_noop.go b/btcutil/net_noop.go new file mode 100644 index 0000000000..b0b7c2e40a --- /dev/null +++ b/btcutil/net_noop.go @@ -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. + +// +build appengine + +package btcutil + +import ( + "net" +) + +// interfaceAddrs returns a list of the system's network interface addresses. +// It is wrapped here so that we can substitute it for a no-op function that +// returns an empty slice of net.Addr when building for systems that do not +// allow access to net.InterfaceAddrs(). +func interfaceAddrs() ([]net.Addr, error) { + return []net.Addr{}, nil +} diff --git a/btcutil/psbt/bip32.go b/btcutil/psbt/bip32.go new file mode 100644 index 0000000000..9fbfc73b61 --- /dev/null +++ b/btcutil/psbt/bip32.go @@ -0,0 +1,77 @@ +package psbt + +import ( + "bytes" + "encoding/binary" +) + +// Bip32Derivation encapsulates the data for the input and output +// Bip32Derivation key-value fields. +// +// TODO(roasbeef): use hdkeychain here instead? +type Bip32Derivation struct { + // PubKey is the raw pubkey serialized in compressed format. + PubKey []byte + + // MasterKeyFingerprint is the finger print of the master pubkey. + MasterKeyFingerprint uint32 + + // Bip32Path is the BIP 32 path with child index as a distinct integer. + Bip32Path []uint32 +} + +// checkValid ensures that the PubKey in the Bip32Derivation struct is valid. +func (pb *Bip32Derivation) checkValid() bool { + return validatePubkey(pb.PubKey) +} + +// Bip32Sorter implements sort.Interface for the Bip32Derivation struct. +type Bip32Sorter []*Bip32Derivation + +func (s Bip32Sorter) Len() int { return len(s) } + +func (s Bip32Sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s Bip32Sorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// readBip32Derivation deserializes a byte slice containing chunks of 4 byte +// little endian encodings of uint32 values, the first of which is the +// masterkeyfingerprint and the remainder of which are the derivation path. +func readBip32Derivation(path []byte) (uint32, []uint32, error) { + + if len(path)%4 != 0 || len(path)/4-1 < 1 { + return 0, nil, ErrInvalidPsbtFormat + } + + masterKeyInt := binary.LittleEndian.Uint32(path[:4]) + + var paths []uint32 + for i := 4; i < len(path); i += 4 { + paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4])) + } + + return masterKeyInt, paths, nil +} + +// SerializeBIP32Derivation takes a master key fingerprint as defined in BIP32, +// along with a path specified as a list of uint32 values, and returns a +// bytestring specifying the derivation in the format required by BIP174: // +// master key fingerprint (4) || child index (4) || child index (4) || .... +func SerializeBIP32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32) []byte { + + var masterKeyBytes [4]byte + binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint) + + derivationPath := make([]byte, 0, 4+4*len(bip32Path)) + derivationPath = append(derivationPath, masterKeyBytes[:]...) + for _, path := range bip32Path { + var pathbytes [4]byte + binary.LittleEndian.PutUint32(pathbytes[:], path) + derivationPath = append(derivationPath, pathbytes[:]...) + } + + return derivationPath +} diff --git a/btcutil/psbt/creator.go b/btcutil/psbt/creator.go new file mode 100644 index 0000000000..a5f832e0dd --- /dev/null +++ b/btcutil/psbt/creator.go @@ -0,0 +1,63 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +import ( + "github.com/btcsuite/btcd/wire" +) + +// MinTxVersion is the lowest transaction version that we'll permit. +const MinTxVersion = 1 + +// New on provision of an input and output 'skeleton' for the transaction, a +// new partially populated PBST packet. The populated packet will include the +// unsigned transaction, and the set of known inputs and outputs contained +// within the unsigned transaction. The values of nLockTime, nSequence (per +// input) and transaction version (must be 1 of 2) must be specified here. Note +// that the default nSequence value is wire.MaxTxInSequenceNum. Referencing +// the PSBT BIP, this function serves the roles of teh Creator. +func New(inputs []*wire.OutPoint, + outputs []*wire.TxOut, version int32, nLockTime uint32, + nSequences []uint32) (*Packet, error) { + + // Create the new struct; the input and output lists will be empty, the + // unsignedTx object must be constructed and serialized, and that + // serialization should be entered as the only entry for the + // globalKVPairs list. + // + // Ensure that the version of the transaction is greater then our + // minimum allowed transaction version. There must be one sequence + // number per input. + if version < MinTxVersion || len(nSequences) != len(inputs) { + return nil, ErrInvalidPsbtFormat + } + + unsignedTx := wire.NewMsgTx(version) + unsignedTx.LockTime = nLockTime + for i, in := range inputs { + unsignedTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *in, + Sequence: nSequences[i], + }) + } + for _, out := range outputs { + unsignedTx.AddTxOut(out) + } + + // The input and output lists are empty, but there is a list of those + // two lists, and each one must be of length matching the unsigned + // transaction; the unknown list can be nil. + pInputs := make([]PInput, len(unsignedTx.TxIn)) + pOutputs := make([]POutput, len(unsignedTx.TxOut)) + + // This new Psbt is "raw" and contains no key-value fields, so sanity + // checking with c.Cpsbt.SanityCheck() is not required. + return &Packet{ + UnsignedTx: unsignedTx, + Inputs: pInputs, + Outputs: pOutputs, + Unknowns: nil, + }, nil +} diff --git a/btcutil/psbt/extractor.go b/btcutil/psbt/extractor.go new file mode 100644 index 0000000000..dc7f10fddb --- /dev/null +++ b/btcutil/psbt/extractor.go @@ -0,0 +1,81 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +// The Extractor requires provision of a single PSBT +// in which all necessary signatures are encoded, and +// uses it to construct a fully valid network serialized +// transaction. + +import ( + "bytes" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// Extract takes a finalized psbt.Packet and outputs a finalized transaction +// instance. Note that if the PSBT is in-complete, then an error +// ErrIncompletePSBT will be returned. As the extracted transaction has been +// fully finalized, it will be ready for network broadcast once returned. +func Extract(p *Packet) (*wire.MsgTx, error) { + // If the packet isn't complete, then we'll return an error as it + // doesn't have all the required witness data. + if !p.IsComplete() { + return nil, ErrIncompletePSBT + } + + // First, we'll make a copy of the underlying unsigned transaction (the + // initial template) so we don't mutate it during our activates below. + finalTx := p.UnsignedTx.Copy() + + // For each input, we'll now populate any relevant witness and + // sigScript data. + for i, tin := range finalTx.TxIn { + // We'll grab the corresponding internal packet input which + // matches this materialized transaction input and emplace that + // final sigScript (if present). + pInput := p.Inputs[i] + if pInput.FinalScriptSig != nil { + tin.SignatureScript = pInput.FinalScriptSig + } + + // Similarly, if there's a final witness, then we'll also need + // to extract that as well, parsing the lower-level transaction + // encoding. + if pInput.FinalScriptWitness != nil { + // In order to set the witness, need to re-deserialize + // the field as encoded within the PSBT packet. For + // each input, the witness is encoded as a stack with + // one or more items. + witnessReader := bytes.NewReader( + pInput.FinalScriptWitness, + ) + + // First we extract the number of witness elements + // encoded in the above witnessReader. + witCount, err := wire.ReadVarInt(witnessReader, 0) + if err != nil { + return nil, err + } + + // Now that we know how may inputs we'll need, we'll + // construct a packing slice, then read out each input + // (with a varint prefix) from the witnessReader. + tin.Witness = make(wire.TxWitness, witCount) + for j := uint64(0); j < witCount; j++ { + wit, err := wire.ReadVarBytes( + witnessReader, 0, txscript.MaxScriptSize, "witness", + ) + if err != nil { + return nil, err + } + tin.Witness[j] = wit + } + } + } + + return finalTx, nil +} diff --git a/btcutil/psbt/finalizer.go b/btcutil/psbt/finalizer.go new file mode 100644 index 0000000000..0bf9dbb56e --- /dev/null +++ b/btcutil/psbt/finalizer.go @@ -0,0 +1,462 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +// The Finalizer requires provision of a single PSBT input +// in which all necessary signatures are encoded, and +// uses it to construct valid final sigScript and scriptWitness +// fields. +// NOTE that p2sh (legacy) and p2wsh currently support only +// multisig and no other custom script. + +import ( + "github.com/btcsuite/btcd/txscript" +) + +// isFinalized considers this input finalized if it contains at least one of +// the FinalScriptSig or FinalScriptWitness are filled (which only occurs in a +// successful call to Finalize*). +func isFinalized(p *Packet, inIndex int) bool { + input := p.Inputs[inIndex] + return input.FinalScriptSig != nil || input.FinalScriptWitness != nil +} + +// isFinalizableWitnessInput returns true if the target input is a witness UTXO +// that can be finalized. +func isFinalizableWitnessInput(pInput *PInput) bool { + pkScript := pInput.WitnessUtxo.PkScript + + switch { + // If this is a native witness output, then we require both + // the witness script, but not a redeem script. + case txscript.IsWitnessProgram(pkScript): + if txscript.IsPayToWitnessScriptHash(pkScript) { + if pInput.WitnessScript == nil || + pInput.RedeemScript != nil { + return false + } + } else { + // A P2WKH output on the other hand doesn't need + // neither a witnessScript or redeemScript. + if pInput.WitnessScript != nil || + pInput.RedeemScript != nil { + return false + } + } + + // For nested P2SH inputs, we verify that a witness script is known. + case txscript.IsPayToScriptHash(pkScript): + if pInput.RedeemScript == nil { + return false + } + + // If this is a nested P2SH input, then it must also have a + // witness script, while we don't need one for P2WKH. + if txscript.IsPayToWitnessScriptHash(pInput.RedeemScript) { + if pInput.WitnessScript == nil { + return false + } + } else if txscript.IsPayToWitnessPubKeyHash(pInput.RedeemScript) { + if pInput.WitnessScript != nil { + return false + } + } else { + // unrecognized type + return false + } + + // If this isn't a nested nested P2SH output or a native witness + // output, then we can't finalize this input as we don't understand it. + default: + return false + } + + return true +} + +// isFinalizableLegacyInput returns true of the passed input a legacy input +// (non-witness) that can be finalized. +func isFinalizableLegacyInput(p *Packet, pInput *PInput, inIndex int) bool { + // If the input has a witness, then it's invalid. + if pInput.WitnessScript != nil { + return false + } + + // Otherwise, we'll verify that we only have a RedeemScript if the prev + // output script is P2SH. + outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + if txscript.IsPayToScriptHash(pInput.NonWitnessUtxo.TxOut[outIndex].PkScript) { + if pInput.RedeemScript == nil { + return false + } + } else { + if pInput.RedeemScript != nil { + return false + } + } + + return true +} + +// isFinalizable checks whether the structure of the entry for the input of the +// psbt.Packet at index inIndex contains sufficient information to finalize +// this input. +func isFinalizable(p *Packet, inIndex int) bool { + pInput := p.Inputs[inIndex] + + // The input cannot be finalized without any signatures + if pInput.PartialSigs == nil { + return false + } + + // For an input to be finalized, we'll one of two possible top-level + // UTXOs present. Each UTXO type has a distinct set of requirements to + // be considered finalized. + switch { + + // A witness input must be either native P2WSH or nested P2SH with all + // relevant sigScript or witness data populated. + case pInput.WitnessUtxo != nil: + if !isFinalizableWitnessInput(&pInput) { + return false + } + + case pInput.NonWitnessUtxo != nil: + if !isFinalizableLegacyInput(p, &pInput, inIndex) { + return false + } + + // If neither a known UTXO type isn't present at all, then we'll + // return false as we need one of them. + default: + return false + } + + return true +} + +// MaybeFinalize attempts to finalize the input at index inIndex in the PSBT p, +// returning true with no error if it succeeds, OR if the input has already +// been finalized. +func MaybeFinalize(p *Packet, inIndex int) (bool, error) { + if isFinalized(p, inIndex) { + return true, nil + } + + if !isFinalizable(p, inIndex) { + return false, ErrNotFinalizable + } + + if err := Finalize(p, inIndex); err != nil { + return false, err + } + + return true, nil +} + +// MaybeFinalizeAll attempts to finalize all inputs of the psbt.Packet that are +// not already finalized, and returns an error if it fails to do so. +func MaybeFinalizeAll(p *Packet) error { + + for i := range p.UnsignedTx.TxIn { + success, err := MaybeFinalize(p, i) + if err != nil || !success { + return err + } + } + + return nil +} + +// Finalize assumes that the provided psbt.Packet struct has all partial +// signatures and redeem scripts/witness scripts already prepared for the +// specified input, and so removes all temporary data and replaces them with +// completed sigScript and witness fields, which are stored in key-types 07 and +// 08. The witness/non-witness utxo fields in the inputs (key-types 00 and 01) +// are left intact as they may be needed for validation (?). If there is any +// invalid or incomplete data, an error is returned. +func Finalize(p *Packet, inIndex int) error { + pInput := p.Inputs[inIndex] + + // Depending on the UTXO type, we either attempt to finalize it as a + // witness or legacy UTXO. + switch { + case pInput.WitnessUtxo != nil: + if err := finalizeWitnessInput(p, inIndex); err != nil { + return err + } + + case pInput.NonWitnessUtxo != nil: + if err := finalizeNonWitnessInput(p, inIndex); err != nil { + return err + } + + default: + return ErrInvalidPsbtFormat + } + + // Before returning we sanity check the PSBT to ensure we don't extract + // an invalid transaction or produce an invalid intermediate state. + if err := p.SanityCheck(); err != nil { + return err + } + + return nil +} + +// checkFinalScriptSigWitness checks whether a given input in the psbt.Packet +// struct already has the fields 07 (FinalInScriptSig) or 08 (FinalInWitness). +// If so, it returns true. It does not modify the Psbt. +func checkFinalScriptSigWitness(p *Packet, inIndex int) bool { + pInput := p.Inputs[inIndex] + + if pInput.FinalScriptSig != nil { + return true + } + + if pInput.FinalScriptWitness != nil { + return true + } + + return false +} + +// finalizeNonWitnessInput attempts to create a PsbtInFinalScriptSig field for +// the input at index inIndex, and removes all other fields except for the UTXO +// field, for an input of type non-witness, or returns an error. +func finalizeNonWitnessInput(p *Packet, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. + if checkFinalScriptSigWitness(p, inIndex) { + return ErrInputAlreadyFinalized + } + + // Our goal here is to construct a sigScript given the pubkey, + // signature (keytype 02), of which there might be multiple, and the + // redeem script field (keytype 04) if present (note, it is not present + // for p2pkh type inputs). + var sigScript []byte + + pInput := p.Inputs[inIndex] + containsRedeemScript := pInput.RedeemScript != nil + + var ( + pubKeys [][]byte + sigs [][]byte + ) + for _, ps := range pInput.PartialSigs { + pubKeys = append(pubKeys, ps.PubKey) + + sigOK := checkSigHashFlags(ps.Signature, &pInput) + if !sigOK { + return ErrInvalidSigHashFlags + } + + sigs = append(sigs, ps.Signature) + } + + // We have failed to identify at least 1 (sig, pub) pair in the PSBT, + // which indicates it was not ready to be finalized. As a result, we + // can't proceed. + if len(sigs) < 1 || len(pubKeys) < 1 { + return ErrNotFinalizable + } + + // If this input doesn't need a redeem script (P2PKH), then we'll + // construct a simple sigScript that's just the signature then the + // pubkey (OP_CHECKSIG). + var err error + if !containsRedeemScript { + // At this point, we should only have a single signature and + // pubkey. + if len(sigs) != 1 || len(pubKeys) != 1 { + return ErrNotFinalizable + } + + // In this case, our sigScript is just: . + builder := txscript.NewScriptBuilder() + builder.AddData(sigs[0]).AddData(pubKeys[0]) + sigScript, err = builder.Script() + if err != nil { + return err + } + } else { + // This is assumed p2sh multisig Given redeemScript and pubKeys + // we can decide in what order signatures must be appended. + orderedSigs, err := extractKeyOrderFromScript( + pInput.RedeemScript, pubKeys, sigs, + ) + if err != nil { + return err + } + + // At this point, we assume that this is a mult-sig input, so + // we construct our sigScript which looks something like this + // (mind the extra element for the extra multi-sig pop): + // * + // + // TODO(waxwing): the below is specific to the multisig case. + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_FALSE) + for _, os := range orderedSigs { + builder.AddData(os) + } + builder.AddData(pInput.RedeemScript) + sigScript, err = builder.Script() + if err != nil { + return err + } + } + + // At this point, a sigScript has been constructed. Remove all fields + // other than non-witness utxo (00) and finaliscriptsig (07) + newInput := NewPsbtInput(pInput.NonWitnessUtxo, nil) + newInput.FinalScriptSig = sigScript + + // Overwrite the entry in the input list at the correct index. Note + // that this removes all the other entries in the list for this input + // index. + p.Inputs[inIndex] = *newInput + + return nil +} + +// finalizeWitnessInput attempts to create PsbtInFinalScriptSig field and +// PsbtInFinalScriptWitness field for input at index inIndex, and removes all +// other fields except for the utxo field, for an input of type witness, or +// returns an error. +func finalizeWitnessInput(p *Packet, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. + if checkFinalScriptSigWitness(p, inIndex) { + return ErrInputAlreadyFinalized + } + + // Depending on the actual output type, we'll either populate a + // serializedWitness or a witness as well asa sigScript. + var ( + sigScript []byte + serializedWitness []byte + ) + + pInput := p.Inputs[inIndex] + + // First we'll validate and collect the pubkey+sig pairs from the set + // of partial signatures. + var ( + pubKeys [][]byte + sigs [][]byte + ) + for _, ps := range pInput.PartialSigs { + pubKeys = append(pubKeys, ps.PubKey) + + sigOK := checkSigHashFlags(ps.Signature, &pInput) + if !sigOK { + return ErrInvalidSigHashFlags + + } + + sigs = append(sigs, ps.Signature) + } + + // If at this point, we don't have any pubkey+sig pairs, then we bail + // as we can't proceed. + if len(sigs) == 0 || len(pubKeys) == 0 { + return ErrNotFinalizable + } + + containsRedeemScript := pInput.RedeemScript != nil + cointainsWitnessScript := pInput.WitnessScript != nil + + // If there's no redeem script, then we assume that this is native + // segwit input. + var err error + if !containsRedeemScript { + // If we have only a sigley pubkey+sig pair, and no witness + // script, then we assume this is a P2WKH input. + if len(pubKeys) == 1 && len(sigs) == 1 && + !cointainsWitnessScript { + + serializedWitness, err = writePKHWitness( + sigs[0], pubKeys[0], + ) + if err != nil { + return err + } + } else { + // Otherwise, we must have a witnessScript field, so + // we'll generate a valid multi-sig witness. + // + // NOTE: We tacitly assume multisig. + // + // TODO(roasbeef): need to add custom finalize for + // non-multisig P2WSH outputs (HTLCs, delay outputs, + // etc). + if !cointainsWitnessScript { + return ErrNotFinalizable + } + + serializedWitness, err = getMultisigScriptWitness( + pInput.WitnessScript, pubKeys, sigs, + ) + if err != nil { + return err + } + } + } else { + // Otherwise, we assume that this is a p2wsh multi-sig output, + // which is nested in a p2sh, or a p2wkh nested in a p2sh. + // + // In this case, we'll take the redeem script (the witness + // program in this case), and push it on the stack within the + // sigScript. + builder := txscript.NewScriptBuilder() + builder.AddData(pInput.RedeemScript) + sigScript, err = builder.Script() + if err != nil { + return err + } + + // If don't have a witness script, then we assume this is a + // nested p2wkh output. + if !cointainsWitnessScript { + // Assumed p2sh-p2wkh Here the witness is just (sig, + // pub) as for p2pkh case + if len(sigs) != 1 || len(pubKeys) != 1 { + return ErrNotFinalizable + } + + serializedWitness, err = writePKHWitness(sigs[0], pubKeys[0]) + if err != nil { + return err + } + + } else { + // Otherwise, we assume that this is a p2wsh multi-sig, + // so we generate the proper witness. + serializedWitness, err = getMultisigScriptWitness( + pInput.WitnessScript, pubKeys, sigs, + ) + if err != nil { + return err + } + } + } + + // At this point, a witness has been constructed, and a sigScript (if + // nested; else it's []). Remove all fields other than witness utxo + // (01) and finalscriptsig (07), finalscriptwitness (08). + newInput := NewPsbtInput(nil, pInput.WitnessUtxo) + if len(sigScript) > 0 { + newInput.FinalScriptSig = sigScript + } + + newInput.FinalScriptWitness = serializedWitness + + // Finally, we overwrite the entry in the input list at the correct + // index. + p.Inputs[inIndex] = *newInput + return nil +} diff --git a/btcutil/psbt/go.mod b/btcutil/psbt/go.mod new file mode 100644 index 0000000000..55ac1ca956 --- /dev/null +++ b/btcutil/psbt/go.mod @@ -0,0 +1,17 @@ +module github.com/btcsuite/btcd/btcutil/psbt + +go 1.17 + +require ( + github.com/btcsuite/btcd v0.20.1-beta + github.com/btcsuite/btcd/btcutil v0.0.0-20190425235716-9e5f4b9a998d + github.com/davecgh/go-spew v1.1.1 +) + +require ( + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect +) + +replace github.com/btcsuite/btcd/btcutil => ../ diff --git a/btcutil/psbt/go.sum b/btcutil/psbt/go.sum new file mode 100644 index 0000000000..4898b254c6 --- /dev/null +++ b/btcutil/psbt/go.sum @@ -0,0 +1,39 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/btcutil/psbt/partial_input.go b/btcutil/psbt/partial_input.go new file mode 100644 index 0000000000..4783c74d31 --- /dev/null +++ b/btcutil/psbt/partial_input.go @@ -0,0 +1,361 @@ +package psbt + +import ( + "bytes" + "encoding/binary" + "io" + "sort" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// PInput is a struct encapsulating all the data that can be attached to any +// specific input of the PSBT. +type PInput struct { + NonWitnessUtxo *wire.MsgTx + WitnessUtxo *wire.TxOut + PartialSigs []*PartialSig + SighashType txscript.SigHashType + RedeemScript []byte + WitnessScript []byte + Bip32Derivation []*Bip32Derivation + FinalScriptSig []byte + FinalScriptWitness []byte + Unknowns []*Unknown +} + +// NewPsbtInput creates an instance of PsbtInput given either a nonWitnessUtxo +// or a witnessUtxo. +// +// NOTE: Only one of the two arguments should be specified, with the other +// being `nil`; otherwise the created PsbtInput object will fail IsSane() +// checks and will not be usable. +func NewPsbtInput(nonWitnessUtxo *wire.MsgTx, + witnessUtxo *wire.TxOut) *PInput { + + return &PInput{ + NonWitnessUtxo: nonWitnessUtxo, + WitnessUtxo: witnessUtxo, + PartialSigs: []*PartialSig{}, + SighashType: 0, + RedeemScript: nil, + WitnessScript: nil, + Bip32Derivation: []*Bip32Derivation{}, + FinalScriptSig: nil, + FinalScriptWitness: nil, + Unknowns: nil, + } +} + +// IsSane returns true only if there are no conflicting values in the Psbt +// PInput. For segwit v0 no checks are currently implemented. +func (pi *PInput) IsSane() bool { + + // TODO(guggero): Implement sanity checks for segwit v1. For segwit v0 + // it is unsafe to only rely on the witness UTXO so we don't check that + // only one is set anymore. + // See https://github.com/bitcoin/bitcoin/pull/19215. + + return true +} + +// deserialize attempts to deserialize a new PInput from the passed io.Reader. +func (pi *PInput) deserialize(r io.Reader) error { + for { + keyint, keydata, err := getKey(r) + if err != nil { + return err + } + if keyint == -1 { + // Reached separator byte + break + } + value, err := wire.ReadVarBytes( + r, 0, MaxPsbtValueLength, "PSBT value", + ) + if err != nil { + return err + } + + switch InputType(keyint) { + + case NonWitnessUtxoType: + if pi.NonWitnessUtxo != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + tx := wire.NewMsgTx(2) + + err := tx.Deserialize(bytes.NewReader(value)) + if err != nil { + return err + } + pi.NonWitnessUtxo = tx + + case WitnessUtxoType: + if pi.WitnessUtxo != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + txout, err := readTxOut(value) + if err != nil { + return err + } + pi.WitnessUtxo = txout + + case PartialSigType: + newPartialSig := PartialSig{ + PubKey: keydata, + Signature: value, + } + + if !newPartialSig.checkValid() { + return ErrInvalidPsbtFormat + } + + // Duplicate keys are not allowed + for _, x := range pi.PartialSigs { + if bytes.Equal(x.PubKey, newPartialSig.PubKey) { + return ErrDuplicateKey + } + } + + pi.PartialSigs = append(pi.PartialSigs, &newPartialSig) + + case SighashType: + if pi.SighashType != 0 { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + + // Bounds check on value here since the sighash type must be a + // 32-bit unsigned integer. + if len(value) != 4 { + return ErrInvalidKeydata + } + + shtype := txscript.SigHashType( + binary.LittleEndian.Uint32(value), + ) + pi.SighashType = shtype + + case RedeemScriptInputType: + if pi.RedeemScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.RedeemScript = value + + case WitnessScriptInputType: + if pi.WitnessScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.WitnessScript = value + + case Bip32DerivationInputType: + if !validatePubkey(keydata) { + return ErrInvalidPsbtFormat + } + master, derivationPath, err := readBip32Derivation(value) + if err != nil { + return err + } + + // Duplicate keys are not allowed + for _, x := range pi.Bip32Derivation { + if bytes.Equal(x.PubKey, keydata) { + return ErrDuplicateKey + } + } + + pi.Bip32Derivation = append( + pi.Bip32Derivation, + &Bip32Derivation{ + PubKey: keydata, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }, + ) + + case FinalScriptSigType: + if pi.FinalScriptSig != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + + pi.FinalScriptSig = value + + case FinalScriptWitnessType: + if pi.FinalScriptWitness != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + + pi.FinalScriptWitness = value + + default: + // A fall through case for any proprietary types. + keyintanddata := []byte{byte(keyint)} + keyintanddata = append(keyintanddata, keydata...) + newUnknown := &Unknown{ + Key: keyintanddata, + Value: value, + } + + // Duplicate key+keydata are not allowed + for _, x := range pi.Unknowns { + if bytes.Equal(x.Key, newUnknown.Key) && + bytes.Equal(x.Value, newUnknown.Value) { + return ErrDuplicateKey + } + } + + pi.Unknowns = append(pi.Unknowns, newUnknown) + } + } + + return nil +} + +// serialize attempts to serialize the target PInput into the passed io.Writer. +func (pi *PInput) serialize(w io.Writer) error { + + if !pi.IsSane() { + return ErrInvalidPsbtFormat + } + + if pi.NonWitnessUtxo != nil { + var buf bytes.Buffer + err := pi.NonWitnessUtxo.Serialize(&buf) + if err != nil { + return err + } + + err = serializeKVPairWithType( + w, uint8(NonWitnessUtxoType), nil, buf.Bytes(), + ) + if err != nil { + return err + } + } + if pi.WitnessUtxo != nil { + var buf bytes.Buffer + err := wire.WriteTxOut(&buf, 0, 0, pi.WitnessUtxo) + if err != nil { + return err + } + + err = serializeKVPairWithType( + w, uint8(WitnessUtxoType), nil, buf.Bytes(), + ) + if err != nil { + return err + } + } + + if pi.FinalScriptSig == nil && pi.FinalScriptWitness == nil { + sort.Sort(PartialSigSorter(pi.PartialSigs)) + for _, ps := range pi.PartialSigs { + err := serializeKVPairWithType( + w, uint8(PartialSigType), ps.PubKey, + ps.Signature, + ) + if err != nil { + return err + } + } + + if pi.SighashType != 0 { + var shtBytes [4]byte + binary.LittleEndian.PutUint32( + shtBytes[:], uint32(pi.SighashType), + ) + + err := serializeKVPairWithType( + w, uint8(SighashType), nil, shtBytes[:], + ) + if err != nil { + return err + } + } + + if pi.RedeemScript != nil { + err := serializeKVPairWithType( + w, uint8(RedeemScriptInputType), nil, + pi.RedeemScript, + ) + if err != nil { + return err + } + } + + if pi.WitnessScript != nil { + err := serializeKVPairWithType( + w, uint8(WitnessScriptInputType), nil, + pi.WitnessScript, + ) + if err != nil { + return err + } + } + + sort.Sort(Bip32Sorter(pi.Bip32Derivation)) + for _, kd := range pi.Bip32Derivation { + err := serializeKVPairWithType( + w, + uint8(Bip32DerivationInputType), kd.PubKey, + SerializeBIP32Derivation( + kd.MasterKeyFingerprint, kd.Bip32Path, + ), + ) + if err != nil { + return err + } + } + } + + if pi.FinalScriptSig != nil { + err := serializeKVPairWithType( + w, uint8(FinalScriptSigType), nil, pi.FinalScriptSig, + ) + if err != nil { + return err + } + } + + if pi.FinalScriptWitness != nil { + err := serializeKVPairWithType( + w, uint8(FinalScriptWitnessType), nil, pi.FinalScriptWitness, + ) + if err != nil { + return err + } + } + + // Unknown is a special case; we don't have a key type, only a key and + // a value field + for _, kv := range pi.Unknowns { + err := serializeKVpair(w, kv.Key, kv.Value) + if err != nil { + return err + } + } + + return nil +} diff --git a/btcutil/psbt/partial_output.go b/btcutil/psbt/partial_output.go new file mode 100644 index 0000000000..64d1bd4cae --- /dev/null +++ b/btcutil/psbt/partial_output.go @@ -0,0 +1,139 @@ +package psbt + +import ( + "bytes" + "io" + "sort" + + "github.com/btcsuite/btcd/wire" +) + +// POutput is a struct encapsulating all the data that can be attached +// to any specific output of the PSBT. +type POutput struct { + RedeemScript []byte + WitnessScript []byte + Bip32Derivation []*Bip32Derivation +} + +// NewPsbtOutput creates an instance of PsbtOutput; the three parameters +// redeemScript, witnessScript and Bip32Derivation are all allowed to be +// `nil`. +func NewPsbtOutput(redeemScript []byte, witnessScript []byte, + bip32Derivation []*Bip32Derivation) *POutput { + return &POutput{ + RedeemScript: redeemScript, + WitnessScript: witnessScript, + Bip32Derivation: bip32Derivation, + } +} + +// deserialize attempts to recode a new POutput from the passed io.Reader. +func (po *POutput) deserialize(r io.Reader) error { + for { + keyint, keydata, err := getKey(r) + if err != nil { + return err + } + if keyint == -1 { + // Reached separator byte + break + } + + value, err := wire.ReadVarBytes( + r, 0, MaxPsbtValueLength, "PSBT value", + ) + if err != nil { + return err + } + + switch OutputType(keyint) { + + case RedeemScriptOutputType: + if po.RedeemScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + po.RedeemScript = value + + case WitnessScriptOutputType: + if po.WitnessScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + po.WitnessScript = value + + case Bip32DerivationOutputType: + if !validatePubkey(keydata) { + return ErrInvalidKeydata + } + master, derivationPath, err := readBip32Derivation(value) + if err != nil { + return err + } + + // Duplicate keys are not allowed + for _, x := range po.Bip32Derivation { + if bytes.Equal(x.PubKey, keydata) { + return ErrDuplicateKey + } + } + + po.Bip32Derivation = append(po.Bip32Derivation, + &Bip32Derivation{ + PubKey: keydata, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }, + ) + + default: + // Unknown type is allowed for inputs but not outputs. + return ErrInvalidPsbtFormat + } + } + + return nil +} + +// serialize attempts to write out the target POutput into the passed +// io.Writer. +func (po *POutput) serialize(w io.Writer) error { + if po.RedeemScript != nil { + err := serializeKVPairWithType( + w, uint8(RedeemScriptOutputType), nil, po.RedeemScript, + ) + if err != nil { + return err + } + } + if po.WitnessScript != nil { + err := serializeKVPairWithType( + w, uint8(WitnessScriptOutputType), nil, po.WitnessScript, + ) + if err != nil { + return err + } + } + + sort.Sort(Bip32Sorter(po.Bip32Derivation)) + for _, kd := range po.Bip32Derivation { + err := serializeKVPairWithType(w, + uint8(Bip32DerivationOutputType), + kd.PubKey, + SerializeBIP32Derivation( + kd.MasterKeyFingerprint, + kd.Bip32Path, + ), + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/btcutil/psbt/partialsig.go b/btcutil/psbt/partialsig.go new file mode 100644 index 0000000000..e11bb80de7 --- /dev/null +++ b/btcutil/psbt/partialsig.go @@ -0,0 +1,52 @@ +package psbt + +import ( + "bytes" + + "github.com/btcsuite/btcd/btcec" +) + +// PartialSig encapsulate a (BTC public key, ECDSA signature) +// pair, note that the fields are stored as byte slices, not +// btcec.PublicKey or btcec.Signature (because manipulations will +// be with the former not the latter, here); compliance with consensus +// serialization is enforced with .checkValid() +type PartialSig struct { + PubKey []byte + Signature []byte +} + +// PartialSigSorter implements sort.Interface for PartialSig. +type PartialSigSorter []*PartialSig + +func (s PartialSigSorter) Len() int { return len(s) } + +func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s PartialSigSorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// validatePubkey checks if pubKey is *any* valid pubKey serialization in a +// Bitcoin context (compressed/uncomp. OK). +func validatePubkey(pubKey []byte) bool { + _, err := btcec.ParsePubKey(pubKey, btcec.S256()) + return err == nil +} + +// validateSignature checks that the passed byte slice is a valid DER-encoded +// ECDSA signature, including the sighash flag. It does *not* of course +// validate the signature against any message or public key. +func validateSignature(sig []byte) bool { + _, err := btcec.ParseDERSignature(sig, btcec.S256()) + return err == nil +} + +// checkValid checks that both the pbukey and sig are valid. See the methods +// (PartialSig, validatePubkey, validateSignature) for more details. +// +// TODO(waxwing): update for Schnorr will be needed here if/when that +// activates. +func (ps *PartialSig) checkValid() bool { + return validatePubkey(ps.PubKey) && validateSignature(ps.Signature) +} diff --git a/btcutil/psbt/psbt.go b/btcutil/psbt/psbt.go new file mode 100644 index 0000000000..73126c3eb2 --- /dev/null +++ b/btcutil/psbt/psbt.go @@ -0,0 +1,407 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// Package psbt is an implementation of Partially Signed Bitcoin +// Transactions (PSBT). The format is defined in BIP 174: +// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +package psbt + +import ( + "bytes" + "encoding/base64" + "errors" + + "io" + + "github.com/btcsuite/btcd/wire" +) + +// psbtMagicLength is the length of the magic bytes used to signal the start of +// a serialized PSBT packet. +const psbtMagicLength = 5 + +var ( + // psbtMagic is the separator + psbtMagic = [psbtMagicLength]byte{0x70, + 0x73, 0x62, 0x74, 0xff, // = "psbt" + 0xff sep + } +) + +// MaxPsbtValueLength is the size of the largest transaction serialization +// that could be passed in a NonWitnessUtxo field. This is definitely +//less than 4M. +const MaxPsbtValueLength = 4000000 + +// MaxPsbtKeyLength is the length of the largest key that we'll successfully +// deserialize from the wire. Anything more will return ErrInvalidKeydata. +const MaxPsbtKeyLength = 10000 + +var ( + + // ErrInvalidPsbtFormat is a generic error for any situation in which a + // provided Psbt serialization does not conform to the rules of BIP174. + ErrInvalidPsbtFormat = errors.New("Invalid PSBT serialization format") + + // ErrDuplicateKey indicates that a passed Psbt serialization is invalid + // due to having the same key repeated in the same key-value pair. + ErrDuplicateKey = errors.New("Invalid Psbt due to duplicate key") + + // ErrInvalidKeydata indicates that a key-value pair in the PSBT + // serialization contains data in the key which is not valid. + ErrInvalidKeydata = errors.New("Invalid key data") + + // ErrInvalidMagicBytes indicates that a passed Psbt serialization is invalid + // due to having incorrect magic bytes. + ErrInvalidMagicBytes = errors.New("Invalid Psbt due to incorrect magic bytes") + + // ErrInvalidRawTxSigned indicates that the raw serialized transaction in the + // global section of the passed Psbt serialization is invalid because it + // contains scriptSigs/witnesses (i.e. is fully or partially signed), which + // is not allowed by BIP174. + ErrInvalidRawTxSigned = errors.New("Invalid Psbt, raw transaction must " + + "be unsigned.") + + // ErrInvalidPrevOutNonWitnessTransaction indicates that the transaction + // hash (i.e. SHA256^2) of the fully serialized previous transaction + // provided in the NonWitnessUtxo key-value field doesn't match the prevout + // hash in the UnsignedTx field in the PSBT itself. + ErrInvalidPrevOutNonWitnessTransaction = errors.New("Prevout hash does " + + "not match the provided non-witness utxo serialization") + + // ErrInvalidSignatureForInput indicates that the signature the user is + // trying to append to the PSBT is invalid, either because it does + // not correspond to the previous transaction hash, or redeem script, + // or witness script. + // NOTE this does not include ECDSA signature checking. + ErrInvalidSignatureForInput = errors.New("Signature does not correspond " + + "to this input") + + // ErrInputAlreadyFinalized indicates that the PSBT passed to a Finalizer + // already contains the finalized scriptSig or witness. + ErrInputAlreadyFinalized = errors.New("Cannot finalize PSBT, finalized " + + "scriptSig or scriptWitnes already exists") + + // ErrIncompletePSBT indicates that the Extractor object + // was unable to successfully extract the passed Psbt struct because + // it is not complete + ErrIncompletePSBT = errors.New("PSBT cannot be extracted as it is " + + "incomplete") + + // ErrNotFinalizable indicates that the PSBT struct does not have + // sufficient data (e.g. signatures) for finalization + ErrNotFinalizable = errors.New("PSBT is not finalizable") + + // ErrInvalidSigHashFlags indicates that a signature added to the PSBT + // uses Sighash flags that are not in accordance with the requirement + // according to the entry in PsbtInSighashType, or otherwise not the + // default value (SIGHASH_ALL) + ErrInvalidSigHashFlags = errors.New("Invalid Sighash Flags") + + // ErrUnsupportedScriptType indicates that the redeem script or + // scriptwitness given is not supported by this codebase, or is otherwise + // not valid. + ErrUnsupportedScriptType = errors.New("Unsupported script type") +) + +// Unknown is a struct encapsulating a key-value pair for which the key type is +// unknown by this package; these fields are allowed in both the 'Global' and +// the 'Input' section of a PSBT. +type Unknown struct { + Key []byte + Value []byte +} + +// Packet is the actual psbt repreesntation. It is a is a set of 1 + N + M +// key-value pair lists, 1 global, defining the unsigned transaction structure +// with N inputs and M outputs. These key-value pairs can contain scripts, +// signatures, key derivations and other transaction-defining data. +type Packet struct { + // UnsignedTx is the decoded unsigned transaction for this PSBT. + UnsignedTx *wire.MsgTx // Deserialization of unsigned tx + + // Inputs contains all the information needed to properly sign this + // target input within the above transaction. + Inputs []PInput + + // Outputs contains all information required to spend any outputs + // produced by this PSBT. + Outputs []POutput + + // Unknowns are the set of custom types (global only) within this PSBT. + Unknowns []Unknown +} + +// validateUnsignedTx returns true if the transaction is unsigned. Note that +// more basic sanity requirements, such as the presence of inputs and outputs, +// is implicitly checked in the call to MsgTx.Deserialize(). +func validateUnsignedTX(tx *wire.MsgTx) bool { + for _, tin := range tx.TxIn { + if len(tin.SignatureScript) != 0 || len(tin.Witness) != 0 { + return false + } + } + + return true +} + +// NewFromUnsignedTx creates a new Psbt struct, without any signatures (i.e. +// only the global section is non-empty) using the passed unsigned transaction. +func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) { + + if !validateUnsignedTX(tx) { + return nil, ErrInvalidRawTxSigned + } + + inSlice := make([]PInput, len(tx.TxIn)) + outSlice := make([]POutput, len(tx.TxOut)) + unknownSlice := make([]Unknown, 0) + + retPsbt := Packet{ + UnsignedTx: tx, + Inputs: inSlice, + Outputs: outSlice, + Unknowns: unknownSlice, + } + + return &retPsbt, nil +} + +// NewFromRawBytes returns a new instance of a Packet struct created by reading +// from a byte slice. If the format is invalid, an error is returned. If the +// argument b64 is true, the passed byte slice is decoded from base64 encoding +// before processing. +// +// NOTE: To create a Packet from one's own data, rather than reading in a +// serialization from a counterparty, one should use a psbt.New. +func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { + + // If the PSBT is encoded in bas64, then we'll create a new wrapper + // reader that'll allow us to incrementally decode the contents of the + // io.Reader. + if b64 { + based64EncodedReader := r + r = base64.NewDecoder(base64.StdEncoding, based64EncodedReader) + } + + // The Packet struct does not store the fixed magic bytes, but they + // must be present or the serialization must be explicitly rejected. + var magic [5]byte + if _, err := io.ReadFull(r, magic[:]); err != nil { + return nil, err + } + if magic != psbtMagic { + return nil, ErrInvalidMagicBytes + } + + // Next we parse the GLOBAL section. There is currently only 1 known + // key type, UnsignedTx. We insist this exists first; unknowns are + // allowed, but only after. + keyint, keydata, err := getKey(r) + if err != nil { + return nil, err + } + if GlobalType(keyint) != UnsignedTxType || keydata != nil { + return nil, ErrInvalidPsbtFormat + } + + // Now that we've verified the global type is present, we'll decode it + // into a proper unsigned transaction, and validate it. + value, err := wire.ReadVarBytes( + r, 0, MaxPsbtValueLength, "PSBT value", + ) + if err != nil { + return nil, err + } + msgTx := wire.NewMsgTx(2) + err = msgTx.Deserialize(bytes.NewReader(value)) + if err != nil { + // If there are no inputs in this yet incomplete transaction, + // the wire package still incorrectly assumes it's encoded in + // the witness format. We can fix this by just trying the non- + // witness encoding too. If that also fails, it's probably an + // invalid transaction. + msgTx = wire.NewMsgTx(2) + err2 := msgTx.DeserializeNoWitness(bytes.NewReader(value)) + + // If the second attempt also failed, something else is wrong + // and it probably makes more sense to return the original + // error instead of the error from the workaround. + if err2 != nil { + return nil, err + } + } + if !validateUnsignedTX(msgTx) { + return nil, ErrInvalidRawTxSigned + } + + // Next we parse any unknowns that may be present, making sure that we + // break at the separator. + var unknownSlice []Unknown + for { + keyint, keydata, err := getKey(r) + if err != nil { + return nil, ErrInvalidPsbtFormat + } + if keyint == -1 { + break + } + + value, err := wire.ReadVarBytes( + r, 0, MaxPsbtValueLength, "PSBT value", + ) + if err != nil { + return nil, err + } + + keyintanddata := []byte{byte(keyint)} + keyintanddata = append(keyintanddata, keydata...) + + newUnknown := Unknown{ + Key: keyintanddata, + Value: value, + } + unknownSlice = append(unknownSlice, newUnknown) + } + + // Next we parse the INPUT section. + inSlice := make([]PInput, len(msgTx.TxIn)) + for i := range msgTx.TxIn { + input := PInput{} + err = input.deserialize(r) + if err != nil { + return nil, err + } + + inSlice[i] = input + } + + // Next we parse the OUTPUT section. + outSlice := make([]POutput, len(msgTx.TxOut)) + for i := range msgTx.TxOut { + output := POutput{} + err = output.deserialize(r) + if err != nil { + return nil, err + } + + outSlice[i] = output + } + + // Populate the new Packet object + newPsbt := Packet{ + UnsignedTx: msgTx, + Inputs: inSlice, + Outputs: outSlice, + Unknowns: unknownSlice, + } + + // Extended sanity checking is applied here to make sure the + // externally-passed Packet follows all the rules. + if err = newPsbt.SanityCheck(); err != nil { + return nil, err + } + + return &newPsbt, nil +} + +// Serialize creates a binary serialization of the referenced Packet struct +// with lexicographical ordering (by key) of the subsections. +func (p *Packet) Serialize(w io.Writer) error { + + // First we write out the precise set of magic bytes that identify a + // valid PSBT transaction. + if _, err := w.Write(psbtMagic[:]); err != nil { + return err + } + + // Next we prep to write out the unsigned transaction by first + // serializing it into an intermediate buffer. + serializedTx := bytes.NewBuffer( + make([]byte, 0, p.UnsignedTx.SerializeSize()), + ) + if err := p.UnsignedTx.Serialize(serializedTx); err != nil { + return err + } + + // Now that we have the serialized transaction, we'll write it out to + // the proper global type. + err := serializeKVPairWithType( + w, uint8(UnsignedTxType), nil, serializedTx.Bytes(), + ) + if err != nil { + return err + } + + // With that our global section is done, so we'll write out the + // separator. + separator := []byte{0x00} + if _, err := w.Write(separator); err != nil { + return err + } + + for _, pInput := range p.Inputs { + err := pInput.serialize(w) + if err != nil { + return err + } + + if _, err := w.Write(separator); err != nil { + return err + } + } + + for _, pOutput := range p.Outputs { + err := pOutput.serialize(w) + if err != nil { + return err + } + + if _, err := w.Write(separator); err != nil { + return err + } + } + + return nil +} + +// B64Encode returns the base64 encoding of the serialization of +// the current PSBT, or an error if the encoding fails. +func (p *Packet) B64Encode() (string, error) { + var b bytes.Buffer + if err := p.Serialize(&b); err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(b.Bytes()), nil +} + +// IsComplete returns true only if all of the inputs are +// finalized; this is particularly important in that it decides +// whether the final extraction to a network serialized signed +// transaction will be possible. +func (p *Packet) IsComplete() bool { + for i := 0; i < len(p.UnsignedTx.TxIn); i++ { + if !isFinalized(p, i) { + return false + } + } + return true +} + +// SanityCheck checks conditions on a PSBT to ensure that it obeys the +// rules of BIP174, and returns true if so, false if not. +func (p *Packet) SanityCheck() error { + + if !validateUnsignedTX(p.UnsignedTx) { + return ErrInvalidRawTxSigned + } + + for _, tin := range p.Inputs { + if !tin.IsSane() { + return ErrInvalidPsbtFormat + } + } + + return nil +} diff --git a/btcutil/psbt/psbt_test.go b/btcutil/psbt/psbt_test.go new file mode 100644 index 0000000000..12038fbfa0 --- /dev/null +++ b/btcutil/psbt/psbt_test.go @@ -0,0 +1,1448 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" + "github.com/davecgh/go-spew/spew" +) + +// Test vectors from: +// // https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors + +// createPsbtFromSignedTx is a utility function to create a PSBT from an +// already-signed transaction, so we can test reconstructing, signing and +// extracting it. Returned are: an unsigned transaction serialization, a list +// of scriptSigs, one per input, and a list of witnesses, one per input. +func createPsbtFromSignedTx(serializedSignedTx []byte) ( + *Packet, [][]byte, []wire.TxWitness, error) { + + tx := wire.NewMsgTx(2) + err := tx.Deserialize(bytes.NewReader(serializedSignedTx)) + if err != nil { + return nil, nil, nil, err + } + scriptSigs := make([][]byte, 0, len(tx.TxIn)) + witnesses := make([]wire.TxWitness, 0, len(tx.TxIn)) + tx2 := tx.Copy() + + // Blank out signature info in inputs + for i, tin := range tx2.TxIn { + tin.SignatureScript = nil + scriptSigs = append(scriptSigs, tx.TxIn[i].SignatureScript) + tin.Witness = nil + witnesses = append(witnesses, tx.TxIn[i].Witness) + + } + + // Outputs always contain: (value, scriptPubkey) so don't need + // amending. Now tx2 is tx with all signing data stripped out + unsignedPsbt, err := NewFromUnsignedTx(tx2) + if err != nil { + return nil, nil, nil, err + } + return unsignedPsbt, scriptSigs, witnesses, nil +} + +// These are all valid PSBTs +var validPsbtHex = map[int]string{ + 0: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000", + 1: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000", + 2: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000", + 3: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000", + 4: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + 5: "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000", +} + +// These are all invalid PSBTs for the indicated +// reasons. +var invalidPsbtHex = map[int]string{ + // wire format, not PSBT format + 0: "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300", + // missing outputs + 1: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000", + // Filled in scriptSig in unsigned tx + 2: "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000", + // No unsigned tx + 3: "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000", + // Duplicate keys in an input + 4: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000", + // Invalid global transaction typed key + 5: "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid input witness utxo typed key + 6: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid pubkey length for input partial signature typed key + 7: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid redeemscript typed key + 8: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid witness script typed key + 9: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid bip32 typed key + 10: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid non-witness utxo typed key + 11: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid final scriptsig typed key + 12: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid final script witness typed key + 13: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid pubkey in output BIP32 derivation paths typed key + 14: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid input sighash type typed key + 15: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Invalid output redeemscript typed key + 16: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Invalid output witnessScript typed key + 17: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Additional cases outside the existing test vectors. + // Invalid duplicate PartialSig + 18: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid duplicate BIP32 derivation (different derivs, same key) + 19: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba670000008000000080050000800000", +} + +// This tests that valid PSBT serializations can be parsed +// into Psbt structs. +func TestReadValidPsbtAndReserialize(t *testing.T) { + for _, v := range validPsbtHex { + PsbtBytes, err := hex.DecodeString(v) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + testPsbt, err := NewFromRawBytes( + bytes.NewReader(PsbtBytes), false, + ) + if err != nil { + t.Fatalf("unable to parse psbt: %v", err) + } + + t.Logf("Successfully parsed test, got transaction: %v", + spew.Sdump(testPsbt.UnsignedTx)) + + var b bytes.Buffer + err = testPsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize created Psbt: %v", err) + } + + raw := b.Bytes() + if !bytes.Equal(raw, PsbtBytes) { + t.Fatalf("Serialized PSBT didn't match: %v", + hex.EncodeToString(raw)) + } + } +} + +func TestReadInvalidPsbt(t *testing.T) { + for _, v := range invalidPsbtHex { + PsbtBytes, err := hex.DecodeString(v) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + _, err = NewFromRawBytes(bytes.NewReader(PsbtBytes), false) + if err == nil { + t.Fatalf("Incorrectly validated psbt: %v", + hex.EncodeToString(PsbtBytes)) + } + + t.Logf("Correctly got error: %v", err) + } +} + +func TestSanityCheck(t *testing.T) { + // TODO(guggero): Remove when checks for segwit v1 are implemented. + t.Skip("Skipping PSBT sanity checks for segwit v0.") + + // Test strategy: + // 1. Create an invalid PSBT from a serialization + // Then ensure that the sanity check fails. + // 2. Create a valid PSBT from a serialization + // Then create an updater, add a witness utxo to a non-witness + // utxo. + // Then ensure that the sanity check fails. + // Then add a witnessScript field to a non-witness utxo. + // Then ensure that the sanity check fails. + + // index 1 contains a psbt with two inputs, first non-witness, + // second witness. + psbtraw1, err := hex.DecodeString(validPsbtHex[1]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbt1, err := NewFromRawBytes(bytes.NewReader(psbtraw1), false) + if err != nil { + t.Fatalf("Unable to create Psbt struct: %v", err) + } + + // Add a non-witness utxo field to input2 using raw insertion function, + // so that it becomes invalid, then NewUpdater should fail. + nonWitnessUtxoRaw, err := hex.DecodeString( + CUTestHexData["NonWitnessUtxo"], + ) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + nonWitnessUtxo := wire.NewMsgTx(2) + err = nonWitnessUtxo.Deserialize(bytes.NewReader(nonWitnessUtxoRaw)) + if err != nil { + t.Fatalf("Unable to deserialize: %v", err) + } + inputs1 := &psbt1.Inputs[1] + inputs1.NonWitnessUtxo = nonWitnessUtxo + + // The PSBT is now in an inconsistent state; Updater creation should + // fail. + updater, err := NewUpdater(psbt1) + if err == nil { + t.Fatalf("Failed to identify invalid PSBT state ( " + + "witness, non-witness fields)") + } + + // Overwrite back with the correct psbt + psbtraw1, err = hex.DecodeString(validPsbtHex[1]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbt1, err = NewFromRawBytes(bytes.NewReader(psbtraw1), false) + updater, err = NewUpdater(psbt1) + if err != nil { + t.Fatalf("Unable to create Updater: %v", err) + } + + // Create a fake non-witness utxo field to overlap with + // the existing witness input at index 1. + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(nonWitnessUtxoRaw)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + err = updater.AddInNonWitnessUtxo(tx, 1) + if err == nil { + t.Fatalf("Incorrectly accepted Psbt with conflicting witness " + + "and non-witness utxo entries in the same input.") + } + + // Now we try again; this time we try to add a witnessScript + // key-value pair to an input which is non-witness, which should + // also be rejected. + psbt2, err := NewFromRawBytes( + bytes.NewReader(psbtraw1), false, + ) + if err != nil { + t.Fatalf("Unable to create Psbt struct: %v", err) + } + updater2, err := NewUpdater(psbt2) + if err != nil { + t.Fatalf("Got error creating updater2: %v", err) + } + witnessScript, err := hex.DecodeString( + CUTestHexData["Input2WitnessScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater2.AddInWitnessScript(witnessScript, 0) + if err == nil { + t.Fatalf("Incorrectly accepted adding witness script field " + + "to non-witness utxo") + } +} + +// Data for creation and updating tests +// =============================================================================== +var CUTestHexData = map[string]string{ + "scriptPubkey1": "0014d85c2b71d0060b09c9886aeb815e50991dda124d", + "scriptPubkey2": "001400aea9a2e5f0f876a588df5546e8742d1d87008f", + "txid1": "75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", + "txid2": "1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", + "COPsbtHex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000", + "NonWitnessUtxo": "0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000", + "WitnessUtxo": "00c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887", + // After adding witnessutxo and nonwitness utxo to inputs: + "UOPsbtHex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887000000", + "Input1RedeemScript": "5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae", + "Input2RedeemScript": "00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903", + "Input2WitnessScript": "522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae", + // After adding redeemscripts and witness scripts to inputs: + "UOPsbtHex2": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae000000", + // After adding bip32 derivations to inputs and outputs: + "UOPsbtHex3": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + //After adding sighash types to inputs + "UOPsbtHex4": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", +} + +// Just one example sanity check of B64 construction; after sighash appending above +var CUTestB64Data = map[string]string{ + "UOPsbtB644": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", +} + +var CUTestAmountData = map[string]int64{ + "amount1": 149990000, + "amount2": 100000000, + "amount3": 200000000, +} + +var CUTestIndexData = map[string]uint32{ + "index1": 0, + "index2": 1, +} + +var CUMasterKeyFingerPrint = "d90c6a4f" + +var CUTestPathData = map[string][]uint32{ + "dpath1": {0 + 0x80000000, 0 + 0x80000000, 0 + 0x80000000}, + "dpath2": {0 + 0x80000000, 0 + 0x80000000, 1 + 0x80000000}, + "dpath3": {0 + 0x80000000, 0 + 0x80000000, 2 + 0x80000000}, + "dpath4": {0 + 0x80000000, 0 + 0x80000000, 3 + 0x80000000}, + "dpath5": {0 + 0x80000000, 0 + 0x80000000, 4 + 0x80000000}, + "dpath6": {0 + 0x80000000, 0 + 0x80000000, 5 + 0x80000000}, +} + +var CUTestPubkeyData = map[string]string{ + "pub1": "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f", + "pub2": "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7", + "pub3": "03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc", + "pub4": "023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73", + "pub5": "03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771", + "pub6": "027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096", +} + +// =============================================================================== + +func TestPsbtCreator(t *testing.T) { + spkOut1, err := hex.DecodeString(CUTestHexData["scriptPubkey1"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + spkOut2, err := hex.DecodeString(CUTestHexData["scriptPubkey2"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + out1 := wire.NewTxOut(CUTestAmountData["amount1"], spkOut1) + out2 := wire.NewTxOut(CUTestAmountData["amount2"], spkOut2) + outputs := []*wire.TxOut{out1, out2} + hash1, err := chainhash.NewHashFromStr(CUTestHexData["txid1"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + prevOut1 := wire.NewOutPoint(hash1, uint32(0)) + hash2, err := chainhash.NewHashFromStr(CUTestHexData["txid2"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + prevOut2 := wire.NewOutPoint(hash2, uint32(1)) + inputs := []*wire.OutPoint{prevOut1, prevOut2} + + // Check creation fails with invalid sequences: + nSequences := []uint32{wire.MaxTxInSequenceNum} + _, err = New(inputs, outputs, int32(3), uint32(0), nSequences) + if err == nil { + t.Fatalf("Did not error when creating transaction with " + + "invalid nSequences") + } + nSequences = append(nSequences, wire.MaxTxInSequenceNum) + + // Check creation fails with invalid version + _, err = New(inputs, outputs, int32(0), uint32(0), nSequences) + if err == nil { + t.Fatalf("Did not error when creating transaction with " + + "invalid version (3)") + } + + // Use valid data to create: + cPsbt, err := New(inputs, outputs, int32(2), uint32(0), nSequences) + var b bytes.Buffer + err = cPsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize created Psbt: %v", err) + } + if CUTestHexData["COPsbtHex"] != hex.EncodeToString(b.Bytes()) { + t.Fatalf("Failed to create expected psbt, instead got: %v", + hex.EncodeToString(b.Bytes())) + } + + // Now simulate passing the created PSBT to an Updater + updater, err := NewUpdater(cPsbt) + if err != nil { + t.Fatalf("Unable to create Updater object") + } + tx := wire.NewMsgTx(2) + nonWitnessUtxoHex, err := hex.DecodeString( + CUTestHexData["NonWitnessUtxo"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = tx.Deserialize(bytes.NewReader(nonWitnessUtxoHex)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + witnessUtxoHex, err := hex.DecodeString( + CUTestHexData["WitnessUtxo"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txout := wire.TxOut{Value: CUTestAmountData["amount3"], + PkScript: witnessUtxoHex[9:]} + err = updater.AddInNonWitnessUtxo(tx, 0) + if err != nil { + t.Fatalf("Unable to add NonWitness Utxo to inputs: %v", err) + } + err = updater.AddInWitnessUtxo(&txout, 1) + if err != nil { + t.Fatalf("Unable to add Witness Utxo to inputs: %v", err) + } + + b.Reset() + + err = updater.Upsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex"] != hex.EncodeToString(b.Bytes()) { + t.Fatal("Failed to create valid updated PSBT after utxos") + } + input1RedeemScript, err := hex.DecodeString(CUTestHexData["Input1RedeemScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInRedeemScript(input1RedeemScript, 0) + if err != nil { + t.Fatalf("Unable to add redeem script: %v", err) + } + input2RedeemScript, err := hex.DecodeString(CUTestHexData["Input2RedeemScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInRedeemScript(input2RedeemScript, 1) + if err != nil { + t.Fatalf("Unable to add redeem script: %v", err) + } + input2WitnessScript, err := hex.DecodeString(CUTestHexData["Input2WitnessScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInWitnessScript(input2WitnessScript, 1) + if err != nil { + t.Fatalf("Unable to add witness script: %v", err) + } + + b.Reset() + err = updater.Upsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex2"] != hex.EncodeToString(b.Bytes()) { + t.Fatal("Failed to create valid updated PSBT after redeem scripts") + } + masterKey, err := hex.DecodeString(CUMasterKeyFingerPrint) + masterKeyInt := binary.LittleEndian.Uint32(masterKey) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input1Path1 := CUTestPathData["dpath1"] + input1Path2 := CUTestPathData["dpath2"] + input1Key1, err := hex.DecodeString(CUTestPubkeyData["pub1"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input1Key2, err := hex.DecodeString(CUTestPubkeyData["pub2"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInBip32Derivation(masterKeyInt, input1Path1, input1Key1, 0) + if err != nil { + t.Fatal("Failed to add first key derivation for input 1") + } + err = updater.AddInBip32Derivation(masterKeyInt, input1Path2, input1Key2, 0) + if err != nil { + t.Fatal("Failed to add second key derivation for input 1") + } + input2Path1 := CUTestPathData["dpath3"] + input2Path2 := CUTestPathData["dpath4"] + input2Key1, err := hex.DecodeString(CUTestPubkeyData["pub3"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input2Key2, err := hex.DecodeString(CUTestPubkeyData["pub4"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // check invalid pubkeys are not accepted + borkedInput2Key1 := append([]byte{0xff}, input2Key1...) + err = updater.AddInBip32Derivation(masterKeyInt, input2Path1, + borkedInput2Key1, 1) + if err == nil { + t.Fatalf("Expected invalid pubkey, got: %v", err) + } + + err = updater.AddInBip32Derivation(masterKeyInt, input2Path1, input2Key1, 1) + if err != nil { + t.Fatal("Failed to add first key derivation for input 2") + } + err = updater.AddInBip32Derivation(masterKeyInt, input2Path2, input2Key2, 1) + if err != nil { + t.Fatal("Failed to add second key derivation for input 2") + } + output1Key1, err := hex.DecodeString(CUTestPubkeyData["pub5"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + output1Path := CUTestPathData["dpath5"] + + // check invalid pubkeys are not accepted + borkedOutput1Key1 := append([]byte{0xab}, output1Key1[:13]...) + err = updater.AddOutBip32Derivation(masterKeyInt, output1Path, + borkedOutput1Key1, 0) + if err == nil { + t.Fatalf("Expected invalid pubkey, got: %v", err) + } + + err = updater.AddOutBip32Derivation(masterKeyInt, output1Path, output1Key1, 0) + if err != nil { + t.Fatal("Failed to add key to first output") + } + output2Key1, err := hex.DecodeString(CUTestPubkeyData["pub6"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + output2Path := CUTestPathData["dpath6"] + err = updater.AddOutBip32Derivation(masterKeyInt, output2Path, output2Key1, 1) + if err != nil { + t.Fatal("Failed to add key to second output") + } + + b.Reset() + err = updater.Upsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex3"] != hex.EncodeToString(b.Bytes()) { + t.Fatal("Failed to create valid updated PSBT after BIP32 derivations") + } + err = updater.AddInSighashType(txscript.SigHashType(1), 0) + if err != nil { + t.Fatal("Failed to add sighash type to first input") + } + err = updater.AddInSighashType(txscript.SigHashType(1), 1) + if err != nil { + t.Fatal("Failed to add sighash type to second input") + } + + b.Reset() + err = updater.Upsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex4"] != hex.EncodeToString(b.Bytes()) { + t.Fatal("Failed to create valid updated PSBT after sighash types") + } + b644, err := updater.Upsbt.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode updated Psbt: %v", err) + } + if b644 != CUTestB64Data["UOPsbtB644"] { + t.Fatalf("Failed to base64 encode updated PSBT after sighash "+ + "types: %v", b644) + } +} + +// Signing test data taken from +// https://github.com/achow101/bitcoin/blob/020628e3a4e88e36647eaf92bac4b3552796ac6a/test/functional/data/rpc_psbt.json +var signerPsbtData = map[string]string{ + "signer1Privkey1": "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr", + "signer1Privkey2": "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d", + "signer1PsbtB64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "signer1Result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "signer2Privkey1": "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", + "signer2Privkey2": "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE", + "signer2Psbt": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080010304010000000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f0000008000000080020000800103040100000000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "signer2Result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", +} + +func TestPsbtSigner(t *testing.T) { + psbt1, err := NewFromRawBytes( + bytes.NewReader([]byte(signerPsbtData["signer1PsbtB64"])), + true, + ) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + psbtUpdater1 := Updater{ + Upsbt: psbt1, + } + sig1, err := hex.DecodeString("3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01") + pub1, err := hex.DecodeString("029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f") + res, err := psbtUpdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + sig2, err := hex.DecodeString("3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01") + pub2, err := hex.DecodeString("03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc") + res, err = psbtUpdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + signer1Result, err := hex.DecodeString(signerPsbtData["signer1Result"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + var b bytes.Buffer + err = psbtUpdater1.Upsbt.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if !bytes.Equal(b.Bytes(), signer1Result) { + t.Fatalf("Failed to add signatures correctly") + } +} + +// Finalizer-extractor test + +var finalizerPsbtData = map[string]string{ + "finalizeb64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "finalize": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "resultb64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "network": "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000", + "twoOfThree": "70736274ff01005e01000000019a5fdb3c36f2168ea34a031857863c63bb776fd8a8a9149efd7341dfaf81c9970000000000ffffffff01e013a8040000000022002001c3a65ccfa5b39e31e6bafa504446200b9c88c58b4f21eb7e18412aff154e3f000000000001012bc817a80400000000220020114c9ab91ea00eb3e81a7aa4d0d8f1bc6bd8761f8f00dbccb38060dc2b9fdd5522020242ecd19afda551d58f496c17e3f51df4488089df4caafac3285ed3b9c590f6a847304402207c6ab50f421c59621323460aaf0f731a1b90ca76eddc635aed40e4d2fc86f97e02201b3f8fe931f1f94fde249e2b5b4dbfaff2f9df66dd97c6b518ffa746a4390bd1012202039f0acfe5a292aafc5331f18f6360a3cc53d645ebf0cc7f0509630b22b5d9f547473044022075329343e01033ebe5a22ea6eecf6361feca58752716bdc2260d7f449360a0810220299740ed32f694acc5f99d80c988bb270a030f63947f775382daf4669b272da0010103040100000001056952210242ecd19afda551d58f496c17e3f51df4488089df4caafac3285ed3b9c590f6a821035a654524d301dd0265c2370225a6837298b8ca2099085568cc61a8491287b63921039f0acfe5a292aafc5331f18f6360a3cc53d645ebf0cc7f0509630b22b5d9f54753ae22060242ecd19afda551d58f496c17e3f51df4488089df4caafac3285ed3b9c590f6a818d5f7375b2c000080000000800000008000000000010000002206035a654524d301dd0265c2370225a6837298b8ca2099085568cc61a8491287b63918e2314cf32c000080000000800000008000000000010000002206039f0acfe5a292aafc5331f18f6360a3cc53d645ebf0cc7f0509630b22b5d9f54718e524a1ce2c000080000000800000008000000000010000000000", +} + +func TestFinalize2of3(t *testing.T) { + b, err := hex.DecodeString(finalizerPsbtData["twoOfThree"]) + if err != nil { + t.Fatalf("Error decoding hex: %v", err) + } + p, err := NewFromRawBytes(bytes.NewReader(b), false) + if p.IsComplete() { + t.Fatalf("Psbt is complete") + } + err = MaybeFinalizeAll(p) + if err != nil { + t.Fatalf("Error in MaybeFinalizeAll: %v", err) + } + if !p.IsComplete() { + t.Fatalf("Psbt is not complete") + } +} + +func TestPsbtExtractor(t *testing.T) { + rawToFinalize, err := base64.StdEncoding.DecodeString( + finalizerPsbtData["finalizeb64"], + ) + if err != nil { + t.Fatalf("Error decoding b64: %v", err) + } + + psbt1, err := NewFromRawBytes( + bytes.NewReader(rawToFinalize), false, + ) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + for i := range psbt1.Inputs { + err = Finalize(psbt1, i) + if err != nil { + t.Fatalf("Error from finalizing PSBT: %v", err) + } + } + + finalizer1Result, err := base64.StdEncoding.DecodeString( + finalizerPsbtData["resultb64"], + ) + if err != nil { + t.Fatalf("Unable to decode b64: %v", err) + } + finalToNetworkExpected, err := hex.DecodeString(finalizerPsbtData["network"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + tx, err := Extract(psbt1) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + var resultToNetwork bytes.Buffer + if err := tx.Serialize(&resultToNetwork); err != nil { + t.Fatalf("unable to serialize: %v", err) + } + + var b bytes.Buffer + err = psbt1.Serialize(&b) + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if !bytes.Equal(b.Bytes(), finalizer1Result) { + t.Fatalf("Failed to finalize transaction: expected %x, "+ + "got %x", finalizer1Result, b.Bytes()) + } + if !bytes.Equal(finalToNetworkExpected, resultToNetwork.Bytes()) { + t.Fatalf("Failed to network serialize transaction: %x", b.Bytes()) + } +} + +func TestImportFromCore1(t *testing.T) { + // This example #1 was created manually using Bitcoin Core 0.17 regtest. + // It contains two inputs, one p2wkh and one p2pkh (non-witness). + // We take the created PSBT as input, then add the fields for each input + // separately, then finalize and extract, and compare with the network + // serialized tx output from Core. + imported := "cHNidP8BAJwCAAAAAjaoF6eKeGsPiDQxxqqhFDfHWjBtZzRqmaZmvyCVWZ5JAQAAAAD/////RhypNiFfnQSMNpo0SGsgIvDOyMQFAYEHZXD5jp4kCrUAAAAAAP////8CgCcSjAAAAAAXqRQFWy8ScSkkhlGMwfOnx15YwRzApofwX5MDAAAAABepFAt4TyLfGnL9QY6GLYHbpSQj+QclhwAAAAAAAAAAAA==" + psbt1, err := NewFromRawBytes(bytes.NewReader([]byte(imported)), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo (witness) and the second input's utxo + // (non-witness) + fundingTxInput1Hex := "02000000014f2cbac7d7691fafca30313097d79be9e78aa6670752fcb1fc15508e77586efb000000004847304402201b5568d7cab977ae0892840b779d84e36d62e42fd93b95e648aaebeacd2577d602201d2ebda2b0cddfa0c1a71d3cbcb602e7c9c860a41ed8b4d18d40c92ccbe92aed01feffffff028c636f91000000001600147447b6d7e6193499565779c8eb5184fcfdfee6ef00879303000000001600149e88f2828a074ebf64af23c2168d1816258311d72d010000" + fundingTxInput2Hex := "020000000001012f03f70c673d83d65da0e8d0db3867b3e7d7bfbd34fd6be65892042e57576eb00000000000feffffff028027128c000000001976a91485780899b61a5506f342bd67a2f635181f50c8b788acb8032c040000000017a914e2e3d32d42d6f043cab39708a6073301df5039db8702473044022047ae396fd8aba8f67482ad16e315fe680db585c1ac6422ffb18dacd9cf5bac350220321176fd6157ef51d9eae9230b0b5bd7dd29bb6247a879189e6aaa8091f3020201210368081f7ff37dfadbed407eba17b232f959e41e6ac78741192c805ebf80d487852f010000" + fundingTxInput1Bytes, err := hex.DecodeString(fundingTxInput1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund1 := wire.NewMsgTx(2) + err = txFund1.Deserialize(bytes.NewReader(fundingTxInput1Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + // First input is witness, take correct output: + txFund1Out := txFund1.TxOut[1] + + fundingTxInput2Bytes, err := hex.DecodeString(fundingTxInput2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund2 := wire.NewMsgTx(2) + err = txFund2.Deserialize(bytes.NewReader(fundingTxInput2Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + psbtupdater1 := Updater{Upsbt: psbt1} + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + err = psbtupdater1.AddInNonWitnessUtxo(txFund2, 1) + if err != nil { + t.Fatalf("Error inserting non-witness utxo: %v", err) + } + + // Signing was done with Core; we manually insert the relevant input + // entries here. + sig1Hex := "304402200da03ac9890f5d724c42c83c2a62844c08425a274f1a5bca50dcde4126eb20dd02205278897b65cb8e390a0868c9582133c7157b2ad3e81c1c70d8fbd65f51a5658b01" + sig1, err := hex.DecodeString(sig1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub1Hex := "024d6b24f372dd4551277c8df4ecc0655101e11c22894c8e05a3468409c865a72c" + pub1, err := hex.DecodeString(pub1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // Check that invalid pubkeys are not accepted. + pubInvalid := append(pub1, 0x00) + + res, err := psbtupdater1.Sign(0, sig1, pubInvalid, nil, nil) + if err == nil { + t.Fatalf("Incorrectly accepted invalid pubkey: %v", + pubInvalid) + } + + res, err = psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + + sig2Hex := "3044022014eb9c4858f71c9f280bc68402aa742a5187f54c56c8eb07c902eb1eb5804e5502203d66656de8386b9b044346d5605f5ae2b200328fb30476f6ac993fc0dbb0455901" + sig2, err := hex.DecodeString(sig2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub2Hex := "03b4c79acdf4e7d978bef4019c421e4c6c67044ed49d27322dc90e808d8080e862" + pub2, err := hex.DecodeString(pub2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // =============================================================== + // Before adding the signature, we'll make a new PSBT with + // modifications to the input data and check it fails sanity checks. + + // First an invalid tx: + psbtBorkedInput2, _ := NewFromRawBytes(bytes.NewReader([]byte(imported)), true) + borkedUpdater, err := NewUpdater(psbtBorkedInput2) + if err != nil { + t.Fatalf("NewUpdater failed while trying to create borked "+ + "version: %v", err) + } + borkedUpdater.AddInWitnessUtxo(txFund1Out, 0) + + res, err = borkedUpdater.Sign(0, sig2, pub2, nil, nil) + if err != ErrInvalidSignatureForInput { + t.Fatalf("AddPartialSig succeeded, but should have failed "+ + "due to mismatch between pubkey and prevOut; err was: %v", err) + } + + // Next, a valid tx serialization, but not the right one + wrongTxBytes, err := hex.DecodeString("020000000001012d1d7b17356d0ad8232a5817d2d2fa5cd97d803c0ed03e013e97b65f4f1e5e7501000000171600147848cfb25bb163c7c63732615980a25eddbadc7bfeffffff022a8227630000000017a91472128ae6b6a1b74e499bedb5efb1cb09c9a6713287107240000000000017a91485f81cb970d854e2513ebf5c5b5d09e4509f4af3870247304402201c09aa8bcd18753ef01d8712a55eea5a0f69b6c4cc2944ac942264ff0662c91402201fc1390bf8b0023dd12ae78d7ec181124e106de57bc8f00812ae92bd024d3045012103ba077fc011aa59393bfe17cf491b3a02a9c4d39df122b2148322da0ec23508f459430800") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + wrongTx := wire.NewMsgTx(2) + err = wrongTx.Deserialize(bytes.NewReader(wrongTxBytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + psbtBorkedInput2.Inputs[1] = *NewPsbtInput(wrongTx, nil) + res, err = borkedUpdater.Sign(1, sig2, pub2, nil, nil) + if err != ErrInvalidSignatureForInput { + t.Fatalf("Error should have been invalid sig for input, was: %v", err) + } + // ====================================================== + + res, err = psbtupdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add signature to second input: %v %v", err, res) + } + + // Neither input (p2pkh and p2wkh) require redeem script nor witness script, + // so there are no more fields to add; we are ready to finalize. + err = Finalize(psbt1, 0) + if err != nil { + t.Fatalf("Failed to finalize the first input, %v", err) + } + if psbt1.IsComplete() { + t.Fatalf("PSBT was complete but has not been fully finalized") + } + err = Finalize(psbt1, 1) + if err != nil { + t.Fatalf("Failed to finalize second input, %v", err) + } + + tx, err := Extract(psbt1) + if err != nil { + t.Fatalf("unable to extract tx: %v", err) + } + var networkSerializedTx bytes.Buffer + if err := tx.Serialize(&networkSerializedTx); err != nil { + t.Fatalf("unable to encode tx: %v", err) + } + + expectedTx := "0200000000010236a817a78a786b0f883431c6aaa11437c75a306d67346a99a666bf2095599e490100000000ffffffff461ca936215f9d048c369a34486b2022f0cec8c4050181076570f98e9e240ab5000000006a473044022014eb9c4858f71c9f280bc68402aa742a5187f54c56c8eb07c902eb1eb5804e5502203d66656de8386b9b044346d5605f5ae2b200328fb30476f6ac993fc0dbb04559012103b4c79acdf4e7d978bef4019c421e4c6c67044ed49d27322dc90e808d8080e862ffffffff028027128c0000000017a914055b2f1271292486518cc1f3a7c75e58c11cc0a687f05f93030000000017a9140b784f22df1a72fd418e862d81dba52423f90725870247304402200da03ac9890f5d724c42c83c2a62844c08425a274f1a5bca50dcde4126eb20dd02205278897b65cb8e390a0868c9582133c7157b2ad3e81c1c70d8fbd65f51a5658b0121024d6b24f372dd4551277c8df4ecc0655101e11c22894c8e05a3468409c865a72c0000000000" + expectedTxBytes, err := hex.DecodeString(expectedTx) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + if !bytes.Equal(expectedTxBytes, networkSerializedTx.Bytes()) { + t.Fatalf("The produced network transaction did not match the expected: %x \n %x \n", + networkSerializedTx.Bytes(), expectedTxBytes) + } + +} + +func TestImportFromCore2(t *testing.T) { + // This example #2 was created manually using Bitcoin Core 0.17 regtest. + // It contains two inputs, one p2sh-p2wkh and one fake utxo. + // The PSBT has been created with walletcreatepsbt and then partial-signed + // on the real input with walletprocessbst in Core. + // We first check that the updating here, using the Core created signature, + // redeem script and signature for the p2sh-p2wkh input, creates the + // same partial-signed intermediate transaction as Core did after + // walletprocesspsbt. + // We then attach a fake + // input of type p2sh-p2wsh, attach its witnessUtxo, redeemscript and + // witnessscript fields, and then finalize the whole transaction. Unlike + // the previous example, we cannot here compare with a Core produced + // network serialized final transaction, because of the fake input. + imported := "cHNidP8BAJsCAAAAAkxTQ+rig5QNnUS5nMc+Pccow4IcOJeQRcNNw+7p5ZA5AQAAAAD/////qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqoNAAAAAP////8CAIYOcAAAAAAWABQ1l7nn13RubTwqRQU2BnVV5WlXBWAxMbUAAAAAF6kUkiuXUjfWFgTp6nl/gf9+8zIWR6KHAAAAAAAAAAAA" + psbt1, err := NewFromRawBytes(bytes.NewReader([]byte(imported)), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo, taken from its funding + // transaction + fundingTxInput1Hex := "02000000017b260536a3c17aee49c41a9b36fdf01a418e0c04df06fbabcb0d4f590b95d175000000006a473044022074a5a13159b6c12d77881c9501aa5c18616fb76c1809fc4d55f18a2e63159a6702200d1aa72be6056a41808898d24da93c0c0192cad65b7c2cc86e00b3e0fbbd57f601210212cc429d61fde565d0c2271a3e4fdb063cb49ae2257fa71460be753ceb56d175feffffff02bc060d8f0000000017a9140b56c31b5dc5a5a22c45a7850e707ad602d94a3087008352840000000017a9149f3679d67a9a486238764f618a93b82a7d999103879a000000" + fundingTxInput1Bytes, err := hex.DecodeString(fundingTxInput1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund1 := wire.NewMsgTx(2) + err = txFund1.Deserialize(bytes.NewReader(fundingTxInput1Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + // First input is witness, take correct output: + txFund1Out := txFund1.TxOut[1] + + psbtupdater1 := Updater{Upsbt: psbt1} + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + + // This input is p2sh-p2wkh, so it requires a redeemscript but not + // a witness script. The redeemscript is the witness program. + redeemScript, err := hex.DecodeString("00147aed39420a8b7ab98a83791327ccb70819d1fbe2") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbtupdater1.AddInRedeemScript(redeemScript, 0) + + // Signing for the first input was done with Core; we manually insert the + // relevant input entries here. + sig1Hex := "30440220546d182d00e45ef659c329dce6197dc19e0abc795e2c9279873f5a887998b273022044143113fc3475d04fc8d5113e0bbcb42d80514a9f1a2247e9b2a7878e20d44901" + sig1, err := hex.DecodeString(sig1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub1Hex := "02bb3ce35af26f4c826eab3e5fc263ef56871b26686a8a995599b7ee6576613104" + pub1, err := hex.DecodeString(pub1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + res, err := psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Unable to add partial signature: %v %v", err, res) + } + + // Since this input is now finalizable, we do so: + err = Finalize(psbt1, 0) + if err != nil { + t.Fatalf("Failed to finalize the first input: %v", err) + } + if psbt1.IsComplete() { + t.Fatalf("PSBT was complete but has not been fully finalized") + } + + // Core also adds the OutRedeemScript field for the output it knows about. + // Note that usually we would not of course re-create, but rather start + // from the half-signed version; so this is needed only for a sanity check + // that we can recreate the half-signed. + output2RedeemScript, err := hex.DecodeString("0014e0846bd17848ab40ca1f56b655c6fa31667880cc") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbtupdater1.AddOutRedeemScript(output2RedeemScript, 1) + // The main function of the test is to compare the thus-generated + // partially (not completely) signed transaction with that generated and + // encoded by Core. + expectedPsbtPartialB64 := "cHNidP8BAJsCAAAAAkxTQ+rig5QNnUS5nMc+Pccow4IcOJeQRcNNw+7p5ZA5AQAAAAD/////qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqoNAAAAAP////8CAIYOcAAAAAAWABQ1l7nn13RubTwqRQU2BnVV5WlXBWAxMbUAAAAAF6kUkiuXUjfWFgTp6nl/gf9+8zIWR6KHAAAAAAABASAAg1KEAAAAABepFJ82edZ6mkhiOHZPYYqTuCp9mZEDhwEHFxYAFHrtOUIKi3q5ioN5EyfMtwgZ0fviAQhrAkcwRAIgVG0YLQDkXvZZwync5hl9wZ4KvHleLJJ5hz9aiHmYsnMCIEQUMRP8NHXQT8jVET4LvLQtgFFKnxoiR+myp4eOINRJASECuzzjWvJvTIJuqz5fwmPvVocbJmhqiplVmbfuZXZhMQQAAAABABYAFOCEa9F4SKtAyh9WtlXG+jFmeIDMAA==" + generatedPsbtPartialB64, err := psbt1.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode Psbt: %v", err) + } + if expectedPsbtPartialB64 != generatedPsbtPartialB64 { + t.Fatalf("Partial did not match expected: %v", generatedPsbtPartialB64) + } + + // We now simulate adding the signing data for the second (fake) input, + // and check that we can finalize and extract. This input is p2sh-p2wsh. + // the second input is fake, we're going to make it witness type, + // so create a TxOut struct that fits + fakeTxOutSerialized, err := hex.DecodeString("00c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + fakevalSerialized := binary.LittleEndian.Uint64(fakeTxOutSerialized[:8]) + fakeScriptPubKey := fakeTxOutSerialized[9:] + txFund2Out := wire.NewTxOut(int64(fakevalSerialized), fakeScriptPubKey) + psbt2, err := NewFromRawBytes(bytes.NewReader([]byte(expectedPsbtPartialB64)), true) + if err != nil { + t.Fatalf("Failed to load partial PSBT: %v", err) + } + psbtupdater2, err := NewUpdater(psbt2) + if err != nil { + t.Fatalf("Failed to create updater: %v", err) + } + psbtupdater2.AddInWitnessUtxo(txFund2Out, 1) + // Add redeemScript, which is the witnessscript/program: + redeemScript, err = hex.DecodeString("00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + err = psbtupdater2.AddInRedeemScript(redeemScript, 1) + if err != nil { + t.Fatalf("Failed to add redeemscript to second input: %v", err) + } + // Add witnessScript, which here is multisig: + witnessScript, err := hex.DecodeString("522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + // To test multisig checks, add a nonsense version of the multisig script + witnessScriptNonsense, err := hex.DecodeString("52ffff") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + err = psbtupdater2.AddInWitnessScript(witnessScript, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + // Construct the two partial signatures to be added + sig21, err := hex.DecodeString("3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + pub21, err := hex.DecodeString("03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + sig22, err := hex.DecodeString("3044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + pub22, err := hex.DecodeString("023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + res, err = psbtupdater2.Sign(1, sig21, pub21, nil, nil) + + // Check that the finalization procedure fails here due to not + // meeting the multisig policy + success, err := MaybeFinalize(psbt2, 1) + if success { + t.Fatalf("Incorrectly succeeded in finalizing without sigs") + } + if err != ErrUnsupportedScriptType { + t.Fatalf("Got unexpected error type: %v", err) + } + + res, err = psbtupdater2.Sign(1, sig22, pub22, nil, nil) + + // Check that the finalization procedure also fails with a nonsense + // script + err = psbtupdater2.AddInWitnessScript(witnessScriptNonsense, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + success, err = MaybeFinalize(psbt2, 1) + if success { + t.Fatalf("Incorrectly succeeded in finalizing with invalid msigscript") + } + if err != ErrUnsupportedScriptType { + t.Fatalf("Got unexpected error type: %v", err) + } + + // Restore the correct witnessScript to complete correctly + err = psbtupdater2.AddInWitnessScript(witnessScript, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + + success, err = MaybeFinalize(psbt2, 1) + if !success { + if err != nil { + t.Fatalf("Failed to finalize second input: %v", err) + } else { + t.Fatalf("Input was not finalizable") + } + } + + // Add a (fake) witnessOut descriptor field to one of the outputs, + // for coverage purposes (we aren't currently using this field) + psbtupdater2.AddOutWitnessScript([]byte{0xff, 0xff, 0xff}, 0) + + // Sanity check; we should not have lost the additional output entry + // provided by Core initially + uoutput1 := psbtupdater2.Upsbt.Outputs[1] + if uoutput1.RedeemScript == nil { + t.Fatalf("PSBT should contain outredeemscript entry, but it does not.") + } + // Nor should we have lost our fake witnessscript output entry + uoutput2 := psbtupdater2.Upsbt.Outputs[0] + if uoutput2.WitnessScript == nil { + t.Fatalf("PSBT should contain outwitnessscript but it does not.") + } + var tx bytes.Buffer + networkSerializedTx, err := Extract(psbt2) + if err != nil { + t.Fatalf("unable to extract tx: %v", err) + } + if err := networkSerializedTx.Serialize(&tx); err != nil { + t.Fatalf("unable to encode tx: %v", err) + } + expectedSerializedTx, err := hex.DecodeString("020000000001024c5343eae283940d9d44b99cc73e3dc728c3821c38979045c34dc3eee9e5903901000000171600147aed39420a8b7ab98a83791327ccb70819d1fbe2ffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0d000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0200860e70000000001600143597b9e7d7746e6d3c2a450536067555e5695705603131b50000000017a914922b975237d61604e9ea797f81ff7ef3321647a287024730440220546d182d00e45ef659c329dce6197dc19e0abc795e2c9279873f5a887998b273022044143113fc3475d04fc8d5113e0bbcb42d80514a9f1a2247e9b2a7878e20d449012102bb3ce35af26f4c826eab3e5fc263ef56871b26686a8a995599b7ee65766131040400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + if !bytes.Equal(expectedSerializedTx, tx.Bytes()) { + t.Fatalf("Failed to create correct network serialized "+ + "transaction: expected %x, got %x", + expectedSerializedTx, tx.Bytes()) + } +} + +func TestMaybeFinalizeAll(t *testing.T) { + // The following data is from a 3rd transaction from Core, + // using 3 inputs, all p2wkh. + imported := "cHNidP8BAKQCAAAAAzJyXH13IqBFvvZ7y1VSgUgkMvMoPgP5CfFNqsjQexKQAQAAAAD/////fMdLydu5bsoiHN9cFSaBL0Qnq2KLSKx0RA4b938CAgQAAAAAAP/////yKNgfsDAHr/zFz8R9k8EFI26allfg9DdE8Gzj6tGlegEAAAAA/////wHw9E0OAAAAABYAFDnPCRduiEWmmSc1j30SJ8k9u7PHAAAAAAAAAAAA" + psbt1, err := NewFromRawBytes(bytes.NewReader([]byte(imported)), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo, taken from its funding + // transaction + fundingTxInput1, err := hex.DecodeString("020000000001017b260536a3c17aee49c41a9b36fdf01a418e0c04df06fbabcb0d4f590b95d1750100000017160014af82cd4409241b1de892726324bd780e3b5cd8aafeffffff02a85f9800000000001600149d21f8b306ddfd4dd035080689e88b4c3471e3cc801d2c0400000000160014d97ccd3dfb60820d7d33d862371ca5a73039bd560247304402201a1d2fdb5a7190b7fa59907769f0fc9c91fd3b34f6424acf5868a8ac21ec287102200a59b9d076ecf98c88f2196ed2be0aafff4966ead754041182fff5f92115a783012103604ffd31dc71db2e32c20f09eafe6353cd7515d3648aff829bb4879b553e30629a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + fundingTxInput2, err := hex.DecodeString("020000000001019c27b886e420fcadb077706b0933efa8bb53e3a250c3ec45cfdba5e05e233f360100000000feffffff0200b4c404000000001600140853f50c7d2d5d2af326a75efdbc83b62551e89afce31c0d000000001600142d6936c082c35607ec3bdb334a932d928150b75802473044022000d962f5e5e6425f9de21da7ac65b4fd8af8f6bfbd33c7ba022827c73866b477022034c59935c1ea10b5ba335d93f55a200c2588ec6058b8c7aedd10d5cbc4654f99012102c30e9f0cd98f6a805464d6b8a326b5679b6c3262934341855ee0436eaedfd2869a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + fundingTxInput3, err := hex.DecodeString("02000000012bf4331bb95df4eadb14f7a28db3fecdc5e87f08c29c2332b66338dd606699f60000000048473044022075ed43f508528da47673550a785702e9a93eca84a11faea91c4e9c66fcab3c9e022054a37610bd40b12263a5933188f062b718e007f290cecde2b6e41da3e1ebbddf01feffffff020c99a8240100000016001483bd916985726094d6d1c5b969722da580b5966a804a5d05000000001600140a2ee13a6696d75006af5e8a026ea49316087dae9a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + psbtupdater1 := Updater{Upsbt: psbt1} + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput1)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund1Out := tx.TxOut[1] + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + + tx = wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput2)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund2Out := tx.TxOut[0] + psbtupdater1.AddInWitnessUtxo(txFund2Out, 1) + + tx = wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput3)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund3Out := tx.TxOut[1] + psbtupdater1.AddInWitnessUtxo(txFund3Out, 2) + + // To be ready for finalization, we need to have partial signature + // fields for each input + sig1, _ := hex.DecodeString("30440220027605ee8015970baf02a72652967a543e1b29a6882d799738ed1baee508822702203818a2f1b9770c46a473f47ad7ae90bcc129a5d047f00fae354c80197a7cf50601") + pub1, _ := hex.DecodeString("03235fc1f9dc8bbf6fa3df35dfeb0dd486f2d488f139579885eb684510f004f6c1") + sig2, _ := hex.DecodeString("304402206f5aea4621696610de48736b95a89b1d3a434a4e536d9aae65e039c477cf4c7202203b27a18b0f63be7d3bbf5be1bc2306a7ec8c2da12c2820ff07b73c7f3f1d4d7301") + pub2, _ := hex.DecodeString("022011b496f0603a268b55a781c7be0c3849f605f09cb2e917ed44288b8144a752") + sig3, _ := hex.DecodeString("3044022036dbc6f8f85a856e7803cbbcf0a97b7a74806fc592e92d7c06826f911610b98e0220111d43c4b20f756581791334d9c5cbb1a9c07558f28404cabf01c782897ad50501") + pub3, _ := hex.DecodeString("0381772a80c69e275e20d7f014555b13031e9cacf1c54a44a67ab2bc7eba64f227") + res, err := psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add partial signature for input 0: %v %v", err, res) + } + res, err = psbtupdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add partial signature for input 1: %v %v", err, res) + } + + // Not ready for finalize all, check it fails: + err = MaybeFinalizeAll(psbt1) + if err != ErrNotFinalizable { + t.Fatalf("Expected finalization failure, got: %v", err) + } + + res, err = psbtupdater1.Sign(2, sig3, pub3, nil, nil) + + // Since this input is now finalizable and is p2wkh only, we can do + // all at once: + err = MaybeFinalizeAll(psbt1) + if err != nil { + t.Fatalf("Failed to finalize PSBT: %v", err) + } + if !psbt1.IsComplete() { + t.Fatalf("PSBT was finalized but not marked complete") + } + +} + +func TestFromUnsigned(t *testing.T) { + serTx, err := hex.DecodeString("00000000000101e165f072311e71825b47a4797221d7ae56d4b40b7707c540049aee43302448a40000000000feffffff0212f1126a0000000017a9143e836801b2b15aa193449d815c62d6c4b6227c898780778e060000000017a914ba4bdb0b07d67bc60f59c1f4fe54170565254974870000000000") + if err != nil { + t.Fatalf("Error: %v", err) + } + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(serTx)) + if err != nil { + t.Fatalf("Error: %v", err) + } + psbt1, err := NewFromUnsignedTx(tx) + if err != nil { + t.Fatalf("Error: %v", err) + } + encoded, err := psbt1.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode Psbt: %v", err) + } + + // Compare with output of Core: + fromCoreB64 := "cHNidP8BAHMAAAAAAeFl8HIxHnGCW0ekeXIh165W1LQLdwfFQASa7kMwJEikAAAAAAD+////AhLxEmoAAAAAF6kUPoNoAbKxWqGTRJ2BXGLWxLYifImHgHeOBgAAAAAXqRS6S9sLB9Z7xg9ZwfT+VBcFZSVJdIcAAAAAAAAAAA==" + if encoded != fromCoreB64 { + t.Fatalf("Got incorrect b64: %v", encoded) + } + _, err = NewFromRawBytes(bytes.NewReader([]byte(fromCoreB64)), true) + if err != nil { + t.Fatalf("Error: %v", err) + } +} + +func TestNonWitnessToWitness(t *testing.T) { + // We'll start with a PSBT produced by Core for which + // the first input is signed and we'll provided the signatures for + // the other three inputs; they are p2sh-p2wkh, p2wkh and legacy + // respectively. + // In each case we'll *first* attach the NonWitnessUtxo field, + // and then call sign; in the first two but not the third case, the + // NonWitnessUtxo will automatically be replaced with the WitnessUtxo. + // Finally we'll check that the fully finalized PSBT produced matches + // the one produced by Core for the same keys. + + psbt1B64 := "cHNidP8BAM4CAAAABHtBMXY+SX95xidmWJP67CTQ02FPUpbNhIxNplAdlvk+AQAAAAD/////G2mt4bX7+sVi1jdbuBa5Q/xsJdgzFCgdHHSZq3ewK6YAAAAAAP/////NrbZb7GzfAg4kOqFWAIbXabq4cAvtVGv+eecIIv1KggEAAAAA/////73s9ifprgErlaONH1rgpNs3l6+t+mz2XGTHsTVWCem/AQAAAAD/////AfAmclMAAAAAF6kUQwsEC5nzbdY5meON2ZQ2thmeFgOHAAAAAAABASAAZc0dAAAAABepFPAv3VTMu5+4WN+/HIji6kG9RpzKhwEHFxYAFLN3PqXSyIHWKqm4ah5m9erc/3OoAQhrAkcwRAIgH7kzGO2iskfCvX0dgkDuzfqJ7tAu7KUZOeykTkJ1SYkCIBv4QRZK1hLz45D0gs+Lz93OE4s37lkPVE+SlXZtazWEASEC3jaf19MMferBn0Bn5lxXJGOqoqmfSvnHclQvB5gJ3nEAAAAAAQAWABTB+Qcq6iqdSvvc6959kd7XHrhYFgA=" + nwutxo1ser, _ := hex.DecodeString("02000000017f7baa6b7377541c4aca372d2dce8e1098ba44aa8379b7ea87644ef27e08ec240000000048473044022072e3b94c33cb5128518cd3903cc0ca19e8c234ac6d462e01ae2bb1da7768ed7d0220167d7ad89f6e1bbb3b866ae6fc2f67b5e7d51eb4f33f7bfe3f4b2673856b815001feffffff0200c2eb0b0000000017a9142dd25c78db2e2e09376eab9cb342e1b03005abe487e4ab953e0000000017a914120b8ca3fb4c7f852e30d4e3714fb64027a0b4c38721020000") + nwutxo2ser, _ := hex.DecodeString("0200000001f51b0bb5d945dd5532448a4d3fb88134d0bd90493813515f9c2ddb1fa15b9ba60000000048473044022047d83caf88d398245c006374bfa9f27ae968f5f51d640cacd5a214ed2cba397a02204519b26035496855f574a72b73bdcfa46d53995faf64c8f0ab394b628cc5383901feffffff020ccb9f3800000000160014e13544a3c718faa6c5ad7089a6660383c12b072700a3e11100000000160014a5439b477c116b79bd4c7c5131f3e58d54f27bb721020000") + nwutxo3ser, _ := hex.DecodeString("0200000001eb452f0fc9a8c39edb79f7174763f3cb25dc56db455926e411719a115ef16509000000004847304402205aa80cc615eb4b3f6e89696db4eadd192581a6c46f5c09807d3d98ece1d77355022025007e58c1992a1e5d877ee324bfe0a65db26d29f80941cfa277ac3efbcad2a701feffffff02bce9a9320000000017a9141590e852ac66eb8798afeb2a5ed67c568a2d6561870084d717000000001976a914a57ea05eacf94900d5fb92bccd273cfdb90af36f88ac21020000") + + nwutxo1 := wire.NewMsgTx(2) + err := nwutxo1.Deserialize(bytes.NewReader(nwutxo1ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + nwutxo2 := wire.NewMsgTx(2) + err = nwutxo2.Deserialize(bytes.NewReader(nwutxo2ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + nwutxo3 := wire.NewMsgTx(2) + err = nwutxo3.Deserialize(bytes.NewReader(nwutxo3ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + + // import the PSBT + psbt1, err := NewFromRawBytes(bytes.NewReader([]byte(psbt1B64)), true) + if err != nil { + t.Fatalf("Failed to create PSBT: %v", err) + } + + // check that we recognize the finality of the first input + if !isFinalized(psbt1, 0) { + t.Fatalf("First input incorrectly read as not finalized.") + } + + // Add NonWitnessUtxo fields for each of the other three inputs + u := Updater{Upsbt: psbt1} + u.AddInNonWitnessUtxo(nwutxo1, 1) + u.AddInNonWitnessUtxo(nwutxo2, 2) + u.AddInNonWitnessUtxo(nwutxo3, 3) + + // Signatures for each of those inputs were created with Core: + sig1, _ := hex.DecodeString("304402205676877e6162ce40a49ee5a74443cdc1e7915637c42da7b872c2ec2298fd371b02203c1d4a05b1e2a7a588d9ec9b8d4892d2cd59bebe0e777483477a0ec692ebbe6d01") + pub1, _ := hex.DecodeString("02534f23cb88a048b649672967263bd7570312d5d31d066fa7b303970010a77b2b") + redeemScript1, _ := hex.DecodeString("00142412be29368c0260cb841eecd9b59d7e01174aa1") + + sig2, _ := hex.DecodeString("3044022065d0a349709b8d8043cfd644cf6c196c1f601a22e1b3fdfbf8c0cc2a80fe2f1702207c87d36b666a8862e81ec5df288707f517d2f35ea1548feb82019de2c8de90f701") + pub2, _ := hex.DecodeString("0257d88eaf1e79b72ea0a33ae89b57dae95ea68499bdc6770257e010ab899f0abb") + + sig3, _ := hex.DecodeString("30440220290abcaacbd759c4f989762a9ee3468a9231788aab8f50bf65955d8597d8dd3602204d7e394f4419dc5392c6edba6945837458dd750a030ac67a746231903a8eb7db01") + pub3, _ := hex.DecodeString("0388025f50bb51c0469421ed13381f22f9d46a070ec2837e055c49c5876f0d0968") + + // Add the signatures and any scripts needed to the inputs + res, err := u.Sign(1, sig1, pub1, redeemScript1, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 1, res, err) + } + res, err = u.Sign(2, sig2, pub2, nil, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 2, res, err) + } + res, err = u.Sign(3, sig3, pub3, nil, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 3, res, err) + } + + // Attempt to finalize the rest of the transaction + _, err = MaybeFinalize(psbt1, 1) + if err != nil { + t.Fatalf("Failed to finalize input 1 %v", err) + } + _, err = MaybeFinalize(psbt1, 2) + if err != nil { + t.Fatalf("Failed to finalize input 2 %v", err) + } + _, err = MaybeFinalize(psbt1, 3) + if err != nil { + t.Fatalf("Failed to finalize input 3 %v", err) + } + + // Finally we can check whether both the B64 encoding of the PSBT, + // and the final network serialized signed transaction, that we generated + // with Core using the 2 wallets, matches what this code produces: + expectedFinalizedPsbt := "cHNidP8BAM4CAAAABHtBMXY+SX95xidmWJP67CTQ02FPUpbNhIxNplAdlvk+AQAAAAD/////G2mt4bX7+sVi1jdbuBa5Q/xsJdgzFCgdHHSZq3ewK6YAAAAAAP/////NrbZb7GzfAg4kOqFWAIbXabq4cAvtVGv+eecIIv1KggEAAAAA/////73s9ifprgErlaONH1rgpNs3l6+t+mz2XGTHsTVWCem/AQAAAAD/////AfAmclMAAAAAF6kUQwsEC5nzbdY5meON2ZQ2thmeFgOHAAAAAAABASAAZc0dAAAAABepFPAv3VTMu5+4WN+/HIji6kG9RpzKhwEHFxYAFLN3PqXSyIHWKqm4ah5m9erc/3OoAQhrAkcwRAIgH7kzGO2iskfCvX0dgkDuzfqJ7tAu7KUZOeykTkJ1SYkCIBv4QRZK1hLz45D0gs+Lz93OE4s37lkPVE+SlXZtazWEASEC3jaf19MMferBn0Bn5lxXJGOqoqmfSvnHclQvB5gJ3nEAAQEgAMLrCwAAAAAXqRQt0lx42y4uCTduq5yzQuGwMAWr5IcBBxcWABQkEr4pNowCYMuEHuzZtZ1+ARdKoQEIawJHMEQCIFZ2h35hYs5ApJ7lp0RDzcHnkVY3xC2nuHLC7CKY/TcbAiA8HUoFseKnpYjZ7JuNSJLSzVm+vg53dINHeg7Gkuu+bQEhAlNPI8uIoEi2SWcpZyY711cDEtXTHQZvp7MDlwAQp3srAAEBHwCj4REAAAAAFgAUpUObR3wRa3m9THxRMfPljVTye7cBCGsCRzBEAiBl0KNJcJuNgEPP1kTPbBlsH2AaIuGz/fv4wMwqgP4vFwIgfIfTa2ZqiGLoHsXfKIcH9RfS816hVI/rggGd4sjekPcBIQJX2I6vHnm3LqCjOuibV9rpXqaEmb3GdwJX4BCriZ8KuwABAL0CAAAAAetFLw/JqMOe23n3F0dj88sl3FbbRVkm5BFxmhFe8WUJAAAAAEhHMEQCIFqoDMYV60s/bolpbbTq3RklgabEb1wJgH09mOzh13NVAiAlAH5YwZkqHl2HfuMkv+CmXbJtKfgJQc+id6w++8rSpwH+////ArzpqTIAAAAAF6kUFZDoUqxm64eYr+sqXtZ8VootZWGHAITXFwAAAAAZdqkUpX6gXqz5SQDV+5K8zSc8/bkK82+IrCECAAABB2pHMEQCICkKvKrL11nE+Yl2Kp7jRoqSMXiKq49Qv2WVXYWX2N02AiBNfjlPRBncU5LG7bppRYN0WN11CgMKxnp0YjGQOo632wEhA4gCX1C7UcBGlCHtEzgfIvnUagcOwoN+BVxJxYdvDQloAAEAFgAUwfkHKuoqnUr73OvefZHe1x64WBYA" + calculatedPsbt, err := u.Upsbt.B64Encode() + if err != nil { + t.Fatalf("Failed to base64 encode") + } + if expectedFinalizedPsbt != calculatedPsbt { + t.Fatalf("Failed to generate correct PSBT") + } + + expectedNetworkSer, _ := hex.DecodeString("020000000001047b4131763e497f79c627665893faec24d0d3614f5296cd848c4da6501d96f93e0100000017160014b3773ea5d2c881d62aa9b86a1e66f5eadcff73a8ffffffff1b69ade1b5fbfac562d6375bb816b943fc6c25d83314281d1c7499ab77b02ba600000000171600142412be29368c0260cb841eecd9b59d7e01174aa1ffffffffcdadb65bec6cdf020e243aa1560086d769bab8700bed546bfe79e70822fd4a820100000000ffffffffbdecf627e9ae012b95a38d1f5ae0a4db3797afadfa6cf65c64c7b1355609e9bf010000006a4730440220290abcaacbd759c4f989762a9ee3468a9231788aab8f50bf65955d8597d8dd3602204d7e394f4419dc5392c6edba6945837458dd750a030ac67a746231903a8eb7db01210388025f50bb51c0469421ed13381f22f9d46a070ec2837e055c49c5876f0d0968ffffffff01f02672530000000017a914430b040b99f36dd63999e38dd99436b6199e1603870247304402201fb93318eda2b247c2bd7d1d8240eecdfa89eed02eeca51939eca44e4275498902201bf841164ad612f3e390f482cf8bcfddce138b37ee590f544f9295766d6b3584012102de369fd7d30c7deac19f4067e65c572463aaa2a99f4af9c772542f079809de710247304402205676877e6162ce40a49ee5a74443cdc1e7915637c42da7b872c2ec2298fd371b02203c1d4a05b1e2a7a588d9ec9b8d4892d2cd59bebe0e777483477a0ec692ebbe6d012102534f23cb88a048b649672967263bd7570312d5d31d066fa7b303970010a77b2b02473044022065d0a349709b8d8043cfd644cf6c196c1f601a22e1b3fdfbf8c0cc2a80fe2f1702207c87d36b666a8862e81ec5df288707f517d2f35ea1548feb82019de2c8de90f701210257d88eaf1e79b72ea0a33ae89b57dae95ea68499bdc6770257e010ab899f0abb0000000000") + tx, err := Extract(psbt1) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + var b bytes.Buffer + if err := tx.Serialize(&b); err != nil { + t.Fatalf("unable to encode tx: %v", err) + } + if !bytes.Equal(expectedNetworkSer, b.Bytes()) { + t.Fatalf("Expected serialized transaction was not produced: %x", b.Bytes()) + } +} + +// TestEmptyInputSerialization tests the special serialization case for a wire +// transaction that has no inputs. +func TestEmptyInputSerialization(t *testing.T) { + // Create and serialize a new, empty PSBT. The wire package will assume + // it's a non-witness transaction, as there are no inputs. + psbt, err := New(nil, nil, 2, 0, nil) + if err != nil { + t.Fatalf("failed to create empty PSBT: %v", err) + } + var buf bytes.Buffer + err = psbt.Serialize(&buf) + if err != nil { + t.Fatalf("failed to serialize empty PSBT: %v", err) + } + + // Try to deserialize the empty transaction again. The wire package will + // assume it's a witness transaction because of the special case where + // there are no inputs. This assumption is wrong and the first attempt + // will fail. But a workaround should try again to deserialize the TX + // with the non-witness format. + psbt2, err := NewFromRawBytes(&buf, false) + if err != nil { + t.Fatalf("failed to deserialize empty PSBT: %v", err) + } + if len(psbt2.UnsignedTx.TxIn) > 0 || len(psbt2.UnsignedTx.TxOut) > 0 { + t.Fatalf("deserialized transaction not empty") + } +} + +// TestWitnessForNonWitnessUtxo makes sure that a packet that only has a non- +// witness UTXO set can still be signed correctly by adding witness data. This +// is to make sure that PSBTs following the CVE-2020-14199 bugfix are not +// rejected. See https://github.com/bitcoin/bitcoin/pull/19215. +func TestWitnessForNonWitnessUtxo(t *testing.T) { + // Our witness UTXO is index 1 of this raw transaction from the test + // vectors. + prevTxRaw, _ := hex.DecodeString("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000") + prevTx := wire.NewMsgTx(2) + err := prevTx.Deserialize(bytes.NewReader(prevTxRaw)) + if err != nil { + t.Fatalf("failed to deserialize previous TX: %v", err) + } + + // First create a packet that contains one input and one output. + outPkScript, _ := hex.DecodeString(CUTestHexData["scriptPubkey1"]) + packet := &Packet{ + UnsignedTx: &wire.MsgTx{ + Version: 2, + LockTime: 0, + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: prevTx.TxHash(), + Index: 1, + }, + }}, + TxOut: []*wire.TxOut{{ + PkScript: outPkScript, + Value: 1.9 * btcutil.SatoshiPerBitcoin, + }}, + }, + Inputs: []PInput{{}}, + Outputs: []POutput{{}}, + } + + // Create an updater for the packet. This also performs a sanity check. + updater, err := NewUpdater(packet) + if err != nil { + t.Fatalf("failed to sanity check raw packet: %v", err) + } + + // Now add our witness UTXO to the input. But because hardware wallets + // that are patched against CVE-2020-14199 require the full non-witness + // UTXO to be set for all inputs, we do what Core does and add the full + // transaction in the NonWitnessUtxo instead of just the outpoint in + // WitnessUtxo. + err = updater.AddInNonWitnessUtxo(prevTx, 0) + if err != nil { + t.Fatalf("failed to update non-witness UTXO: %v", err) + } + + // Then add the redeem scripts and witness scripts. + redeemScript, _ := hex.DecodeString(CUTestHexData["Input2RedeemScript"]) + err = updater.AddInRedeemScript(redeemScript, 0) + if err != nil { + t.Fatalf("failed to update redeem script: %v", err) + } + witnessScript, _ := hex.DecodeString(CUTestHexData["Input2WitnessScript"]) + err = updater.AddInWitnessScript(witnessScript, 0) + if err != nil { + t.Fatalf("failed to update redeem script: %v", err) + } + + // Add the first of the two partial signatures. + sig1, _ := hex.DecodeString("3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01") + pub1, _ := hex.DecodeString("03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc") + res, err := updater.Sign(0, sig1, pub1, nil, nil) + if err != nil { + t.Fatalf("failed to sign with pubkey 1: %v", err) + } + if res != SignSuccesful { + t.Fatalf("signing was not successful, got result %v", res) + } + + // Check that the finalization procedure fails here due to not + // meeting the multisig policy + success, err := MaybeFinalize(packet, 0) + if success { + t.Fatalf("Incorrectly succeeded in finalizing without sigs") + } + if err != ErrUnsupportedScriptType { + t.Fatalf("Got unexpected error type: %v", err) + } + + // Add the second partial signature. + sig2, _ := hex.DecodeString("3044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201") + pub2, _ := hex.DecodeString("023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73") + res, err = updater.Sign(0, sig2, pub2, nil, nil) + if err != nil { + t.Fatalf("failed to sign with pubkey 2: %v", err) + } + if res != SignSuccesful { + t.Fatalf("signing was not successful, got result %v", res) + } + + // Finally make sure we can finalize the packet and extract the raw TX. + err = MaybeFinalizeAll(packet) + if err != nil { + t.Fatalf("error finalizing PSBT: %v", err) + } + _, err = Extract(packet) + if err != nil { + t.Fatalf("unable to extract funding TX: %v", err) + } +} diff --git a/btcutil/psbt/signer.go b/btcutil/psbt/signer.go new file mode 100644 index 0000000000..588265317f --- /dev/null +++ b/btcutil/psbt/signer.go @@ -0,0 +1,155 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +// signer encapsulates the role 'Signer' as specified in BIP174; it controls +// the insertion of signatures; the Sign() function will attempt to insert +// signatures using Updater.addPartialSignature, after first ensuring the Psbt +// is in the correct state. + +import ( + "github.com/btcsuite/btcd/txscript" +) + +// SignOutcome is a enum-like value that expresses the outcome of a call to the +// Sign method. +type SignOutcome int + +const ( + // SignSuccesful indicates that the partial signature was successfully + // attached. + SignSuccesful = 0 + + // SignFinalized indicates that this input is already finalized, so the provided + // signature was *not* attached + SignFinalized = 1 + + // SignInvalid indicates that the provided signature data was not valid. In this case + // an error will also be returned. + SignInvalid = -1 +) + +// Sign allows the caller to sign a PSBT at a particular input; they +// must provide a signature and a pubkey, both as byte slices; they can also +// optionally provide both witnessScript and/or redeemScript, otherwise these +// arguments must be set as nil (and in that case, they must already be present +// in the PSBT if required for signing to succeed). +// +// This serves as a wrapper around Updater.addPartialSignature; it ensures that +// the redeemScript and witnessScript are updated as needed (note that the +// Updater is allowed to add redeemScripts and witnessScripts independently, +// before signing), and ensures that the right form of utxo field +// (NonWitnessUtxo or WitnessUtxo) is included in the input so that signature +// insertion (and then finalization) can take place. +func (u *Updater) Sign(inIndex int, sig []byte, pubKey []byte, + redeemScript []byte, witnessScript []byte) (SignOutcome, error) { + + if isFinalized(u.Upsbt, inIndex) { + return SignFinalized, nil + } + + // Add the witnessScript to the PSBT in preparation. If it already + // exists, it will be overwritten. + if witnessScript != nil { + err := u.AddInWitnessScript(witnessScript, inIndex) + if err != nil { + return SignInvalid, err + } + } + + // Add the redeemScript to the PSBT in preparation. If it already + // exists, it will be overwritten. + if redeemScript != nil { + err := u.AddInRedeemScript(redeemScript, inIndex) + if err != nil { + return SignInvalid, err + } + } + + // At this point, the PSBT must have the requisite witnessScript or + // redeemScript fields for signing to succeed. + // + // Case 1: if witnessScript is present, it must be of type witness; + // if not, signature insertion will of course fail. + switch { + case u.Upsbt.Inputs[inIndex].WitnessScript != nil: + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return SignInvalid, err + } + } + + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + + // Case 2: no witness script, only redeem script; can be legacy p2sh or + // p2sh-wrapped p2wkh. + case u.Upsbt.Inputs[inIndex].RedeemScript != nil: + // We only need to decide if the input is witness, and we don't + // rely on the witnessutxo/nonwitnessutxo in the PSBT, instead + // we check the redeemScript content. + if txscript.IsWitnessProgram(redeemScript) { + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return SignInvalid, err + } + } + } + + // If it is not a valid witness program, we here assume that + // the provided WitnessUtxo/NonWitnessUtxo field was correct. + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + + // Case 3: Neither provided only works for native p2wkh, or non-segwit + // non-p2sh. To check if it's segwit, check the scriptPubKey of the + // output. + default: + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + outIndex := u.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + script := u.Upsbt.Inputs[inIndex].NonWitnessUtxo.TxOut[outIndex].PkScript + + if txscript.IsWitnessProgram(script) { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return SignInvalid, err + } + } + } + + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + } + + return SignSuccesful, nil +} + +// nonWitnessToWitness extracts the TxOut from the existing NonWitnessUtxo +// field in the given PSBT input and sets it as type witness by replacing the +// NonWitnessUtxo field with a WitnessUtxo field. See +// https://github.com/bitcoin/bitcoin/pull/14197. +func nonWitnessToWitness(p *Packet, inIndex int) error { + outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + txout := p.Inputs[inIndex].NonWitnessUtxo.TxOut[outIndex] + + // TODO(guggero): For segwit v1, we'll want to remove the NonWitnessUtxo + // from the packet. For segwit v0 it is unsafe to only rely on the + // witness UTXO. See https://github.com/bitcoin/bitcoin/pull/19215. + // p.Inputs[inIndex].NonWitnessUtxo = nil + + u := Updater{ + Upsbt: p, + } + + return u.AddInWitnessUtxo(txout, inIndex) +} diff --git a/btcutil/psbt/sort.go b/btcutil/psbt/sort.go new file mode 100644 index 0000000000..2232d68feb --- /dev/null +++ b/btcutil/psbt/sort.go @@ -0,0 +1,102 @@ +package psbt + +import ( + "bytes" + "sort" + + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +// InPlaceSort modifies the passed packet's wire TX inputs and outputs to be +// sorted based on BIP 69. The sorting happens in a way that the packet's +// partial inputs and outputs are also modified to match the sorted TxIn and +// TxOuts of the wire transaction. +// +// WARNING: This function must NOT be called with packages that already contain +// (partial) witness data since it will mutate the transaction if it's not +// already sorted. This can cause issues if you mutate a tx in a block, for +// example, which would invalidate the block. It could also cause cached hashes, +// such as in a btcutil.Tx to become invalidated. +// +// The function should only be used if the caller is creating the transaction or +// is otherwise 100% positive mutating will not cause adverse affects due to +// other dependencies. +func InPlaceSort(packet *Packet) error { + // To make sure we don't run into any nil pointers or array index + // violations during sorting, do a very basic sanity check first. + err := VerifyInputOutputLen(packet, false, false) + if err != nil { + return err + } + + sort.Sort(&sortableInputs{p: packet}) + sort.Sort(&sortableOutputs{p: packet}) + + return nil +} + +// sortableInputs is a simple wrapper around a packet that implements the +// sort.Interface for sorting the wire and partial inputs of a packet. +type sortableInputs struct { + p *Packet +} + +// sortableOutputs is a simple wrapper around a packet that implements the +// sort.Interface for sorting the wire and partial outputs of a packet. +type sortableOutputs struct { + p *Packet +} + +// For sortableInputs and sortableOutputs, three functions are needed to make +// them sortable with sort.Sort() -- Len, Less, and Swap. +// Len and Swap are trivial. Less is BIP 69 specific. +func (s *sortableInputs) Len() int { return len(s.p.UnsignedTx.TxIn) } +func (s sortableOutputs) Len() int { return len(s.p.UnsignedTx.TxOut) } + +// Swap swaps two inputs. +func (s *sortableInputs) Swap(i, j int) { + tx := s.p.UnsignedTx + tx.TxIn[i], tx.TxIn[j] = tx.TxIn[j], tx.TxIn[i] + s.p.Inputs[i], s.p.Inputs[j] = s.p.Inputs[j], s.p.Inputs[i] +} + +// Swap swaps two outputs. +func (s *sortableOutputs) Swap(i, j int) { + tx := s.p.UnsignedTx + tx.TxOut[i], tx.TxOut[j] = tx.TxOut[j], tx.TxOut[i] + s.p.Outputs[i], s.p.Outputs[j] = s.p.Outputs[j], s.p.Outputs[i] +} + +// Less is the input comparison function. First sort based on input hash +// (reversed / rpc-style), then index. +func (s *sortableInputs) Less(i, j int) bool { + ins := s.p.UnsignedTx.TxIn + + // Input hashes are the same, so compare the index. + ihash := ins[i].PreviousOutPoint.Hash + jhash := ins[j].PreviousOutPoint.Hash + if ihash == jhash { + return ins[i].PreviousOutPoint.Index < + ins[j].PreviousOutPoint.Index + } + + // At this point, the hashes are not equal, so reverse them to + // big-endian and return the result of the comparison. + const hashSize = chainhash.HashSize + for b := 0; b < hashSize/2; b++ { + ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b] + jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b] + } + return bytes.Compare(ihash[:], jhash[:]) == -1 +} + +// Less is the output comparison function. First sort based on amount (smallest +// first), then PkScript. +func (s *sortableOutputs) Less(i, j int) bool { + outs := s.p.UnsignedTx.TxOut + + if outs[i].Value == outs[j].Value { + return bytes.Compare(outs[i].PkScript, outs[j].PkScript) < 0 + } + return outs[i].Value < outs[j].Value +} diff --git a/btcutil/psbt/sort_test.go b/btcutil/psbt/sort_test.go new file mode 100644 index 0000000000..3dee0f48d8 --- /dev/null +++ b/btcutil/psbt/sort_test.go @@ -0,0 +1,167 @@ +package psbt + +import ( + "reflect" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +func TestInPlaceSort(t *testing.T) { + testCases := []struct { + name string + packet *Packet + expectedTxIn []*wire.TxIn + expectedTxOut []*wire.TxOut + expectedPIn []PInput + expectedPOut []POutput + expectErr bool + }{{ + name: "packet nil", + packet: nil, + expectErr: true, + }, { + name: "no inputs or outputs", + packet: &Packet{UnsignedTx: &wire.MsgTx{}}, + expectErr: false, + }, { + name: "inputs only", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{99, 88}, + Index: 7, + }, + }, { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{77, 88}, + Index: 12, + }, + }, { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{77, 88}, + Index: 7, + }, + }}, + }, + // Abuse the SighashType as an index to make sure the + // partial inputs are also sorted together with the wire + // inputs. + Inputs: []PInput{{ + SighashType: 0, + }, { + SighashType: 1, + }, { + SighashType: 2, + }}, + }, + expectedTxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{77, 88}, + Index: 7, + }, + }, { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{77, 88}, + Index: 12, + }, + }, { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{99, 88}, + Index: 7, + }, + }}, + expectedPIn: []PInput{{ + SighashType: 2, + }, { + SighashType: 1, + }, { + SighashType: 0, + }}, + expectErr: false, + }, { + name: "outputs only", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{ + TxOut: []*wire.TxOut{{ + PkScript: []byte{99, 88}, + Value: 7, + }, { + PkScript: []byte{77, 88}, + Value: 12, + }, { + PkScript: []byte{77, 88}, + Value: 7, + }}, + }, + // Abuse the RedeemScript as an index to make sure the + // partial inputs are also sorted together with the wire + // inputs. + Outputs: []POutput{{ + RedeemScript: []byte{0}, + }, { + RedeemScript: []byte{1}, + }, { + RedeemScript: []byte{2}, + }}, + }, + expectedTxOut: []*wire.TxOut{{ + PkScript: []byte{77, 88}, + Value: 7, + }, { + PkScript: []byte{99, 88}, + Value: 7, + }, { + PkScript: []byte{77, 88}, + Value: 12, + }}, + expectedPOut: []POutput{{ + RedeemScript: []byte{2}, + }, { + RedeemScript: []byte{0}, + }, { + RedeemScript: []byte{1}, + }}, + expectErr: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + p := tc.packet + err := InPlaceSort(p) + if (tc.expectErr && err == nil) || + (!tc.expectErr && err != nil) { + + t.Fatalf("got error '%v' but wanted it to be "+ + "nil: %v", err, tc.expectErr) + } + + // Don't continue on this special test case. + if p == nil { + return + } + + tx := p.UnsignedTx + if !reflect.DeepEqual(tx.TxIn, tc.expectedTxIn) { + t.Fatalf("unexpected txin, got %#v wanted %#v", + tx.TxIn, tc.expectedTxIn) + } + if !reflect.DeepEqual(tx.TxOut, tc.expectedTxOut) { + t.Fatalf("unexpected txout, got %#v wanted %#v", + tx.TxOut, tc.expectedTxOut) + } + + if !reflect.DeepEqual(p.Inputs, tc.expectedPIn) { + t.Fatalf("unexpected pin, got %#v wanted %#v", + p.Inputs, tc.expectedPIn) + } + if !reflect.DeepEqual(p.Outputs, tc.expectedPOut) { + t.Fatalf("unexpected pout, got %#v wanted %#v", + p.Inputs, tc.expectedPOut) + } + }) + } +} diff --git a/btcutil/psbt/types.go b/btcutil/psbt/types.go new file mode 100644 index 0000000000..1b4a26a4dc --- /dev/null +++ b/btcutil/psbt/types.go @@ -0,0 +1,149 @@ +package psbt + +// GlobalType is the set of types that are used at the global scope level +// within the PSBT. +type GlobalType uint8 + +const ( + // UnsignedTxType is the global scope key that houses the unsigned + // transaction of the PSBT. The value is a transaction in network + // serialization. The scriptSigs and witnesses for each input must be + // empty. The transaction must be in the old serialization format + // (without witnesses). A PSBT must have a transaction, otherwise it is + // invalid. + UnsignedTxType GlobalType = 0 + + // XpubType houses a global xpub for the entire PSBT packet. + // + // The key ({0x01}|{xpub}) is he 78 byte serialized extended public key + // as defined by BIP 32. Extended public keys are those that can be + // used to derive public keys used in the inputs and outputs of this + // transaction. It should be the public key at the highest hardened + // derivation index so that + // the unhardened child keys used in the transaction can be derived. + // + // The value is the master key fingerprint as defined by BIP 32 + // concatenated with the derivation path of the public key. The + // derivation path is represented as 32-bit little endian unsigned + // integer indexes concatenated with each other. The number of 32 bit + // unsigned integer indexes must match the depth provided in the + // extended public key. + XpubType GlobalType = 1 + + // VersionType houses the global version number of this PSBT. There is + // no key (only contains the byte type), then the value if omitted, is + // assumed to be zero. + VersionType GlobalType = 0xFB + + // ProprietaryGlobalType is used to house any proper chary global-scope + // keys within the PSBT. + // + // The key is ({0xFC}||{subtype}|{key data}) a variable length + // identifier prefix, followed by a subtype, followed by the key data + // itself. + // + // The value is any data as defined by the proprietary type user. + ProprietaryGlobalType = 0xFC +) + +// InputType is the set of types that are defined for each input included +// within the PSBT. +type InputType uint32 + +const ( + // NonWitnessUtxoType has no key ({0x00}) and houses the transaction in + // network serialization format the current input spends from. This + // should only be present for inputs which spend non-segwit outputs. + // However, if it is unknown whether an input spends a segwit output, + // this type should be used. The entire input transaction is needed in + // order to be able to verify the values of the input (pre-segwit they + // aren't in the signature digest). + NonWitnessUtxoType InputType = 0 + + // WitnessUtxoType has no key ({0x01}), and houses the entire + // transaction output in network serialization which the current input + // spends from. This should only be present for inputs which spend + // segwit outputs, including P2SH embedded ones (value || script). + WitnessUtxoType InputType = 1 + + // PartialSigType is used to include a partial signature with key + // ({0x02}|{public key}). + // + // The value is the signature as would be pushed to the stack from a + // scriptSig or witness.. + PartialSigType InputType = 2 + + // SighashType is an empty key ({0x03}). + // + // The value contains the 32-bit unsigned integer specifying the + // sighash type to be used for this input. Signatures for this input + // must use the sighash type, finalizers must fail to finalize inputs + // which have signatures that do not match the specified sighash type. + // Signers who cannot produce signatures with the sighash type must not + // provide a signature. + SighashType InputType = 3 + + // RedeemScriptInputType is an empty key ({0x40}). + // + // The value is the redeem script of the input if present. + RedeemScriptInputType InputType = 4 + + // WitnessScriptInputType is an empty key ({0x05}). + // + // The value is the witness script of this input, if it has one. + WitnessScriptInputType InputType = 5 + + // Bip32DerivationInputType is a type that carries the pubkey along + // with the key ({0x06}|{public key}). + // + // The value is master key fingerprint as defined by BIP 32 + // concatenated with the derivation path of the public key. The + // derivation path is represented as 32 bit unsigned integer indexes + // concatenated with each other. Public keys are those that will be + // needed to sign this input. + Bip32DerivationInputType InputType = 6 + + // FinalScriptSigType is an empty key ({0x07}). + // + // The value contains a fully constructed scriptSig with signatures and + // any other scripts necessary for the input to pass validation. + FinalScriptSigType InputType = 7 + + // FinalScriptWitnessType is an empty key ({0x08}). The value is a + // fully constructed scriptWitness with signatures and any other + // scripts necessary for the input to pass validation. + FinalScriptWitnessType InputType = 8 + + // ProprietaryInputType is a custom type for use by devs. + // + // The key ({0xFC}||{subtype}|{key data}), is a Variable length + // identifier prefix, followed by a subtype, followed by the key data + // itself. + // + // The value is any value data as defined by the proprietary type user. + ProprietaryInputType InputType = 0xFC +) + +// OutputType is the set of types defined per output within the PSBT. +type OutputType uint32 + +const ( + // RedeemScriptOutputType is an empty key ({0x00}> + // + // The value is the redeemScript for this output if it has one. + RedeemScriptOutputType OutputType = 0 + + // WitnessScriptOutputType is an empty key ({0x01}). + // + // The value is the witness script of this input, if it has one. + WitnessScriptOutputType OutputType = 1 + + j // Bip32DerivationOutputType is used to communicate derivation information + // needed to spend this output. The key is ({0x02}|{public key}). + // + // The value is master key fingerprint concatenated with the derivation + // path of the public key. The derivation path is represented as 32-bit + // little endian unsigned integer indexes concatenated with each other. + // Public keys are those needed to spend this output. + Bip32DerivationOutputType OutputType = 2 +) diff --git a/btcutil/psbt/updater.go b/btcutil/psbt/updater.go new file mode 100644 index 0000000000..56a33654b6 --- /dev/null +++ b/btcutil/psbt/updater.go @@ -0,0 +1,377 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +// The Updater requires provision of a single PSBT and is able to add data to +// both input and output sections. It can be called repeatedly to add more +// data. It also allows addition of signatures via the addPartialSignature +// function; this is called internally to the package in the Sign() function of +// Updater, located in signer.go + +import ( + "bytes" + "crypto/sha256" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil" +) + +// Updater encapsulates the role 'Updater' as specified in BIP174; it accepts +// Psbt structs and has methods to add fields to the inputs and outputs. +type Updater struct { + Upsbt *Packet +} + +// NewUpdater returns a new instance of Updater, if the passed Psbt struct is +// in a valid form, else an error. +func NewUpdater(p *Packet) (*Updater, error) { + if err := p.SanityCheck(); err != nil { + return nil, err + } + + return &Updater{Upsbt: p}, nil + +} + +// AddInNonWitnessUtxo adds the utxo information for an input which is +// non-witness. This requires provision of a full transaction (which is the +// source of the corresponding prevOut), and the input index. If addition of +// this key-value pair to the Psbt fails, an error is returned. +func (p *Updater) AddInNonWitnessUtxo(tx *wire.MsgTx, inIndex int) error { + if inIndex > len(p.Upsbt.Inputs)-1 { + return ErrInvalidPrevOutNonWitnessTransaction + } + + p.Upsbt.Inputs[inIndex].NonWitnessUtxo = tx + + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + + return nil +} + +// AddInWitnessUtxo adds the utxo information for an input which is witness. +// This requires provision of a full transaction *output* (which is the source +// of the corresponding prevOut); not the full transaction because BIP143 means +// the output information is sufficient, and the input index. If addition of +// this key-value pair to the Psbt fails, an error is returned. +func (p *Updater) AddInWitnessUtxo(txout *wire.TxOut, inIndex int) error { + if inIndex > len(p.Upsbt.Inputs)-1 { + return ErrInvalidPsbtFormat + } + + p.Upsbt.Inputs[inIndex].WitnessUtxo = txout + + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + + return nil +} + +// addPartialSignature allows the Updater role to insert fields of type partial +// signature into a Psbt, consisting of both the pubkey (as keydata) and the +// ECDSA signature (as value). Note that the Signer role is encapsulated in +// this function; signatures are only allowed to be added that follow the +// sanity-check on signing rules explained in the BIP under `Signer`; if the +// rules are not satisfied, an ErrInvalidSignatureForInput is returned. +// +// NOTE: This function does *not* validate the ECDSA signature itself. +func (p *Updater) addPartialSignature(inIndex int, sig []byte, + pubkey []byte) error { + + partialSig := PartialSig{ + PubKey: pubkey, Signature: sig, + } + + // First validate the passed (sig, pub). + if !partialSig.checkValid() { + return ErrInvalidPsbtFormat + } + + pInput := p.Upsbt.Inputs[inIndex] + + // First check; don't add duplicates. + for _, x := range pInput.PartialSigs { + if bytes.Equal(x.PubKey, partialSig.PubKey) { + return ErrDuplicateKey + } + } + + // Attaching signature without utxo field is not allowed. + if pInput.WitnessUtxo == nil && pInput.NonWitnessUtxo == nil { + return ErrInvalidPsbtFormat + } + + // Next, we perform a series of additional sanity checks. + if pInput.NonWitnessUtxo != nil { + if len(p.Upsbt.UnsignedTx.TxIn) < inIndex+1 { + return ErrInvalidPrevOutNonWitnessTransaction + } + + if pInput.NonWitnessUtxo.TxHash() != + p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Hash { + return ErrInvalidSignatureForInput + } + + // To validate that the redeem script matches, we must pull out + // the scriptPubKey of the corresponding output and compare + // that with the P2SH scriptPubKey that is generated by + // redeemScript. + if pInput.RedeemScript != nil { + outIndex := p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + scriptPubKey := pInput.NonWitnessUtxo.TxOut[outIndex].PkScript + scriptHash := btcutil.Hash160(pInput.RedeemScript) + + scriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160). + AddData(scriptHash). + AddOp(txscript.OP_EQUAL). + Script() + if err != nil { + return err + } + + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + } + + } + + // It could be that we set both the non-witness and witness UTXO fields + // in case it's from a wallet that patched the CVE-2020-14199 + // vulnerability. We detect whether the input being spent is actually a + // witness input and then copy it over to the witness UTXO field in the + // signer. Run the witness checks as well, even if we might already have + // checked the script hash. But that should be a negligible performance + // penalty. + if pInput.WitnessUtxo != nil { + scriptPubKey := pInput.WitnessUtxo.PkScript + + var script []byte + if pInput.RedeemScript != nil { + scriptHash := btcutil.Hash160(pInput.RedeemScript) + scriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160). + AddData(scriptHash). + AddOp(txscript.OP_EQUAL). + Script() + if err != nil { + return err + } + + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + + script = pInput.RedeemScript + } else { + script = scriptPubKey + } + + // If a witnessScript field is present, this is a P2WSH, + // whether nested or not (that is handled by the assignment to + // `script` above); in that case, sanity check that `script` is + // the p2wsh of witnessScript. Contrariwise, if no + // witnessScript field is present, this will be signed as + // p2wkh. + if pInput.WitnessScript != nil { + witnessScriptHash := sha256.Sum256(pInput.WitnessScript) + witnessScriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_0). + AddData(witnessScriptHash[:]). + Script() + if err != nil { + return err + } + + if !bytes.Equal(script, witnessScriptHashScript[:]) { + return ErrInvalidSignatureForInput + } + } else { + // Otherwise, this is a p2wkh input. + pubkeyHash := btcutil.Hash160(pubkey) + pubkeyHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_0). + AddData(pubkeyHash). + Script() + if err != nil { + return err + } + + // Validate that we're able to properly reconstruct the + // witness program. + if !bytes.Equal(pubkeyHashScript, script) { + return ErrInvalidSignatureForInput + } + } + } + + p.Upsbt.Inputs[inIndex].PartialSigs = append( + p.Upsbt.Inputs[inIndex].PartialSigs, &partialSig, + ) + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + + // Addition of a non-duplicate-key partial signature cannot violate + // sanity-check rules. + return nil +} + +// AddInSighashType adds the sighash type information for an input. The +// sighash type is passed as a 32 bit unsigned integer, along with the index +// for the input. An error is returned if addition of this key-value pair to +// the Psbt fails. +func (p *Updater) AddInSighashType(sighashType txscript.SigHashType, + inIndex int) error { + + p.Upsbt.Inputs[inIndex].SighashType = sighashType + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +} + +// AddInRedeemScript adds the redeem script information for an input. The +// redeem script is passed serialized, as a byte slice, along with the index of +// the input. An error is returned if addition of this key-value pair to the +// Psbt fails. +func (p *Updater) AddInRedeemScript(redeemScript []byte, + inIndex int) error { + + p.Upsbt.Inputs[inIndex].RedeemScript = redeemScript + + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + + return nil +} + +// AddInWitnessScript adds the witness script information for an input. The +// witness script is passed serialized, as a byte slice, along with the index +// of the input. An error is returned if addition of this key-value pair to the +// Psbt fails. +func (p *Updater) AddInWitnessScript(witnessScript []byte, + inIndex int) error { + + p.Upsbt.Inputs[inIndex].WitnessScript = witnessScript + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + + return nil +} + +// AddInBip32Derivation takes a master key fingerprint as defined in BIP32, a +// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte +// slice, along with the integer index of the input, and inserts this data into +// that input. +// +// NOTE: This can be called multiple times for the same input. An error is +// returned if addition of this key-value pair to the Psbt fails. +func (p *Updater) AddInBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, inIndex int) error { + + bip32Derivation := Bip32Derivation{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + + if !bip32Derivation.checkValid() { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range p.Upsbt.Inputs[inIndex].Bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + p.Upsbt.Inputs[inIndex].Bip32Derivation = append( + p.Upsbt.Inputs[inIndex].Bip32Derivation, &bip32Derivation, + ) + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + + return nil +} + +// AddOutBip32Derivation takes a master key fingerprint as defined in BIP32, a +// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte +// slice, along with the integer index of the output, and inserts this data +// into that output. +// +// NOTE: That this can be called multiple times for the same output. An error +// is returned if addition of this key-value pair to the Psbt fails. +func (p *Updater) AddOutBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, outIndex int) error { + + bip32Derivation := Bip32Derivation{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + + if !bip32Derivation.checkValid() { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range p.Upsbt.Outputs[outIndex].Bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + p.Upsbt.Outputs[outIndex].Bip32Derivation = append( + p.Upsbt.Outputs[outIndex].Bip32Derivation, &bip32Derivation, + ) + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + + return nil +} + +// AddOutRedeemScript takes a redeem script as a byte slice and appends it to +// the output at index outIndex. +func (p *Updater) AddOutRedeemScript(redeemScript []byte, + outIndex int) error { + + p.Upsbt.Outputs[outIndex].RedeemScript = redeemScript + + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + + return nil +} + +// AddOutWitnessScript takes a witness script as a byte slice and appends it to +// the output at index outIndex. +func (p *Updater) AddOutWitnessScript(witnessScript []byte, + outIndex int) error { + + p.Upsbt.Outputs[outIndex].WitnessScript = witnessScript + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + + return nil +} diff --git a/btcutil/psbt/utils.go b/btcutil/psbt/utils.go new file mode 100644 index 0000000000..494d04070f --- /dev/null +++ b/btcutil/psbt/utils.go @@ -0,0 +1,422 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psbt + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "sort" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// WriteTxWitness is a utility function due to non-exported witness +// serialization (writeTxWitness encodes the bitcoin protocol encoding for a +// transaction input's witness into w). +func WriteTxWitness(w io.Writer, wit [][]byte) error { + if err := wire.WriteVarInt(w, 0, uint64(len(wit))); err != nil { + return err + } + + for _, item := range wit { + err := wire.WriteVarBytes(w, 0, item) + if err != nil { + return err + } + } + return nil +} + +// writePKHWitness writes a witness for a p2wkh spending input +func writePKHWitness(sig []byte, pub []byte) ([]byte, error) { + var ( + buf bytes.Buffer + witnessItems = [][]byte{sig, pub} + ) + + if err := WriteTxWitness(&buf, witnessItems); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// checkIsMultisigScript is a utility function to check whether a given +// redeemscript fits the standard multisig template used in all P2SH based +// multisig, given a set of pubkeys for redemption. +func checkIsMultiSigScript(pubKeys [][]byte, sigs [][]byte, + script []byte) bool { + + // First insist that the script type is multisig. + if txscript.GetScriptClass(script) != txscript.MultiSigTy { + return false + } + + // Inspect the script to ensure that the number of sigs and pubkeys is + // correct + _, numSigs, err := txscript.CalcMultiSigStats(script) + if err != nil { + return false + } + + // If the number of sigs provided, doesn't match the number of required + // pubkeys, then we can't proceed as we're not yet final. + if numSigs != len(pubKeys) || numSigs != len(sigs) { + return false + } + + return true +} + +// extractKeyOrderFromScript is a utility function to extract an ordered list +// of signatures, given a serialized script (redeemscript or witness script), a +// list of pubkeys and the signatures corresponding to those pubkeys. This +// function is used to ensure that the signatures will be embedded in the final +// scriptSig or scriptWitness in the correct order. +func extractKeyOrderFromScript(script []byte, expectedPubkeys [][]byte, + sigs [][]byte) ([][]byte, error) { + + // If this isn't a proper finalized multi-sig script, then we can't + // proceed. + if !checkIsMultiSigScript(expectedPubkeys, sigs, script) { + return nil, ErrUnsupportedScriptType + } + + // Arrange the pubkeys and sigs into a slice of format: + // * [[pub,sig], [pub,sig],..] + type sigWithPub struct { + pubKey []byte + sig []byte + } + var pubsSigs []sigWithPub + for i, pub := range expectedPubkeys { + pubsSigs = append(pubsSigs, sigWithPub{ + pubKey: pub, + sig: sigs[i], + }) + } + + // Now that we have the set of (pubkey, sig) pairs, we'll construct a + // position map that we can use to swap the order in the slice above to + // match how things are laid out in the script. + type positionEntry struct { + index int + value sigWithPub + } + var positionMap []positionEntry + + // For each pubkey in our pubsSigs slice, we'll now construct a proper + // positionMap entry, based on _where_ in the script the pubkey first + // appears. + for _, p := range pubsSigs { + pos := bytes.Index(script, p.pubKey) + if pos < 0 { + return nil, errors.New("script does not contain pubkeys") + } + + positionMap = append(positionMap, positionEntry{ + index: pos, + value: p, + }) + } + + // Now that we have the position map full populated, we'll use the + // index data to properly sort the entries in the map based on where + // they appear in the script. + sort.Slice(positionMap, func(i, j int) bool { + return positionMap[i].index < positionMap[j].index + }) + + // Finally, we can simply iterate through the position map in order to + // extract the proper signature ordering. + sortedSigs := make([][]byte, 0, len(positionMap)) + for _, x := range positionMap { + sortedSigs = append(sortedSigs, x.value.sig) + } + + return sortedSigs, nil +} + +// getMultisigScriptWitness creates a full psbt serialized Witness field for +// the transaction, given the public keys and signatures to be appended. This +// function will only accept witnessScripts of the type M of N multisig. This +// is used for both p2wsh and nested p2wsh multisig cases. +func getMultisigScriptWitness(witnessScript []byte, pubKeys [][]byte, + sigs [][]byte) ([]byte, error) { + + // First using the script as a guide, we'll properly order the sigs + // according to how their corresponding pubkeys appear in the + // witnessScript. + orderedSigs, err := extractKeyOrderFromScript( + witnessScript, pubKeys, sigs, + ) + if err != nil { + return nil, err + } + + // Now that we know the proper order, we'll append each of the + // signatures into a new witness stack, then top it off with the + // witness script at the end, prepending the nil as we need the extra + // pop.. + witnessElements := make(wire.TxWitness, 0, len(sigs)+2) + witnessElements = append(witnessElements, nil) + for _, os := range orderedSigs { + witnessElements = append(witnessElements, os) + } + witnessElements = append(witnessElements, witnessScript) + + // Now that we have the full witness stack, we'll serialize it in the + // expected format, and return the final bytes. + var buf bytes.Buffer + if err = WriteTxWitness(&buf, witnessElements); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// checkSigHashFlags compares the sighash flag byte on a signature with the +// value expected according to any PsbtInSighashType field in this section of +// the PSBT, and returns true if they match, false otherwise. +// If no SighashType field exists, it is assumed to be SIGHASH_ALL. +// +// TODO(waxwing): sighash type not restricted to one byte in future? +func checkSigHashFlags(sig []byte, input *PInput) bool { + expectedSighashType := txscript.SigHashAll + if input.SighashType != 0 { + expectedSighashType = input.SighashType + } + + return expectedSighashType == txscript.SigHashType(sig[len(sig)-1]) +} + +// serializeKVpair writes out a kv pair using a varbyte prefix for each. +func serializeKVpair(w io.Writer, key []byte, value []byte) error { + if err := wire.WriteVarBytes(w, 0, key); err != nil { + return err + } + + return wire.WriteVarBytes(w, 0, value) +} + +// serializeKVPairWithType writes out to the passed writer a type coupled with +// a key. +func serializeKVPairWithType(w io.Writer, kt uint8, keydata []byte, + value []byte) error { + + // If the key has no data, then we write a blank slice. + if keydata == nil { + keydata = []byte{} + } + + // The final key to be written is: {type} || {keyData} + serializedKey := append([]byte{kt}, keydata...) + return serializeKVpair(w, serializedKey, value) +} + +// getKey retrieves a single key - both the key type and the keydata (if +// present) from the stream and returns the key type as an integer, or -1 if +// the key was of zero length. This integer is is used to indicate the presence +// of a separator byte which indicates the end of a given key-value pair list, +// and the keydata as a byte slice or nil if none is present. +func getKey(r io.Reader) (int, []byte, error) { + + // For the key, we read the varint separately, instead of using the + // available ReadVarBytes, because we have a specific treatment of 0x00 + // here: + count, err := wire.ReadVarInt(r, 0) + if err != nil { + return -1, nil, ErrInvalidPsbtFormat + } + if count == 0 { + // A separator indicates end of key-value pair list. + return -1, nil, nil + } + + // Check that we don't attempt to decode a dangerously large key. + if count > MaxPsbtKeyLength { + return -1, nil, ErrInvalidKeydata + } + + // Next, we ready out the designated number of bytes, which may include + // a type, key, and optional data. + keyTypeAndData := make([]byte, count) + if _, err := io.ReadFull(r, keyTypeAndData[:]); err != nil { + return -1, nil, err + } + + keyType := int(string(keyTypeAndData)[0]) + + // Note that the second return value will usually be empty, since most + // keys contain no more than the key type byte. + if len(keyTypeAndData) == 1 { + return keyType, nil, nil + } + + // Otherwise, we return the key, along with any data that it may + // contain. + return keyType, keyTypeAndData[1:], nil + +} + +// readTxOut is a limited version of wire.ReadTxOut, because the latter is not +// exported. +func readTxOut(txout []byte) (*wire.TxOut, error) { + if len(txout) < 10 { + return nil, ErrInvalidPsbtFormat + } + + valueSer := binary.LittleEndian.Uint64(txout[:8]) + scriptPubKey := txout[9:] + + return wire.NewTxOut(int64(valueSer), scriptPubKey), nil +} + +// SumUtxoInputValues tries to extract the sum of all inputs specified in the +// UTXO fields of the PSBT. An error is returned if an input is specified that +// does not contain any UTXO information. +func SumUtxoInputValues(packet *Packet) (int64, error) { + // We take the TX ins of the unsigned TX as the truth for how many + // inputs there should be, as the fields in the extra data part of the + // PSBT can be empty. + if len(packet.UnsignedTx.TxIn) != len(packet.Inputs) { + return 0, fmt.Errorf("TX input length doesn't match PSBT " + + "input length") + } + + inputSum := int64(0) + for idx, in := range packet.Inputs { + switch { + case in.WitnessUtxo != nil: + // Witness UTXOs only need to reference the TxOut. + inputSum += in.WitnessUtxo.Value + + case in.NonWitnessUtxo != nil: + // Non-witness UTXOs reference to the whole transaction + // the UTXO resides in. + utxOuts := in.NonWitnessUtxo.TxOut + txIn := packet.UnsignedTx.TxIn[idx] + + // Check that utxOuts actually has enough space to + // contain the previous outpoint's index. + opIdx := txIn.PreviousOutPoint.Index + if opIdx >= uint32(len(utxOuts)) { + return 0, fmt.Errorf("input %d has malformed "+ + "TxOut field", idx) + } + + inputSum += utxOuts[txIn.PreviousOutPoint.Index].Value + + default: + return 0, fmt.Errorf("input %d has no UTXO information", + idx) + } + } + return inputSum, nil +} + +// TxOutsEqual returns true if two transaction outputs are equal. +func TxOutsEqual(out1, out2 *wire.TxOut) bool { + if out1 == nil || out2 == nil { + return out1 == out2 + } + return out1.Value == out2.Value && + bytes.Equal(out1.PkScript, out2.PkScript) +} + +// VerifyOutputsEqual verifies that the two slices of transaction outputs are +// deep equal to each other. We do the length check and manual loop to provide +// better error messages to the user than just returning "not equal". +func VerifyOutputsEqual(outs1, outs2 []*wire.TxOut) error { + if len(outs1) != len(outs2) { + return fmt.Errorf("number of outputs are different") + } + for idx, out := range outs1 { + // There is a byte slice in the output so we can't use the + // equality operator. + if !TxOutsEqual(out, outs2[idx]) { + return fmt.Errorf("output %d is different", idx) + } + } + return nil +} + +// VerifyInputPrevOutpointsEqual verifies that the previous outpoints of the +// two slices of transaction inputs are deep equal to each other. We do the +// length check and manual loop to provide better error messages to the user +// than just returning "not equal". +func VerifyInputPrevOutpointsEqual(ins1, ins2 []*wire.TxIn) error { + if len(ins1) != len(ins2) { + return fmt.Errorf("number of inputs are different") + } + for idx, in := range ins1 { + if in.PreviousOutPoint != ins2[idx].PreviousOutPoint { + return fmt.Errorf("previous outpoint of input %d is "+ + "different", idx) + } + } + return nil +} + +// VerifyInputOutputLen makes sure a packet is non-nil, contains a non-nil wire +// transaction and that the wire input/output lengths match the partial input/ +// output lengths. A caller also can specify if they expect any inputs and/or +// outputs to be contained in the packet. +func VerifyInputOutputLen(packet *Packet, needInputs, needOutputs bool) error { + if packet == nil || packet.UnsignedTx == nil { + return fmt.Errorf("PSBT packet cannot be nil") + } + + if len(packet.UnsignedTx.TxIn) != len(packet.Inputs) { + return fmt.Errorf("invalid PSBT, wire inputs don't match " + + "partial inputs") + } + if len(packet.UnsignedTx.TxOut) != len(packet.Outputs) { + return fmt.Errorf("invalid PSBT, wire outputs don't match " + + "partial outputs") + } + + if needInputs && len(packet.UnsignedTx.TxIn) == 0 { + return fmt.Errorf("PSBT packet must contain at least one " + + "input") + } + if needOutputs && len(packet.UnsignedTx.TxOut) == 0 { + return fmt.Errorf("PSBT packet must contain at least one " + + "output") + } + + return nil +} + +// NewFromSignedTx is a utility function to create a packet from an +// already-signed transaction. Returned are: an unsigned transaction +// serialization, a list of scriptSigs, one per input, and a list of witnesses, +// one per input. +func NewFromSignedTx(tx *wire.MsgTx) (*Packet, [][]byte, + []wire.TxWitness, error) { + + scriptSigs := make([][]byte, 0, len(tx.TxIn)) + witnesses := make([]wire.TxWitness, 0, len(tx.TxIn)) + tx2 := tx.Copy() + + // Blank out signature info in inputs + for i, tin := range tx2.TxIn { + tin.SignatureScript = nil + scriptSigs = append(scriptSigs, tx.TxIn[i].SignatureScript) + tin.Witness = nil + witnesses = append(witnesses, tx.TxIn[i].Witness) + } + + // Outputs always contain: (value, scriptPubkey) so don't need + // amending. Now tx2 is tx with all signing data stripped out + unsignedPsbt, err := NewFromUnsignedTx(tx2) + if err != nil { + return nil, nil, nil, err + } + return unsignedPsbt, scriptSigs, witnesses, nil +} diff --git a/btcutil/psbt/utils_test.go b/btcutil/psbt/utils_test.go new file mode 100644 index 0000000000..90593ffca5 --- /dev/null +++ b/btcutil/psbt/utils_test.go @@ -0,0 +1,370 @@ +package psbt + +import ( + "bytes" + "reflect" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +func TestSumUtxoInputValues(t *testing.T) { + // Expect sum to fail for packet with non-matching txIn and PInputs. + tx := wire.NewMsgTx(2) + badPacket, err := NewFromUnsignedTx(tx) + if err != nil { + t.Fatalf("could not create packet from TX: %v", err) + } + badPacket.Inputs = append(badPacket.Inputs, PInput{}) + + _, err = SumUtxoInputValues(badPacket) + if err == nil { + t.Fatalf("expected sum of bad packet to fail") + } + + // Expect sum to fail if any inputs don't have UTXO information added. + op := []*wire.OutPoint{{}, {}} + noUtxoInfoPacket, err := New(op, nil, 2, 0, []uint32{0, 0}) + if err != nil { + t.Fatalf("could not create new packet: %v", err) + } + + _, err = SumUtxoInputValues(noUtxoInfoPacket) + if err == nil { + t.Fatalf("expected sum of missing UTXO info to fail") + } + + // Create a packet that is OK and contains both witness and non-witness + // UTXO information. + okPacket, err := New(op, nil, 2, 0, []uint32{0, 0}) + if err != nil { + t.Fatalf("could not create new packet: %v", err) + } + okPacket.Inputs[0].WitnessUtxo = &wire.TxOut{Value: 1234} + okPacket.Inputs[1].NonWitnessUtxo = &wire.MsgTx{ + TxOut: []*wire.TxOut{{Value: 6543}}, + } + + sum, err := SumUtxoInputValues(okPacket) + if err != nil { + t.Fatalf("could not sum input: %v", err) + } + if sum != (1234 + 6543) { + t.Fatalf("unexpected sum, got %d wanted %d", sum, 1234+6543) + } + + // Create a malformed packet where NonWitnessUtxo.TxOut does not + // contain the index specified by the PreviousOutPoint in the + // packet's Unsigned.TxIn field. + badOp := []*wire.OutPoint{{}, {Index: 500}} + malformedPacket, err := New(badOp, nil, 2, 0, []uint32{0, 0}) + if err != nil { + t.Fatalf("could not create malformed packet: %v", err) + } + malformedPacket.Inputs[0].WitnessUtxo = &wire.TxOut{Value: 1234} + malformedPacket.Inputs[1].NonWitnessUtxo = &wire.MsgTx{ + TxOut: []*wire.TxOut{{Value: 6543}}, + } + + _, err = SumUtxoInputValues(malformedPacket) + if err == nil { + t.Fatalf("expected sum of malformed packet to fail") + } +} + +func TestTxOutsEqual(t *testing.T) { + testCases := []struct { + name string + out1 *wire.TxOut + out2 *wire.TxOut + expectEqual bool + }{{ + name: "both nil", + out1: nil, + out2: nil, + expectEqual: true, + }, { + name: "one nil", + out1: nil, + out2: &wire.TxOut{}, + expectEqual: false, + }, { + name: "both empty", + out1: &wire.TxOut{}, + out2: &wire.TxOut{}, + expectEqual: true, + }, { + name: "one pk script set", + out1: &wire.TxOut{}, + out2: &wire.TxOut{ + PkScript: []byte("foo"), + }, + expectEqual: false, + }, { + name: "both fully set", + out1: &wire.TxOut{ + Value: 1234, + PkScript: []byte("bar"), + }, + out2: &wire.TxOut{ + Value: 1234, + PkScript: []byte("bar"), + }, + expectEqual: true, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + result := TxOutsEqual(tc.out1, tc.out2) + if result != tc.expectEqual { + t.Fatalf("unexpected result, got %v wanted %v", + result, tc.expectEqual) + } + }) + } +} + +func TestVerifyOutputsEqual(t *testing.T) { + testCases := []struct { + name string + outs1 []*wire.TxOut + outs2 []*wire.TxOut + expectErr bool + }{{ + name: "both nil", + outs1: nil, + outs2: nil, + expectErr: false, + }, { + name: "one nil", + outs1: nil, + outs2: []*wire.TxOut{{}}, + expectErr: true, + }, { + name: "both empty", + outs1: []*wire.TxOut{{}}, + outs2: []*wire.TxOut{{}}, + expectErr: false, + }, { + name: "one pk script set", + outs1: []*wire.TxOut{{}}, + outs2: []*wire.TxOut{{ + PkScript: []byte("foo"), + }}, + expectErr: true, + }, { + name: "both fully set", + outs1: []*wire.TxOut{{ + Value: 1234, + PkScript: []byte("bar"), + }, {}}, + outs2: []*wire.TxOut{{ + Value: 1234, + PkScript: []byte("bar"), + }, {}}, + expectErr: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := VerifyOutputsEqual(tc.outs1, tc.outs2) + if (tc.expectErr && err == nil) || + (!tc.expectErr && err != nil) { + + t.Fatalf("got error '%v' but wanted it to be "+ + "nil: %v", err, tc.expectErr) + } + }) + } +} + +func TestVerifyInputPrevOutpointsEqual(t *testing.T) { + testCases := []struct { + name string + ins1 []*wire.TxIn + ins2 []*wire.TxIn + expectErr bool + }{{ + name: "both nil", + ins1: nil, + ins2: nil, + expectErr: false, + }, { + name: "one nil", + ins1: nil, + ins2: []*wire.TxIn{{}}, + expectErr: true, + }, { + name: "both empty", + ins1: []*wire.TxIn{{}}, + ins2: []*wire.TxIn{{}}, + expectErr: false, + }, { + name: "one previous output set", + ins1: []*wire.TxIn{{}}, + ins2: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{11, 22, 33}, + Index: 7, + }, + }}, + expectErr: true, + }, { + name: "both fully set", + ins1: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{11, 22, 33}, + Index: 7, + }, + }, {}}, + ins2: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{11, 22, 33}, + Index: 7, + }, + }, {}}, + expectErr: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := VerifyInputPrevOutpointsEqual(tc.ins1, tc.ins2) + if (tc.expectErr && err == nil) || + (!tc.expectErr && err != nil) { + + t.Fatalf("got error '%v' but wanted it to be "+ + "nil: %v", err, tc.expectErr) + } + }) + } +} + +func TestVerifyInputOutputLen(t *testing.T) { + testCases := []struct { + name string + packet *Packet + needInputs bool + needOutputs bool + expectErr bool + }{{ + name: "packet nil", + packet: nil, + expectErr: true, + }, { + name: "wire tx nil", + packet: &Packet{}, + expectErr: true, + }, { + name: "both empty don't need outputs", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{}, + }, + expectErr: false, + }, { + name: "both empty but need outputs", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{}, + }, + needOutputs: true, + expectErr: true, + }, { + name: "both empty but need inputs", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{}, + }, + needInputs: true, + expectErr: true, + }, { + name: "input len mismatch", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{{}}, + }, + }, + needInputs: true, + expectErr: true, + }, { + name: "output len mismatch", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{ + TxOut: []*wire.TxOut{{}}, + }, + }, + needOutputs: true, + expectErr: true, + }, { + name: "all fully set", + packet: &Packet{ + UnsignedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{{}}, + TxOut: []*wire.TxOut{{}}, + }, + Inputs: []PInput{{}}, + Outputs: []POutput{{}}, + }, + needInputs: true, + needOutputs: true, + expectErr: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := VerifyInputOutputLen( + tc.packet, tc.needInputs, tc.needOutputs, + ) + if (tc.expectErr && err == nil) || + (!tc.expectErr && err != nil) { + + t.Fatalf("got error '%v' but wanted it to be "+ + "nil: %v", err, tc.expectErr) + } + }) + } +} + +func TestNewFromSignedTx(t *testing.T) { + orig := &wire.MsgTx{ + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{}, + SignatureScript: []byte("script"), + Witness: [][]byte{[]byte("witness")}, + Sequence: 1234, + }}, + TxOut: []*wire.TxOut{{ + PkScript: []byte{77, 88}, + Value: 99, + }}, + } + + packet, scripts, witnesses, err := NewFromSignedTx(orig) + if err != nil { + t.Fatalf("could not create packet from signed TX: %v", err) + } + + tx := packet.UnsignedTx + expectedTxIn := []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{}, + Sequence: 1234, + }} + if !reflect.DeepEqual(tx.TxIn, expectedTxIn) { + t.Fatalf("unexpected txin, got %#v wanted %#v", + tx.TxIn, expectedTxIn) + } + if !reflect.DeepEqual(tx.TxOut, orig.TxOut) { + t.Fatalf("unexpected txout, got %#v wanted %#v", + tx.TxOut, orig.TxOut) + } + if len(scripts) != 1 || !bytes.Equal(scripts[0], []byte("script")) { + t.Fatalf("script not extracted correctly") + } + if len(witnesses) != 1 || + !bytes.Equal(witnesses[0][0], []byte("witness")) { + + t.Fatalf("witness not extracted correctly") + } +} diff --git a/btcutil/tx.go b/btcutil/tx.go new file mode 100644 index 0000000000..5633fef90e --- /dev/null +++ b/btcutil/tx.go @@ -0,0 +1,124 @@ +// 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 btcutil + +import ( + "bytes" + "io" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// TxIndexUnknown is the value returned for a transaction index that is unknown. +// This is typically because the transaction has not been inserted into a block +// yet. +const TxIndexUnknown = -1 + +// Tx defines a bitcoin transaction that provides easier and more efficient +// manipulation of raw transactions. It also memoizes the hash for the +// transaction on its first access so subsequent accesses don't have to repeat +// the relatively expensive hashing operations. +type Tx struct { + msgTx *wire.MsgTx // Underlying MsgTx + txHash *chainhash.Hash // Cached transaction hash + txHashWitness *chainhash.Hash // Cached transaction witness hash + txHasWitness *bool // If the transaction has witness data + txIndex int // Position within a block or TxIndexUnknown +} + +// MsgTx returns the underlying wire.MsgTx for the transaction. +func (t *Tx) MsgTx() *wire.MsgTx { + // Return the cached transaction. + return t.msgTx +} + +// Hash returns the hash of the transaction. This is equivalent to +// calling TxHash on the underlying wire.MsgTx, however it caches the +// result so subsequent calls are more efficient. +func (t *Tx) Hash() *chainhash.Hash { + // Return the cached hash if it has already been generated. + if t.txHash != nil { + return t.txHash + } + + // Cache the hash and return it. + hash := t.msgTx.TxHash() + t.txHash = &hash + return &hash +} + +// WitnessHash returns the witness hash (wtxid) of the transaction. This is +// equivalent to calling WitnessHash on the underlying wire.MsgTx, however it +// caches the result so subsequent calls are more efficient. +func (t *Tx) WitnessHash() *chainhash.Hash { + // Return the cached hash if it has already been generated. + if t.txHashWitness != nil { + return t.txHashWitness + } + + // Cache the hash and return it. + hash := t.msgTx.WitnessHash() + t.txHashWitness = &hash + return &hash +} + +// HasWitness returns false if none of the inputs within the transaction +// contain witness data, true false otherwise. This equivalent to calling +// HasWitness on the underlying wire.MsgTx, however it caches the result so +// subsequent calls are more efficient. +func (t *Tx) HasWitness() bool { + if t.txHasWitness != nil { + return *t.txHasWitness + } + + hasWitness := t.msgTx.HasWitness() + t.txHasWitness = &hasWitness + return hasWitness +} + +// Index returns the saved index of the transaction within a block. This value +// will be TxIndexUnknown if it hasn't already explicitly been set. +func (t *Tx) Index() int { + return t.txIndex +} + +// SetIndex sets the index of the transaction in within a block. +func (t *Tx) SetIndex(index int) { + t.txIndex = index +} + +// NewTx returns a new instance of a bitcoin transaction given an underlying +// wire.MsgTx. See Tx. +func NewTx(msgTx *wire.MsgTx) *Tx { + return &Tx{ + msgTx: msgTx, + txIndex: TxIndexUnknown, + } +} + +// NewTxFromBytes returns a new instance of a bitcoin transaction given the +// serialized bytes. See Tx. +func NewTxFromBytes(serializedTx []byte) (*Tx, error) { + br := bytes.NewReader(serializedTx) + return NewTxFromReader(br) +} + +// NewTxFromReader returns a new instance of a bitcoin transaction given a +// Reader to deserialize the transaction. See Tx. +func NewTxFromReader(r io.Reader) (*Tx, error) { + // Deserialize the bytes into a MsgTx. + var msgTx wire.MsgTx + err := msgTx.Deserialize(r) + if err != nil { + return nil, err + } + + t := Tx{ + msgTx: &msgTx, + txIndex: TxIndexUnknown, + } + return &t, nil +} diff --git a/btcutil/tx_test.go b/btcutil/tx_test.go new file mode 100644 index 0000000000..828fc065b1 --- /dev/null +++ b/btcutil/tx_test.go @@ -0,0 +1,136 @@ +// 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 btcutil_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/btcutil" + "github.com/davecgh/go-spew/spew" +) + +// TestTx tests the API for Tx. +func TestTx(t *testing.T) { + testTx := Block100000.Transactions[0] + tx := btcutil.NewTx(testTx) + + // Ensure we get the same data back out. + if msgTx := tx.MsgTx(); !reflect.DeepEqual(msgTx, testTx) { + t.Errorf("MsgTx: mismatched MsgTx - got %v, want %v", + spew.Sdump(msgTx), spew.Sdump(testTx)) + } + + // Ensure transaction index set and get work properly. + wantIndex := 0 + tx.SetIndex(0) + if gotIndex := tx.Index(); gotIndex != wantIndex { + t.Errorf("Index: mismatched index - got %v, want %v", + gotIndex, wantIndex) + } + + // Hash for block 100,000 transaction 0. + wantHashStr := "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87" + wantHash, err := chainhash.NewHashFromStr(wantHashStr) + if err != nil { + t.Errorf("NewHashFromStr: %v", err) + } + + // Request the hash multiple times to test generation and caching. + for i := 0; i < 2; i++ { + hash := tx.Hash() + if !hash.IsEqual(wantHash) { + t.Errorf("Hash #%d mismatched hash - got %v, want %v", i, + hash, wantHash) + } + } +} + +// TestNewTxFromBytes tests creation of a Tx from serialized bytes. +func TestNewTxFromBytes(t *testing.T) { + // Serialize the test transaction. + testTx := Block100000.Transactions[0] + var testTxBuf bytes.Buffer + err := testTx.Serialize(&testTxBuf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + testTxBytes := testTxBuf.Bytes() + + // Create a new transaction from the serialized bytes. + tx, err := btcutil.NewTxFromBytes(testTxBytes) + if err != nil { + t.Errorf("NewTxFromBytes: %v", err) + return + } + + // Ensure the generated MsgTx is correct. + if msgTx := tx.MsgTx(); !reflect.DeepEqual(msgTx, testTx) { + t.Errorf("MsgTx: mismatched MsgTx - got %v, want %v", + spew.Sdump(msgTx), spew.Sdump(testTx)) + } +} + +// TestTxErrors tests the error paths for the Tx API. +func TestTxErrors(t *testing.T) { + // Serialize the test transaction. + testTx := Block100000.Transactions[0] + var testTxBuf bytes.Buffer + err := testTx.Serialize(&testTxBuf) + if err != nil { + t.Errorf("Serialize: %v", err) + } + testTxBytes := testTxBuf.Bytes() + + // Truncate the transaction byte buffer to force errors. + shortBytes := testTxBytes[:4] + _, err = btcutil.NewTxFromBytes(shortBytes) + if err != io.EOF { + t.Errorf("NewTxFromBytes: did not get expected error - "+ + "got %v, want %v", err, io.EOF) + } +} + +// TestTxHasWitness tests the HasWitness() method. +func TestTxHasWitness(t *testing.T) { + msgTx := Block100000.Transactions[0] // contains witness data + tx := btcutil.NewTx(msgTx) + + tx.WitnessHash() // Populate the witness hash cache + tx.HasWitness() // Should not fail (see btcsuite/btcd#1543) + + if !tx.HasWitness() { + t.Errorf("HasWitness: got false, want true") + } + + for _, msgTxWithoutWitness := range Block100000.Transactions[1:] { + txWithoutWitness := btcutil.NewTx(msgTxWithoutWitness) + if txWithoutWitness.HasWitness() { + t.Errorf("HasWitness: got false, want true") + } + } +} + +// TestTxWitnessHash tests the WitnessHash() method. +func TestTxWitnessHash(t *testing.T) { + msgTx := Block100000.Transactions[0] // contains witness data + tx := btcutil.NewTx(msgTx) + + if tx.WitnessHash().IsEqual(tx.Hash()) { + t.Errorf("WitnessHash: witness hash and tx id must NOT be same - "+ + "got %v, want %v", tx.WitnessHash(), tx.Hash()) + } + + for _, msgTxWithoutWitness := range Block100000.Transactions[1:] { + txWithoutWitness := btcutil.NewTx(msgTxWithoutWitness) + if !txWithoutWitness.WitnessHash().IsEqual(txWithoutWitness.Hash()) { + t.Errorf("WitnessHash: witness hash and tx id must be same - "+ + "got %v, want %v", txWithoutWitness.WitnessHash(), txWithoutWitness.Hash()) + } + } +} diff --git a/btcutil/txsort/README.md b/btcutil/txsort/README.md new file mode 100644 index 0000000000..cb8e4130d1 --- /dev/null +++ b/btcutil/txsort/README.md @@ -0,0 +1,31 @@ +txsort +====== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcutil/txsort) + +Package txsort provides the transaction sorting according to [BIP 69](https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki). + +BIP 69 defines a standard lexicographical sort order of transaction inputs and +outputs. This is useful to standardize transactions for faster multi-party +agreement as well as preventing information leaks in a single-party use case. + +The BIP goes into more detail, but for a quick and simplistic overview, the +order for inputs is defined as first sorting on the previous output hash and +then on the index as a tie breaker. The order for outputs is defined as first +sorting on the amount and then on the raw public key script bytes as a tie +breaker. + +A comprehensive suite of tests is provided to ensure proper functionality. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcd/btcutil/txsort +``` + +## License + +Package txsort is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/btcutil/txsort/doc.go b/btcutil/txsort/doc.go new file mode 100644 index 0000000000..e89c4d23d3 --- /dev/null +++ b/btcutil/txsort/doc.go @@ -0,0 +1,20 @@ +// 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. + +/* +Package txsort provides the transaction sorting according to BIP 69. + +Overview + +BIP 69 defines a standard lexicographical sort order of transaction inputs and +outputs. This is useful to standardize transactions for faster multi-party +agreement as well as preventing information leaks in a single-party use case. + +The BIP goes into more detail, but for a quick and simplistic overview, the +order for inputs is defined as first sorting on the previous output hash and +then on the index as a tie breaker. The order for outputs is defined as first +sorting on the amount and then on the raw public key script bytes as a tie +breaker. +*/ +package txsort diff --git a/btcutil/txsort/testdata/bip69-1.hex b/btcutil/txsort/testdata/bip69-1.hex new file mode 100644 index 0000000000..dbd18422b7 --- /dev/null +++ b/btcutil/txsort/testdata/bip69-1.hex @@ -0,0 +1 @@ +0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000 \ No newline at end of file diff --git a/btcutil/txsort/testdata/bip69-2.hex b/btcutil/txsort/testdata/bip69-2.hex new file mode 100644 index 0000000000..c42d0d2dc1 --- /dev/null +++ b/btcutil/txsort/testdata/bip69-2.hex @@ -0,0 +1 @@ +010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000 \ No newline at end of file diff --git a/btcutil/txsort/testdata/bip69-3.hex b/btcutil/txsort/testdata/bip69-3.hex new file mode 100644 index 0000000000..6fd859a9cf --- /dev/null +++ b/btcutil/txsort/testdata/bip69-3.hex @@ -0,0 +1 @@ +0100000001d992e5a888a86d4c7a6a69167a4728ee69497509740fc5f456a24528c340219a000000008b483045022100f0519bdc9282ff476da1323b8ef7ffe33f495c1a8d52cc522b437022d83f6a230220159b61d197fbae01b4a66622a23bc3f1def65d5fa24efd5c26fa872f3a246b8e014104839f9023296a1fabb133140128ca2709f6818c7d099491690bd8ac0fd55279def6a2ceb6ab7b5e4a71889b6e739f09509565eec789e86886f6f936fa42097adeffffffff02000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac00e32321000000001976a9140c34f4e29ab5a615d5ea28d4817f12b137d62ed588ac00000000 \ No newline at end of file diff --git a/btcutil/txsort/testdata/bip69-4.hex b/btcutil/txsort/testdata/bip69-4.hex new file mode 100644 index 0000000000..f41d23f8b2 --- /dev/null +++ b/btcutil/txsort/testdata/bip69-4.hex @@ -0,0 +1 @@ +01000000059daf0abe7a92618546a9dbcfd65869b6178c66ec21ccfda878c1175979cfd9ef000000004a493046022100c2f7f25be5de6ce88ac3c1a519514379e91f39b31ddff279a3db0b1a229b708b022100b29efbdbd9837cc6a6c7318aa4900ed7e4d65662c34d1622a2035a3a5534a99a01ffffffffd516330ebdf075948da56db13d22632a4fb941122df2884397dda45d451acefb0000000048473044022051243debe6d4f2b433bee0cee78c5c4073ead0e3bde54296dbed6176e128659c022044417bfe16f44eb7b6eb0cdf077b9ce972a332e15395c09ca5e4f602958d266101ffffffffe1f5aa33961227b3c344e57179417ce01b7ccd421117fe2336289b70489883f900000000484730440220593252bb992ce3c85baf28d6e3aa32065816271d2c822398fe7ee28a856bc943022066d429dd5025d3c86fd8fd8a58e183a844bd94aa312cefe00388f57c85b0ca3201ffffffffe207e83718129505e6a7484831442f668164ae659fddb82e9e5421a081fb90d50000000049483045022067cf27eb733e5bcae412a586b25a74417c237161a084167c2a0b439abfebdcb2022100efcc6baa6824b4c5205aa967e0b76d31abf89e738d4b6b014e788c9a8cccaf0c01ffffffffe23b8d9d80a9e9d977fab3c94dbe37befee63822443c3ec5ae5a713ede66c3940000000049483045022020f2eb35036666b1debe0d1d2e77a36d5d9c4e96c1dba23f5100f193dbf524790221008ce79bc1321fb4357c6daee818038d41544749127751726e46b2b320c8b565a201ffffffff0200ba1dd2050000001976a914366a27645806e817a6cd40bc869bdad92fe5509188ac40420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac00000000 \ No newline at end of file diff --git a/btcutil/txsort/testdata/bip69-5.hex b/btcutil/txsort/testdata/bip69-5.hex new file mode 100644 index 0000000000..e1cb4d6131 --- /dev/null +++ b/btcutil/txsort/testdata/bip69-5.hex @@ -0,0 +1 @@ +01000000011f636d0003f673b3aeea4971daef16b8eed784cf6e8019a5ae7da4985fbb06e5000000008a47304402205103941e2b11e746dfa817888d422f6e7f4d16dbbfb8ffa61d15ffb924a84b8802202fe861b0f23f17139d15a3374bfc6c7196d371f3d1a324e31cc0aadbba87e53c0141049e7e1b251a7e26cae9ee7553b278ef58ef3c28b4b20134d51b747d9b18b0a19b94b66cef320e2549dec0ea3d725cb4c742f368928b1fb74b4603e24a1e262c80ffffffff0240420f00000000001976a914bcfa0e27218a7c97257b351b03a9eac95c25a23988ac40420f00000000001976a9140c6a68f20bafc678164d171ee4f077adfa9b091688ac00000000 \ No newline at end of file diff --git a/btcutil/txsort/txsort.go b/btcutil/txsort/txsort.go new file mode 100644 index 0000000000..f72a7db970 --- /dev/null +++ b/btcutil/txsort/txsort.go @@ -0,0 +1,95 @@ +// 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. + +// Provides functions for sorting tx inputs and outputs according to BIP 69 +// (https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki) + +package txsort + +import ( + "bytes" + "sort" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// InPlaceSort modifies the passed transaction inputs and outputs to be sorted +// based on BIP 69. +// +// WARNING: This function must NOT be called with published transactions since +// it will mutate the transaction if it's not already sorted. This can cause +// issues if you mutate a tx in a block, for example, which would invalidate the +// block. It could also cause cached hashes, such as in a btcutil.Tx to become +// invalidated. +// +// The function should only be used if the caller is creating the transaction or +// is otherwise 100% positive mutating will not cause adverse affects due to +// other dependencies. +func InPlaceSort(tx *wire.MsgTx) { + sort.Sort(sortableInputSlice(tx.TxIn)) + sort.Sort(sortableOutputSlice(tx.TxOut)) +} + +// Sort returns a new transaction with the inputs and outputs sorted based on +// BIP 69. The passed transaction is not modified and the new transaction +// might have a different hash if any sorting was done. +func Sort(tx *wire.MsgTx) *wire.MsgTx { + txCopy := tx.Copy() + sort.Sort(sortableInputSlice(txCopy.TxIn)) + sort.Sort(sortableOutputSlice(txCopy.TxOut)) + return txCopy +} + +// IsSorted checks whether tx has inputs and outputs sorted according to BIP +// 69. +func IsSorted(tx *wire.MsgTx) bool { + if !sort.IsSorted(sortableInputSlice(tx.TxIn)) { + return false + } + if !sort.IsSorted(sortableOutputSlice(tx.TxOut)) { + return false + } + return true +} + +type sortableInputSlice []*wire.TxIn +type sortableOutputSlice []*wire.TxOut + +// For SortableInputSlice and SortableOutputSlice, three functions are needed +// to make it sortable with sort.Sort() -- Len, Less, and Swap +// Len and Swap are trivial. Less is BIP 69 specific. +func (s sortableInputSlice) Len() int { return len(s) } +func (s sortableOutputSlice) Len() int { return len(s) } +func (s sortableOutputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s sortableInputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// Input comparison function. +// First sort based on input hash (reversed / rpc-style), then index. +func (s sortableInputSlice) Less(i, j int) bool { + // Input hashes are the same, so compare the index. + ihash := s[i].PreviousOutPoint.Hash + jhash := s[j].PreviousOutPoint.Hash + if ihash == jhash { + return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index + } + + // At this point, the hashes are not equal, so reverse them to + // big-endian and return the result of the comparison. + const hashSize = chainhash.HashSize + for b := 0; b < hashSize/2; b++ { + ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b] + jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b] + } + return bytes.Compare(ihash[:], jhash[:]) == -1 +} + +// Output comparison function. +// First sort based on amount (smallest first), then PkScript. +func (s sortableOutputSlice) Less(i, j int) bool { + if s[i].Value == s[j].Value { + return bytes.Compare(s[i].PkScript, s[j].PkScript) < 0 + } + return s[i].Value < s[j].Value +} diff --git a/btcutil/txsort/txsort_test.go b/btcutil/txsort/txsort_test.go new file mode 100644 index 0000000000..7d5c2d3eaf --- /dev/null +++ b/btcutil/txsort/txsort_test.go @@ -0,0 +1,124 @@ +// 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 txsort_test + +import ( + "bytes" + "encoding/hex" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcd/btcutil/txsort" +) + +// TestSort ensures the transaction sorting works according to the BIP. +func TestSort(t *testing.T) { + tests := []struct { + name string + hexFile string + isSorted bool + unsortedHash string + sortedHash string + }{ + { + name: "first test case from BIP 69 - sorts inputs only, based on hash", + hexFile: "bip69-1.hex", + isSorted: false, + unsortedHash: "0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3", + sortedHash: "839503cb611a3e3734bd521c608f881be2293ff77b7384057ab994c794fce623", + }, + { + name: "second test case from BIP 69 - already sorted", + hexFile: "bip69-2.hex", + isSorted: true, + unsortedHash: "28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f", + sortedHash: "28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f", + }, + { + name: "block 100001 tx[1] - sorts outputs only, based on amount", + hexFile: "bip69-3.hex", + isSorted: false, + unsortedHash: "fbde5d03b027d2b9ba4cf5d4fecab9a99864df2637b25ea4cbcb1796ff6550ca", + sortedHash: "0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f", + }, + { + name: "block 100001 tx[2] - sorts both inputs and outputs", + hexFile: "bip69-4.hex", + isSorted: false, + unsortedHash: "8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb", + sortedHash: "a3196553b928b0b6154b002fa9a1ce875adabc486fedaaaf4c17430fd4486329", + }, + { + name: "block 100998 tx[6] - sorts outputs only, based on output script", + hexFile: "bip69-5.hex", + isSorted: false, + unsortedHash: "ff85e8fc92e71bbc217e3ea9a3bacb86b435e52b6df0b089d67302c293a2b81d", + sortedHash: "9a6c24746de024f77cac9b2138694f11101d1c66289261224ca52a25155a7c94", + }, + } + + for _, test := range tests { + // Load and deserialize the test transaction. + filePath := filepath.Join("testdata", test.hexFile) + txHexBytes, err := ioutil.ReadFile(filePath) + if err != nil { + t.Errorf("ReadFile (%s): failed to read test file: %v", + test.name, err) + continue + } + txBytes, err := hex.DecodeString(string(txHexBytes)) + if err != nil { + t.Errorf("DecodeString (%s): failed to decode tx: %v", + test.name, err) + continue + } + var tx wire.MsgTx + err = tx.Deserialize(bytes.NewReader(txBytes)) + if err != nil { + t.Errorf("Deserialize (%s): unexpected error %v", + test.name, err) + continue + } + + // Ensure the sort order of the original transaction matches the + // expected value. + if got := txsort.IsSorted(&tx); got != test.isSorted { + t.Errorf("IsSorted (%s): sort does not match "+ + "expected - got %v, want %v", test.name, got, + test.isSorted) + continue + } + + // Sort the transaction and ensure the resulting hash is the + // expected value. + sortedTx := txsort.Sort(&tx) + if got := sortedTx.TxHash().String(); got != test.sortedHash { + t.Errorf("Sort (%s): sorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.sortedHash) + continue + } + + // Ensure the original transaction is not modified. + if got := tx.TxHash().String(); got != test.unsortedHash { + t.Errorf("Sort (%s): unsorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.unsortedHash) + continue + } + + // Now sort the transaction using the mutable version and ensure + // the resulting hash is the expected value. + txsort.InPlaceSort(&tx) + if got := tx.TxHash().String(); got != test.sortedHash { + t.Errorf("SortMutate (%s): sorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.sortedHash) + continue + } + } +} diff --git a/btcutil/wif.go b/btcutil/wif.go new file mode 100644 index 0000000000..e727a0c44a --- /dev/null +++ b/btcutil/wif.go @@ -0,0 +1,169 @@ +// 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 btcutil + +import ( + "bytes" + "errors" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/btcutil/base58" +) + +// ErrMalformedPrivateKey describes an error where a WIF-encoded private +// key cannot be decoded due to being improperly formatted. This may occur +// if the byte length is incorrect or an unexpected magic number was +// encountered. +var ErrMalformedPrivateKey = errors.New("malformed private key") + +// compressMagic is the magic byte used to identify a WIF encoding for +// an address created from a compressed serialized public key. +const compressMagic byte = 0x01 + +// WIF contains the individual components described by the Wallet Import Format +// (WIF). A WIF string is typically used to represent a private key and its +// associated address in a way that may be easily copied and imported into or +// exported from wallet software. WIF strings may be decoded into this +// structure by calling DecodeWIF or created with a user-provided private key +// by calling NewWIF. +type WIF struct { + // PrivKey is the private key being imported or exported. + PrivKey *btcec.PrivateKey + + // CompressPubKey specifies whether the address controlled by the + // imported or exported private key was created by hashing a + // compressed (33-byte) serialized public key, rather than an + // uncompressed (65-byte) one. + CompressPubKey bool + + // netID is the bitcoin network identifier byte used when + // WIF encoding the private key. + netID byte +} + +// NewWIF creates a new WIF structure to export an address and its private key +// as a string encoded in the Wallet Import Format. The compress argument +// specifies whether the address intended to be imported or exported was created +// by serializing the public key compressed rather than uncompressed. +func NewWIF(privKey *btcec.PrivateKey, net *chaincfg.Params, compress bool) (*WIF, error) { + if net == nil { + return nil, errors.New("no network") + } + return &WIF{privKey, compress, net.PrivateKeyID}, nil +} + +// IsForNet returns whether or not the decoded WIF structure is associated +// with the passed bitcoin network. +func (w *WIF) IsForNet(net *chaincfg.Params) bool { + return w.netID == net.PrivateKeyID +} + +// DecodeWIF creates a new WIF structure by decoding the string encoding of +// the import format. +// +// The WIF string must be a base58-encoded string of the following byte +// sequence: +// +// * 1 byte to identify the network, must be 0x80 for mainnet or 0xef for +// either testnet3 or the regression test network +// * 32 bytes of a binary-encoded, big-endian, zero-padded private key +// * Optional 1 byte (equal to 0x01) if the address being imported or exported +// was created by taking the RIPEMD160 after SHA256 hash of a serialized +// compressed (33-byte) public key +// * 4 bytes of checksum, must equal the first four bytes of the double SHA256 +// of every byte before the checksum in this sequence +// +// If the base58-decoded byte sequence does not match this, DecodeWIF will +// return a non-nil error. ErrMalformedPrivateKey is returned when the WIF +// is of an impossible length or the expected compressed pubkey magic number +// does not equal the expected value of 0x01. ErrChecksumMismatch is returned +// if the expected WIF checksum does not match the calculated checksum. +func DecodeWIF(wif string) (*WIF, error) { + decoded := base58.Decode(wif) + decodedLen := len(decoded) + var compress bool + + // Length of base58 decoded WIF must be 32 bytes + an optional 1 byte + // (0x01) if compressed, plus 1 byte for netID + 4 bytes of checksum. + switch decodedLen { + case 1 + btcec.PrivKeyBytesLen + 1 + 4: + if decoded[33] != compressMagic { + return nil, ErrMalformedPrivateKey + } + compress = true + case 1 + btcec.PrivKeyBytesLen + 4: + compress = false + default: + return nil, ErrMalformedPrivateKey + } + + // Checksum is first four bytes of double SHA256 of the identifier byte + // and privKey. Verify this matches the final 4 bytes of the decoded + // private key. + var tosum []byte + if compress { + tosum = decoded[:1+btcec.PrivKeyBytesLen+1] + } else { + tosum = decoded[:1+btcec.PrivKeyBytesLen] + } + cksum := chainhash.DoubleHashB(tosum)[:4] + if !bytes.Equal(cksum, decoded[decodedLen-4:]) { + return nil, ErrChecksumMismatch + } + + netID := decoded[0] + privKeyBytes := decoded[1 : 1+btcec.PrivKeyBytesLen] + privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) + return &WIF{privKey, compress, netID}, nil +} + +// String creates the Wallet Import Format string encoding of a WIF structure. +// See DecodeWIF for a detailed breakdown of the format and requirements of +// a valid WIF string. +func (w *WIF) String() string { + // Precalculate size. Maximum number of bytes before base58 encoding + // is one byte for the network, 32 bytes of private key, possibly one + // extra byte if the pubkey is to be compressed, and finally four + // bytes of checksum. + encodeLen := 1 + btcec.PrivKeyBytesLen + 4 + if w.CompressPubKey { + encodeLen++ + } + + a := make([]byte, 0, encodeLen) + a = append(a, w.netID) + // Pad and append bytes manually, instead of using Serialize, to + // avoid another call to make. + a = paddedAppend(btcec.PrivKeyBytesLen, a, w.PrivKey.D.Bytes()) + if w.CompressPubKey { + a = append(a, compressMagic) + } + cksum := chainhash.DoubleHashB(a)[:4] + a = append(a, cksum...) + return base58.Encode(a) +} + +// SerializePubKey serializes the associated public key of the imported or +// exported private key in either a compressed or uncompressed format. The +// serialization format chosen depends on the value of w.CompressPubKey. +func (w *WIF) SerializePubKey() []byte { + pk := (*btcec.PublicKey)(&w.PrivKey.PublicKey) + if w.CompressPubKey { + return pk.SerializeCompressed() + } + return pk.SerializeUncompressed() +} + +// paddedAppend appends the src byte slice to dst, returning the new slice. +// If the length of the source is smaller than the passed size, leading zero +// bytes are appended to the dst slice before appending src. +func paddedAppend(size uint, dst, src []byte) []byte { + for i := 0; i < int(size)-len(src); i++ { + dst = append(dst, 0) + } + return append(dst, src...) +} diff --git a/btcutil/wif_test.go b/btcutil/wif_test.go new file mode 100644 index 0000000000..1c21b4871b --- /dev/null +++ b/btcutil/wif_test.go @@ -0,0 +1,160 @@ +// Copyright (c) 2013 - 2020 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcutil_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + . "github.com/btcsuite/btcd/btcutil" +) + +func TestEncodeDecodeWIF(t *testing.T) { + validEncodeCases := []struct { + privateKey []byte // input + net *chaincfg.Params // input + compress bool // input + wif string // output + publicKey []byte // output + name string // name of subtest + }{ + { + privateKey: []byte{ + 0x0c, 0x28, 0xfc, 0xa3, 0x86, 0xc7, 0xa2, 0x27, + 0x60, 0x0b, 0x2f, 0xe5, 0x0b, 0x7c, 0xae, 0x11, + 0xec, 0x86, 0xd3, 0xbf, 0x1f, 0xbe, 0x47, 0x1b, + 0xe8, 0x98, 0x27, 0xe1, 0x9d, 0x72, 0xaa, 0x1d}, + net: &chaincfg.MainNetParams, + compress: false, + wif: "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ", + publicKey: []byte{ + 0x04, 0xd0, 0xde, 0x0a, 0xae, 0xae, 0xfa, 0xd0, + 0x2b, 0x8b, 0xdc, 0x8a, 0x01, 0xa1, 0xb8, 0xb1, + 0x1c, 0x69, 0x6b, 0xd3, 0xd6, 0x6a, 0x2c, 0x5f, + 0x10, 0x78, 0x0d, 0x95, 0xb7, 0xdf, 0x42, 0x64, + 0x5c, 0xd8, 0x52, 0x28, 0xa6, 0xfb, 0x29, 0x94, + 0x0e, 0x85, 0x8e, 0x7e, 0x55, 0x84, 0x2a, 0xe2, + 0xbd, 0x11, 0x5d, 0x1e, 0xd7, 0xcc, 0x0e, 0x82, + 0xd9, 0x34, 0xe9, 0x29, 0xc9, 0x76, 0x48, 0xcb, + 0x0a}, + name: "encodeValidUncompressedMainNetWif", + }, + { + privateKey: []byte{ + 0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6, + 0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81, + 0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9, + 0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98}, + net: &chaincfg.TestNet3Params, + compress: true, + wif: "cV1Y7ARUr9Yx7BR55nTdnR7ZXNJphZtCCMBTEZBJe1hXt2kB684q", + publicKey: []byte{ + 0x02, 0xee, 0xc2, 0x54, 0x06, 0x61, 0xb0, 0xc3, + 0x9d, 0x27, 0x15, 0x70, 0x74, 0x24, 0x13, 0xbd, + 0x02, 0x93, 0x2d, 0xd0, 0x09, 0x34, 0x93, 0xfd, + 0x0b, 0xec, 0xed, 0x0b, 0x7f, 0x93, 0xad, 0xde, + 0xc4}, + name: "encodeValidCompressedTestNet3Wif", + }, + } + + for _, validCase := range validEncodeCases { + validCase := validCase + + t.Run(validCase.name, func(t *testing.T) { + priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), validCase.privateKey) + wif, err := NewWIF(priv, validCase.net, validCase.compress) + if err != nil { + t.Fatalf("NewWIF failed: expected no error, got '%v'", err) + } + + if !wif.IsForNet(validCase.net) { + t.Fatal("IsForNet failed: got 'false', want 'true'") + } + + if gotPubKey := wif.SerializePubKey(); !bytes.Equal(gotPubKey, validCase.publicKey) { + t.Fatalf("SerializePubKey failed: got '%s', want '%s'", + hex.EncodeToString(gotPubKey), hex.EncodeToString(validCase.publicKey)) + } + + // Test that encoding the WIF structure matches the expected string. + got := wif.String() + if got != validCase.wif { + t.Fatalf("NewWIF failed: want '%s', got '%s'", + validCase.wif, got) + } + + // Test that decoding the expected string results in the original WIF + // structure. + decodedWif, err := DecodeWIF(got) + if err != nil { + t.Fatalf("DecodeWIF failed: expected no error, got '%v'", err) + } + if decodedWifString := decodedWif.String(); decodedWifString != validCase.wif { + t.Fatalf("NewWIF failed: want '%v', got '%v'", validCase.wif, decodedWifString) + } + }) + } + + invalidDecodeCases := []struct { + name string + wif string + err error + }{ + { + name: "decodeInvalidLengthWif", + wif: "deadbeef", + err: ErrMalformedPrivateKey, + }, + { + name: "decodeInvalidCompressMagicWif", + wif: "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sfZr2ym", + err: ErrMalformedPrivateKey, + }, + { + name: "decodeInvalidChecksumWif", + wif: "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTj", + err: ErrChecksumMismatch, + }, + } + + for _, invalidCase := range invalidDecodeCases { + invalidCase := invalidCase + + t.Run(invalidCase.name, func(t *testing.T) { + decodedWif, err := DecodeWIF(invalidCase.wif) + if decodedWif != nil { + t.Fatalf("DecodeWIF: unexpectedly succeeded - got '%v', want '%v'", + decodedWif, nil) + } + if err != invalidCase.err { + t.Fatalf("DecodeWIF: expected error '%v', got '%v'", + invalidCase.err, err) + } + }) + } + + t.Run("encodeInvalidNetworkWif", func(t *testing.T) { + privateKey := []byte{ + 0x0c, 0x28, 0xfc, 0xa3, 0x86, 0xc7, 0xa2, 0x27, + 0x60, 0x0b, 0x2f, 0xe5, 0x0b, 0x7c, 0xae, 0x11, + 0xec, 0x86, 0xd3, 0xbf, 0x1f, 0xbe, 0x47, 0x1b, + 0xe8, 0x98, 0x27, 0xe1, 0x9d, 0x72, 0xaa, 0x1d} + priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKey) + + wif, err := NewWIF(priv, nil, true) + + if wif != nil { + t.Fatalf("NewWIF: unexpectedly succeeded - got '%v', want '%v'", + wif, nil) + } + if err == nil || err.Error() != "no network" { + t.Fatalf("NewWIF: expected error 'no network', got '%v'", err) + } + }) +} diff --git a/chaincfg/README.md b/chaincfg/README.md index 72fac2e7dc..d1a534bafe 100644 --- a/chaincfg/README.md +++ b/chaincfg/README.md @@ -24,7 +24,7 @@ import ( "fmt" "log" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" ) diff --git a/chaincfg/doc.go b/chaincfg/doc.go index 3659adbf46..1595b2769f 100644 --- a/chaincfg/doc.go +++ b/chaincfg/doc.go @@ -25,7 +25,7 @@ // "fmt" // "log" // -// "github.com/btcsuite/btcutil" +// "github.com/btcsuite/btcd/btcutil" // "github.com/btcsuite/btcd/chaincfg" // ) // diff --git a/cmd/addblock/config.go b/cmd/addblock/config.go index 90620c8fda..c638196534 100644 --- a/cmd/addblock/config.go +++ b/cmd/addblock/config.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/database" _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" flags "github.com/jessevdk/go-flags" ) diff --git a/cmd/addblock/import.go b/cmd/addblock/import.go index b7a30369ec..4875ce11cb 100644 --- a/cmd/addblock/import.go +++ b/cmd/addblock/import.go @@ -16,7 +16,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var zeroHash = chainhash.Hash{} diff --git a/cmd/btcctl/config.go b/cmd/btcctl/config.go index 1cc2a260f3..3db735c5d8 100644 --- a/cmd/btcctl/config.go +++ b/cmd/btcctl/config.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" flags "github.com/jessevdk/go-flags" ) diff --git a/cmd/findcheckpoint/config.go b/cmd/findcheckpoint/config.go index 87f04cecd5..5671b5b186 100644 --- a/cmd/findcheckpoint/config.go +++ b/cmd/findcheckpoint/config.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/database" _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" flags "github.com/jessevdk/go-flags" ) diff --git a/cmd/gencerts/gencerts.go b/cmd/gencerts/gencerts.go index 27d1073e74..c0a27ad37b 100644 --- a/cmd/gencerts/gencerts.go +++ b/cmd/gencerts/gencerts.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" flags "github.com/jessevdk/go-flags" ) diff --git a/config.go b/config.go index 156084bcf9..5eca3f2174 100644 --- a/config.go +++ b/config.go @@ -30,7 +30,7 @@ import ( "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/go-socks/socks" flags "github.com/jessevdk/go-flags" ) diff --git a/database/cmd/dbtool/globalconfig.go b/database/cmd/dbtool/globalconfig.go index aa1e0e0483..2ec746a43d 100644 --- a/database/cmd/dbtool/globalconfig.go +++ b/database/cmd/dbtool/globalconfig.go @@ -14,7 +14,7 @@ import ( "github.com/btcsuite/btcd/database" _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var ( diff --git a/database/cmd/dbtool/insecureimport.go b/database/cmd/dbtool/insecureimport.go index 7564eb68ad..a01c74bb55 100644 --- a/database/cmd/dbtool/insecureimport.go +++ b/database/cmd/dbtool/insecureimport.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // importCmd defines the configuration options for the insecureimport command. diff --git a/database/example_test.go b/database/example_test.go index 8b6fe7bce4..b64baf2c8e 100644 --- a/database/example_test.go +++ b/database/example_test.go @@ -7,14 +7,15 @@ package database_test import ( "bytes" "fmt" + "io/ioutil" "os" "path/filepath" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" _ "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) // This example demonstrates creating a new database. @@ -122,9 +123,14 @@ func Example_blockStorageAndRetrieval() { // Typically you wouldn't want to remove the database right away like // this, nor put it in the temp directory, but it's done here to ensure // the example cleans up after itself. - dbPath := filepath.Join(os.TempDir(), "exampleblkstorage") + dbPath, err := ioutil.TempDir("", "exampleblkstorage") + if err != nil { + fmt.Println(err) + return + } db, err := database.Create("ffldb", dbPath, wire.MainNet) if err != nil { + fmt.Println("fail here") fmt.Println(err) return } diff --git a/database/ffldb/bench_test.go b/database/ffldb/bench_test.go index 8d020313d8..f4a0eb32e5 100644 --- a/database/ffldb/bench_test.go +++ b/database/ffldb/bench_test.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // BenchmarkBlockHeader benchmarks how long it takes to load the mainnet genesis diff --git a/database/ffldb/db.go b/database/ffldb/db.go index b499442101..ee36b5b006 100644 --- a/database/ffldb/db.go +++ b/database/ffldb/db.go @@ -18,7 +18,7 @@ import ( "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/database/internal/treap" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/goleveldb/leveldb" "github.com/btcsuite/goleveldb/leveldb/comparer" ldberrors "github.com/btcsuite/goleveldb/leveldb/errors" diff --git a/database/ffldb/driver_test.go b/database/ffldb/driver_test.go index f3db909d79..ef35f07840 100644 --- a/database/ffldb/driver_test.go +++ b/database/ffldb/driver_test.go @@ -14,7 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/database/ffldb" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // dbType is the database type name for this driver. diff --git a/database/ffldb/interface_test.go b/database/ffldb/interface_test.go index 1ce991cc09..af26faccab 100644 --- a/database/ffldb/interface_test.go +++ b/database/ffldb/interface_test.go @@ -29,7 +29,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var ( diff --git a/database/ffldb/whitebox_test.go b/database/ffldb/whitebox_test.go index 161d866d29..1db69e4215 100644 --- a/database/ffldb/whitebox_test.go +++ b/database/ffldb/whitebox_test.go @@ -20,7 +20,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/goleveldb/leveldb" ldberrors "github.com/btcsuite/goleveldb/leveldb/errors" ) diff --git a/database/interface.go b/database/interface.go index 435a8ff581..aa88cc3723 100644 --- a/database/interface.go +++ b/database/interface.go @@ -9,7 +9,7 @@ package database import ( "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // Cursor represents a cursor over key/value pairs and nested buckets of a diff --git a/docs/developer_resources.md b/docs/developer_resources.md index cec8ce9972..c595c8330e 100644 --- a/docs/developer_resources.md +++ b/docs/developer_resources.md @@ -28,7 +28,7 @@ * [mempool](https://github.com/btcsuite/btcd/tree/master/mempool) - Package mempool provides a policy-enforced pool of unmined bitcoin transactions. - * [btcutil](https://github.com/btcsuite/btcutil) - Provides Bitcoin-specific + * [btcutil](https://github.com/btcsuite/btcd/btcutil) - Provides Bitcoin-specific convenience functions and types * [chainhash](https://github.com/btcsuite/btcd/tree/master/chaincfg/chainhash) - Provides a generic hash type and associated functions that allows the diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index db292d2ba2..d1b43ce8c4 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -1100,7 +1100,7 @@ import ( "path/filepath" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func main() { @@ -1164,7 +1164,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func main() { @@ -1256,7 +1256,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func main() { diff --git a/go.mod b/go.mod index 767c2740db..a2e5bed578 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/btcsuite/btcd require ( + github.com/btcsuite/btcd/btcutil v1.0.0 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/goleveldb v1.0.0 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 @@ -11,8 +11,21 @@ require ( github.com/decred/dcrd/lru v1.0.0 github.com/jessevdk/go-flags v1.4.0 github.com/jrick/logrotate v1.0.0 - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 ) -go 1.16 +require ( + github.com/aead/siphash v1.0.1 // indirect + github.com/btcsuite/snappy-go v1.0.0 // indirect + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect + github.com/onsi/ginkgo v1.14.0 // indirect + github.com/onsi/gomega v1.10.1 // indirect + golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect + golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed // indirect + golang.org/x/text v0.3.3 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) + +replace github.com/btcsuite/btcd/btcutil => ./btcutil + +go 1.17 diff --git a/go.sum b/go.sum index e9ee276db4..a23498435b 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= @@ -19,12 +17,12 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -33,11 +31,9 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= @@ -46,49 +42,46 @@ github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -97,11 +90,10 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/goclean.sh b/goclean.sh deleted file mode 100755 index dad9f8f1dc..0000000000 --- a/goclean.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# The script does automatic checking on a Go package and its sub-packages, including: -# 1. gofmt (http://golang.org/cmd/gofmt/) -# 3. go vet (http://golang.org/cmd/vet) -# 4. gosimple (https://github.com/dominikh/go-simple) -# 5. unconvert (https://github.com/mdempsky/unconvert) -# 6. race detector (http://blog.golang.org/race-detector) -# 7. test coverage (http://blog.golang.org/cover) - -set -ex - -env GORACE="halt_on_error=1" go test -race -tags="rpctest" -covermode atomic -coverprofile=profile.cov ./... - -# Automatic checks -golangci-lint run --deadline=10m --disable-all \ ---enable=gofmt \ ---enable=vet \ ---enable=gosimple \ ---enable=unconvert diff --git a/integration/csv_fork_test.go b/integration/csv_fork_test.go index 3146634966..5ffd5d5ab2 100644 --- a/integration/csv_fork_test.go +++ b/integration/csv_fork_test.go @@ -21,7 +21,7 @@ import ( "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/integration/rpctest/blockgen.go b/integration/rpctest/blockgen.go index 0d802f5a48..a35c66e6ac 100644 --- a/integration/rpctest/blockgen.go +++ b/integration/rpctest/blockgen.go @@ -17,7 +17,7 @@ import ( "github.com/btcsuite/btcd/mining" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // solveBlock attempts to find a nonce which makes the passed block header hash diff --git a/integration/rpctest/memwallet.go b/integration/rpctest/memwallet.go index 59b0ef4c08..c94124d82c 100644 --- a/integration/rpctest/memwallet.go +++ b/integration/rpctest/memwallet.go @@ -17,8 +17,8 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" ) var ( diff --git a/integration/rpctest/node.go b/integration/rpctest/node.go index 73dc15fca9..be52a15404 100644 --- a/integration/rpctest/node.go +++ b/integration/rpctest/node.go @@ -15,7 +15,7 @@ import ( "time" rpc "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // nodeConfig contains all the args, and data required to launch a btcd process diff --git a/integration/rpctest/rpc_harness.go b/integration/rpctest/rpc_harness.go index 679ae4e478..cb49e42c26 100644 --- a/integration/rpctest/rpc_harness.go +++ b/integration/rpctest/rpc_harness.go @@ -20,7 +20,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/integration/rpctest/rpc_harness_test.go b/integration/rpctest/rpc_harness_test.go index df753e3126..d1184eb6ba 100644 --- a/integration/rpctest/rpc_harness_test.go +++ b/integration/rpctest/rpc_harness_test.go @@ -17,7 +17,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func testSendOutputs(r *Harness, t *testing.T) { diff --git a/mempool/estimatefee.go b/mempool/estimatefee.go index 55fe4810df..f3a7cfe95b 100644 --- a/mempool/estimatefee.go +++ b/mempool/estimatefee.go @@ -18,7 +18,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mining" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TODO incorporate Alex Morcos' modifications to Gavin's initial model diff --git a/mempool/estimatefee_test.go b/mempool/estimatefee_test.go index 16dcfadc95..c5ea85c635 100644 --- a/mempool/estimatefee_test.go +++ b/mempool/estimatefee_test.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mining" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // newTestFeeEstimator creates a feeEstimator with some different parameters diff --git a/mempool/mempool.go b/mempool/mempool.go index b54856c8b3..f830a51382 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -20,7 +20,7 @@ import ( "github.com/btcsuite/btcd/mining" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 96d5054417..96777b9758 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -18,7 +18,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // fakeChain is used by the pool harness to provide generated test utxos and diff --git a/mempool/policy.go b/mempool/policy.go index 1fe850797f..edba16ac2e 100644 --- a/mempool/policy.go +++ b/mempool/policy.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/mempool/policy_test.go b/mempool/policy_test.go index b9cdbac47a..a81f98ea83 100644 --- a/mempool/policy_test.go +++ b/mempool/policy_test.go @@ -14,7 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API. diff --git a/mining/cpuminer/cpuminer.go b/mining/cpuminer/cpuminer.go index 3d5b3b1936..038e6645bb 100644 --- a/mining/cpuminer/cpuminer.go +++ b/mining/cpuminer/cpuminer.go @@ -17,7 +17,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mining" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/mining/mining.go b/mining/mining.go index e918328df8..4ed61f3f32 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/mining/mining_test.go b/mining/mining_test.go index 362253e5fe..ba1d97771c 100644 --- a/mining/mining_test.go +++ b/mining/mining_test.go @@ -9,7 +9,7 @@ import ( "math/rand" "testing" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // TestTxFeePrioHeap ensures the priority queue for transaction fees and diff --git a/mining/policy.go b/mining/policy.go index c3f059c521..f8ce411602 100644 --- a/mining/policy.go +++ b/mining/policy.go @@ -7,7 +7,7 @@ package mining import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/mining/policy_test.go b/mining/policy_test.go index f66a9c8dbd..0a362f5471 100644 --- a/mining/policy_test.go +++ b/mining/policy_test.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // newHashFromStr converts the passed big-endian hex string into a diff --git a/netsync/blocklogger.go b/netsync/blocklogger.go index 18480e782a..788192ccb2 100644 --- a/netsync/blocklogger.go +++ b/netsync/blocklogger.go @@ -9,7 +9,7 @@ import ( "time" "github.com/btcsuite/btclog" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // blockProgressLogger provides periodic logging for other services in order diff --git a/netsync/interface.go b/netsync/interface.go index 3e6ca1c12d..2ce479bf2d 100644 --- a/netsync/interface.go +++ b/netsync/interface.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // PeerNotifier exposes methods to notify peers of status changes to diff --git a/netsync/manager.go b/netsync/manager.go index 2b6c041156..a297bb3f24 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -19,7 +19,7 @@ import ( "github.com/btcsuite/btcd/mempool" peerpkg "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/rpcadapters.go b/rpcadapters.go index ddcdf79bf7..02d33710ac 100644 --- a/rpcadapters.go +++ b/rpcadapters.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/netsync" "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // rpcPeer provides a peer for use with the RPC server and implements the diff --git a/rpcclient/examples/btcdwebsockets/main.go b/rpcclient/examples/btcdwebsockets/main.go index 56d12d8274..1f18b9aab4 100644 --- a/rpcclient/examples/btcdwebsockets/main.go +++ b/rpcclient/examples/btcdwebsockets/main.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) func main() { diff --git a/rpcclient/examples/btcwalletwebsockets/main.go b/rpcclient/examples/btcwalletwebsockets/main.go index e803138d64..7f177e0b1f 100644 --- a/rpcclient/examples/btcwalletwebsockets/main.go +++ b/rpcclient/examples/btcwalletwebsockets/main.go @@ -11,7 +11,7 @@ import ( "time" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" ) diff --git a/rpcclient/extensions.go b/rpcclient/extensions.go index c8615293e3..efb6c1c710 100644 --- a/rpcclient/extensions.go +++ b/rpcclient/extensions.go @@ -15,7 +15,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // FutureDebugLevelResult is a future promise to deliver the result of a diff --git a/rpcclient/mining.go b/rpcclient/mining.go index 717825eeaa..680a63b6d5 100644 --- a/rpcclient/mining.go +++ b/rpcclient/mining.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // FutureGenerateResult is a future promise to deliver the result of a diff --git a/rpcclient/notify.go b/rpcclient/notify.go index 6879099acd..225af281cf 100644 --- a/rpcclient/notify.go +++ b/rpcclient/notify.go @@ -16,7 +16,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) var ( diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index cfc322c769..3643f2b0ca 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/rpcclient/wallet.go b/rpcclient/wallet.go index 71435d99f8..7268ad0a19 100644 --- a/rpcclient/wallet.go +++ b/rpcclient/wallet.go @@ -12,7 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // ***************************** diff --git a/rpcserver.go b/rpcserver.go index 6f60261a9c..8a4ecaac8d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -40,7 +40,7 @@ import ( "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/websocket" ) diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 356a897455..4d140b4825 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -27,7 +27,7 @@ import ( "github.com/btcsuite/btcd/database" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/websocket" "golang.org/x/crypto/ripemd160" ) diff --git a/server.go b/server.go index 746c48dde3..45bfb17ee4 100644 --- a/server.go +++ b/server.go @@ -36,8 +36,8 @@ import ( "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bloom" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bloom" ) const ( diff --git a/txscript/example_test.go b/txscript/example_test.go index 6e17341c4a..7983b08ff2 100644 --- a/txscript/example_test.go +++ b/txscript/example_test.go @@ -14,7 +14,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // This example demonstrates creating a script which pays to a bitcoin address. diff --git a/txscript/pkscript.go b/txscript/pkscript.go index f5b11e6d53..f8aa783aef 100644 --- a/txscript/pkscript.go +++ b/txscript/pkscript.go @@ -8,7 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" "golang.org/x/crypto/ripemd160" ) diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 9d9c8f39ed..3d4c591427 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -17,7 +17,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // scriptTestName returns a descriptive test name for the given reference script diff --git a/txscript/sign.go b/txscript/sign.go index 138e31cdd4..84d8d1f3ea 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -11,7 +11,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // RawTxInWitnessSignature returns the serialized ECDA signature for the input diff --git a/txscript/sign_test.go b/txscript/sign_test.go index b97a8a64d4..47084f18bf 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) type addressToKey struct { diff --git a/txscript/standard.go b/txscript/standard.go index 44902971e9..343a9b2727 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -9,7 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 582d30eee4..809bf485d9 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcd/btcutil" ) // mustParseShortForm parses the passed short form script and returns the