Merge remaining dcrutil code into a dcrd package.

This merge commit adds the following code from the
github.com/decred/dcrutil package into a new
github.com/decred/dcrd/dcrutil package:

* Address handling
* Amount type
* AppDataDir func
* bitflags functions
* Block wrapper type
* Hash160 func
* Tx wrapper type
* WIF type

as well as all tests for this code.

The old github.com/decred/dcrutil/hdkeychain package has also been
merged and moved to github.com/decred/dcrd/dcrutil/hdkeychain.

dcrd packages have been updated to use the new packages and the dep
files have been updated for this change.
This commit is contained in:
Josh Rickmar 2017-10-11 21:31:01 -04:00
commit 6842aa006d
113 changed files with 6105 additions and 97 deletions

12
Gopkg.lock generated
View File

@ -57,15 +57,15 @@
[[projects]]
branch = "master"
name = "github.com/decred/bitset"
name = "github.com/decred/base58"
packages = ["."]
revision = "484b833245d5f9046e2893a6bd2e54b1df3a53a4"
revision = "b3520e187fa8ebe65eb74245408cf4b83e6a65d3"
[[projects]]
branch = "master"
name = "github.com/decred/dcrutil"
packages = [".","base58","hdkeychain"]
revision = "ddbde93f65ab0692e54ed8a5ad325fa2e8af4daa"
name = "github.com/decred/bitset"
packages = ["."]
revision = "484b833245d5f9046e2893a6bd2e54b1df3a53a4"
[[projects]]
name = "github.com/jessevdk/go-flags"
@ -94,6 +94,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "9f7191295caf3e7930cd45f5b3a0c5f4b84611c4cfdcf8c6cc042fcf84c3e904"
inputs-digest = "51999d250cc8d4082114b01f3bd8ea4887cd823769dbd41c3ed4c7eeb6c821e5"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -32,11 +32,11 @@
[[constraint]]
branch = "master"
name = "github.com/decred/bitset"
name = "github.com/decred/base58"
[[constraint]]
branch = "master"
name = "github.com/decred/dcrutil"
name = "github.com/decred/bitset"
[[constraint]]
name = "github.com/jessevdk/go-flags"

View File

@ -13,8 +13,8 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrutil"
)
// checkCoinbaseUniqueHeight checks to ensure that for all blocks height > 1

View File

@ -17,9 +17,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -16,7 +16,7 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// cloneParams returns a deep copy of the provided parameters so the caller is

View File

@ -17,9 +17,9 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -19,8 +19,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -11,8 +11,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrutil"
)
// CheckpointConfirmations is the number of blocks before the end of the current

View File

@ -15,7 +15,7 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// This example demonstrates how to create a new chain instance and use

View File

@ -13,8 +13,8 @@ import (
"github.com/decred/dcrd/blockchain/fullblocktests"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// TestFullBlocks ensures all tests generated by the fullblocktests package

View File

@ -11,7 +11,7 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/chaingen"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// TestStakeVersion ensures that the stake version field in the block header is

View File

@ -18,9 +18,9 @@ import (
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -16,9 +16,9 @@ import (
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -13,7 +13,7 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/database"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
var (

View File

@ -11,9 +11,9 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -15,8 +15,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -12,8 +12,8 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -11,8 +11,8 @@ import (
"github.com/btcsuite/btclog"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// BlockProgressLogger provides periodic logging for other services in order

View File

@ -9,7 +9,7 @@ import (
"math"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// nextPowerOfTwo returns the next highest power of two from a given number if

View File

@ -9,7 +9,7 @@ import (
"fmt"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// NotificationType represents the type of a notification message.

View File

@ -11,7 +11,7 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when

View File

@ -16,7 +16,7 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// reorgTestLong does a single, large reorganization.

View File

@ -10,9 +10,9 @@ import (
"math"
"runtime"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// txValidateItem holds a transaction along with which input to validate.

View File

@ -8,8 +8,8 @@ import (
"fmt"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// SequenceLock represents the minimum timestamp and minimum block height after

View File

@ -10,8 +10,8 @@ import (
"time"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// mustLockTimeToSeq converts the passed relative lock time to a sequence number

View File

@ -18,9 +18,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// TxType indicates the type of tx (regular or stake type).

View File

@ -13,9 +13,9 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// SSTX TESTING -------------------------------------------------------------------

View File

@ -19,7 +19,7 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
const (

View File

@ -8,8 +8,8 @@ package blockchain
import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrutil"
)
// NextLotteryData returns the next tickets eligible for spending as SSGen

View File

@ -12,9 +12,9 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// The number of values to precalculate on initialization of the subsidy

View File

@ -13,7 +13,7 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/chaingen"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
const (

View File

@ -11,8 +11,8 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrutil"
)
// StakeViewpoint is the viewpoint of the blockchain depending on stake

View File

@ -16,9 +16,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -19,9 +19,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// recalculateMsgBlockMerkleRootsSize recalculates the merkle roots for a msgBlock,

View File

@ -5,7 +5,7 @@ import (
"time"
"github.com/btcsuite/btclog"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// blockProgressLogger provides periodic logging for other services in order

View File

@ -21,9 +21,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mempool"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -11,9 +11,9 @@ import (
"sync"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// ln2Squared is simply the square of the natural log of 2.

View File

@ -12,8 +12,8 @@ import (
"github.com/decred/dcrd/bloom"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// TestFilterLarge ensures a maximum sized filter can be created.

View File

@ -8,8 +8,8 @@ package bloom
import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// merkleBlock is used to house intermediate information needed to generate a

View File

@ -12,8 +12,8 @@ import (
"github.com/decred/dcrd/bloom"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
func TestMerkleBlock3(t *testing.T) {

View File

@ -24,7 +24,7 @@ import (
"fmt"
"log"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/chaincfg"
)

View File

@ -25,7 +25,7 @@
// "fmt"
// "log"
//
// "github.com/decred/dcrutil"
// "github.com/decred/dcrd/dcrutil"
// "github.com/decred/dcrd/chaincfg"
// )
//

View File

@ -13,8 +13,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -16,8 +16,8 @@ import (
"github.com/decred/dcrd/blockchain/indexers"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var zeroHash = chainhash.Hash{}

View File

@ -11,7 +11,7 @@ import (
"path/filepath"
"strings"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -15,7 +15,7 @@ import (
"strings"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -13,8 +13,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -15,7 +15,7 @@ import (
"time"
"github.com/decred/dcrd/certgen"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -25,9 +25,9 @@ import (
"github.com/decred/dcrd/connmgr"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mempool"
"github.com/decred/dcrd/sampleconfig"
"github.com/decred/dcrutil"
flags "github.com/jessevdk/go-flags"
)

View File

@ -15,9 +15,9 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mining"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -14,7 +14,7 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
var (

View File

@ -15,8 +15,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// importCmd defines the configuration options for the insecureimport command.

View File

@ -14,8 +14,8 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
_ "github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// This example demonstrates creating a new database.

View File

@ -12,7 +12,7 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// BenchmarkBlockHeader benchmarks how long it takes to load the mainnet genesis

View File

@ -25,8 +25,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/database/internal/treap"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -16,7 +16,7 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/database/ffldb"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// dbType is the database type name for this driver.

View File

@ -27,8 +27,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -22,8 +22,8 @@ import (
"github.com/btcsuite/goleveldb/leveldb"
ldberrors "github.com/btcsuite/goleveldb/leveldb/errors"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -10,7 +10,7 @@ package database
import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// Cursor represents a cursor over key/value pairs and nested buckets of a

26
dcrutil/README.md Normal file
View File

@ -0,0 +1,26 @@
dcrutil
=======
[![Build Status](http://img.shields.io/travis/decred/dcrd.svg)](https://travis-ci.org/decred/dcrd)
[![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/decred/dcrd/dcrutil)
Package dcrutil provides decred-specific convenience functions and types.
A comprehensive suite of tests is provided to ensure proper functionality.
This package was developed for dcrd, a full-node implementation of Decred which
is under active development by Company 0. Although it was primarily written for
dcrd, 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/decred/dcrd/dcrutil
```
## License
Package dcrutil is licensed under the [copyfree](http://copyfree.org) ISC
License.

756
dcrutil/address.go Normal file
View File

@ -0,0 +1,756 @@
// Copyright (c) 2013, 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"errors"
"fmt"
"golang.org/x/crypto/ripemd160"
"github.com/decred/base58"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
)
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")
// ErrMissingDefaultNet describes an error in DecodeAddress that
// attempts to decode an address without defining which network to decode
// for.
ErrMissingDefaultNet = errors.New("default net not defined")
)
// encodeAddress returns a human-readable payment address given a ripemd160 hash
// and netID which encodes the 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 [2]byte) string {
// Format is 2 bytes 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)
}
// encodePKAddress returns a human-readable payment address to a public key
// given a serialized public key, a netID, and a signature suite.
func encodePKAddress(serializedPK []byte, netID [2]byte, algo int) string {
pubKeyBytes := []byte{0x00}
switch algo {
case chainec.ECTypeSecp256k1:
pubKeyBytes[0] = byte(chainec.ECTypeSecp256k1)
case chainec.ECTypeEdwards:
pubKeyBytes[0] = byte(chainec.ECTypeEdwards)
case chainec.ECTypeSecSchnorr:
pubKeyBytes[0] = byte(chainec.ECTypeSecSchnorr)
}
// Pubkeys are encoded as [0] = type/ybit, [1:33] = serialized pubkey
compressed := serializedPK
if algo == chainec.ECTypeSecp256k1 || algo == chainec.ECTypeSecSchnorr {
pub, err := chainec.Secp256k1.ParsePubKey(serializedPK)
if err != nil {
return ""
}
pubSerComp := pub.SerializeCompressed()
// Set the y-bit if needed.
if pubSerComp[0] == 0x03 {
pubKeyBytes[0] |= (1 << 7)
}
compressed = pubSerComp[1:]
}
pubKeyBytes = append(pubKeyBytes, compressed...)
return base58.CheckEncode(pubKeyBytes, netID)
}
// 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
// Hash160 returns the Hash160(data) where data is the data normally
// hashed to 160 bits from the respective address type.
Hash160() *[ripemd160.Size]byte
// IsForNet returns whether or not the address is associated with the
// passed network.
IsForNet(*chaincfg.Params) bool
// DSA returns the digital signature algorithm for the address.
DSA(*chaincfg.Params) int
// Net returns the network parameters of the address.
Net() *chaincfg.Params
}
// NewAddressPubKey returns a new Address. decoded must
// be 33 bytes.
func NewAddressPubKey(decoded []byte, net *chaincfg.Params) (Address, error) {
if len(decoded) == 33 {
// First byte is the signature suite and ybit.
suite := decoded[0]
suite &= ^uint8(1 << 7)
ybit := !(decoded[0]&(1<<7) == 0)
toAppend := uint8(0x02)
if ybit {
toAppend = 0x03
}
switch int(suite) {
case chainec.ECTypeSecp256k1:
return NewAddressSecpPubKey(
append([]byte{toAppend}, decoded[1:]...),
net)
case chainec.ECTypeEdwards:
return NewAddressEdwardsPubKey(decoded, net)
case chainec.ECTypeSecSchnorr:
return NewAddressSecSchnorrPubKey(
append([]byte{toAppend}, decoded[1:]...),
net)
}
return nil, ErrUnknownAddressType
}
return nil, ErrUnknownAddressType
}
// DecodeAddress decodes the string encoding of an address and returns
// the Address if addr is a valid encoding for a known address type
func DecodeAddress(addr string) (Address, error) {
// 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, fmt.Errorf("decoded address is of unknown format: %v",
err.Error())
}
net, err := detectNetworkForAddress(addr)
if err != nil {
return nil, ErrUnknownAddressType
}
switch netID {
case net.PubKeyAddrID:
return NewAddressPubKey(decoded, net)
case net.PubKeyHashAddrID:
return NewAddressPubKeyHash(decoded, net, chainec.ECTypeSecp256k1)
case net.PKHEdwardsAddrID:
return NewAddressPubKeyHash(decoded, net, chainec.ECTypeEdwards)
case net.PKHSchnorrAddrID:
return NewAddressPubKeyHash(decoded, net, chainec.ECTypeSecSchnorr)
case net.ScriptHashAddrID:
return NewAddressScriptHashFromHash(decoded, net)
default:
return nil, ErrUnknownAddressType
}
}
// detectNetworkForAddress pops the first character from a string encoded
// address and detects what network type it is for.
func detectNetworkForAddress(addr string) (*chaincfg.Params, error) {
if len(addr) < 1 {
return nil, fmt.Errorf("empty string given for network detection")
}
networkChar := addr[0:1]
switch networkChar {
case chaincfg.MainNetParams.NetworkAddressPrefix:
return &chaincfg.MainNetParams, nil
case chaincfg.TestNet2Params.NetworkAddressPrefix:
return &chaincfg.TestNet2Params, nil
case chaincfg.SimNetParams.NetworkAddressPrefix:
return &chaincfg.SimNetParams, nil
}
return nil, fmt.Errorf("unknown network type in string encoded address")
}
// AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH)
// transaction.
type AddressPubKeyHash struct {
net *chaincfg.Params
hash [ripemd160.Size]byte
netID [2]byte
}
// NewAddressPubKeyHash returns a new AddressPubKeyHash. pkHash must
// be 20 bytes.
func NewAddressPubKeyHash(pkHash []byte, net *chaincfg.Params,
algo int) (*AddressPubKeyHash, error) {
var addrID [2]byte
switch algo {
case chainec.ECTypeSecp256k1:
addrID = net.PubKeyHashAddrID
case chainec.ECTypeEdwards:
addrID = net.PKHEdwardsAddrID
case chainec.ECTypeSecSchnorr:
addrID = net.PKHSchnorrAddrID
default:
return nil, errors.New("unknown ECDSA algorithm")
}
apkh, err := newAddressPubKeyHash(pkHash, addrID)
if err != nil {
return nil, err
}
apkh.net = net
return apkh, nil
}
// 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 [2]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 network.
func (a *AddressPubKeyHash) IsForNet(net *chaincfg.Params) bool {
return a.netID == net.PubKeyHashAddrID ||
a.netID == net.PKHEdwardsAddrID ||
a.netID == net.PKHSchnorrAddrID
}
// 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
}
// DSA returns the digital signature algorithm for the associated public key
// hash.
func (a *AddressPubKeyHash) DSA(net *chaincfg.Params) int {
switch a.netID {
case net.PubKeyHashAddrID:
return chainec.ECTypeSecp256k1
case net.PKHEdwardsAddrID:
return chainec.ECTypeEdwards
case net.PKHSchnorrAddrID:
return chainec.ECTypeSecSchnorr
}
return -1
}
// Net returns the network for the address.
func (a *AddressPubKeyHash) Net() *chaincfg.Params {
return a.net
}
// AddressScriptHash is an Address for a pay-to-script-hash (P2SH)
// transaction.
type AddressScriptHash struct {
net *chaincfg.Params
hash [ripemd160.Size]byte
netID [2]byte
}
// NewAddressScriptHash returns a new AddressScriptHash.
func NewAddressScriptHash(serializedScript []byte,
net *chaincfg.Params) (*AddressScriptHash, error) {
scriptHash := Hash160(serializedScript)
ash, err := newAddressScriptHashFromHash(scriptHash, net.ScriptHashAddrID)
if err != nil {
return nil, err
}
ash.net = net
return ash, nil
}
// NewAddressScriptHashFromHash returns a new AddressScriptHash. scriptHash
// must be 20 bytes.
func NewAddressScriptHashFromHash(scriptHash []byte,
net *chaincfg.Params) (*AddressScriptHash, error) {
ash, err := newAddressScriptHashFromHash(scriptHash, net.ScriptHashAddrID)
if err != nil {
return nil, err
}
ash.net = net
return ash, nil
}
// 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 [2]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 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
}
// DSA returns -1 (invalid) as the digital signature algorithm for scripts,
// as scripts may not involve digital signatures at all.
func (a *AddressScriptHash) DSA(net *chaincfg.Params) int {
return -1
}
// Net returns the network for the address.
func (a *AddressScriptHash) Net() *chaincfg.Params {
return a.net
}
// 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
)
// AddressSecpPubKey is an Address for a secp256k1 pay-to-pubkey transaction.
type AddressSecpPubKey struct {
net *chaincfg.Params
pubKeyFormat PubKeyFormat
pubKey chainec.PublicKey
pubKeyHashID [2]byte
}
// NewAddressSecpPubKey returns a new AddressSecpPubKey which represents a
// pay-to-pubkey address, using a secp256k1 pubkey. The serializedPubKey
// parameter must be a valid pubkey and can be uncompressed, compressed, or
// hybrid.
func NewAddressSecpPubKey(serializedPubKey []byte,
net *chaincfg.Params) (*AddressSecpPubKey, error) {
pubKey, err := chainec.Secp256k1.ParsePubKey(serializedPubKey)
if err != nil {
return nil, err
}
// Set the format of the pubkey. This probably should be returned
// from dcrec, 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 &AddressSecpPubKey{
net: net,
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 *AddressSecpPubKey) 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 Decred addresses
// are pay-to-pubkey-hash constructed from the uncompressed public key.
//
// Part of the Address interface.
func (a *AddressSecpPubKey) 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 *AddressSecpPubKey) ScriptAddress() []byte {
return a.serialize()
}
// 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 *AddressSecpPubKey) Hash160() *[ripemd160.Size]byte {
h160 := Hash160(a.pubKey.SerializeCompressed())
array := new([ripemd160.Size]byte)
copy(array[:], h160)
return array
}
// IsForNet returns whether or not the pay-to-pubkey address is associated
// with the passed network.
func (a *AddressSecpPubKey) 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 *AddressSecpPubKey) String() string {
return encodePKAddress(a.serialize(), a.net.PubKeyAddrID,
chainec.ECTypeSecp256k1)
}
// Format returns the format (uncompressed, compressed, etc) of the
// pay-to-pubkey address.
func (a *AddressSecpPubKey) Format() PubKeyFormat {
return a.pubKeyFormat
}
// 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 Decred addresses
// are pay-to-pubkey-hash constructed from the uncompressed public key.
func (a *AddressSecpPubKey) AddressPubKeyHash() *AddressPubKeyHash {
addr := &AddressPubKeyHash{net: a.net, netID: a.pubKeyHashID}
copy(addr.hash[:], Hash160(a.serialize()))
return addr
}
// PubKey returns the underlying public key for the address.
func (a *AddressSecpPubKey) PubKey() chainec.PublicKey {
return a.pubKey
}
// DSA returns the underlying digital signature algorithm for the
// address.
func (a *AddressSecpPubKey) DSA(net *chaincfg.Params) int {
switch a.pubKeyHashID {
case net.PubKeyHashAddrID:
return chainec.ECTypeSecp256k1
case net.PKHSchnorrAddrID:
return chainec.ECTypeSecSchnorr
}
return -1
}
// Net returns the network for the address.
func (a *AddressSecpPubKey) Net() *chaincfg.Params {
return a.net
}
// NewAddressSecpPubKeyCompressed creates a new address using a compressed public key
func NewAddressSecpPubKeyCompressed(pubkey chainec.PublicKey, params *chaincfg.Params) (*AddressSecpPubKey, error) {
return NewAddressSecpPubKey(pubkey.SerializeCompressed(), params)
}
// AddressEdwardsPubKey is an Address for an Ed25519 pay-to-pubkey transaction.
type AddressEdwardsPubKey struct {
net *chaincfg.Params
pubKey chainec.PublicKey
pubKeyHashID [2]byte
}
// NewAddressEdwardsPubKey returns a new AddressEdwardsPubKey which represents a
// pay-to-pubkey address, using an Ed25519 pubkey. The serializedPubKey
// parameter must be a valid 32 byte serialized public key.
func NewAddressEdwardsPubKey(serializedPubKey []byte,
net *chaincfg.Params) (*AddressEdwardsPubKey, error) {
pubKey, err := chainec.Edwards.ParsePubKey(serializedPubKey)
if err != nil {
return nil, err
}
return &AddressEdwardsPubKey{
net: net,
pubKey: pubKey,
pubKeyHashID: net.PKHEdwardsAddrID,
}, nil
}
// serialize returns the serialization of the public key.
func (a *AddressEdwardsPubKey) serialize() []byte {
return a.pubKey.Serialize()
}
// EncodeAddress returns the string encoding of the public key as a
// pay-to-pubkey-hash.
//
// Part of the Address interface.
func (a *AddressEdwardsPubKey) 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 *AddressEdwardsPubKey) ScriptAddress() []byte {
return a.serialize()
}
// 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 *AddressEdwardsPubKey) Hash160() *[ripemd160.Size]byte {
h160 := Hash160(a.pubKey.Serialize())
array := new([ripemd160.Size]byte)
copy(array[:], h160)
return array
}
// IsForNet returns whether or not the pay-to-pubkey address is associated
// with the passed network.
func (a *AddressEdwardsPubKey) IsForNet(net *chaincfg.Params) bool {
return a.pubKeyHashID == net.PKHEdwardsAddrID
}
// String returns the hex-encoded human-readable string for the pay-to-pubkey
// address. This is not the same as calling EncodeAddress.
func (a *AddressEdwardsPubKey) String() string {
return encodePKAddress(a.serialize(), a.net.PubKeyAddrID,
chainec.ECTypeEdwards)
}
// AddressPubKeyHash returns the pay-to-pubkey address converted to a
// pay-to-pubkey-hash address.
func (a *AddressEdwardsPubKey) AddressPubKeyHash() *AddressPubKeyHash {
addr := &AddressPubKeyHash{net: a.net, netID: a.pubKeyHashID}
copy(addr.hash[:], Hash160(a.serialize()))
return addr
}
// PubKey returns the underlying public key for the address.
func (a *AddressEdwardsPubKey) PubKey() chainec.PublicKey {
return a.pubKey
}
// DSA returns the underlying digital signature algorithm for the
// address.
func (a *AddressEdwardsPubKey) DSA(net *chaincfg.Params) int {
return chainec.ECTypeEdwards
}
// Net returns the network for the address.
func (a *AddressEdwardsPubKey) Net() *chaincfg.Params {
return a.net
}
// AddressSecSchnorrPubKey is an Address for a secp256k1 pay-to-pubkey
// transaction.
type AddressSecSchnorrPubKey struct {
net *chaincfg.Params
pubKey chainec.PublicKey
pubKeyHashID [2]byte
}
// NewAddressSecSchnorrPubKey returns a new AddressSecpPubKey which represents a
// pay-to-pubkey address, using a secp256k1 pubkey. The serializedPubKey
// parameter must be a valid pubkey and can be uncompressed, compressed, or
// hybrid.
func NewAddressSecSchnorrPubKey(serializedPubKey []byte,
net *chaincfg.Params) (*AddressSecSchnorrPubKey, error) {
pubKey, err := chainec.SecSchnorr.ParsePubKey(serializedPubKey)
if err != nil {
return nil, err
}
return &AddressSecSchnorrPubKey{
net: net,
pubKey: pubKey,
pubKeyHashID: net.PKHSchnorrAddrID,
}, nil
}
// serialize returns the serialization of the public key according to the
// format associated with the address.
func (a *AddressSecSchnorrPubKey) serialize() []byte {
return a.pubKey.Serialize()
}
// 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 Decred addresses
// are pay-to-pubkey-hash constructed from the uncompressed public key.
//
// Part of the Address interface.
func (a *AddressSecSchnorrPubKey) 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 *AddressSecSchnorrPubKey) ScriptAddress() []byte {
return a.serialize()
}
// 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 *AddressSecSchnorrPubKey) Hash160() *[ripemd160.Size]byte {
h160 := Hash160(a.pubKey.Serialize())
array := new([ripemd160.Size]byte)
copy(array[:], h160)
return array
}
// IsForNet returns whether or not the pay-to-pubkey address is associated
// with the passed network.
func (a *AddressSecSchnorrPubKey) 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 *AddressSecSchnorrPubKey) String() string {
return encodePKAddress(a.serialize(), a.net.PubKeyAddrID,
chainec.ECTypeSecSchnorr)
}
// AddressPubKeyHash returns the pay-to-pubkey address converted to a
// pay-to-pubkey-hash address.
func (a *AddressSecSchnorrPubKey) AddressPubKeyHash() *AddressPubKeyHash {
addr := &AddressPubKeyHash{net: a.net, netID: a.pubKeyHashID}
copy(addr.hash[:], Hash160(a.serialize()))
return addr
}
// DSA returns the underlying digital signature algorithm for the
// address.
func (a *AddressSecSchnorrPubKey) DSA(net *chaincfg.Params) int {
return chainec.ECTypeSecSchnorr
}
// Net returns the network for the address.
func (a *AddressSecSchnorrPubKey) Net() *chaincfg.Params {
return a.net
}

579
dcrutil/address_test.go Normal file
View File

@ -0,0 +1,579 @@
// Copyright (c) 2013, 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"bytes"
"encoding/hex"
"fmt"
"reflect"
"testing"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"golang.org/x/crypto/ripemd160"
)
// invalidNet is an invalid network.
const invalidNet = wire.CurrencyNet(0xffffffff)
func TestAddresses(t *testing.T) {
tests := []struct {
name string
addr string
saddr string
encoded string
valid bool
result dcrutil.Address
f func() (dcrutil.Address, error)
net *chaincfg.Params
}{
// Positive P2PKH tests.
{
name: "mainnet p2pkh",
addr: "DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
encoded: "DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
valid: true,
result: dcrutil.TstAddressPubKeyHash(
[ripemd160.Size]byte{
0x27, 0x89, 0xd5, 0x8c, 0xfa, 0x09, 0x57, 0xd2, 0x06, 0xf0,
0x25, 0xc2, 0xaf, 0x05, 0x6f, 0xc8, 0xa7, 0x7c, 0xeb, 0xb0},
chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
pkHash := []byte{
0x27, 0x89, 0xd5, 0x8c, 0xfa, 0x09, 0x57, 0xd2, 0x06, 0xf0,
0x25, 0xc2, 0xaf, 0x05, 0x6f, 0xc8, 0xa7, 0x7c, 0xeb, 0xb0}
return dcrutil.NewAddressPubKeyHash(pkHash,
&chaincfg.MainNetParams, chainec.ECTypeSecp256k1)
},
net: &chaincfg.MainNetParams,
},
{
name: "mainnet p2pkh 2",
addr: "DsU7xcg53nxaKLLcAUSKyRndjG78Z2VZnX9",
encoded: "DsU7xcg53nxaKLLcAUSKyRndjG78Z2VZnX9",
valid: true,
result: dcrutil.TstAddressPubKeyHash(
[ripemd160.Size]byte{
0x22, 0x9e, 0xba, 0xc3, 0x0e, 0xfd, 0x6a, 0x69, 0xee, 0xc9,
0xc1, 0xa4, 0x8e, 0x04, 0x8b, 0x7c, 0x97, 0x5c, 0x25, 0xf2},
chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
pkHash := []byte{
0x22, 0x9e, 0xba, 0xc3, 0x0e, 0xfd, 0x6a, 0x69, 0xee, 0xc9,
0xc1, 0xa4, 0x8e, 0x04, 0x8b, 0x7c, 0x97, 0x5c, 0x25, 0xf2}
return dcrutil.NewAddressPubKeyHash(pkHash,
&chaincfg.MainNetParams, chainec.ECTypeSecp256k1)
},
net: &chaincfg.MainNetParams,
},
{
name: "testnet p2pkh",
addr: "Tso2MVTUeVrjHTBFedFhiyM7yVTbieqp91h",
encoded: "Tso2MVTUeVrjHTBFedFhiyM7yVTbieqp91h",
valid: true,
result: dcrutil.TstAddressPubKeyHash(
[ripemd160.Size]byte{
0xf1, 0x5d, 0xa1, 0xcb, 0x8d, 0x1b, 0xcb, 0x16, 0x2c, 0x6a,
0xb4, 0x46, 0xc9, 0x57, 0x57, 0xa6, 0xe7, 0x91, 0xc9, 0x16},
chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
pkHash := []byte{
0xf1, 0x5d, 0xa1, 0xcb, 0x8d, 0x1b, 0xcb, 0x16, 0x2c, 0x6a,
0xb4, 0x46, 0xc9, 0x57, 0x57, 0xa6, 0xe7, 0x91, 0xc9, 0x16}
return dcrutil.NewAddressPubKeyHash(pkHash,
&chaincfg.TestNet2Params, chainec.ECTypeSecp256k1)
},
net: &chaincfg.TestNet2Params,
},
// Negative P2PKH tests.
{
name: "p2pkh wrong hash length",
addr: "",
valid: false,
f: func() (dcrutil.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 dcrutil.NewAddressPubKeyHash(pkHash,
&chaincfg.MainNetParams,
chainec.ECTypeSecp256k1)
},
},
{
name: "p2pkh bad checksum",
addr: "TsmWaPM77WSyA3aiQ2Q1KnwGDVWvEkhip23",
valid: false,
net: &chaincfg.TestNet2Params,
},
// Positive P2SH tests.
{
// Taken from transactions:
// output: 3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac
// input: 837dea37ddc8b1e3ce646f1a656e79bbd8cc7f558ac56a169626d649ebe2a3ba.
name: "mainnet p2sh",
addr: "DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
encoded: "DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
valid: true,
result: dcrutil.TstAddressScriptHash(
[ripemd160.Size]byte{
0xf0, 0xb4, 0xe8, 0x51, 0x00, 0xae, 0xe1, 0xa9, 0x96, 0xf2,
0x29, 0x15, 0xeb, 0x3c, 0x3f, 0x76, 0x4d, 0x53, 0x77, 0x9a},
chaincfg.MainNetParams.ScriptHashAddrID),
f: func() (dcrutil.Address, error) {
txscript := []byte{
0x51, 0x21, 0x03, 0xaa, 0x43, 0xf0, 0xa6, 0xc1, 0x57, 0x30,
0xd8, 0x86, 0xcc, 0x1f, 0x03, 0x42, 0x04, 0x6d, 0x20, 0x17,
0x54, 0x83, 0xd9, 0x0d, 0x7c, 0xcb, 0x65, 0x7f, 0x90, 0xc4,
0x89, 0x11, 0x1d, 0x79, 0x4c, 0x51, 0xae}
return dcrutil.NewAddressScriptHash(txscript, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
// Taken from transactions:
// output: b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d
// input: (not yet redeemed at time test was written)
name: "mainnet p2sh 2",
addr: "DcqgK4N4Ccucu2Sq4VDAdu4wH4LASLhzLVp",
encoded: "DcqgK4N4Ccucu2Sq4VDAdu4wH4LASLhzLVp",
valid: true,
result: dcrutil.TstAddressScriptHash(
[ripemd160.Size]byte{
0xc7, 0xda, 0x50, 0x95, 0x68, 0x34, 0x36, 0xf4, 0x43, 0x5f,
0xc4, 0xe7, 0x16, 0x3d, 0xca, 0xfd, 0xa1, 0xa2, 0xd0, 0x07},
chaincfg.MainNetParams.ScriptHashAddrID),
f: func() (dcrutil.Address, error) {
hash := []byte{
0xc7, 0xda, 0x50, 0x95, 0x68, 0x34, 0x36, 0xf4, 0x43, 0x5f,
0xc4, 0xe7, 0x16, 0x3d, 0xca, 0xfd, 0xa1, 0xa2, 0xd0, 0x07}
return dcrutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
// Taken from bitcoind base58_keys_valid.
name: "testnet p2sh",
addr: "TccWLgcquqvwrfBocq5mcK5kBiyw8MvyvCi",
encoded: "TccWLgcquqvwrfBocq5mcK5kBiyw8MvyvCi",
valid: true,
result: dcrutil.TstAddressScriptHash(
[ripemd160.Size]byte{
0x36, 0xc1, 0xca, 0x10, 0xa8, 0xa6, 0xa4, 0xb5, 0xd4, 0x20,
0x4a, 0xc9, 0x70, 0x85, 0x39, 0x79, 0x90, 0x3a, 0xa2, 0x84},
chaincfg.TestNet2Params.ScriptHashAddrID),
f: func() (dcrutil.Address, error) {
hash := []byte{
0x36, 0xc1, 0xca, 0x10, 0xa8, 0xa6, 0xa4, 0xb5, 0xd4, 0x20,
0x4a, 0xc9, 0x70, 0x85, 0x39, 0x79, 0x90, 0x3a, 0xa2, 0x84}
return dcrutil.NewAddressScriptHashFromHash(hash, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
// Negative P2SH tests.
{
name: "p2sh wrong hash length",
addr: "",
valid: false,
f: func() (dcrutil.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 dcrutil.NewAddressScriptHashFromHash(hash, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
// Positive P2PK tests.
{
name: "mainnet p2pk compressed (0x02)",
addr: "DsT4FDqBKYG1Xr8aGrT1rKP3kiv6TZ5K5th",
encoded: "DsT4FDqBKYG1Xr8aGrT1rKP3kiv6TZ5K5th",
valid: true,
result: dcrutil.TstAddressPubKey(
[]byte{
0x02, 0x8f, 0x53, 0x83, 0x8b, 0x76, 0x39, 0x56, 0x3f, 0x27,
0xc9, 0x48, 0x45, 0x54, 0x9a, 0x41, 0xe5, 0x14, 0x6b, 0xcd,
0x52, 0xe7, 0xfe, 0xf0, 0xea, 0x6d, 0xa1, 0x43, 0xa0, 0x2b,
0x0f, 0xe2, 0xed},
dcrutil.PKFCompressed, chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x02, 0x8f, 0x53, 0x83, 0x8b, 0x76, 0x39, 0x56, 0x3f, 0x27,
0xc9, 0x48, 0x45, 0x54, 0x9a, 0x41, 0xe5, 0x14, 0x6b, 0xcd,
0x52, 0xe7, 0xfe, 0xf0, 0xea, 0x6d, 0xa1, 0x43, 0xa0, 0x2b,
0x0f, 0xe2, 0xed}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "mainnet p2pk compressed (0x03)",
addr: "DsfiE2y23CGwKNxSGjbfPGeEW4xw1tamZdc",
encoded: "DsfiE2y23CGwKNxSGjbfPGeEW4xw1tamZdc",
valid: true,
result: dcrutil.TstAddressPubKey(
[]byte{
0x03, 0xe9, 0x25, 0xaa, 0xfc, 0x1e, 0xdd, 0x44, 0xe7, 0xc7,
0xf1, 0xea, 0x4f, 0xb7, 0xd2, 0x65, 0xdc, 0x67, 0x2f, 0x20,
0x4c, 0x3d, 0x0c, 0x81, 0x93, 0x03, 0x89, 0xc1, 0x0b, 0x81,
0xfb, 0x75, 0xde},
dcrutil.PKFCompressed, chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x03, 0xe9, 0x25, 0xaa, 0xfc, 0x1e, 0xdd, 0x44, 0xe7, 0xc7,
0xf1, 0xea, 0x4f, 0xb7, 0xd2, 0x65, 0xdc, 0x67, 0x2f, 0x20,
0x4c, 0x3d, 0x0c, 0x81, 0x93, 0x03, 0x89, 0xc1, 0x0b, 0x81,
0xfb, 0x75, 0xde}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
// Hybrid, uncompressed and compressed key types are supported, dcrd consensus rules require a compressed key type however.
{
name: "mainnet p2pk uncompressed (0x04)",
addr: "DkM3EyZ546GghVSkvzb6J47PvGDyntqiDtFgipQhNj78Xm2mUYRpf",
encoded: "DsfFjaADsV8c5oHWx85ZqfxCZy74K8RFuhK",
valid: true,
saddr: "0264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0",
result: dcrutil.TstAddressPubKey(
[]byte{
0x04, 0x64, 0xc4, 0x46, 0x53, 0xd6, 0x56, 0x7e, 0xff, 0x57,
0x53, 0xc5, 0xd2, 0x4a, 0x68, 0x2d, 0xdc, 0x2b, 0x2c, 0xad,
0xfe, 0x1b, 0x0c, 0x64, 0x33, 0xb1, 0x63, 0x74, 0xda, 0xce,
0x67, 0x78, 0xf0, 0xb8, 0x7c, 0xa4, 0x27, 0x9b, 0x56, 0x5d,
0x21, 0x30, 0xce, 0x59, 0xf7, 0x5b, 0xfb, 0xb2, 0xb8, 0x8d,
0xa7, 0x94, 0x14, 0x3d, 0x7c, 0xfd, 0x3e, 0x80, 0x80, 0x8a,
0x1f, 0xa3, 0x20, 0x39, 0x04},
dcrutil.PKFUncompressed, chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x04, 0x64, 0xc4, 0x46, 0x53, 0xd6, 0x56, 0x7e, 0xff, 0x57,
0x53, 0xc5, 0xd2, 0x4a, 0x68, 0x2d, 0xdc, 0x2b, 0x2c, 0xad,
0xfe, 0x1b, 0x0c, 0x64, 0x33, 0xb1, 0x63, 0x74, 0xda, 0xce,
0x67, 0x78, 0xf0, 0xb8, 0x7c, 0xa4, 0x27, 0x9b, 0x56, 0x5d,
0x21, 0x30, 0xce, 0x59, 0xf7, 0x5b, 0xfb, 0xb2, 0xb8, 0x8d,
0xa7, 0x94, 0x14, 0x3d, 0x7c, 0xfd, 0x3e, 0x80, 0x80, 0x8a,
0x1f, 0xa3, 0x20, 0x39, 0x04}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "mainnet p2pk hybrid (0x06)",
addr: "DkM3EyZ546GghVSkvzb6J47PvGDyntqiDtFgipQhNj78Xm2mUYRpf",
encoded: "DsfFjaADsV8c5oHWx85ZqfxCZy74K8RFuhK",
valid: true,
saddr: "0264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0",
result: dcrutil.TstAddressPubKey(
[]byte{
0x06, 0x64, 0xc4, 0x46, 0x53, 0xd6, 0x56, 0x7e, 0xff, 0x57,
0x53, 0xc5, 0xd2, 0x4a, 0x68, 0x2d, 0xdc, 0x2b, 0x2c, 0xad,
0xfe, 0x1b, 0x0c, 0x64, 0x33, 0xb1, 0x63, 0x74, 0xda, 0xce,
0x67, 0x78, 0xf0, 0xb8, 0x7c, 0xa4, 0x27, 0x9b, 0x56, 0x5d,
0x21, 0x30, 0xce, 0x59, 0xf7, 0x5b, 0xfb, 0xb2, 0xb8, 0x8d,
0xa7, 0x94, 0x14, 0x3d, 0x7c, 0xfd, 0x3e, 0x80, 0x80, 0x8a,
0x1f, 0xa3, 0x20, 0x39, 0x04},
dcrutil.PKFHybrid, chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x06, 0x64, 0xc4, 0x46, 0x53, 0xd6, 0x56, 0x7e, 0xff, 0x57,
0x53, 0xc5, 0xd2, 0x4a, 0x68, 0x2d, 0xdc, 0x2b, 0x2c, 0xad,
0xfe, 0x1b, 0x0c, 0x64, 0x33, 0xb1, 0x63, 0x74, 0xda, 0xce,
0x67, 0x78, 0xf0, 0xb8, 0x7c, 0xa4, 0x27, 0x9b, 0x56, 0x5d,
0x21, 0x30, 0xce, 0x59, 0xf7, 0x5b, 0xfb, 0xb2, 0xb8, 0x8d,
0xa7, 0x94, 0x14, 0x3d, 0x7c, 0xfd, 0x3e, 0x80, 0x80, 0x8a,
0x1f, 0xa3, 0x20, 0x39, 0x04}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "mainnet p2pk hybrid (0x07)",
addr: "DkRKh2aTdwjKKL1mkCb2DFp2Hr7SqMyx3zWqNwyc37PYiGpKmGRsi",
encoded: "DskEQZMCs4nifL7wx7iHYGWxMQvR9ThCBKQ",
valid: true,
saddr: "03348d8aeb4253ca52456fe5da94ab1263bfee16bb8192497f666389ca964f8479",
result: dcrutil.TstAddressPubKey(
[]byte{
0x07, 0x34, 0x8d, 0x8a, 0xeb, 0x42, 0x53, 0xca, 0x52, 0x45,
0x6f, 0xe5, 0xda, 0x94, 0xab, 0x12, 0x63, 0xbf, 0xee, 0x16,
0xbb, 0x81, 0x92, 0x49, 0x7f, 0x66, 0x63, 0x89, 0xca, 0x96,
0x4f, 0x84, 0x79, 0x83, 0x75, 0x12, 0x9d, 0x79, 0x58, 0x84,
0x3b, 0x14, 0x25, 0x8b, 0x90, 0x5d, 0xc9, 0x4f, 0xae, 0xd3,
0x24, 0xdd, 0x8a, 0x9d, 0x67, 0xff, 0xac, 0x8c, 0xc0, 0xa8,
0x5b, 0xe8, 0x4b, 0xac, 0x5d},
dcrutil.PKFHybrid, chaincfg.MainNetParams.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x07, 0x34, 0x8d, 0x8a, 0xeb, 0x42, 0x53, 0xca, 0x52, 0x45,
0x6f, 0xe5, 0xda, 0x94, 0xab, 0x12, 0x63, 0xbf, 0xee, 0x16,
0xbb, 0x81, 0x92, 0x49, 0x7f, 0x66, 0x63, 0x89, 0xca, 0x96,
0x4f, 0x84, 0x79, 0x83, 0x75, 0x12, 0x9d, 0x79, 0x58, 0x84,
0x3b, 0x14, 0x25, 0x8b, 0x90, 0x5d, 0xc9, 0x4f, 0xae, 0xd3,
0x24, 0xdd, 0x8a, 0x9d, 0x67, 0xff, 0xac, 0x8c, 0xc0, 0xa8,
0x5b, 0xe8, 0x4b, 0xac, 0x5d}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.MainNetParams)
},
net: &chaincfg.MainNetParams,
},
{
name: "testnet p2pk compressed (0x02)",
addr: "Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
encoded: "Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
valid: true,
result: dcrutil.TstAddressPubKey(
[]byte{
0x02, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e},
dcrutil.PKFCompressed, chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x02, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
{
name: "testnet p2pk compressed (0x03)",
addr: "TsWZ1EzypJfMwBKAEDYKuyHRGctqGAxMje2",
encoded: "TsWZ1EzypJfMwBKAEDYKuyHRGctqGAxMje2",
valid: true,
result: dcrutil.TstAddressPubKey(
[]byte{
0x03, 0x08, 0x44, 0xee, 0x70, 0xd8, 0x38, 0x4d, 0x52, 0x50,
0xe9, 0xbb, 0x3a, 0x6a, 0x73, 0xd4, 0xb5, 0xbe, 0xc7, 0x70,
0xe8, 0xb3, 0x1d, 0x6a, 0x0a, 0xe9, 0xfb, 0x73, 0x90, 0x09,
0xd9, 0x1a, 0xf5},
dcrutil.PKFCompressed, chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x03, 0x08, 0x44, 0xee, 0x70, 0xd8, 0x38, 0x4d, 0x52, 0x50,
0xe9, 0xbb, 0x3a, 0x6a, 0x73, 0xd4, 0xb5, 0xbe, 0xc7, 0x70,
0xe8, 0xb3, 0x1d, 0x6a, 0x0a, 0xe9, 0xfb, 0x73, 0x90, 0x09,
0xd9, 0x1a, 0xf5}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
{
name: "testnet p2pk uncompressed (0x04)",
addr: "TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
encoded: "Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
valid: true,
saddr: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
result: dcrutil.TstAddressPubKey(
[]byte{
0x04, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e, 0xd5, 0x48, 0xc8, 0xc1, 0x6f, 0xb5, 0xeb,
0x90, 0x07, 0xcb, 0x94, 0x22, 0x0b, 0x3b, 0xb8, 0x94, 0x91,
0xd5, 0xa1, 0xfd, 0x2d, 0x77, 0x86, 0x7f, 0xca, 0x64, 0x21,
0x7a, 0xce, 0xcf, 0x22, 0x44},
dcrutil.PKFUncompressed, chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x04, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e, 0xd5, 0x48, 0xc8, 0xc1, 0x6f, 0xb5, 0xeb,
0x90, 0x07, 0xcb, 0x94, 0x22, 0x0b, 0x3b, 0xb8, 0x94, 0x91,
0xd5, 0xa1, 0xfd, 0x2d, 0x77, 0x86, 0x7f, 0xca, 0x64, 0x21,
0x7a, 0xce, 0xcf, 0x22, 0x44}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
{
name: "testnet p2pk hybrid (0x06)",
addr: "TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
encoded: "Tso9sQD3ALqRsmEkAm7KvPrkGbeG2Vun7Kv",
valid: true,
saddr: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
result: dcrutil.TstAddressPubKey(
[]byte{
0x06, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e, 0xd5, 0x48, 0xc8, 0xc1, 0x6f, 0xb5, 0xeb,
0x90, 0x07, 0xcb, 0x94, 0x22, 0x0b, 0x3b, 0xb8, 0x94, 0x91,
0xd5, 0xa1, 0xfd, 0x2d, 0x77, 0x86, 0x7f, 0xca, 0x64, 0x21,
0x7a, 0xce, 0xcf, 0x22, 0x44},
dcrutil.PKFHybrid, chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x06, 0x6a, 0x40, 0xc4, 0x03, 0xe7, 0x46, 0x70, 0xc4, 0xde,
0x76, 0x56, 0xa0, 0x9c, 0xaa, 0x23, 0x53, 0xd4, 0xb3, 0x83,
0xa9, 0xce, 0x66, 0xee, 0xf5, 0x1e, 0x12, 0x20, 0xea, 0xcf,
0x4b, 0xe0, 0x6e, 0xd5, 0x48, 0xc8, 0xc1, 0x6f, 0xb5, 0xeb,
0x90, 0x07, 0xcb, 0x94, 0x22, 0x0b, 0x3b, 0xb8, 0x94, 0x91,
0xd5, 0xa1, 0xfd, 0x2d, 0x77, 0x86, 0x7f, 0xca, 0x64, 0x21,
0x7a, 0xce, 0xcf, 0x22, 0x44}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
{
name: "testnet p2pk hybrid (0x07)",
addr: "TkQ5Ax2ieEZpBDA963VDH4y27KMpXtP8qyeykzwBNFocDc8ZKqTGz",
encoded: "TsTFLdM32YVrYsEQDFxo2zmPuKFcFhH5ZT3",
valid: true,
saddr: "03edd40747de905a9becb14987a1a26c1adbd617c45e1583c142a635bfda9493df",
result: dcrutil.TstAddressPubKey(
[]byte{
0x07, 0xed, 0xd4, 0x07, 0x47, 0xde, 0x90, 0x5a, 0x9b, 0xec,
0xb1, 0x49, 0x87, 0xa1, 0xa2, 0x6c, 0x1a, 0xdb, 0xd6, 0x17,
0xc4, 0x5e, 0x15, 0x83, 0xc1, 0x42, 0xa6, 0x35, 0xbf, 0xda,
0x94, 0x93, 0xdf, 0xa1, 0xc6, 0xd3, 0x67, 0x35, 0x97, 0x49,
0x65, 0xfe, 0x7b, 0x86, 0x1e, 0x7f, 0x6f, 0xcc, 0x08, 0x7d,
0xc7, 0xfe, 0x47, 0x38, 0x0f, 0xa8, 0xbd, 0xe0, 0xd9, 0xc3,
0x22, 0xd5, 0x3c, 0x0e, 0x89},
dcrutil.PKFHybrid, chaincfg.TestNet2Params.PubKeyHashAddrID),
f: func() (dcrutil.Address, error) {
serializedPubKey := []byte{
0x07, 0xed, 0xd4, 0x07, 0x47, 0xde, 0x90, 0x5a, 0x9b, 0xec,
0xb1, 0x49, 0x87, 0xa1, 0xa2, 0x6c, 0x1a, 0xdb, 0xd6, 0x17,
0xc4, 0x5e, 0x15, 0x83, 0xc1, 0x42, 0xa6, 0x35, 0xbf, 0xda,
0x94, 0x93, 0xdf, 0xa1, 0xc6, 0xd3, 0x67, 0x35, 0x97, 0x49,
0x65, 0xfe, 0x7b, 0x86, 0x1e, 0x7f, 0x6f, 0xcc, 0x08, 0x7d,
0xc7, 0xfe, 0x47, 0x38, 0x0f, 0xa8, 0xbd, 0xe0, 0xd9, 0xc3,
0x22, 0xd5, 0x3c, 0x0e, 0x89}
return dcrutil.NewAddressSecpPubKey(serializedPubKey, &chaincfg.TestNet2Params)
},
net: &chaincfg.TestNet2Params,
},
}
for _, test := range tests {
// Decode addr and compare error against valid.
decoded, err := dcrutil.DecodeAddress(test.addr)
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 {
if test.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 *dcrutil.AddressPubKeyHash:
saddr = dcrutil.TstAddressSAddr(encoded)
case *dcrutil.AddressScriptHash:
saddr = dcrutil.TstAddressSAddr(encoded)
case *dcrutil.AddressSecpPubKey:
// Ignore the error here since the script
// address is checked below.
saddr, err = hex.DecodeString(d.String())
if err != nil {
saddr, _ = hex.DecodeString(test.saddr)
}
case *dcrutil.AddressEdwardsPubKey:
// Ignore the error here since the script
// address is checked below.
saddr, _ = hex.DecodeString(d.String())
case *dcrutil.AddressSecSchnorrPubKey:
// Ignore the error here since the script
// address is checked below.
saddr, _ = hex.DecodeString(d.String())
}
// 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 *dcrutil.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 *dcrutil.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
}
}
// 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
}
}
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.ScriptAddress(), test.result.ScriptAddress()) {
t.Errorf("%v: created address does not match expected result \n "+
" got %x, expected %x",
test.name, addr.ScriptAddress(), test.result.ScriptAddress())
return
}
}
}

146
dcrutil/amount.go Normal file
View File

@ -0,0 +1,146 @@
// Copyright (c) 2013, 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"errors"
"math"
"strconv"
)
// AmountUnit describes a method of converting an Amount to something
// other than the base unit of a coin. The value of the AmountUnit
// is the exponent component of the decadic multiple to convert from
// an amount in coins to an amount counted in atomic units.
type AmountUnit int
// These constants define various units used when describing a coin
// monetary amount.
const (
AmountMegaCoin AmountUnit = 6
AmountKiloCoin AmountUnit = 3
AmountCoin AmountUnit = 0
AmountMilliCoin AmountUnit = -3
AmountMicroCoin AmountUnit = -6
AmountAtom AmountUnit = -8
)
// String returns the unit as a string. For recognized units, the SI
// prefix is used, or "Atom" for the base unit. For all unrecognized
// units, "1eN DCR" is returned, where N is the AmountUnit.
func (u AmountUnit) String() string {
switch u {
case AmountMegaCoin:
return "MDCR"
case AmountKiloCoin:
return "kDCR"
case AmountCoin:
return "DCR"
case AmountMilliCoin:
return "mDCR"
case AmountMicroCoin:
return "μDCR"
case AmountAtom:
return "Atom"
default:
return "1e" + strconv.FormatInt(int64(u), 10) + " DCR"
}
}
// Amount represents the base coin monetary unit (colloquially referred
// to as an `Atom'). A single Amount is equal to 1e-8 of a coin.
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 the currency. NewAmount errors if f is NaN or +-Infinity,
// but does not check that the amount is within the total amount of coins
// producible as f may not refer to an amount at a single moment in time.
//
// NewAmount is for specifically for converting DCR to Atoms (atomic units).
// For creating a new Amount with an int64 value which denotes a quantity of
// Atoms, do a simple type conversion from type int64 to Amount.
// See GoDoc for example: http://godoc.org/github.com/decred/dcrd/dcrutil#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 coin amount")
}
return round(f * AtomsPerCoin), nil
}
// ToUnit converts a monetary amount counted in coin base units to a
// floating point value representing an amount of coins.
func (a Amount) ToUnit(u AmountUnit) float64 {
return float64(a) / math.Pow10(int(u+8))
}
// ToCoin is the equivalent of calling ToUnit with AmountCoin.
func (a Amount) ToCoin() float64 {
return a.ToUnit(AmountCoin)
}
// Format formats a monetary amount counted in coin 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 "atom" 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 AmountCoin.
func (a Amount) String() string {
return a.Format(AmountCoin)
}
// 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 decred (for example, calculating
// a fee by multiplying by a percentage).
func (a Amount) MulF64(f float64) Amount {
return round(float64(a) * f)
}
// AmountSorter implements sort.Interface to allow a slice of Amounts to
// be sorted.
type AmountSorter []Amount
// Len returns the number of Amounts in the slice. It is part of the
// sort.Interface implementation.
func (s AmountSorter) Len() int {
return len(s)
}
// Swap swaps the Amounts at the passed indices. It is part of the
// sort.Interface implementation.
func (s AmountSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less returns whether the Amount with index i should sort before the
// Amount with index j. It is part of the sort.Interface
// implementation.
func (s AmountSorter) Less(i, j int) bool {
return s[i] < s[j]
}

352
dcrutil/amount_test.go Normal file
View File

@ -0,0 +1,352 @@
// Copyright (c) 2013, 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"math"
"reflect"
"sort"
"testing"
. "github.com/decred/dcrd/dcrutil"
)
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 producable",
amount: 21e6,
valid: true,
expected: MaxAmount,
},
{
name: "min producable",
amount: -21e6,
valid: true,
expected: -MaxAmount,
},
{
name: "exceeds max producable",
amount: 21e6 + 1e-8,
valid: true,
expected: MaxAmount + 1,
},
{
name: "exceeds min producable",
amount: -21e6 - 1e-8,
valid: true,
expected: -MaxAmount - 1,
},
{
name: "one hundred",
amount: 100,
valid: true,
expected: 100 * AtomsPerCoin,
},
{
name: "fraction",
amount: 0.01234567,
valid: true,
expected: 1234567,
},
{
name: "rounding up",
amount: 54.999999999999943157,
valid: true,
expected: 55 * AtomsPerCoin,
},
{
name: "rounding down",
amount: 55.000000000000056843,
valid: true,
expected: 55 * AtomsPerCoin,
},
// 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: "MDCR",
amount: MaxAmount,
unit: AmountMegaCoin,
converted: 21,
s: "21 MDCR",
},
{
name: "kDCR",
amount: 44433322211100,
unit: AmountKiloCoin,
converted: 444.33322211100,
s: "444.333222111 kDCR",
},
{
name: "Coin",
amount: 44433322211100,
unit: AmountCoin,
converted: 444333.22211100,
s: "444333.222111 DCR",
},
{
name: "mDCR",
amount: 44433322211100,
unit: AmountMilliCoin,
converted: 444333222.11100,
s: "444333222.111 mDCR",
},
{
name: "μDCR",
amount: 44433322211100,
unit: AmountMicroCoin,
converted: 444333222111.00,
s: "444333222111 μDCR",
},
{
name: "atom",
amount: 44433322211100,
unit: AmountAtom,
converted: 44433322211100,
s: "44433322211100 Atom",
},
{
name: "non-standard unit",
amount: 44433322211100,
unit: AmountUnit(-1),
converted: 4443332.2211100,
s: "4443332.22111 1e-1 DCR",
},
}
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.ToCoin works as advertised.
f1 := test.amount.ToUnit(AmountCoin)
f2 := test.amount.ToCoin()
if f1 != f2 {
t.Errorf("%v: ToCoin does not match ToUnit(AmountCoin): %v != %v", test.name, f1, f2)
}
// Verify that Amount.String works as advertised.
s1 := test.amount.Format(AmountCoin)
s2 := test.amount.String()
if s1 != s2 {
t.Errorf("%v: String does not match Format(AmountCoin): %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 DCR by 2",
amt: 100e5, // 0.1 DCR
mul: 2,
res: 200e5, // 0.2 DCR
},
{
name: "Multiply 0.2 DCR by 0.02",
amt: 200e5, // 0.2 DCR
mul: 1.02,
res: 204e5, // 0.204 DCR
},
{
name: "Multiply 0.1 DCR by -2",
amt: 100e5, // 0.1 DCR
mul: -2,
res: -200e5, // -0.2 DCR
},
{
name: "Multiply 0.2 DCR by -0.02",
amt: 200e5, // 0.2 DCR
mul: -1.02,
res: -204e5, // -0.204 DCR
},
{
name: "Multiply -0.1 DCR by 2",
amt: -100e5, // -0.1 DCR
mul: 2,
res: -200e5, // -0.2 DCR
},
{
name: "Multiply -0.2 DCR by 0.02",
amt: -200e5, // -0.2 DCR
mul: 1.02,
res: -204e5, // -0.204 DCR
},
{
name: "Multiply -0.1 DCR by -2",
amt: -100e5, // -0.1 DCR
mul: -2,
res: 200e5, // 0.2 DCR
},
{
name: "Multiply -0.2 DCR by -0.02",
amt: -200e5, // -0.2 DCR
mul: -1.02,
res: 204e5, // 0.204 DCR
},
{
name: "Round down",
amt: 49, // 49 Atoms
mul: 0.01,
res: 0,
},
{
name: "Round up",
amt: 50, // 50 Atoms
mul: 0.01,
res: 1, // 1 Atom
},
{
name: "Multiply by 0.",
amt: 1e8, // 1 DCR
mul: 0,
res: 0, // 0 DCR
},
{
name: "Multiply 1 by 0.5.",
amt: 1, // 1 Atom
mul: 0.5,
res: 1, // 1 Atom
},
{
name: "Multiply 100 by 66%.",
amt: 100, // 100 Atoms
mul: 0.66,
res: 66, // 66 Atoms
},
{
name: "Multiply 100 by 66.6%.",
amt: 100, // 100 Atoms
mul: 0.666,
res: 67, // 67 Atoms
},
{
name: "Multiply 100 by 2/3.",
amt: 100, // 100 Atoms
mul: 2.0 / 3,
res: 67, // 67 Atoms
},
}
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)
}
}
}
func TestAmountSorter(t *testing.T) {
tests := []struct {
name string
as []Amount
want []Amount
}{
{
name: "Sort zero length slice of Amounts",
as: []Amount{},
want: []Amount{},
},
{
name: "Sort 1-element slice of Amounts",
as: []Amount{7},
want: []Amount{7},
},
{
name: "Sort 2-element slice of Amounts",
as: []Amount{7, 5},
want: []Amount{5, 7},
},
{
name: "Sort 6-element slice of Amounts",
as: []Amount{0, 9e8, 4e6, 4e6, 3, 9e12},
want: []Amount{0, 3, 4e6, 4e6, 9e8, 9e12},
},
}
for i, test := range tests {
result := make([]Amount, len(test.as))
copy(result, test.as)
sort.Sort(AmountSorter(result))
if !reflect.DeepEqual(result, test.want) {
t.Errorf("AmountSorter #%d got %v want %v", i, result,
test.want)
continue
}
}
}

108
dcrutil/appdata.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
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 stripping it.
if strings.HasPrefix(appName, ".") {
appName = appName[1:]
}
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)
}

134
dcrutil/appdata_test.go Normal file
View File

@ -0,0 +1,134 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"os"
"os/user"
"path/filepath"
"runtime"
"testing"
"unicode"
"github.com/decred/dcrd/dcrutil"
)
// 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 := dcrutil.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
}
}
}

61
dcrutil/bitflags.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
// bitflags contains a simple series of functions to handle bitwise boolean
// operations in uint16s.
// Flags16 is the type for 2 bytes of flags; not really used except in the
// declaration below.
type Flags16 uint16
// Flag fields for uint16.
const (
FlagNone Flags16 = 0
BlockValid = 1 << 0 // Describes whether TxTreeRegular is valid
Flag01 = 1 << 1
Flag02 = 1 << 2
Flag03 = 1 << 3
Flag04 = 1 << 4
Flag05 = 1 << 5
Flag06 = 1 << 6
Flag07 = 1 << 7
Flag08 = 1 << 8
Flag09 = 1 << 9
Flag10 = 1 << 10
Flag11 = 1 << 11
Flag12 = 1 << 12
Flag13 = 1 << 13
Flag14 = 1 << 14
Flag15 = 1 << 15
)
// IsFlagSet16 returns true/false for a flag at flag field 'flag'.
func IsFlagSet16(flags uint16, flag uint16) bool {
return flags&flag != 0
}
// SetFlag16 sets a bit flag at flag position 'flag' to bool 'b'.
func SetFlag16(flags *uint16, flag uint16, b bool) {
if b {
*flags = *flags | flag
} else {
*flags = *flags &^ flag
}
}
// BoolArray16 is a bool array that is generated from a uint16 containing flags.
type BoolArray16 [16]bool
// GenerateBoolArray16 generates a BoolArray16 from a uint16 containing flags.
func GenerateBoolArray16(flags uint16) BoolArray16 {
var ba BoolArray16
for i := uint8(0); i < 16; i++ {
ba[i] = (flags&(1<<i) != 0)
}
return ba
}

414
dcrutil/block.go Normal file
View File

@ -0,0 +1,414 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"bytes"
"fmt"
"io"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/wire"
)
// OutOfRangeError describes an error due to accessing an element that is out
// of range.
type OutOfRangeError string
// assertBlockImmutability throws a panic when a block has been
// mutated.
var assertBlockImmutability = false
// 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 = int64(-1)
// Error satisfies the error interface and prints human-readable errors.
func (e OutOfRangeError) Error() string {
return string(e)
}
// Block defines a cryptocurrency 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
hash chainhash.Hash // Cached block hash
transactions []*Tx // Transactions
sTransactions []*Tx // Stake transactions
txnsGenerated bool // ALL wrapped transactions generated
sTxnsGenerated bool // ALL wrapped stake 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.
var w bytes.Buffer
w.Grow(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
}
// BlockHeaderBytes returns the serialized bytes for the Block's header. This is
// equivalent to calling Serialize on the underlying wire.MsgBlock, but it
// returns a byte slice.
func (b *Block) BlockHeaderBytes() ([]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.
var w bytes.Buffer
w.Grow(wire.MaxBlockHeaderPayload)
err := b.msgBlock.Header.Serialize(&w)
if err != nil {
return nil, err
}
serializedBlockHeader := w.Bytes()
// Cache the serialized bytes and return them.
return serializedBlockHeader, 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 {
if assertBlockImmutability {
hash := b.msgBlock.BlockHash()
if !hash.IsEqual(&b.hash) {
str := fmt.Sprintf("ASSERT: mutated util.block detected, old hash "+
"%v, new hash %v", b.hash, hash)
panic(str)
}
}
return &b.hash
}
// Tx returns a wrapped transaction (dcrutil.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)
newTx.SetTree(wire.TxTreeRegular)
b.transactions[txNum] = newTx
return newTx, nil
}
// STx returns a wrapped transaction (dcrutil.Tx) for the stake transaction at
// the specified index in the Block. The supplied index is 0 based.
func (b *Block) STx(txNum int) (*Tx, error) {
// Ensure the requested transaction is in range.
numTx := uint64(len(b.msgBlock.STransactions))
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.sTransactions) == 0 {
b.sTransactions = make([]*Tx, numTx)
}
// Return the wrapped transaction if it has already been generated.
if b.sTransactions[txNum] != nil {
return b.sTransactions[txNum], nil
}
// Generate and cache the wrapped transaction and return it.
newTx := NewTx(b.msgBlock.STransactions[txNum])
newTx.SetIndex(txNum)
newTx.SetTree(wire.TxTreeStake)
b.sTransactions[txNum] = newTx
return newTx, nil
}
// Transactions returns a slice of wrapped transactions (dcrutil.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 (dcrutil.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)
newTx.SetTree(wire.TxTreeRegular)
b.transactions[i] = newTx
}
}
b.txnsGenerated = true
return b.transactions
}
// STransactions returns a slice of wrapped stake transactions (dcrutil.Tx) for all
// stake transactions in the Block. This is nearly equivalent to accessing the raw
// transactions (dcrwire.MsgTx) in the underlying wire.MsgBlock, however it
// instead provides easy access to wrapped versions (util.Tx) of them.
func (b *Block) STransactions() []*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.sTxnsGenerated {
return b.sTransactions
}
// Generate slice to hold all of the wrapped transactions if needed.
if len(b.sTransactions) == 0 {
b.sTransactions = make([]*Tx, len(b.msgBlock.STransactions))
}
// Generate and cache the wrapped transactions for all that haven't
// already been done.
for i, tx := range b.sTransactions {
if tx == nil {
newTx := NewTx(b.msgBlock.STransactions[i])
newTx.SetIndex(i)
newTx.SetTree(wire.TxTreeStake)
b.sTransactions[i] = newTx
}
}
b.sTxnsGenerated = true
return b.sTransactions
}
// 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
}
// STxHash returns the hash for the requested stake 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) STxHash(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.STx(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, []wire.TxLoc, error) {
rawMsg, err := b.Bytes()
if err != nil {
return nil, nil, err
}
rbuf := bytes.NewBuffer(rawMsg)
var mblock wire.MsgBlock
txLocs, sTxLocs, err := mblock.DeserializeTxLoc(rbuf)
if err != nil {
return nil, nil, err
}
return txLocs, sTxLocs, err
}
// Height returns a casted int64 height from the block header.
//
// This function should not be used for new code and will be
// removed in the future.
func (b *Block) Height() int64 {
return int64(b.msgBlock.Header.Height)
}
// NewBlock returns a new instance of a block given an underlying
// wire.MsgBlock. See Block.
func NewBlock(msgBlock *wire.MsgBlock) *Block {
return &Block{
hash: msgBlock.BlockHash(),
msgBlock: msgBlock,
}
}
// NewBlockDeepCopyCoinbase returns a new instance of a block given an underlying
// wire.MsgBlock, but makes a deep copy of the coinbase transaction since it's
// sometimes mutable.
func NewBlockDeepCopyCoinbase(msgBlock *wire.MsgBlock) *Block {
// Copy the msgBlock and the pointers to all the transactions.
msgBlockCopy := new(wire.MsgBlock)
lenTxs := len(msgBlock.Transactions)
mtxsCopy := make([]*wire.MsgTx, lenTxs)
for i, mtx := range msgBlock.Transactions {
mtxsCopy[i] = mtx
}
msgBlockCopy.Transactions = mtxsCopy
lenStxs := len(msgBlock.STransactions)
smtxsCopy := make([]*wire.MsgTx, lenStxs)
for i, smtx := range msgBlock.STransactions {
smtxsCopy[i] = smtx
}
msgBlockCopy.STransactions = smtxsCopy
msgBlockCopy.Header = msgBlock.Header
// Deep copy the first transaction. Also change the coinbase pointer.
msgBlockCopy.Transactions[0] =
NewTxDeep(msgBlockCopy.Transactions[0]).MsgTx()
bl := &Block{
msgBlock: msgBlockCopy,
}
bl.hash = msgBlock.BlockHash()
return bl
}
// NewBlockDeepCopy deep copies an entire block down to the wire components and
// returns the new block based off of this copy.
func NewBlockDeepCopy(msgBlock *wire.MsgBlock) *Block {
// Deep copy the header and all the transactions.
msgBlockCopy := new(wire.MsgBlock)
lenTxs := len(msgBlock.Transactions)
mtxsCopy := make([]*wire.MsgTx, lenTxs)
for i, mtx := range msgBlock.Transactions {
txd := NewTxDeep(mtx)
mtxsCopy[i] = txd.MsgTx()
}
msgBlockCopy.Transactions = mtxsCopy
lenStxs := len(msgBlock.STransactions)
smtxsCopy := make([]*wire.MsgTx, lenStxs)
for i, smtx := range msgBlock.STransactions {
stxd := NewTxDeep(smtx)
smtxsCopy[i] = stxd.MsgTx()
}
msgBlockCopy.STransactions = smtxsCopy
msgBlockCopy.Header = msgBlock.Header
bl := &Block{
msgBlock: msgBlockCopy,
}
bl.hash = msgBlock.BlockHash()
return bl
}
// NewBlockFromBytes returns a new instance of a 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 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{
hash: msgBlock.BlockHash(),
msgBlock: &msgBlock,
}
return &b, nil
}
// NewBlockFromBlockAndBytes returns a new instance of a block given
// an underlying wire.MsgBlock and the serialized bytes for it. See Block.
func NewBlockFromBlockAndBytes(msgBlock *wire.MsgBlock, serializedBlock []byte) *Block {
return &Block{
hash: msgBlock.BlockHash(),
msgBlock: msgBlock,
serializedBlock: serializedBlock,
}
}

562
dcrutil/block_test.go Normal file
View File

@ -0,0 +1,562 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"bytes"
"io"
"reflect"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
)
// TestBlock tests the API for Block.
func TestBlock(t *testing.T) {
b := dcrutil.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 := int64(100000)
if gotHeight := b.Height(); gotHeight != wantHeight {
t.Errorf("Height: mismatched height - got %v, want %v",
gotHeight, wantHeight)
}
// Hash for block 100,000.
wantHashStr := "142c5f5b6f868b0e70172b78cea2cff21e6580612b3a360cf6bb2a5976e25ed1"
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{
"1cbd9fe1a143a265cc819ff9d8132a7cbc4ca48eb68c0de39cfdf7ecf42cbbd1",
"f3f9bc9473b6fe18d66e3ac2a1a95b6317b280f4e6687a074075b56aebf1eb53",
"ba2ed6210a561a4dab34ec8668ad61ec97f126826dae893719dff7383b9d6928",
"c5dde35b55b856cf73b2d85737c68b0dcfdaad01d0271ee509f3a7efacc025b3",
}
// Create a new block to nuke all cached data.
b = dcrutil.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 = dcrutil.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
block100000Buf.Grow(Block100000.SerializeSize())
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: 181, TxLen: 159},
{TxStart: 340, TxLen: 285},
{TxStart: 625, TxLen: 283},
{TxStart: 908, TxLen: 249},
}
// 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
block100000Buf.Grow(Block100000.SerializeSize())
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 := dcrutil.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
block100000Buf.Grow(Block100000.SerializeSize())
err := Block100000.Serialize(&block100000Buf)
if err != nil {
t.Errorf("Serialize: %v", err)
}
block100000Bytes := block100000Buf.Bytes()
// Create a new block from the serialized bytes.
b := dcrutil.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 := dcrutil.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
block100000Buf.Grow(Block100000.SerializeSize())
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 := dcrutil.NewBlockFromBytes(block100000Bytes)
if err != nil {
t.Errorf("NewBlockFromBytes: %v", err)
return
}
// Truncate the block byte buffer to force errors.
shortBytes := block100000Bytes[:100]
_, err = dcrutil.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.(dcrutil.OutOfRangeError); !ok {
t.Errorf("TxHash: wrong error - got: %v <%T>, "+
"want: <%T>", err, err, dcrutil.OutOfRangeError(""))
}
_, err = b.TxHash(len(Block100000.Transactions) + 1)
if _, ok := err.(dcrutil.OutOfRangeError); !ok {
t.Errorf("TxHash: wrong error - got: %v <%T>, "+
"want: <%T>", err, err, dcrutil.OutOfRangeError(""))
}
// Ensure Tx returns expected error on invalid indices.
_, err = b.Tx(-1)
if _, ok := err.(dcrutil.OutOfRangeError); !ok {
t.Errorf("Tx: wrong error - got: %v <%T>, "+
"want: <%T>", err, err, dcrutil.OutOfRangeError(""))
}
_, err = b.Tx(len(Block100000.Transactions) + 1)
if _, ok := err.(dcrutil.OutOfRangeError); !ok {
t.Errorf("Tx: wrong error - got: %v <%T>, "+
"want: <%T>", err, err, dcrutil.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,
Height: 100000,
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{
{
SerType: wire.TxSerializeFull,
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,
},
},
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,
},
{
SerType: wire.TxSerializeFull,
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,
},
{
SerType: wire.TxSerializeFull,
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,
},
{
SerType: wire.TxSerializeFull,
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,
},
},
STransactions: []*wire.MsgTx{},
}

18
dcrutil/const.go Normal file
View File

@ -0,0 +1,18 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
const (
// AtomsPerCent is the number of atomic units in one coin cent.
AtomsPerCent = 1e6
// AtomsPerCoin is the number of atomic units in one coin.
AtomsPerCoin = 1e8
// MaxAmount is the maximum transaction amount allowed in atoms.
// Decred - Changeme for release
MaxAmount = 21e6 * AtomsPerCoin
)

47
dcrutil/doc.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package dcrutil provides decred-specific convenience functions and types.
Block Overview
A Block defines a decred 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 decred 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 Decred address. While the
most common type is a pay-to-pubkey-hash, Decred 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 := dcrutil.DecodeAddress(addrString, defaultNet)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(addr.EncodeAddress())
*/
package dcrutil

76
dcrutil/example_test.go Normal file
View File

@ -0,0 +1,76 @@
package dcrutil_test
import (
"fmt"
"math"
"github.com/decred/dcrd/dcrutil"
)
func ExampleAmount() {
a := dcrutil.Amount(0)
fmt.Println("Zero Atom:", a)
a = dcrutil.Amount(1e8)
fmt.Println("100,000,000 Atoms:", a)
a = dcrutil.Amount(1e5)
fmt.Println("100,000 Atoms:", a)
// Output:
// Zero Atom: 0 DCR
// 100,000,000 Atoms: 1 DCR
// 100,000 Atoms: 0.001 DCR
}
func ExampleNewAmount() {
amountOne, err := dcrutil.NewAmount(1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(amountOne) //Output 1
amountFraction, err := dcrutil.NewAmount(0.01234567)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(amountFraction) //Output 2
amountZero, err := dcrutil.NewAmount(0)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(amountZero) //Output 3
amountNaN, err := dcrutil.NewAmount(math.NaN())
if err != nil {
fmt.Println(err)
return
}
fmt.Println(amountNaN) //Output 4
// Output: 1 DCR
// 0.01234567 DCR
// 0 DCR
// invalid coin amount
}
func ExampleAmount_unitConversions() {
amount := dcrutil.Amount(44433322211100)
fmt.Println("Atom to kCoin:", amount.Format(dcrutil.AmountKiloCoin))
fmt.Println("Atom to Coin:", amount)
fmt.Println("Atom to MilliCoin:", amount.Format(dcrutil.AmountMilliCoin))
fmt.Println("Atom to MicroCoin:", amount.Format(dcrutil.AmountMicroCoin))
fmt.Println("Atom to Atom:", amount.Format(dcrutil.AmountAtom))
// Output:
// Atom to kCoin: 444.333222111 kDCR
// Atom to Coin: 444333.222111 DCR
// Atom to MilliCoin: 444333222.111 mDCR
// Atom to MicroCoin: 444333222111 μDCR
// Atom to Atom: 44433322211100 Atom
}

25
dcrutil/hash160.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"hash"
"golang.org/x/crypto/ripemd160"
"github.com/decred/dcrd/chaincfg/chainhash"
)
// 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(hash256(b)).
func Hash160(buf []byte) []byte {
return calcHash(chainhash.HashB(buf), ripemd160.New())
}

View File

@ -0,0 +1,59 @@
hdkeychain
==========
[![Build Status](http://img.shields.io/travis/decred/dcrd.svg)](https://travis-ci.org/decred/dcrd)
[![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/decred/dcrd/dcrutil/hdkeychain)
Package hdkeychain provides an API for Decred hierarchical deterministic
extended keys (based on 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 decred
addresses ties in seamlessly with existing btcec and dcrutil 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/decred/dcrd/dcrutil/hdkeychain
```
## Examples
* [NewMaster Example](http://godoc.org/github.com/decred/dcrd/dcrutil/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/decred/dcrd/dcrutil/hdkeychain#example-package--DefaultWalletLayout)
Demonstrates the default hierarchical deterministic wallet layout as described
in BIP0032.
* [Audits Use Case Example](http://godoc.org/github.com/decred/dcrd/dcrutil/hdkeychain#example-package--Audits)
Demonstrates the audits use case in BIP0032.
## License
Package hdkeychain is licensed under the [copyfree](http://copyfree.org) ISC
License.

View File

@ -0,0 +1,85 @@
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package hdkeychain_test
import (
"testing"
"github.com/decred/dcrd/dcrutil/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.Child(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.Child(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()
}
}

85
dcrutil/hdkeychain/doc.go Normal file
View File

@ -0,0 +1,85 @@
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred 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 decred hierarchical deterministic
extended keys (based on 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 Child
function. The Child 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
Child function. This provides the ability to cascade the keys into a tree and
hence generate the hierarchical deterministic key chains.
Normal vs Hardened Child 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 Decred 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

View File

@ -0,0 +1,187 @@
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred 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/decred/dcrd/chaincfg"
"github.com/decred/dcrd/dcrutil/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 exteded key for the
// master node is being hard coded here.
master := "dprv3hCznBesA6jBushjx7y9NrfheE4ZshnaKYtsoLXefmLPzrXgEiXkd" +
"RMD6UngnmBYZzgNhdEd4K3PidxcaCiR6HC9hmpj8FcrP4Cv7zBwELA"
// 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.Child(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.Child(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.Child(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.Child(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.Child(0)
if err != nil {
fmt.Println(err)
return
}
// Get and show the address associated with the extended keys for the
// main decred 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: DshMmJ3bfvMDdk1mkXRD3x5xDuPwSxoYGfi
// Account 0 Internal Address 0: DsoTyktAyEDkYpgKSex6zx5rrkFDi2gAsHr
}
// 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 exteded key for the
// master node is being hard coded here.
master := "dprv3hCznBesA6jBushjx7y9NrfheE4ZshnaKYtsoLXefmLPzrXgEiXkd" +
"RMD6UngnmBYZzgNhdEd4K3PidxcaCiR6HC9hmpj8FcrP4Cv7zBwELA"
// 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.
mpks, err := masterPubKey.String()
if err != nil {
panic("unexpected error creating string of extended public key")
}
fmt.Println("Audit key N(m/*):", mpks)
// Output:
// Audit key N(m/*): dpubZ9169KDAEUnypHbWCe2Vu5TxGEcqJeNeX6XCYFU1fqw2iQZK7fsMhzsEFArbLmyUdprUw9aXHneUNd92bjc31TqC6sUduMY6PK2z4JXDS8j
}

View File

@ -0,0 +1,558 @@
// Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package hdkeychain
// References:
// [BIP32]: BIP0032 - Hierarchical Deterministic Wallets
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
"math/big"
"github.com/decred/base58"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
)
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 hardended key starts. Each
// extended key has 2^31 normal child keys and 2^31 hardned 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
)
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")
// 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 uint16
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.
func newExtendedKey(version, key, chainCode, parentFP []byte, depth uint16,
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 := chainec.Secp256k1.ScalarBaseMult(k.key)
pubKey := chainec.Secp256k1.NewPublicKey(pkx, 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
}
// 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)
}
// Child returns a derived child extended key at the given index. 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 hardended 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) Child(i uint32) (*ExtendedKey, error) {
// 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.
copy(data[1:], 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(chainec.Secp256k1.GetN()) >= 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, chainec.Secp256k1.GetN())
childKey = ilNum.Bytes()
isPrivate = true
} else {
// Case #3.
// Calculate the corresponding intermediate public key for
// intermediate private key.
ilx, ily := chainec.Secp256k1.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 := chainec.Secp256k1.ParsePubKey(k.key)
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 := chainec.Secp256k1.Add(ilx, ily, pubKey.GetX(),
pubKey.GetY())
pk := chainec.Secp256k1.NewPublicKey(childX, childY)
childKey = pk.SerializeCompressed()
}
// The fingerprint of the parent for the derived child is the first 4
// bytes of the RIPEMD160(SHA256(parentPubKey)).
parentFP := dcrutil.Hash160(k.pubKeyBytes())[:4]
return newExtendedKey(k.version, childKey, childChainCode, parentFP,
k.depth+1, i, isPrivate), nil
}
// 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
}
// ECPubKey converts the extended key to a dcrec public key and returns it.
func (k *ExtendedKey) ECPubKey() (chainec.PublicKey, error) {
return chainec.Secp256k1.ParsePubKey(k.pubKeyBytes())
}
// ECPrivKey converts the extended key to a dcrec 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() (chainec.PrivateKey, error) {
if !k.isPrivate {
return nil, ErrNotPrivExtKey
}
privKey, _ := chainec.Secp256k1.PrivKeyFromBytes(k.key)
return privKey, nil
}
// Address converts the extended key to a standard decred pay-to-pubkey-hash
// address for the passed network.
func (k *ExtendedKey) Address(net *chaincfg.Params) (*dcrutil.AddressPubKeyHash, error) {
pkHash := dcrutil.Hash160(k.pubKeyBytes())
return dcrutil.NewAddressPubKeyHash(pkHash, net, chainec.ECTypeSecp256k1)
}
// 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, error) {
if len(k.key) == 0 {
return "", fmt.Errorf("zeroed extended key")
}
var childNumBytes [4]byte
depthByte := byte(k.depth % 256)
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, depthByte)
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.HashB(chainhash.HashB(serializedBytes))[:4]
serializedBytes = append(serializedBytes, checkSum...)
return base58.Encode(serializedBytes), nil
}
// IsForNet returns whether or not the extended key is associated with the
// passed decred network.
func (k *ExtendedKey) IsForNet(net *chaincfg.Params) bool {
return bytes.Equal(k.version, net.HDPrivateKeyID[:]) ||
bytes.Equal(k.version, net.HDPublicKeyID[:])
}
// SetNet associates the extended key, and any child keys yet to be derived from
// it, with the passed network.
func (k *ExtendedKey) SetNet(net *chaincfg.Params) {
if k.isPrivate {
k.version = net.HDPrivateKeyID[:]
} else {
k.version = net.HDPublicKeyID[:]
}
}
// zero sets all bytes in the passed slice to zero. This is used to
// explicitly clear private key material from memory.
func zero(b []byte) {
lenb := len(b)
for i := 0; i < lenb; i++ {
b[i] = 0
}
}
// Zero manually clears all fields and bytes in the extended key. This can be
// used to explicitly clear key material from memory for enhanced security
// against memory scraping. This function only clears this particular key and
// not any children that have already been derived.
func (k *ExtendedKey) Zero() {
zero(k.key)
zero(k.pubKey)
zero(k.chainCode)
zero(k.parentFP)
k.version = nil
k.key = nil
k.depth = 0
k.childNum = 0
k.isPrivate = false
}
// NewMaster creates a new master node for use in creating a hierarchical
// deterministic key chain. The seed must be between 128 and 512 bits and
// should be generated by a cryptographically secure random generation source.
//
// NOTE: There is an extremely small chance (< 1 in 2^127) the provided seed
// will derive to an unusable secret key. The ErrUnusable error will be
// returned if this should occur, so the caller must check for it and generate a
// new seed accordingly.
func NewMaster(seed []byte, net *chaincfg.Params) (*ExtendedKey, error) {
// Per [BIP32], the seed must be in range [MinSeedBytes, MaxSeedBytes].
if len(seed) < MinSeedBytes || len(seed) > MaxSeedBytes {
return nil, ErrInvalidSeedLen
}
// First take the HMAC-SHA512 of the master key and the seed data:
// I = HMAC-SHA512(Key = "Bitcoin seed", Data = S)
hmac512 := hmac.New(sha512.New, masterKey)
hmac512.Write(seed)
lr := hmac512.Sum(nil)
// Split "I" into two 32-byte sequences Il and Ir where:
// Il = master secret key
// Ir = master chain code
secretKey := lr[:len(lr)/2]
chainCode := lr[len(lr)/2:]
// Ensure the key in usable.
secretKeyNum := new(big.Int).SetBytes(secretKey)
if secretKeyNum.Cmp(chainec.Secp256k1.GetN()) >= 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.HashB(chainhash.HashB(payload))[:4]
if !bytes.Equal(checkSum, expectedCheckSum) {
return nil, ErrBadChecksum
}
// Deserialize each of the payload fields.
version := payload[:4]
depth := uint16(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(chainec.Secp256k1.GetN()) >= 0 || keyNum.Sign() == 0 {
return nil, ErrUnusableSeed
}
} else {
// Ensure the public key parses correctly and is actually on the
// secp256k1 curve.
_, err := chainec.Secp256k1.ParsePubKey(keyData)
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
}

View File

@ -0,0 +1,982 @@
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package hdkeychain_test
// References:
// [BIP32]: BIP0032 - Hierarchical Deterministic Wallets
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
import (
"bytes"
"encoding/hex"
"errors"
"reflect"
"testing"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/dcrutil/hdkeychain"
)
// 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"
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: "dpubZ9169KDAEUnyoBhjjmT2VaEodr6pUTDoqCEAeqgbfr2JfkB88BbK77jbTYbcYXb2FVz7DKBdW4P618yd51MwF8DjKVopSbS7Lkgi6bowX5w",
wantPriv: "dprv3hCznBesA6jBtmoyVFPfyMSZ1qYZ3WdjdebquvkEfmRfxC9VFEFi2YDaJqHnx7uGe75eGSa3Mn3oHK11hBW7KZUrPxwbCPBmuCi1nwm182s",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 1 chain m/0H",
master: testVec1MasterHex,
path: []uint32{hkStart},
wantPub: "dpubZCGVaKZBiMo7pMgLaZm1qmchjWenTeVcUdFQkTNsFGFEA6xs4EW8PKiqYqP7HBAitt9Hw16VQkQ1tjsZQSHNWFc6bEK6bLqrbco24FzBTY4",
wantPriv: "dprv3kUQDBztdyjKuwnaL3hfKYpT7W6X2huYH5d61YSWFBebSYwEBHAXJkCpQ7rvMAxPzKqxVCGLvBqWvGxXjAyMJsV1XwKkfnQCM9KctC8k8bk",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 1 chain m/0H/1",
master: testVec1MasterHex,
path: []uint32{hkStart, 1},
wantPub: "dpubZEDyZgdnFBMHxqNhfCUwBfAg1UmXHiTmB5jKtzbAZhF8PTzy2PwAicNdkg1CmW6TARxQeUbgC7nAQenJts4YoG3KMiqcjsjgeMvwLc43w6C",
wantPriv: "dprv3nRtCZ5VAoHW4RUwQgRafSNRPUDFrmsgyY71A5eoZceVfuyL9SbZe2rcbwDW2UwpkEniE4urffgbypegscNchPajWzy9QS4cRxF8QYXsZtq",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 1 chain m/0H/1/2H",
master: testVec1MasterHex,
path: []uint32{hkStart, 1, hkStart + 2},
wantPub: "dpubZGLz7gsJAWzUksvtw3opxx5eeLq5fRaUMDABA3bdUVfnGUk5fiS5Cc3kZGTjWtYr3jrEavQQnAF6jv2WCpZtFX4uFgifXqev6ED1TM9rTCB",
wantPriv: "dprv3pYtkZK168vgrU38gXkUSjHQ2LGpEUzQ9fXrR8fGUR59YviSnm6U82XjQYhpJEUPnVcC9bguJBQU5xVM4VFcDHu9BgScGPA6mQMH4bn5Cth",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 1 chain m/0H/1/2H/2",
master: testVec1MasterHex,
path: []uint32{hkStart, 1, hkStart + 2, 2},
wantPub: "dpubZHv6Cfp2XRSWHQXZBo1dLmVM421Zdkc4MePkyBXCLFttVkCmwZkxth4ZV9PzkFP3DtD5xcVq2CPSYpJMWMaoxu1ixz4GNZFVcE2xnHP6chJ",
wantPriv: "dprv3r7zqYFjT3NiNzdnwGxGpYh6S1TJCp1zA6mSEGaqLBJFnCB94cRMp7YYLR49aTZHZ7ya1CXwQJ6rodKeU9NgQTxkPSK7pzgZRgjYkQ7rgJh",
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: "dpubZL6d9amjfRy1zeoZM2zHDU7uoMvwPqtxHRQAiJjeEtQQWjP3retQV1qKJyzUd6ZJNgbJGXjtc5pdoBcTTYTLoxQzvV9JJCzCjB2eCWpRf8T",
wantPriv: "dprv3tJXnTDSb3uE6Euo6WvvhFKfBMNfxuJt5smqyPoHEoomoBMQyhYoQSKJAHWtWxmuqdUVb8q9J2NaTkF6rYm6XDrSotkJ55bM21fffa7VV97",
net: &chaincfg.MainNetParams,
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterHex,
path: []uint32{},
wantPub: "dpubZ9169KDAEUnynoD4qvXJwmxZt3FFA5UdWn1twnRReE9AxjCKJLNFY1uBoegbFmwzA4Du7yqnu8tLivhrCCH6P3DgBS1HH5vmf8MpNXvvYT9",
wantPriv: "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterHex,
path: []uint32{0},
wantPub: "dpubZBA4RCkCybJFaNbqPuBiyfXY1rvmG1XTdCy1AY1U96dxkFqWc2i5KREMh7NYPpy7ZPMhdpFMAesex3JdFDfX4J5FEW3HjSacqEYPfwb9Cj7",
wantPriv: "dprv3jMy45BuuDETfxi59P8NTSjHPrNVq4wPRfLgRd57923L2hosj5NUEqiLYQ4i7fJtUpiXZLr2wUeToJY2Tm5sCpAJdajEHDmieVJiPQNXwu9",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 2 chain m/0/2147483647H",
master: testVec2MasterHex,
path: []uint32{0, hkStart + 2147483647},
wantPub: "dpubZDUNkZEcCRCZEizDGL9sAQbZRKSnaxQLeqN9zpueeqCyq2VY7NUGMXASacsK96S8XzNjq3YgFgwLtj8MJBToW6To9U5zxuazEyh89bjR1xA",
wantPriv: "dprv3mgHPRgK838mLK6T1p6WeBoJoJtXA1pGTHjqFuyHekcM7UTuER8fGweRRsoLqSuHa98uskVPnJnfWZEBUC1AVmXnSCPDvUFKydXNnnPHTuQ",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 2 chain m/0/2147483647H/1",
master: testVec2MasterHex,
path: []uint32{0, hkStart + 2147483647, 1},
wantPub: "dpubZF3wJh7SfggGg74QZW3EE9ei8uQSJEFgd62uyuK5iMgQzUNjpSnprgTpYz3d6Q3fXXtEEXQqpzWcP4LUVuXFsgA8JKt1Hot5kyUk4pPRhDz",
wantPriv: "dprv3oFqwZZ9bJcUmhAeJyyshvrTWtrAsHfcRYQbEzNiiH5nGvM6wVTDn6woQEz92b2EHTYZBtLi82jKEnxSouA3cVaW8YWBsw5c3f4mwAhA3d2",
net: &chaincfg.MainNetParams,
},
{
name: "test vector 2 chain m/0/2147483647H/1/2147483646H",
master: testVec2MasterHex,
path: []uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646},
wantPub: "dpubZH38NEg1CW19dGZs8NdaT4hDkz7wXPstio1mGpHSAXHpSGW3UnTrn25ERT1Mp8ae5GMoQHMbgQiPrChMXQMdx3UqS8YqFkT1pqait8fY92u",
wantPriv: "dprv3qF3177i87wMirg6sraDvqty8yZg6THpXFPSXuM5AShBiiUQbq8FhSZDGkYmBNR3RKfBrxzkKDBpsRFJfTnQfLsvpPPqRnakat6hHQA43X9",
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: "dpubZJoBFoQJ35zvEBgsfhJBssnAp8TY5gvruzQFLmyxcqRb7enVtGfSkLo2CkAZJMpa6T2fx6fUtvTgXtUvSVgAZ56bEwGxQsToeZfFV8VadE1",
wantPriv: "dprv3s15tfqzxhw8Kmo7RBEqMeyvC7uGekLniSmvbs3bckpxQ6ks1KKqfmH144Jgh3PLxkyZRcS367kp7DrtUmnG16NpnsoNhxSXRgKbJJ7MUQR",
net: &chaincfg.MainNetParams,
},
// Test vector 1 - Testnet
{
name: "test vector 1 chain m - testnet",
master: testVec1MasterHex,
path: []uint32{},
wantPub: "tpubVhnMyQmZAhoosedBTX7oacwyCNc5qtdEMoNHudUCW1R6WZTvqCZQoNJHSn4H11puwdk4qyDv2ET637EDap4r8HH3odjBC5nEjmnPcsDfLwm",
wantPriv: "tprvZUo1ZuEfLLFWfAYiMVaoDV1EeLmbSRuNzaSh7F4awft7dm8nHfFAFZyobWQyV8Qr26r8M2CmNw6nEb35HaECWFGy1vzx2ZGdyfBeaaHudoi",
net: &chaincfg.TestNet2Params,
},
{
name: "test vector 1 chain m/0H - testnet",
master: testVec1MasterHex,
path: []uint32{hkStart},
wantPub: "tpubVm3mQR7aeaowtpbnJKRnvpKsJ3A3q5u31EPY1FAU5Re1zvFfmFUE5aHXY4qmjfQcb1uFZf8mvvU1vi89vEzHPQfR5NETLqByzdthaYfQGja",
wantPriv: "tprvZY4QzuagpDFegLXKCHtnZgP8k1KZRdBBe1TwCrkrX67387vXDi9yXmy3gnz6tBTyNKcSZmu4wLtVsYzbKZhSVZH89uP7VxV4RboFfozTBMQ",
net: &chaincfg.TestNet2Params,
},
{
name: "test vector 1 chain m/0H/1 - testnet",
master: testVec1MasterHex,
path: []uint32{hkStart, 1},
wantPub: "tpubVo1FPnCBBQN83JJ9Nx9iGhsqa1Gnf9sBhgsT9nNmPrdvEHHmjQuGQrwKjuTsDzLLrZiNH8dxiHrASd2uQfmTgR6dqrkyVN5p3P2crvfgpEQ",
wantPriv: "tprvZa1tzGfHM2opppDgGvchuZw71ySJFh9LLTwrMPy9qX6wMUxdBsb1s4cqtcLgZVTQ8EZCJeYagpjaw6gkU16ht5Nr8y2WEc9UWQimC4Y6MFs",
net: &chaincfg.TestNet2Params,
},
{
name: "test vector 1 chain m/0H/1/2H - testnet",
master: testVec1MasterHex,
path: []uint32{hkStart, 1, hkStart + 2},
wantPub: "tpubVq8FwnRh6k1JqLrLeoUc3znpCsLM2rytspJJQqPEJf4a7J2tNjQAtrcSYVvPyNnjjscCDaShJLK6mtH6idGo8g8Djpe2HL13VFJgygvRmc9",
wantPriv: "tprvZc8uYGtoGNT1crmsYmwbgrr5eqVrdQG3WbNhcSyckKXbEVhjqC5vM4HxhDpzqEyyAVNgEBKdKLTT3EXQesyhPyhFoeVy6ZExqrpurCCRvrF",
net: &chaincfg.TestNet2Params,
},
{
name: "test vector 1 chain m/0H/1/2H/2 - testnet",
master: testVec1MasterHex,
path: []uint32{hkStart, 1, hkStart + 2, 2},
wantPub: "tpubVrhN2mNRTeTLMsSzuYgQRpCWcYWq1C1UtFXtDyJoARHgLZVaeaj4awdFUNrfCjcvv1y3bGY7YNTSanYx2AHir453T7yd83bd1F8eJgtdq3F",
wantPriv: "tprvZdi1dFqXdGu39PNXoX9Q4gFn4WgLbjHdX2cHRauBc5khTmAS73Qp39Jmd6BL7U4rw7k45nAfRT9qkuMi4Y6mb9ks1QNUfAmRW9DBY5g5ELT",
net: &chaincfg.TestNet2Params,
},
{
name: "test vector 1 chain m/0H/1/2H/2/1000000000 - testnet",
master: testVec1MasterHex,
path: []uint32{hkStart, 1, hkStart + 2, 2, 1000000000},
wantPub: "tpubVtstygL8beyr57j14nf4JWq5MtSCmHJNp2YHy6XF53oCMYfrZfrWBGQ1JDT95aoC4pMFuBnB8Ftdq9s3yMAFh7UKQd4f3hLL8C8KipwSek6",
wantPriv: "tprvZftYaAoEmHRYrdeXxm83wNtLorbiMpaXSochAi7dWiGDUkLi28YFdU5XSxe53yHVDdEyfiTsKBRZR2HASwVBhueZRroeuFgD6U9JTC1mUyU",
net: &chaincfg.TestNet2Params,
},
}
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 := hdkeychain.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.Child(childNum)
if err != nil {
t.Errorf("err: %v", err)
continue tests
}
}
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 := "dprv3hCznBesA6jBucms1ZhyGeFfvJfBSwfs7ZFrxS8tdYzbjDZe2UwSaL7EbYo1qa88DmtyyG5cL9tdGxHkD89JmeZTbz5sVYU4Dgtijiio4Sc"
testVec2MasterPrivKey := "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y"
tests := []struct {
name string
master string
path []uint32
wantPriv string
}{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPrivKey,
path: []uint32{},
wantPriv: "dprv3hCznBesA6jBucms1ZhyGeFfvJfBSwfs7ZFrxS8tdYzbjDZe2UwSaL7EbYo1qa88DmtyyG5cL9tdGxHkD89JmeZTbz5sVYU4Dgtijiio4Sc",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPrivKey,
path: []uint32{0},
wantPriv: "dprv3jFfEhxvVxy6NJWopujhfg7syQL71xCRgNoGUpQTtjTpCwzigwtCwssQGbRQsby7PBs1Yp8Wu7isu396qeNof13EZuxbCTJVF1xkoFAQHWj",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPrivKey,
path: []uint32{0, 1},
wantPriv: "dprv3mWLns1v1fdLhxStaJHh3BqxmTi14RHHeWdNU6oU8sSkTDmAr54yK6La2APy3rAZr9ZJAdm5asTJaqBZ3vBYVSPHqyL8kbcCp5jgqfxBs4x",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2},
wantPriv: "dprv3oDxSziXR1rQVWwWWBRKgCQU3vN6dnR3ekzHzvZRdgfVvrYSE35saJh8UdfSxCgtMn7pnbeMXWbyBbwxoncC9LMrnuH1AoJSB259c4XgmnN",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2},
wantPriv: "dprv3rYHNih25i8MqeRjhFq8mLnK4a3J63a9zkYTCFJd8kaJuNc5aDAAuG1XopkU7h93HvfbNvQaWdQLtwFmUEbDN3GCZ2Mxw6tq5ZSh8d1Chyw",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantPriv: "dprv3tKkzgLFKaX2VcQTir9JeNHWxikiKokSJtdyj7sYoiDkU3np2rc3DGYPRVmDhb2FFaAk98fnqRotQYTVCRaoyAZiHaoyNoPCFeYA9pEshBT",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPrivKey,
path: []uint32{},
wantPriv: "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPrivKey,
path: []uint32{0},
wantPriv: "dprv3jMy45BuuDETfxi59P8NTSjHPrNVq4wPRfLgRd57923L2hosj5NUEqiLYQ4i7fJtUpiXZLr2wUeToJY2Tm5sCpAJdajEHDmieVJiPQNXwu9",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647},
wantPriv: "dprv3mgHPRgAnNboAb9edL4RPscKYrNLG77BhPvFe3eiTGPSiigDeXct3WeiZ2QqRrm9TiseBuYWGEG79xkBzazpBGfym1vRXjcEo5KUi4rbhZ1",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1},
wantPriv: "dprv3onqUUAjN1xQUAW1BzwiBa5wisDCE8hX8YcAEgVe1DPvbgHVLauDt2NsZPQX6tJs6ozcQSU9GdsffhueTbxTxFMPEMPxM2iHiooMz2oQHWS",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantPriv: "dprv3r8NTJgGzAjY5cU7sLo5rhT8o2wdco2iqjks6nJoiDACTucrPMcsciccv9skwGMX69uRa8EaZofskV7YyzBDbVi6v4RXbJ4DyeZ6JpUgdUi",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPriv: "dprv3sp4xvFP9mL9UUEddSZUxrtNnhe5UcHs5wrpxdZVEFCXoT4EpYeHZJjCDhvVEQFK2KfSHXFmew6MeBuvtrJfQv1BnkiSV7xxUji66uvWasp",
},
// Custom tests to trigger specific conditions.
{
// Seed 000000000000000000000000000000da.
name: "Derived privkey with zero high byte m/0",
master: "dprv3jFfEhxvVxy6NJWopujhfg7syQL71xCRgNoGUpQTtjTpCwzigwtCwssQGbRQsby7PBs1Yp8Wu7isu396qeNof13EZuxbCTJVF1xkoFAQHWj",
path: []uint32{0},
wantPriv: "dprv3mWLns1v1fdLfeu5DKTA6NWQHLF6pFsPSwKCS6q4h4nkjm2DfuH5X2iDnW15jhHTGa3rzxSpvskuXugcbBcUUVWCETKKzjW7ja4V2jL4aw4",
},
}
tests:
for i, test := range tests {
extKey, err := hdkeychain.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.Child(childNum)
if err != nil {
t.Errorf("err: %v", err)
continue tests
}
}
privStr, _ := extKey.String()
if privStr != test.wantPriv {
t.Errorf("Child #%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 := "dpubZF8BRmciAzYoTjXZ3bbRWLVCwUKtTquact3Tr6ye77Rgmw76VyqMb9TB9KpfrvUYEM5d1Au4fQzE2BbtxRjwzGsqnWHmtQP9UV1kxZaqvb6"
testVec2MasterPubKey := "dpubZF4LSCdF9YKZfNzTVYhz4RBxsjYXqms8AQnMBHXZ8GUKoRSigG7kQnKiJt5pzk93Q8FxcdVBEkQZruSXduGtWnkwXzGnjbSovQ97dCxqaXc"
tests := []struct {
name string
master string
path []uint32
wantPub string
}{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPubKey,
path: []uint32{},
wantPub: "dpubZF8BRmciAzYoTjXZ3bbRWLVCwUKtTquact3Tr6ye77Rgmw76VyqMb9TB9KpfrvUYEM5d1Au4fQzE2BbtxRjwzGsqnWHmtQP9UV1kxZaqvb6",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPubKey,
path: []uint32{0},
wantPub: "dpubZHm6cmVU9pvfDCe3BY7iESzsEnV6xfi4DfoYvycnWLM9cryzKA84DqJ2CphYq6cfiEXgo9C3YLJA4ou81mavw9NDtNc3bLCWVqJz8Fx8qxB",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPubKey,
path: []uint32{0, 1},
wantPub: "dpubZKtA6UTDuxeXV2PcYqoe68u7cgDhbTNbA4dUJoaAvfWzuCcRQCyG5S6dbpDZb2p3B5Y2XxLtD94Nemc8QRV4RspmvGwHvE2FZsfE5Pqpeor",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2},
wantPub: "dpubZMwLXm5dRVEJRvJHU8gNV7RwHeXMRRUnYFD4f6C8uNFfqksD1FCDARTwNPsQB3Pg4LuoKXkZbPnE6woUyedwNYVPvZToT5x4Kt6rs4GKa9c",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2},
wantPub: "dpubZPfASfojwk6MhtAtkM6wPdQBr1ycVjoyqs3N51zR1keK6FcBhjBTtdW3Wn3kDLBZqgLnGozu8Gh3FV8GrFGpu3knmGVoF1Z6yGdqLU1Rz1S",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantPub: "dpubZR5Pf8cbUGikESevygwydenBaTsgcvoYnRSi7tygu23PxmVEG4GeMQj54oHFoPyRdt7Pg4sMad56yprQszbNyZVewaNEhDkn112C3mqB1fd",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPubKey,
path: []uint32{},
wantPub: "dpubZF4LSCdF9YKZfNzTVYhz4RBxsjYXqms8AQnMBHXZ8GUKoRSigG7kQnKiJt5pzk93Q8FxcdVBEkQZruSXduGtWnkwXzGnjbSovQ97dCxqaXc",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPubKey,
path: []uint32{0},
wantPub: "dpubZHJs2Z3PtHbbpaXQCi5wBKPhU8tC5ztBKUYBCYNGKk8eZ1EmBs3MhnLJbxHFMAahGnDnZT7qZxC7AXKP8PB6BDNUZgkG77moNMRmXyQ6s6s",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647},
wantPub: "dpubZJgFEUcAZawGaLZdFEX6FfQBQVgU4bUC5qvDERUTD5dfcB2AQPnJ1dKp1R2DrAzC36BznZG43317s2oBJv3PuaZmA6HqmwMu6vNna4Gfumf",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1},
wantPub: "dpubZLbgtFNyjt3k2cJtg4a3dD2iXPKFTLgNKP8rLC1p5UE3AyfRHLTcYrZ6brg8eUmGvKRrXZ7A3XyVfwGxvYtjfz8514dUoJPkmSnBmC6qQK6",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantPub: "dpubZNyWTupEG35S6d4uN93vWXpGxQuxtW9zuThQbnWpWTHwRCzxREqSSc9eDYivRGiZnEkEhPece5ciSoHtW6Khc729f6eAxjPnBgU38U9hgYw",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPub: "dpubZRuRErXqhdJaZWD1AzXB6d5w2zw7UZ7ALxiS1gHbnQbVEohBzQzsVwGRzq97pmuE7ToA6DGn2QTH4DexxzdnMvkiYUpk8Nh2KEuYUM2RCeU",
},
}
tests:
for i, test := range tests {
extKey, err := hdkeychain.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.Child(childNum)
if err != nil {
t.Errorf("err: %v", err)
continue tests
}
}
pubStr, _ := extKey.String()
if pubStr != test.wantPub {
t.Errorf("Child #%d (%s): mismatched serialized "+
"public extended key -- got: %s, want: %s", i,
test.name, pubStr, test.wantPub)
continue
}
}
}
// TestGenenerateSeed ensures the GenerateSeed function works as intended.
func TestGenenerateSeed(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 := hdkeychain.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
privKey string
privKeyErr error
pubKey string
address string
}{
{
name: "test vector 1 master node private",
extKey: "dprv3hCznBesA6jBteCheVrorP4Nix6oEWFwtk79FaLy1rPzedNGg9Jhy9y596C9uCPMeeKJMvtEGZRaxPKxGuCFgR4uo2EySsA29GsnXcrsby8",
isPrivate: true,
parentFP: 0,
privKey: "33a63922ea4e6686c9fc31daf136888297537f66c1aabe3363df06af0b8274c7",
pubKey: "039f2e1d7b50b8451911c64cf745f9ba16193b319212a64096e5679555449d8f37",
address: "Dsk8SfRLF2hssYuLcb6Gu4zh19rg2QBEDGs",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
extKey: "dpubZRuRErXqhdJaZWD1AzXB6d5w2zw7UZ7ALxiS1gHbnQbVEohBzQzsVwGRzq97pmuE7ToA6DGn2QTH4DexxzdnMvkiYUpk8Nh2KEuYUM2RCeU",
isPrivate: false,
parentFP: 4220580796,
privKeyErr: hdkeychain.ErrNotPrivExtKey,
pubKey: "03dceb0b07698ec3d6ac08ae7297e7f5e63d7fda99d3fce1ded31d36badcdd4d36",
address: "DsZcjfdSKUrEQxoyjkWEo7dM4YZKhma8wCa",
},
}
for i, test := range tests {
key, err := hdkeychain.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
}
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: "dprv3hCznBesA6jBu46PsJ9vNJoiCj9ouxtfwCBNjUYuXwbbAS4oEkF6Bnp5G3QbBAjRXy4uWWZYmC5Y71s3ovCyPLrCjEkYGPErrueuPPjvNWh",
origNet: &chaincfg.MainNetParams,
newNet: &chaincfg.SimNetParams,
newPriv: "sprvZ9xkGEZkBei2p9e1uBZRQMGtfGEQNGApP1W19PyNRqg9nuEs2X4ynkvAXWaBiGb5WKiaqcbiKgmyB1HYgcX3mnxiUs7UWeWEfe4tnSpbXLv",
newPub: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao",
isPrivate: true,
},
{
name: "simnet -> mainnet",
key: "sprvZ9xkGEZkBei2p9e1uBZRQMGtfGEQNGApP1W19PyNRqg9nuEs2X4ynkvAXWaBiGb5WKiaqcbiKgmyB1HYgcX3mnxiUs7UWeWEfe4tnSpbXLv",
origNet: &chaincfg.SimNetParams,
newNet: &chaincfg.MainNetParams,
newPriv: "dprv3hCznBesA6jBu46PsJ9vNJoiCj9ouxtfwCBNjUYuXwbbAS4oEkF6Bnp5G3QbBAjRXy4uWWZYmC5Y71s3ovCyPLrCjEkYGPErrueuPPjvNWh",
newPub: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye",
isPrivate: true,
},
// Public extended keys.
{
name: "mainnet -> simnet",
key: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye",
origNet: &chaincfg.MainNetParams,
newNet: &chaincfg.SimNetParams,
newPub: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao",
isPrivate: false,
},
{
name: "simnet -> mainnet",
key: "spubVNx6fk6e22GL2diV1D6RmVDdDJ4tmitfkERbwnNyzBD8fha1a4PELZEeNoUfNofdyJS2Y19tFgHZQ62tzKwELiBA3xVeZowLr4DJQ7xGuao",
origNet: &chaincfg.SimNetParams,
newNet: &chaincfg.MainNetParams,
newPub: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QmyavE5fgonsepiACAa7FQPsCDeLFnoSSAGiQEQhimBGGK84nye",
isPrivate: false,
},
}
for i, test := range tests {
extKey, err := hdkeychain.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 := hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 15), net)
if err != hdkeychain.ErrInvalidSeedLen {
t.Errorf("NewMaster: mismatched error -- got: %v, want: %v",
err, hdkeychain.ErrInvalidSeedLen)
}
// Should get an error when seed has too many bytes.
_, err = hdkeychain.NewMaster(bytes.Repeat([]byte{0x00}, 65), net)
if err != hdkeychain.ErrInvalidSeedLen {
t.Errorf("NewMaster: mismatched error -- got: %v, want: %v",
err, hdkeychain.ErrInvalidSeedLen)
}
// Generate a new key and neuter it to a public extended key.
seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
if err != nil {
t.Errorf("GenerateSeed: unexpected error: %v", err)
return
}
extKey, err := hdkeychain.NewMaster(seed, net)
if err != nil {
t.Errorf("NewMaster: unexpected error: %v", err)
return
}
pubKey, err := extKey.Neuter()
if err != nil {
t.Errorf("Neuter: unexpected error: %v", err)
return
}
// Deriving a hardened child extended key should fail from a public key.
_, err = pubKey.Child(hdkeychain.HardenedKeyStart)
if err != hdkeychain.ErrDeriveHardFromPublic {
t.Errorf("Child: mismatched error -- got: %v, want: %v",
err, hdkeychain.ErrDeriveHardFromPublic)
}
// NewKeyFromString failure tests.
tests := []struct {
name string
key string
err error
neuter bool
neuterErr error
}{
{
name: "invalid key length",
key: "dpub1234",
err: hdkeychain.ErrInvalidKeyLen,
},
{
name: "bad checksum",
key: "dpubZF6AWaFizAuUcbkZSs8cP8Gxzr6Sg5tLYYM7gEjZMC5GDaSHB4rW4F51zkWyo9U19BnXhc99kkEiPg248bYin8m9b8mGss9nxV6N2QpU8vj",
err: hdkeychain.ErrBadChecksum,
},
{
name: "pubkey not on curve",
key: "dpubZ9169KDAEUnyoTzA7pDGtXbxpji5LuUk8johUPVGY2CDsz6S7hahGNL6QkeYrUeAPnaJD1MBmrsUnErXScGZdjL6b2gjCRX1Z1GNhLdVCjv",
err: errors.New("pubkey [0,50963827496501355358210603252497135226159332537351223778668747140855667399507] isn't on secp256k1 curve"),
},
{
name: "unsupported version",
key: "4s9bfpYH9CkJboPNLFC4BhTENPrjfmKwUxesnqxHBjv585bCLzVdQKuKQ5TouA57FkdDskrR695Z5U2wWwDUUVWXPg7V57sLpc9dMgx74LsVZGEB",
err: nil,
neuter: true,
neuterErr: chaincfg.ErrUnknownHDKeyID,
},
}
for i, test := range tests {
extKey, err := hdkeychain.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: "dprv3hCznBesA6jBtmoyVFPfyMSZ1qYZ3WdjdebquvkEfmRfxC9VFEFi2YDaJqHnx7uGe75eGSa3Mn3oHK11hBW7KZUrPxwbCPBmuCi1nwm182s",
net: &chaincfg.MainNetParams,
},
// Test vector 2
{
name: "test vector 2 chain m",
master: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
extKey: "dprv3hCznBesA6jBtPKJbQTxRZAKG2gyj8tZKEPaCsV4e9YYFBAgRP2eTSPAeu4r8dTMt9q51j2Vdt5zNqj7jbtovvocrP1qLj6WUTLF9xYQt4y",
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 *hdkeychain.ExtendedKey) bool {
// Zeroing a key should result in it no longer being private
if key.IsPrivate() != false {
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"
_, errZeroed := key.String()
if errZeroed.Error() != wantKey {
t.Errorf("String #%d (%s): mismatched serialized key "+
"-- want %s, got %s", i, testName, wantKey,
errZeroed)
return false
}
wantErr := hdkeychain.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 := "DsWuefL3Rgj6NXoMFqqBzxY2nmh87RZyPkv"
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 := hdkeychain.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 = hdkeychain.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
}
}
}

74
dcrutil/internal_test.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
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 dcrutil
import (
"github.com/decred/base58"
"github.com/decred/dcrd/chaincfg/chainec"
"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 [2]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 [2]byte) *AddressScriptHash {
return &AddressScriptHash{
hash: hash,
netID: netID,
}
}
// TstAddressPubKey makes an AddressPubKey, setting the unexported fields with
// the parameters.
func TstAddressPubKey(serializedPubKey []byte, pubKeyFormat PubKeyFormat,
netID [2]byte) *AddressSecpPubKey {
pubKey, _ := chainec.Secp256k1.ParsePubKey(serializedPubKey)
return &AddressSecpPubKey{
pubKeyFormat: pubKeyFormat,
pubKey: chainec.PublicKey(pubKey),
pubKeyHashID: netID,
}
}
// TstAddressSAddr returns the expected script address bytes for
// P2PKH and P2SH decred addresses.
func TstAddressSAddr(addr string) []byte {
decoded := base58.Decode(addr)
return decoded[2 : 2+ripemd160.Size]
}

224
dcrutil/tx.go Normal file
View File

@ -0,0 +1,224 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"bytes"
"fmt"
"io"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/wire"
)
// assertTransactionImmutability throws a panic when a transaction has been
// mutated.
var assertTransactionImmutability = false
// 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 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 {
hash chainhash.Hash // Cached transaction hash
msgTx *wire.MsgTx // Underlying MsgTx
txTree int8 // Indicates which tx tree the tx is found in
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 {
if assertTransactionImmutability {
hash := t.msgTx.TxHash()
if !hash.IsEqual(&t.hash) {
str := fmt.Sprintf("ASSERT: mutated util.tx detected, old hash %v, "+
"new hash %v",
t.hash,
hash)
panic(str)
}
}
return &t.hash
}
// 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
}
// Tree returns the saved tree of the transaction within a block. This value
// will be TxTreeUnknown if it hasn't already explicitly been set.
func (t *Tx) Tree() int8 {
return t.txTree
}
// SetTree sets the tree of the transaction in within a block.
func (t *Tx) SetTree(tree int8) {
t.txTree = tree
}
// NewTx returns a new instance of a transaction given an underlying
// wire.MsgTx. See Tx.
func NewTx(msgTx *wire.MsgTx) *Tx {
return &Tx{
hash: msgTx.TxHash(),
msgTx: msgTx,
txTree: wire.TxTreeUnknown,
txIndex: TxIndexUnknown,
}
}
// NewTxDeep returns a new instance of a transaction given an underlying
// wire.MsgTx. Until NewTx, it completely copies the data in the msgTx
// so that there are new memory allocations, in case you were to somewhere
// else modify the data assigned to these pointers.
func NewTxDeep(msgTx *wire.MsgTx) *Tx {
txIns := make([]*wire.TxIn, len(msgTx.TxIn), len(msgTx.TxIn))
txOuts := make([]*wire.TxOut, len(msgTx.TxOut), len(msgTx.TxOut))
for i, txin := range msgTx.TxIn {
sigScript := make([]byte, len(txin.SignatureScript),
len(txin.SignatureScript))
copy(sigScript[:], txin.SignatureScript[:])
txIns[i] = &wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: txin.PreviousOutPoint.Hash,
Index: txin.PreviousOutPoint.Index,
Tree: txin.PreviousOutPoint.Tree,
},
Sequence: txin.Sequence,
ValueIn: txin.ValueIn,
BlockHeight: txin.BlockHeight,
BlockIndex: txin.BlockIndex,
SignatureScript: sigScript,
}
}
for i, txout := range msgTx.TxOut {
pkScript := make([]byte, len(txout.PkScript),
len(txout.PkScript))
copy(pkScript[:], txout.PkScript[:])
txOuts[i] = &wire.TxOut{
Value: txout.Value,
Version: txout.Version,
PkScript: pkScript,
}
}
mtx := &wire.MsgTx{
CachedHash: nil,
SerType: msgTx.SerType,
Version: msgTx.Version,
TxIn: txIns,
TxOut: txOuts,
LockTime: msgTx.LockTime,
Expiry: msgTx.Expiry,
}
return &Tx{
hash: mtx.TxHash(),
msgTx: mtx,
txTree: wire.TxTreeUnknown,
txIndex: TxIndexUnknown,
}
}
// NewTxDeepTxIns is used to deep copy a transaction, maintaining the old
// pointers to the TxOuts while replacing the old pointers to the TxIns with
// deep copies. This is to prevent races when the fraud proofs for the
// transactions are set by the miner.
func NewTxDeepTxIns(msgTx *wire.MsgTx) *Tx {
if msgTx == nil {
return nil
}
newMsgTx := new(wire.MsgTx)
// Copy the fixed fields.
newMsgTx.Version = msgTx.Version
newMsgTx.LockTime = msgTx.LockTime
newMsgTx.Expiry = msgTx.Expiry
// Copy the TxIns deeply.
for _, txIn := range msgTx.TxIn {
sigScrLen := len(txIn.SignatureScript)
sigScrCopy := make([]byte, sigScrLen, sigScrLen)
txInCopy := new(wire.TxIn)
txInCopy.PreviousOutPoint.Hash = txIn.PreviousOutPoint.Hash
txInCopy.PreviousOutPoint.Index = txIn.PreviousOutPoint.Index
txInCopy.PreviousOutPoint.Tree = txIn.PreviousOutPoint.Tree
txInCopy.Sequence = txIn.Sequence
txInCopy.ValueIn = txIn.ValueIn
txInCopy.BlockHeight = txIn.BlockHeight
txInCopy.BlockIndex = txIn.BlockIndex
txInCopy.SignatureScript = sigScrCopy
newMsgTx.AddTxIn(txIn)
}
// Shallow copy the TxOuts.
for _, txOut := range msgTx.TxOut {
newMsgTx.AddTxOut(txOut)
}
return &Tx{
hash: msgTx.TxHash(),
msgTx: msgTx,
txTree: wire.TxTreeUnknown,
txIndex: TxIndexUnknown,
}
}
// NewTxFromBytes returns a new instance of a 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 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{
hash: msgTx.TxHash(),
msgTx: &msgTx,
txTree: wire.TxTreeUnknown,
txIndex: TxIndexUnknown,
}
return &t, nil
}

124
dcrutil/tx_test.go Normal file
View File

@ -0,0 +1,124 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"bytes"
"io"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
)
// TestTx tests the API for Tx.
func TestTx(t *testing.T) {
testTx := Block100000.Transactions[0]
tx := dcrutil.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)
}
// Ensure tree type set and get work properly.
wantTree := int8(0)
tx.SetTree(0)
if gotTree := tx.Tree(); gotTree != wantTree {
t.Errorf("Index: mismatched index - got %v, want %v",
gotTree, wantTree)
}
// Ensure stake 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)
}
// Ensure tree type set and get work properly.
wantTree = int8(1)
tx.SetTree(1)
if gotTree := tx.Tree(); gotTree != wantTree {
t.Errorf("Index: mismatched index - got %v, want %v",
gotTree, wantTree)
}
// Hash for block 100,000 transaction 0.
wantHashStr := "1cbd9fe1a143a265cc819ff9d8132a7cbc4ca48eb68c0de39cfdf7ecf42cbbd1"
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
testTxBuf.Grow(testTx.SerializeSize())
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 := dcrutil.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
testTxBuf.Grow(testTx.SerializeSize())
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 = dcrutil.NewTxFromBytes(shortBytes)
if err != io.EOF {
t.Errorf("NewTxFromBytes: did not get expected error - "+
"got %v, want %v", err, io.EOF)
}
}

165
dcrutil/wif.go Normal file
View File

@ -0,0 +1,165 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil
import (
"bytes"
"errors"
"github.com/decred/base58"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
)
// 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")
// 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 {
// ecType is the type of ECDSA used.
ecType int
// PrivKey is the private key being imported or exported.
PrivKey chainec.PrivateKey
// netID is the network identifier byte used when
// WIF encoding the private key.
netID [2]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 chainec.PrivateKey, net *chaincfg.Params, ecType int) (*WIF,
error) {
if net == nil {
return nil, errors.New("no network")
}
return &WIF{ecType, privKey, net.PrivateKeyID}, nil
}
// IsForNet returns whether or not the decoded WIF structure is associated
// with the passed 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:
//
// * 2 bytes to identify the network, must be 0x80 for mainnet or 0xef for testnet
// * 1 byte for ECDSA type
// * 32 bytes of a binary-encoded, big-endian, zero-padded private 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. 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)
if decodedLen != 39 {
return nil, ErrMalformedPrivateKey
}
// Checksum is first four bytes of hash of the identifier byte
// and privKey. Verify this matches the final 4 bytes of the decoded
// private key.
cksum := chainhash.HashB(decoded[:decodedLen-4])
if !bytes.Equal(cksum[:4], decoded[decodedLen-4:]) {
return nil, ErrChecksumMismatch
}
netID := [2]byte{decoded[0], decoded[1]}
var privKey chainec.PrivateKey
ecType := 0
switch int(decoded[2]) {
case chainec.ECTypeSecp256k1:
privKeyBytes := decoded[3 : 3+chainec.Secp256k1.PrivKeyBytesLen()]
privKey, _ = chainec.Secp256k1.PrivKeyFromScalar(privKeyBytes)
ecType = chainec.ECTypeSecp256k1
case chainec.ECTypeEdwards:
privKeyBytes := decoded[3 : 3+32]
privKey, _ = chainec.Edwards.PrivKeyFromScalar(privKeyBytes)
ecType = chainec.ECTypeEdwards
case chainec.ECTypeSecSchnorr:
privKeyBytes := decoded[3 : 3+chainec.SecSchnorr.PrivKeyBytesLen()]
privKey, _ = chainec.SecSchnorr.PrivKeyFromScalar(privKeyBytes)
ecType = chainec.ECTypeSecSchnorr
}
return &WIF{ecType, privKey, 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 two bytes for the network, one byte for the ECDSA type, 32 bytes
// of private key and finally four bytes of checksum.
encodeLen := 2 + 1 + 32 + 4
a := make([]byte, 0, encodeLen)
a = append(a, w.netID[:]...)
a = append(a, byte(w.ecType))
a = append(a, w.PrivKey.Serialize()...)
cksum := chainhash.HashB(a)
a = append(a, cksum[:4]...)
return base58.Encode(a)
}
// SerializePubKey serializes the associated public key of the imported or
// exported private key in compressed format. The serialization format
// chosen depends on the value of w.ecType.
func (w *WIF) SerializePubKey() []byte {
pkx, pky := w.PrivKey.Public()
var pk chainec.PublicKey
switch w.ecType {
case chainec.ECTypeSecp256k1:
pk = chainec.Secp256k1.NewPublicKey(pkx, pky)
case chainec.ECTypeEdwards:
pk = chainec.Edwards.NewPublicKey(pkx, pky)
case chainec.ECTypeSecSchnorr:
pk = chainec.SecSchnorr.NewPublicKey(pkx, pky)
}
return pk.SerializeCompressed()
}
// DSA returns the ECDSA type for the private key.
func (w *WIF) DSA() int {
return w.ecType
}
// 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...)
}

161
dcrutil/wif_test.go Normal file
View File

@ -0,0 +1,161 @@
// Copyright (c) 2013, 2014 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrutil_test
import (
"testing"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
. "github.com/decred/dcrd/dcrutil"
)
func TestEncodeDecodeWIF(t *testing.T) {
suites := []int{
chainec.ECTypeSecp256k1,
chainec.ECTypeEdwards,
chainec.ECTypeSecSchnorr,
}
for _, suite := range suites {
var priv1, priv2 chainec.PrivateKey
switch suite {
case chainec.ECTypeSecp256k1:
priv1, _ = chainec.Secp256k1.PrivKeyFromBytes([]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})
priv2, _ = chainec.Secp256k1.PrivKeyFromBytes([]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})
case chainec.ECTypeEdwards:
priv1, _ = chainec.Edwards.PrivKeyFromScalar([]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})
priv2, _ = chainec.Edwards.PrivKeyFromScalar([]byte{
0x0c, 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})
case chainec.ECTypeSecSchnorr:
priv1, _ = chainec.SecSchnorr.PrivKeyFromBytes([]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})
priv2, _ = chainec.SecSchnorr.PrivKeyFromBytes([]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})
}
wif1, err := NewWIF(priv1, &chaincfg.MainNetParams, suite)
if err != nil {
t.Fatal(err)
}
wif2, err := NewWIF(priv2, &chaincfg.TestNet2Params, suite)
if err != nil {
t.Fatal(err)
}
wif3, err := NewWIF(priv2, &chaincfg.SimNetParams, suite)
if err != nil {
t.Fatal(err)
}
var tests []struct {
wif *WIF
encoded string
}
switch suite {
case chainec.ECTypeSecp256k1:
tests = []struct {
wif *WIF
encoded string
}{
{
wif1,
"PmQdMn8xafwaQouk8ngs1CccRCB1ZmsqQxBaxNR4vhQi5a5QB5716",
},
{
wif2,
"PtWVDUidYaiiNT5e2Sfb1Ah4evbaSopZJkkpFBuzkJYcYteugvdFg",
},
{
wif3,
"PsURoUb7FMeJQdTYea8pkbUQFBZAsxtfDcfTLGja5sCLZvLZWRtjK",
},
}
case chainec.ECTypeEdwards:
tests = []struct {
wif *WIF
encoded string
}{
{
wif1,
"PmQfJXKC2ho1633ZiVbSdCZw1y68BVXYFpyE2UfDcbQN5xa3DByDn",
},
{
wif2,
"PtWVaBGeCfbFQfgqFew8YvdrSH5TH439K7rvpo3aWnSfDvyK8ijbK",
},
{
wif3,
"PsUSAB97uSWqSr4jsnQNJMRC2Y33iD7FDymZuss9rM6PExexSPyTQ",
},
}
case chainec.ECTypeSecSchnorr:
tests = []struct {
wif *WIF
encoded string
}{
{
wif1,
"PmQhFGVRUjeRmGBPJCW2FCXFck1EoDBF6hks6auNJVQ26M4h73W9W",
},
{
wif2,
"PtWZ6y56SeRZiuMHBrUkFAbhrURogF7xzWL6PQQJ86XvZfeE3jf1a",
},
{
wif3,
"PsUVgxwa9RM9m5jBoywyzbP3SjPQ7QC4uNEjUVDsTfBeahKkmETvQ",
},
}
}
for _, test := range tests {
// Test that encoding the WIF structure matches the expected string.
s := test.wif.String()
if s != test.encoded {
t.Errorf("TestEncodeDecodePrivateKey failed: want '%s', got '%s'",
test.encoded, s)
continue
}
// Test that decoding the expected string results in the original WIF
// structure.
w, err := DecodeWIF(test.encoded)
if err != nil {
t.Error(err)
continue
}
if got := w.String(); got != test.encoded {
t.Errorf("NewWIF failed: want '%v', got '%v'", test.wif, got)
}
w.SerializePubKey()
}
}
}

View File

@ -216,7 +216,7 @@ information.
Decred scripts
* [database](https://github.com/decred/dcrd/tree/master/database) -
Provides a database interface for the Decred block chain
* [dcrutil](https://github.com/decred/dcrutil) - Provides Decred-specific
* [dcrutil](https://github.com/decred/dcrd/tree/master/dcrutil) - Provides Decred-specific
convenience functions and types
* [chainhash](https://github.com/decred/dcrd/tree/master/chaincfg/chainhash) -
Provides a generic hash type and associated functions that allows the

View File

@ -1015,7 +1015,7 @@ package main
import (
"github.com/decred/dcrrpcclient"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
"io/ioutil"
"log"
"path/filepath"
@ -1076,7 +1076,7 @@ package main
import (
"github.com/decred/dcrrpcclient"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"io/ioutil"
"log"
@ -1167,7 +1167,7 @@ package main
import (
"github.com/decred/dcrrpcclient"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"io/ioutil"
"log"

View File

@ -21,10 +21,10 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mining"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -18,9 +18,9 @@ import (
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// fakeChain is used by the pool harness to provide generated test utxos and

View File

@ -11,9 +11,9 @@ import (
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -15,9 +15,9 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API.

View File

@ -18,11 +18,11 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mempool"
"github.com/decred/dcrd/mining"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
const (

View File

@ -10,7 +10,7 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// TxDesc is a descriptor about a transaction in a transaction source along with

View File

@ -5,7 +5,7 @@
package mining
import "github.com/decred/dcrutil"
import "github.com/decred/dcrd/dcrutil"
// Policy houses the policy (configuration parameters) which is used to control
// the generation of block templates. See the documentation for

View File

@ -12,8 +12,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// FutureGetBestBlockHashResult is a future promise to deliver the result of a

View File

@ -11,8 +11,8 @@ import (
"path/filepath"
"time"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/rpcclient"
"github.com/decred/dcrutil"
)
func main() {

View File

@ -12,8 +12,8 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/rpcclient"
"github.com/decred/dcrutil"
)
func main() {

View File

@ -13,8 +13,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -12,7 +12,7 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrutil"
"github.com/decred/dcrd/dcrutil"
)
// FutureGenerateResult is a future promise to deliver the result of a

View File

@ -15,8 +15,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
var (

View File

@ -12,8 +12,8 @@ import (
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// SigHashType enumerates the available signature hashing types that the

Some files were not shown because too many files have changed in this diff Show More