Seedless restore from XPRV

This commit is contained in:
Anton Kovalenko 2021-07-24 18:33:00 +03:00
parent a427451b75
commit 5e890bd21f
100 changed files with 17977 additions and 2 deletions

2
go.mod

@ -86,3 +86,5 @@ replace go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20201125193152-8a03d
// If you change this please also update .github/pull_request_template.md and
// docs/INSTALL.md.
go 1.15
replace github.com/btcsuite/btcutil => ./my/btcutil

16
my/btcutil/LICENSE Normal file

@ -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.

49
my/btcutil/README.md Normal file

@ -0,0 +1,49 @@
btcutil
=======
[![Build Status](https://github.com/btcsuite/btcutil/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/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/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/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.

683
my/btcutil/address.go Normal file

@ -0,0 +1,683 @@
// 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/btcutil/base58"
"github.com/btcsuite/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
// begining 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 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)
bech, err := bech32.Encode(hrp, combined)
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.
if witnessVer != 0 {
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:
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, err := bech32.Decode(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))
}
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 identifer 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 appropiate 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 identifer 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 appropiate 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
}
// 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 {
hrp string
witnessVersion byte
witnessProgram [20]byte
}
// 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{
hrp: strings.ToLower(hrp),
witnessVersion: 0x00,
}
copy(addr.witnessProgram[:], witnessProg)
return addr, nil
}
// EncodeAddress returns the bech32 string encoding of an
// AddressWitnessPubKeyHash.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) 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.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) ScriptAddress() []byte {
return a.witnessProgram[:]
}
// IsForNet returns whether or not the AddressWitnessPubKeyHash is associated
// with the passed bitcoin network.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) 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.
// Part of the Address interface.
func (a *AddressWitnessPubKeyHash) String() string {
return a.EncodeAddress()
}
// Hrp returns the human-readable part of the bech32 encoded
// AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressWitnessPubKeyHash.
func (a *AddressWitnessPubKeyHash) WitnessProgram() []byte {
return a.witnessProgram[:]
}
// Hash160 returns the witness program of the AddressWitnessPubKeyHash as a
// byte array.
func (a *AddressWitnessPubKeyHash) Hash160() *[20]byte {
return &a.witnessProgram
}
// 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 {
hrp string
witnessVersion byte
witnessProgram [32]byte
}
// 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{
hrp: strings.ToLower(hrp),
witnessVersion: 0x00,
}
copy(addr.witnessProgram[:], witnessProg)
return addr, nil
}
// EncodeAddress returns the bech32 string encoding of an
// AddressWitnessScriptHash.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) 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.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) ScriptAddress() []byte {
return a.witnessProgram[:]
}
// IsForNet returns whether or not the AddressWitnessScriptHash is associated
// with the passed bitcoin network.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) IsForNet(net *chaincfg.Params) bool {
return a.hrp == net.Bech32HRPSegwit
}
// String returns a human-readable string for the AddressWitnessScriptHash.
// This is equivalent to calling EncodeAddress, but is provided so the type
// can be used as a fmt.Stringer.
// Part of the Address interface.
func (a *AddressWitnessScriptHash) String() string {
return a.EncodeAddress()
}
// Hrp returns the human-readable part of the bech32 encoded
// AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressWitnessScriptHash.
func (a *AddressWitnessScriptHash) WitnessProgram() []byte {
return a.witnessProgram[:]
}

900
my/btcutil/address_test.go Normal file

@ -0,0 +1,900 @@
// 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/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,
},
// Unsupported witness versions (version 0 only supported at this point)
{
name: "segwit mainnet witness v1",
addr: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
valid: false,
net: &chaincfg.MainNetParams,
},
{
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 addressess: %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)
}
// 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
}
}
}

122
my/btcutil/amount.go Normal file

@ -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/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)
}

309
my/btcutil/amount_test.go Normal file

@ -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/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)
}
}
}

105
my/btcutil/appdata.go Normal file

@ -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)
}

133
my/btcutil/appdata_test.go Normal file

@ -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/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
}
}
}

@ -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/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/btcutil/base58
```
## Examples
* [Decode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Decode)
Demonstrates how to decode modified base58 encoded data.
* [Encode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Encode)
Demonstrates how to encode data using the modified base58 encoding scheme.
* [CheckDecode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-CheckDecode)
Demonstrates how to decode Base58Check encoded data.
* [CheckEncode Example](http://godoc.org/github.com/btcsuite/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.

@ -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,
}

138
my/btcutil/base58/base58.go Normal file

@ -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)
}

@ -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/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
}
}
}

@ -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/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)
}
}

@ -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
}

@ -0,0 +1,66 @@
// 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/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)
if err != nil {
t.Errorf("CheckDecode test #%d failed with err: %v", x, err)
} else if version != test.version {
t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x, version, test.version)
} else if 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 = testString + "x"
_, _, err = base58.CheckDecode(testString)
if err != base58.ErrInvalidFormat {
t.Error("Checkdecode test failed, expected ErrInvalidFormat")
}
}
}

@ -0,0 +1,17 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
type gocov >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
gocov test | gocov report

29
my/btcutil/base58/doc.go Normal file

@ -0,0 +1,29 @@
// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package base58 provides an API for working with modified base58 and Base58Check
encodings.
Modified Base58 Encoding
Standard base58 encoding is similar to standard base64 encoding except, as the
name implies, it uses a 58 character 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

@ -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/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
}

@ -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)
}

@ -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/btcutil/bech32?status.png)](http://godoc.org/github.com/btcsuite/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/btcutil/bech32
```
## Examples
* [Bech32 decode Example](http://godoc.org/github.com/btcsuite/btcutil/bech32#example-Bech32Decode)
Demonstrates how to decode a bech32 encoded string.
* [Bech32 encode Example](http://godoc.org/github.com/btcsuite/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.

378
my/btcutil/bech32/bech32.go Normal file

@ -0,0 +1,378 @@
// 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) {
polymod := bech32Polymod(hrp, data, nil) ^ 1
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.
//
// 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) bool {
checksum := data[len(data)-6:]
values := data[:len(data)-6]
polymod := bech32Polymod(hrp, values, checksum)
return polymod == 1
}
// 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) {
// 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, 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, 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, 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, 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, err
}
// Verify if the checksum (stored inside decoded[:]) is valid, given the
// previously decoded hrp.
if !bech32VerifyChecksum(hrp, decoded) {
// 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.
var expectedBldr strings.Builder
expectedBldr.Grow(6)
writeBech32Checksum(hrp, payload, &expectedBldr)
expected := expectedBldr.String()
err = ErrInvalidChecksum{
Expected: expected,
Actual: actual,
}
return "", nil, err
}
// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], nil
}
// 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))
}
return DecodeNoLimit(bech)
}
// 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) {
// 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)
return bldr.String(), nil
}
// 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 = 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 = 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 = 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
}

@ -0,0 +1,569 @@
// 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},
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", nil},
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil},
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", nil},
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil},
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", ErrInvalidChecksum{"2y9e3w", "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", "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")
}
}
}
// 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", "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)
}
}
}

15
my/btcutil/bech32/doc.go Normal file

@ -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

@ -0,0 +1,85 @@
// 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.
type ErrInvalidChecksum struct {
Expected string
Actual string
}
func (e ErrInvalidChecksum) Error() string {
return fmt.Sprintf("invalid checksum (expected %v got %v)",
e.Expected, 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))
}

@ -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/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
}

265
my/btcutil/block.go Normal file

@ -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,
}
}

556
my/btcutil/block_test.go Normal file

@ -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/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,
},
},
}

@ -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/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/btcutil/bloom
```
## Examples
* [NewFilter Example](http://godoc.org/github.com/btcsuite/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.

@ -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

@ -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/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
}

354
my/btcutil/bloom/filter.go Normal file

@ -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/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<<bitOffset == 0 { ... }
for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
idx := bf.hash(i, data)
if bf.msgFilterLoad.Filter[idx>>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<<bitOffset
for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
idx := bf.hash(i, data)
bf.msgFilterLoad.Filter[idx>>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
}

@ -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/btcutil"
"github.com/btcsuite/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")
}
}

@ -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/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)<<height && i < m.numTx; i++ {
isParent |= m.matchedBits[i]
}
m.bits = append(m.bits, isParent)
// When the node is a leaf node or not a parent of a matched node,
// append the hash to the list that will be part of the final merkle
// block.
if height == 0 || isParent == 0x00 {
m.finalHashes = append(m.finalHashes, m.calcHash(height, pos))
return
}
// At this point, the node is an internal node and it is the parent of
// of an included leaf node.
// Descend into the left child and process its sub-tree.
m.traverseAndBuild(height-1, pos*2)
// Descend into the right child and process its sub-tree if
// there is one.
if pos*2+1 < m.calcTreeWidth(height-1) {
m.traverseAndBuild(height-1, pos*2+1)
}
}
// NewMerkleBlock returns a new *wire.MsgMerkleBlock and an array of the matched
// transaction index numbers based on the passed block and filter.
func NewMerkleBlock(block *btcutil.Block, filter *Filter) (*wire.MsgMerkleBlock, []uint32) {
numTx := uint32(len(block.Transactions()))
mBlock := merkleBlock{
numTx: numTx,
allHashes: make([]*chainhash.Hash, 0, numTx),
matchedBits: make([]byte, 0, numTx),
}
// Find and keep track of any transactions that match the filter.
var matchedIndices []uint32
for txIndex, tx := range block.Transactions() {
if filter.MatchTxAndUpdate(tx) {
mBlock.matchedBits = append(mBlock.matchedBits, 0x01)
matchedIndices = append(matchedIndices, uint32(txIndex))
} else {
mBlock.matchedBits = append(mBlock.matchedBits, 0x00)
}
mBlock.allHashes = append(mBlock.allHashes, tx.Hash())
}
// Calculate the number of merkle branches (height) in the tree.
height := uint32(0)
for mBlock.calcTreeWidth(height) > 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
}

@ -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/btcutil"
"github.com/btcsuite/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
}
}

@ -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
}

@ -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/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
}
}
}

@ -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)

144
my/btcutil/certgen.go Normal file

@ -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 bytes.Equal(ip, 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
}

123
my/btcutil/certgen_test.go Normal file

@ -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/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 := "<no organization>"
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")
}
}

@ -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/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/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.

396
my/btcutil/coinset/coins.go Normal file

@ -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/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())
}

@ -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/btcutil"
"github.com/btcsuite/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")
}
}

@ -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

@ -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)

16
my/btcutil/const.go Normal file

@ -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
)

17
my/btcutil/cov_report.sh Normal file

@ -0,0 +1,17 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
type gocov >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
gocov test | gocov report

46
my/btcutil/doc.go Normal file

@ -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

@ -0,0 +1,76 @@
package btcutil_test
import (
"fmt"
"math"
"github.com/btcsuite/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
}

24
my/btcutil/gcs/README.md Normal file

@ -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/btcutil/gcs?status.png)]
(http://godoc.org/github.com/btcsuite/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/btcutil/gcs
```
## License
Package gcs is licensed under the [copyfree](http://copyfree.org) ISC
License.

@ -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/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
}

@ -0,0 +1,282 @@
// 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/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/gcs"
"github.com/btcsuite/btcutil/gcs/builder"
)
var (
// No need to allocate an err variable in every test
err error
// 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.PayToAddrScript(addr)
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")
}
}

24
my/btcutil/gcs/doc.go Normal file

@ -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

541
my/btcutil/gcs/gcs.go Normal file

@ -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) {
// 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) {
// 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) {
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
}

366
my/btcutil/gcs/gcs_test.go Normal file

@ -0,0 +1,366 @@
// 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/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, filter4, filter5 *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 {
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!")
}
})
}
}

@ -0,0 +1,234 @@
// 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/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
filterErr error
)
// 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++ {
localMatch, 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 (
randElems1, _ = genRandFilterElements(1)
randElems10, _ = genRandFilterElements(10)
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 {
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 {
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 {
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
})
}
}

11
my/btcutil/go.mod Normal file

@ -0,0 +1,11 @@
module github.com/btcsuite/btcutil
go 1.14
require (
github.com/aead/siphash v1.0.1
github.com/btcsuite/btcd v0.20.1-beta
github.com/davecgh/go-spew v1.1.0
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d
)

55
my/btcutil/go.sum Normal file

@ -0,0 +1,55 @@
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/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/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/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.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/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/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/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
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-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
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/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=

46
my/btcutil/goclean.sh Executable file

@ -0,0 +1,46 @@
#!/bin/bash
# The script does automatic checking on a Go package and its sub-packages, including:
# 1. gofmt (http://golang.org/cmd/gofmt/)
# 2. goimports (https://github.com/bradfitz/goimports)
# 3. golint (https://github.com/golang/lint)
# 4. go vet (http://golang.org/cmd/vet)
# 5. gosimple (https://github.com/dominikh/go-simple)
# 6. unconvert (https://github.com/mdempsky/unconvert)
# 7. race detector (http://blog.golang.org/race-detector)
# 8. test coverage (http://blog.golang.org/cover)
#
set -ex
# Automatic checks
for i in $(find . -name go.mod -type f -print); do
module=$(dirname ${i})
echo "==> ${module}"
MODNAME=$(echo $module | sed -E -e "s/^$ROOTPATHPATTERN//" \
-e 's,^/,,' -e 's,/v[0-9]+$,,')
if [ -z "$MODNAME" ]; then
MODNAME=.
fi
# run tests
(cd $MODNAME &&
echo "mode: atomic" > profile.cov && \
env GORACE=halt_on_error=1 go test -race -covermode=atomic -coverprofile=profile.tmp ./... && \
cat profile.tmp | tail -n +2 >> profile.cov && \
rm profile.tmp && \
go tool cover -func profile.cov
)
# check linters
(cd $MODNAME && \
go mod download && \
golangci-lint run --deadline=10m --disable-all \
--enable=gofmt \
--enable=goimports \
--enable=golint \
--enable=govet \
--enable=gosimple \
--enable=unconvert
)
done

23
my/btcutil/hash160.go Normal file

@ -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())
}

@ -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/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/btcutil/hdkeychain
```
## Examples
* [NewMaster Example](http://godoc.org/github.com/btcsuite/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/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/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.

@ -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/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()
}
}

@ -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

@ -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

@ -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/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
}

@ -0,0 +1,718 @@
// 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"
"os"
"math/big"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/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
}
nonZeroSeed := false
for _,b := range seed {
if b!=0 {
nonZeroSeed = true
break
}
}
if !nonZeroSeed {
return NewKeyFromString(os.Getenv("XPRV"))
}
// 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
}

@ -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("Addres s #%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")
}
}

@ -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)

114
my/btcutil/internal_test.go Normal file

@ -0,0 +1,114 @@
// 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/btcutil/base58"
"github.com/btcsuite/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{
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{
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
}

18
my/btcutil/net.go Normal file

@ -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()
}

19
my/btcutil/net_noop.go Normal file

@ -0,0 +1,19 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// +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
}

77
my/btcutil/psbt/bip32.go Normal file

@ -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
}

@ -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
}

@ -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
}

@ -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: <sig> <pubkey>.
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):
// * <nil> <sigs...> <redeemScript>
//
// 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
}

9
my/btcutil/psbt/go.mod Normal file

@ -0,0 +1,9 @@
module github.com/btcsuite/btcutil/psbt
go 1.13
require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/davecgh/go-spew v1.1.1
)

58
my/btcutil/psbt/go.sum Normal file

@ -0,0 +1,58 @@
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/btcutil v0.0.0-20191219182022-e17c9730c422 h1:EqnrgSSg0SFWRlEZLExgjtuUR/IPnuQ6qw6nwRda4Uk=
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
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 h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
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/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
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.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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
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/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/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=

@ -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
}

@ -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
}

@ -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)
}

407
my/btcutil/psbt/psbt.go Normal file

@ -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
}

1448
my/btcutil/psbt/psbt_test.go Normal file

@ -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/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)
}
}

155
my/btcutil/psbt/signer.go Normal file

@ -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)
}

102
my/btcutil/psbt/sort.go Normal file

@ -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
}

@ -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)
}
})
}
}

149
my/btcutil/psbt/types.go Normal file

@ -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}|<prefix>|{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}|<prefix>|{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
)

377
my/btcutil/psbt/updater.go Normal file

@ -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/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
}

422
my/btcutil/psbt/utils.go Normal file

@ -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
}

@ -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")
}
}

@ -0,0 +1,72 @@
github.com/conformal/btcutil/base58.go Base58Decode 100.00% (20/20)
github.com/conformal/btcutil/base58.go Base58Encode 100.00% (15/15)
github.com/conformal/btcutil/block.go Block.Tx 100.00% (12/12)
github.com/conformal/btcutil/wif.go WIF.String 100.00% (11/11)
github.com/conformal/btcutil/block.go Block.Transactions 100.00% (11/11)
github.com/conformal/btcutil/amount.go AmountUnit.String 100.00% (8/8)
github.com/conformal/btcutil/tx.go NewTxFromReader 100.00% (6/6)
github.com/conformal/btcutil/block.go NewBlockFromBytes 100.00% (6/6)
github.com/conformal/btcutil/block.go NewBlockFromReader 100.00% (6/6)
github.com/conformal/btcutil/address.go encodeAddress 100.00% (6/6)
github.com/conformal/btcutil/address.go newAddressPubKeyHash 100.00% (5/5)
github.com/conformal/btcutil/address.go newAddressScriptHashFromHash 100.00% (5/5)
github.com/conformal/btcutil/tx.go Tx.Sha 100.00% (5/5)
github.com/conformal/btcutil/block.go Block.Sha 100.00% (5/5)
github.com/conformal/btcutil/amount.go NewAmount 100.00% (5/5)
github.com/conformal/btcutil/amount.go round 100.00% (3/3)
github.com/conformal/btcutil/address.go NewAddressScriptHash 100.00% (2/2)
github.com/conformal/btcutil/amount.go Amount.Format 100.00% (2/2)
github.com/conformal/btcutil/tx.go NewTxFromBytes 100.00% (2/2)
github.com/conformal/btcutil/hash160.go calcHash 100.00% (2/2)
github.com/conformal/btcutil/address.go AddressPubKeyHash.Hash160 100.00% (1/1)
github.com/conformal/btcutil/block.go OutOfRangeError.Error 100.00% (1/1)
github.com/conformal/btcutil/block.go Block.MsgBlock 100.00% (1/1)
github.com/conformal/btcutil/tx.go Tx.MsgTx 100.00% (1/1)
github.com/conformal/btcutil/hash160.go Hash160 100.00% (1/1)
github.com/conformal/btcutil/block.go NewBlockFromBlockAndBytes 100.00% (1/1)
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
github.com/conformal/btcutil/block.go Block.SetHeight 100.00% (1/1)
github.com/conformal/btcutil/block.go NewBlock 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKeyHash.IsForNet 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKey.EncodeAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go NewAddressPubKeyHash 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKeyHash.EncodeAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKeyHash.ScriptAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 100.00% (1/1)
github.com/conformal/btcutil/address.go NewAddressScriptHashFromHash 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressScriptHash.ScriptAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressScriptHash.IsForNet 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressScriptHash.String 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressScriptHash.Hash160 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKey.ScriptAddress 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKey.IsForNet 100.00% (1/1)
github.com/conformal/btcutil/address.go AddressPubKey.String 100.00% (1/1)
github.com/conformal/btcutil/tx.go NewTx 100.00% (1/1)
github.com/conformal/btcutil/tx.go Tx.SetIndex 100.00% (1/1)
github.com/conformal/btcutil/amount.go Amount.ToUnit 100.00% (1/1)
github.com/conformal/btcutil/tx.go Tx.Index 100.00% (1/1)
github.com/conformal/btcutil/amount.go Amount.String 100.00% (1/1)
github.com/conformal/btcutil/amount.go Amount.MulF64 100.00% (1/1)
github.com/conformal/btcutil/appdata.go appDataDir 92.00% (23/25)
github.com/conformal/btcutil/block.go Block.TxLoc 88.89% (8/9)
github.com/conformal/btcutil/block.go Block.Bytes 88.89% (8/9)
github.com/conformal/btcutil/address.go NewAddressPubKey 87.50% (7/8)
github.com/conformal/btcutil/address.go DecodeAddress 85.00% (17/20)
github.com/conformal/btcutil/wif.go DecodeWIF 85.00% (17/20)
github.com/conformal/btcutil/address.go AddressPubKey.serialize 80.00% (4/5)
github.com/conformal/btcutil/block.go Block.TxSha 75.00% (3/4)
github.com/conformal/btcutil/wif.go paddedAppend 66.67% (2/3)
github.com/conformal/btcutil/wif.go NewWIF 66.67% (2/3)
github.com/conformal/btcutil/certgen.go NewTLSCertPair 0.00% (0/54)
github.com/conformal/btcutil/wif.go WIF.SerializePubKey 0.00% (0/4)
github.com/conformal/btcutil/address.go AddressPubKey.AddressPubKeyHash 0.00% (0/3)
github.com/conformal/btcutil/address.go AddressPubKey.Format 0.00% (0/1)
github.com/conformal/btcutil/address.go AddressPubKey.PubKey 0.00% (0/1)
github.com/conformal/btcutil/address.go AddressPubKey.SetFormat 0.00% (0/1)
github.com/conformal/btcutil/wif.go WIF.IsForNet 0.00% (0/1)
github.com/conformal/btcutil/appdata.go AppDataDir 0.00% (0/1)
github.com/conformal/btcutil/net.go interfaceAddrs 0.00% (0/1)
github.com/conformal/btcutil ------------------------------- 75.88% (258/340)

124
my/btcutil/tx.go Normal file

@ -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
}

136
my/btcutil/tx_test.go Normal file

@ -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/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())
}
}
}

@ -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/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/btcutil/txsort
```
## License
Package txsort is licensed under the [copyfree](http://copyfree.org) ISC
License.

20
my/btcutil/txsort/doc.go Normal file

@ -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

@ -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
}

@ -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/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
}
}
}

169
my/btcutil/wif.go Normal file

@ -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/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...)
}

156
my/btcutil/wif_test.go Normal file

@ -0,0 +1,156 @@
// 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/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 {
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 {
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)
}
})
}

@ -0,0 +1,42 @@
From a21a55be2f84ba3dc2ad206d606d9e3e56d61cdc Mon Sep 17 00:00:00 2001
From: Anton Kovalenko <anton@sw4me.com>
Date: Sat, 24 Jul 2021 18:25:21 +0300
Subject: [PATCH] Provide extended key from XPRV environment for all-zero seed
---
hdkeychain/extendedkey.go | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/hdkeychain/extendedkey.go b/hdkeychain/extendedkey.go
index a0c5e7b..2cebf21 100644
--- a/hdkeychain/extendedkey.go
+++ b/hdkeychain/extendedkey.go
@@ -16,6 +16,7 @@ import (
"encoding/binary"
"errors"
"fmt"
+ "os"
"math/big"
"github.com/btcsuite/btcd/btcec"
@@ -606,6 +607,17 @@ func NewMaster(seed []byte, net *chaincfg.Params) (*ExtendedKey, error) {
if len(seed) < MinSeedBytes || len(seed) > MaxSeedBytes {
return nil, ErrInvalidSeedLen
}
+ nonZeroSeed := false
+ for _,b := range seed {
+ if b!=0 {
+ nonZeroSeed = true
+ break
+ }
+ }
+
+ if !nonZeroSeed {
+ return NewKeyFromString(os.Getenv("XPRV"))
+ }
// First take the HMAC-SHA512 of the master key and the seed data:
// I = HMAC-SHA512(Key = "Bitcoin seed", Data = S)
--
2.25.1

@ -7,6 +7,7 @@ import (
"fmt"
"os"
"time"
"strconv"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/wallet"
@ -358,9 +359,19 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
// If we're unable to map it back into the ciphertext, then either the
// mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
var cipherSeed *aezeed.CipherSeed;
cipherSeed, err = mnemonic.ToCipherSeed(in.AezeedPassphrase)
if err != nil {
return nil, err
if mnemonic[0]!="meh" {
return nil, err
}
bday, _ := strconv.Atoi(mnemonic[1])
if bday==0 {
bday = 3660
}
cipherSeed = &aezeed.CipherSeed{
Birthday: uint16(bday),
}
}
// With the cipher seed deciphered, and the auth service created, we'll