mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-08-23 17:47:29 +00:00
Implement the deposit side of Voting Pools
This contains the APIs to create and retrieve Voting Pools and Series (with public/private keys) from a walletdb namespace, plus the generation of deposit addresses (using m-of-n multi-sig P2SH scripts according to the series configuration).
This commit is contained in:
parent
454d290b68
commit
24dcd206d2
17 changed files with 2913 additions and 34 deletions
|
@ -31,6 +31,8 @@ func zero(b []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Expose secretbox's Overhead const here for convenience.
|
||||||
|
Overhead = secretbox.Overhead
|
||||||
KeySize = 32
|
KeySize = 32
|
||||||
NonceSize = 24
|
NonceSize = 24
|
||||||
DefaultN = 16384 // 2^14
|
DefaultN = 16384 // 2^14
|
||||||
|
|
40
votingpool/README.md
Normal file
40
votingpool/README.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
votingpool
|
||||||
|
========
|
||||||
|
|
||||||
|
[]
|
||||||
|
(https://travis-ci.org/conformal/btcwallet)
|
||||||
|
|
||||||
|
Package votingpool provides voting pool functionality for btcwallet as
|
||||||
|
described here:
|
||||||
|
[Voting Pools](http://opentransactions.org/wiki/index.php?title=Category:Voting_Pools).
|
||||||
|
|
||||||
|
A 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. Package votingpool is licensed under the liberal ISC license.
|
||||||
|
|
||||||
|
Note that this is still a work in progress.
|
||||||
|
|
||||||
|
## Feature Overview
|
||||||
|
|
||||||
|
- Create/Load pools
|
||||||
|
- Create series
|
||||||
|
- Replace series
|
||||||
|
- Create deposit addresses
|
||||||
|
- Comprehensive test coverage
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[]
|
||||||
|
(http://godoc.org/github.com/conformal/btcwallet/votingpool)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://godoc.org/github.com/conformal/btcwallet/votingpool
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/conformal/btcwallet/votingpool
|
||||||
|
|
||||||
|
Package votingpool is licensed under the [copyfree](http://copyfree.org) ISC
|
||||||
|
License.
|
17
votingpool/cov_report.sh
Normal file
17
votingpool/cov_report.sh
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
type gocov >/dev/null 2>&1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gocov test | gocov report
|
283
votingpool/db.go
Normal file
283
votingpool/db.go
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package votingpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/snacl"
|
||||||
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants define the serialized length for a given encrypted extended
|
||||||
|
// public or private key.
|
||||||
|
const (
|
||||||
|
// We can calculate the encrypted extended key length this way:
|
||||||
|
// snacl.Overhead == overhead for encrypting (16)
|
||||||
|
// actual base58 extended key length = (111)
|
||||||
|
// snacl.NonceSize == nonce size used for encryption (24)
|
||||||
|
seriesKeyLength = snacl.Overhead + 111 + snacl.NonceSize
|
||||||
|
// 4 bytes version + 1 byte active + 4 bytes nKeys + 4 bytes reqSigs
|
||||||
|
seriesMinSerial = 4 + 1 + 4 + 4
|
||||||
|
// 15 is the max number of keys in a voting pool, 1 each for
|
||||||
|
// pubkey and privkey
|
||||||
|
seriesMaxSerial = seriesMinSerial + 15*seriesKeyLength*2
|
||||||
|
// version of serialized Series that we support
|
||||||
|
seriesMaxVersion = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// string representing a non-existent private key
|
||||||
|
seriesNullPrivKey = [seriesKeyLength]byte{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbSeriesRow struct {
|
||||||
|
version uint32
|
||||||
|
active bool
|
||||||
|
reqSigs uint32
|
||||||
|
pubKeysEncrypted [][]byte
|
||||||
|
privKeysEncrypted [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// putPool stores a voting pool in the database, creating a bucket named
|
||||||
|
// after the voting pool id.
|
||||||
|
func putPool(tx walletdb.Tx, votingPoolID []byte) error {
|
||||||
|
_, err := tx.RootBucket().CreateBucket(votingPoolID)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot create voting pool %v", votingPoolID)
|
||||||
|
return managerError(waddrmgr.ErrDatabase, str, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAllSeries returns a map of all the series stored inside a voting pool
|
||||||
|
// bucket, keyed by id.
|
||||||
|
func loadAllSeries(tx walletdb.Tx, votingPoolID []byte) (map[uint32]*dbSeriesRow, error) {
|
||||||
|
bucket := tx.RootBucket().Bucket(votingPoolID)
|
||||||
|
allSeries := make(map[uint32]*dbSeriesRow)
|
||||||
|
err := bucket.ForEach(
|
||||||
|
func(k, v []byte) error {
|
||||||
|
seriesID := bytesToUint32(k)
|
||||||
|
series, err := deserializeSeriesRow(v)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot deserialize series %v", v)
|
||||||
|
return managerError(waddrmgr.ErrSeriesStorage, str, err)
|
||||||
|
}
|
||||||
|
allSeries[seriesID] = series
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allSeries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// existsPool checks the existence of a bucket named after the given
|
||||||
|
// voting pool id.
|
||||||
|
func existsPool(tx walletdb.Tx, votingPoolID []byte) bool {
|
||||||
|
bucket := tx.RootBucket().Bucket(votingPoolID)
|
||||||
|
return bucket != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putSeries stores the given series inside a voting pool bucket named after
|
||||||
|
// votingPoolID. The voting pool bucket does not need to be created beforehand.
|
||||||
|
func putSeries(tx walletdb.Tx, votingPoolID []byte, version, ID uint32, active bool, reqSigs uint32, pubKeysEncrypted, privKeysEncrypted [][]byte) error {
|
||||||
|
row := &dbSeriesRow{
|
||||||
|
version: version,
|
||||||
|
active: active,
|
||||||
|
reqSigs: reqSigs,
|
||||||
|
pubKeysEncrypted: pubKeysEncrypted,
|
||||||
|
privKeysEncrypted: privKeysEncrypted,
|
||||||
|
}
|
||||||
|
return putSeriesRow(tx, votingPoolID, ID, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putSeriesRow stores the given series row inside a voting pool bucket named
|
||||||
|
// after votingPoolID. The voting pool bucket does not need to be created
|
||||||
|
// beforehand.
|
||||||
|
func putSeriesRow(tx walletdb.Tx, votingPoolID []byte, ID uint32, row *dbSeriesRow) error {
|
||||||
|
bucket, err := tx.RootBucket().CreateBucketIfNotExists(votingPoolID)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot create bucket %v", votingPoolID)
|
||||||
|
return managerError(waddrmgr.ErrDatabase, str, err)
|
||||||
|
}
|
||||||
|
serialized, err := serializeSeriesRow(row)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot serialize series %v", row)
|
||||||
|
return managerError(waddrmgr.ErrSeriesStorage, str, err)
|
||||||
|
}
|
||||||
|
err = bucket.Put(uint32ToBytes(ID), serialized)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot put series %v into bucket %v", serialized, votingPoolID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesStorage, str, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserializeSeriesRow deserializes a series storage into a dbSeriesRow struct.
|
||||||
|
func deserializeSeriesRow(serializedSeries []byte) (*dbSeriesRow, error) {
|
||||||
|
// The serialized series format is:
|
||||||
|
// <version><active><reqSigs><nKeys><pubKey1><privKey1>...<pubkeyN><privKeyN>
|
||||||
|
//
|
||||||
|
// 4 bytes version + 1 byte active + 4 bytes reqSigs + 4 bytes nKeys
|
||||||
|
// + seriesKeyLength * 2 * nKeys (1 for priv, 1 for pub)
|
||||||
|
|
||||||
|
// Given the above, the length of the serialized series should be
|
||||||
|
// at minimum the length of the constants.
|
||||||
|
if len(serializedSeries) < seriesMinSerial {
|
||||||
|
str := fmt.Sprintf("serialized series is too short: %v",
|
||||||
|
serializedSeries)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum number of public keys is 15 and the same for public keys
|
||||||
|
// this gives us an upper bound.
|
||||||
|
if len(serializedSeries) > seriesMaxSerial {
|
||||||
|
str := fmt.Sprintf("serialized series is too long: %v",
|
||||||
|
serializedSeries)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps track of the position of the next set of bytes to deserialize.
|
||||||
|
current := 0
|
||||||
|
row := dbSeriesRow{}
|
||||||
|
|
||||||
|
row.version = bytesToUint32(serializedSeries[current : current+4])
|
||||||
|
if row.version > seriesMaxVersion {
|
||||||
|
str := fmt.Sprintf("deserialization supports up to version %v not %v",
|
||||||
|
seriesMaxVersion, row.version)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesVersion, str, nil)
|
||||||
|
}
|
||||||
|
current += 4
|
||||||
|
|
||||||
|
row.active = serializedSeries[current] == 0x01
|
||||||
|
current++
|
||||||
|
|
||||||
|
row.reqSigs = bytesToUint32(serializedSeries[current : current+4])
|
||||||
|
current += 4
|
||||||
|
|
||||||
|
nKeys := bytesToUint32(serializedSeries[current : current+4])
|
||||||
|
current += 4
|
||||||
|
|
||||||
|
// Check to see if we have the right number of bytes to consume.
|
||||||
|
if len(serializedSeries) < current+int(nKeys)*seriesKeyLength*2 {
|
||||||
|
str := fmt.Sprintf("serialized series has not enough data: %v",
|
||||||
|
serializedSeries)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
} else if len(serializedSeries) > current+int(nKeys)*seriesKeyLength*2 {
|
||||||
|
str := fmt.Sprintf("serialized series has too much data: %v",
|
||||||
|
serializedSeries)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the pubkey/privkey pairs.
|
||||||
|
row.pubKeysEncrypted = make([][]byte, nKeys)
|
||||||
|
row.privKeysEncrypted = make([][]byte, nKeys)
|
||||||
|
for i := 0; i < int(nKeys); i++ {
|
||||||
|
pubKeyStart := current + seriesKeyLength*i*2
|
||||||
|
pubKeyEnd := current + seriesKeyLength*i*2 + seriesKeyLength
|
||||||
|
privKeyEnd := current + seriesKeyLength*(i+1)*2
|
||||||
|
row.pubKeysEncrypted[i] = serializedSeries[pubKeyStart:pubKeyEnd]
|
||||||
|
privKeyEncrypted := serializedSeries[pubKeyEnd:privKeyEnd]
|
||||||
|
if bytes.Equal(privKeyEncrypted, seriesNullPrivKey[:]) {
|
||||||
|
row.privKeysEncrypted[i] = nil
|
||||||
|
} else {
|
||||||
|
row.privKeysEncrypted[i] = privKeyEncrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &row, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serializeSeriesRow serializes a dbSeriesRow struct into storage format.
|
||||||
|
func serializeSeriesRow(row *dbSeriesRow) ([]byte, error) {
|
||||||
|
// The serialized series format is:
|
||||||
|
// <version><active><reqSigs><nKeys><pubKey1><privKey1>...<pubkeyN><privKeyN>
|
||||||
|
//
|
||||||
|
// 4 bytes version + 1 byte active + 4 bytes reqSigs + 4 bytes nKeys
|
||||||
|
// + seriesKeyLength * 2 * nKeys (1 for priv, 1 for pub)
|
||||||
|
serializedLen := 4 + 1 + 4 + 4 + (seriesKeyLength * 2 * len(row.pubKeysEncrypted))
|
||||||
|
|
||||||
|
if len(row.privKeysEncrypted) != 0 &&
|
||||||
|
len(row.pubKeysEncrypted) != len(row.privKeysEncrypted) {
|
||||||
|
str := fmt.Sprintf("different # of pub (%v) and priv (%v) keys",
|
||||||
|
len(row.pubKeysEncrypted), len(row.privKeysEncrypted))
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.version > seriesMaxVersion {
|
||||||
|
str := fmt.Sprintf("serialization supports up to version %v, not %v",
|
||||||
|
seriesMaxVersion, row.version)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesVersion, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized := make([]byte, 0, serializedLen)
|
||||||
|
serialized = append(serialized, uint32ToBytes(row.version)...)
|
||||||
|
if row.active {
|
||||||
|
serialized = append(serialized, 0x01)
|
||||||
|
} else {
|
||||||
|
serialized = append(serialized, 0x00)
|
||||||
|
}
|
||||||
|
serialized = append(serialized, uint32ToBytes(row.reqSigs)...)
|
||||||
|
nKeys := uint32(len(row.pubKeysEncrypted))
|
||||||
|
serialized = append(serialized, uint32ToBytes(nKeys)...)
|
||||||
|
|
||||||
|
var privKeyEncrypted []byte
|
||||||
|
for i, pubKeyEncrypted := range row.pubKeysEncrypted {
|
||||||
|
// check that the encrypted length is correct
|
||||||
|
if len(pubKeyEncrypted) != seriesKeyLength {
|
||||||
|
str := fmt.Sprintf("wrong length of Encrypted Public Key: %v",
|
||||||
|
pubKeyEncrypted)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
}
|
||||||
|
serialized = append(serialized, pubKeyEncrypted...)
|
||||||
|
|
||||||
|
if len(row.privKeysEncrypted) == 0 {
|
||||||
|
privKeyEncrypted = seriesNullPrivKey[:]
|
||||||
|
} else {
|
||||||
|
privKeyEncrypted = row.privKeysEncrypted[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if privKeyEncrypted == nil {
|
||||||
|
serialized = append(serialized, seriesNullPrivKey[:]...)
|
||||||
|
} else if len(privKeyEncrypted) != seriesKeyLength {
|
||||||
|
str := fmt.Sprintf("wrong length of Encrypted Private Key: %v",
|
||||||
|
len(privKeyEncrypted))
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesStorage, str, nil)
|
||||||
|
} else {
|
||||||
|
serialized = append(serialized, privKeyEncrypted...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serialized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in
|
||||||
|
// little-endian order: 1 -> [1 0 0 0].
|
||||||
|
func uint32ToBytes(number uint32) []byte {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(buf, number)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToUint32 converts a 4-byte slice in little-endian order into a 32 bit
|
||||||
|
// unsigned integer: [1 0 0 0] -> 1.
|
||||||
|
func bytesToUint32(encoded []byte) uint32 {
|
||||||
|
return binary.LittleEndian.Uint32(encoded)
|
||||||
|
}
|
70
votingpool/doc.go
Normal file
70
votingpool/doc.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package votingpool provides voting pool functionality for btcwallet.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
|
||||||
|
The purpose of the voting pool package is to make it possible to store
|
||||||
|
bitcoins using m-of-n multisig transactions. Each member of the pool
|
||||||
|
holds one of the n private keys needed to create a transaction and can
|
||||||
|
only create transactions that can spend the bitcoins if m - 1 other
|
||||||
|
members of the pool agree to it.
|
||||||
|
|
||||||
|
This package depends on the waddrmgr package, and in particular
|
||||||
|
instances of the waddrgmgr.Manager structure.
|
||||||
|
|
||||||
|
Creating a voting pool
|
||||||
|
|
||||||
|
A voting pool is created via the Create function. This function
|
||||||
|
accepts a database namespace which will be used to store all
|
||||||
|
information about the pool as well as a poolID.
|
||||||
|
|
||||||
|
Loading an existing pool
|
||||||
|
|
||||||
|
An existing voting pool is loaded via the Load function, which accepts
|
||||||
|
the database name used when creating the pool as well as the poolID.
|
||||||
|
|
||||||
|
Creating a series
|
||||||
|
|
||||||
|
A series can be created via the CreateSeries method, which accepts a
|
||||||
|
version number, a series identifier, a number of required signatures
|
||||||
|
(m in m-of-n multisig, and a set of public keys.
|
||||||
|
|
||||||
|
Deposit Addresses
|
||||||
|
|
||||||
|
A deposit address can be created via the DepositScriptAddress
|
||||||
|
method, which based on a seriesID a branch number and an index
|
||||||
|
creates a pay-to-script-hash address, where the script is a multisig
|
||||||
|
script. The public keys used as inputs for generating the address are
|
||||||
|
generated from the public keys passed to CreateSeries. In [1] the
|
||||||
|
generated public keys correspend to the lowest level or the
|
||||||
|
'address_index' in the hierarchy.
|
||||||
|
|
||||||
|
Replacing a series
|
||||||
|
|
||||||
|
A series can be replaced via the ReplaceSeries method. It accepts
|
||||||
|
the same parameters as the CreateSeries method.
|
||||||
|
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
|
||||||
|
[1] https://github.com/justusranvier/bips/blob/master/bip-draft-Hierarchy%20for%20Non-Colored%20Voting%20Pool%20Deterministic%20Multisig%20Wallets.mediawiki
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package votingpool
|
125
votingpool/example_test.go
Normal file
125
votingpool/example_test.go
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package votingpool_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/conformal/btcnet"
|
||||||
|
"github.com/conformal/btcwallet/votingpool"
|
||||||
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
_ "github.com/conformal/btcwallet/walletdb/bdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_basic() {
|
||||||
|
// This example demonstrates how to create a voting pool, create a
|
||||||
|
// series, get a deposit address from a series and lastly how to
|
||||||
|
// replace a series.
|
||||||
|
|
||||||
|
// Create a new wallet DB.
|
||||||
|
dir, err := ioutil.TempDir("", "pool_test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create db dir: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create wallet DB: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create a new walletdb namespace for the address manager.
|
||||||
|
mgrNamespace, err := db.Namespace([]byte("waddrmgr"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create addr manager DB namespace: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the address manager
|
||||||
|
mgr, err := waddrmgr.Create(mgrNamespace, seed, pubPassphrase, privPassphrase,
|
||||||
|
&btcnet.MainNetParams, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create addr manager: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mgr.Close()
|
||||||
|
|
||||||
|
// Create a walletdb for votingpools.
|
||||||
|
vpNamespace, err := db.Namespace([]byte("votingpool"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create VotingPool DB namespace: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the voting pool.
|
||||||
|
pool, err := votingpool.Create(vpNamespace, mgr, []byte{0x00})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Voting Pool creation failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a 2-of-3 series.
|
||||||
|
apiVersion := uint32(1)
|
||||||
|
seriesID := uint32(1)
|
||||||
|
requiredSignatures := uint32(2)
|
||||||
|
pubKeys := []string{
|
||||||
|
"xpub661MyMwAqRbcFDDrR5jY7LqsRioFDwg3cLjc7tML3RRcfYyhXqqgCH5SqMSQdpQ1Xh8EtVwcfm8psD8zXKPcRaCVSY4GCqbb3aMEs27GitE",
|
||||||
|
"xpub661MyMwAqRbcGsxyD8hTmJFtpmwoZhy4NBBVxzvFU8tDXD2ME49A6JjQCYgbpSUpHGP1q4S2S1Pxv2EqTjwfERS5pc9Q2yeLkPFzSgRpjs9",
|
||||||
|
"xpub661MyMwAqRbcEbc4uYVXvQQpH9L3YuZLZ1gxCmj59yAhNy33vXxbXadmRpx5YZEupNSqWRrR7PqU6duS2FiVCGEiugBEa5zuEAjsyLJjKCh",
|
||||||
|
}
|
||||||
|
err = pool.CreateSeries(apiVersion, seriesID, requiredSignatures, pubKeys)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot create series: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a deposit address.
|
||||||
|
branch := uint32(0) // The change branch
|
||||||
|
index := uint32(1)
|
||||||
|
addr, err := pool.DepositScriptAddress(seriesID, branch, index)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("DepositScriptAddress failed for series: %d, branch: %d, index: %d\n",
|
||||||
|
seriesID, branch, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Generated deposit address:", addr.EncodeAddress())
|
||||||
|
|
||||||
|
// Replace the existing series with a 3-of-5 series.
|
||||||
|
pubKeys = []string{
|
||||||
|
"xpub661MyMwAqRbcFQfXKHwz8ZbTtePwAKu8pmGYyVrWEM96DYUTWDYipMnHrFcemZHn13jcRMfsNU3UWQUudiaE7mhkWCHGFRMavF167DQM4Va",
|
||||||
|
"xpub661MyMwAqRbcGnTEXx3ehjx8EiqQGnL4uhwZw3ZxvZAa2E6E4YVAp63UoVtvm2vMDDF8BdPpcarcf7PWcEKvzHhxzAYw1zG23C2egeh82AR",
|
||||||
|
"xpub661MyMwAqRbcG83KwFyr1RVrNUmqVwYxV6nzxbqoRTNc8fRnWxq1yQiTBifTHhevcEM9ucZ1TqFS7Kv17Gd81cesv6RDrrvYS9SLPjPXhV5",
|
||||||
|
"xpub661MyMwAqRbcFGJbLPhMjtpC1XntFpg6jjQWjr6yXN8b9wfS1RiU5EhJt5L7qoFuidYawc3XJoLjT2PcjVpXryS3hn1WmSPCyvQDNuKsfgM",
|
||||||
|
"xpub661MyMwAqRbcGJDX4GYocn7qCzvMJwNisxpzkYZAakcvXtWV6CanXuz9xdfe5kTptFMJ4hDt2iTiT11zyN14u8R5zLvoZ1gnEVqNLxp1r3v",
|
||||||
|
"xpub661MyMwAqRbcG13FtwvZVaA15pTerP4JdAGvytPykqDr2fKXePqw3wLhCALPAixsE176jFkc2ac9K3tnF4KwaTRKUqFF5apWD6XL9LHCu7E",
|
||||||
|
}
|
||||||
|
requiredSignatures = 3
|
||||||
|
err = pool.ReplaceSeries(apiVersion, seriesID, requiredSignatures, pubKeys)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot replace series: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Generated deposit address: 3QTzpc9d3tTbNLJLB7xwt87nWM38boAhAw
|
||||||
|
}
|
117
votingpool/internal_test.go
Normal file
117
votingpool/internal_test.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package votingpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcutil/hdkeychain"
|
||||||
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TstPutSeries transparently wraps the voting pool putSeries method.
|
||||||
|
func (vp *Pool) TstPutSeries(version, seriesID, reqSigs uint32, inRawPubKeys []string) error {
|
||||||
|
return vp.putSeries(version, seriesID, reqSigs, inRawPubKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
var TstBranchOrder = branchOrder
|
||||||
|
|
||||||
|
// TstExistsSeries checks whether a series is stored in the database.
|
||||||
|
func (vp *Pool) TstExistsSeries(seriesID uint32) (bool, error) {
|
||||||
|
return vp.existsSeries(seriesID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstNamespace exposes the Pool's namespace as it's needed in some tests.
|
||||||
|
func (vp *Pool) TstNamespace() walletdb.Namespace {
|
||||||
|
return vp.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstGetRawPublicKeys gets a series public keys in string format.
|
||||||
|
func (s *SeriesData) TstGetRawPublicKeys() []string {
|
||||||
|
rawKeys := make([]string, len(s.publicKeys))
|
||||||
|
for i, key := range s.publicKeys {
|
||||||
|
rawKeys[i] = key.String()
|
||||||
|
}
|
||||||
|
return rawKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstGetRawPrivateKeys gets a series private keys in string format.
|
||||||
|
func (s *SeriesData) TstGetRawPrivateKeys() []string {
|
||||||
|
rawKeys := make([]string, len(s.privateKeys))
|
||||||
|
for i, key := range s.privateKeys {
|
||||||
|
if key != nil {
|
||||||
|
rawKeys[i] = key.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstGetReqSigs expose the series reqSigs attribute.
|
||||||
|
func (s *SeriesData) TstGetReqSigs() uint32 {
|
||||||
|
return s.reqSigs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstEmptySeriesLookup empties the voting pool seriesLookup attribute.
|
||||||
|
func (vp *Pool) TstEmptySeriesLookup() {
|
||||||
|
vp.seriesLookup = make(map[uint32]*SeriesData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstDecryptExtendedKey expose the decryptExtendedKey method.
|
||||||
|
func (vp *Pool) TstDecryptExtendedKey(keyType waddrmgr.CryptoKeyType, encrypted []byte) (*hdkeychain.ExtendedKey, error) {
|
||||||
|
return vp.decryptExtendedKey(keyType, encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeriesRow mimics dbSeriesRow defined in db.go .
|
||||||
|
type SeriesRow struct {
|
||||||
|
Version uint32
|
||||||
|
Active bool
|
||||||
|
ReqSigs uint32
|
||||||
|
PubKeysEncrypted [][]byte
|
||||||
|
PrivKeysEncrypted [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSeries wraps serializeSeriesRow by passing it a freshly-built
|
||||||
|
// dbSeriesRow.
|
||||||
|
func SerializeSeries(version uint32, active bool, reqSigs uint32, pubKeys, privKeys [][]byte) ([]byte, error) {
|
||||||
|
row := &dbSeriesRow{
|
||||||
|
version: version,
|
||||||
|
active: active,
|
||||||
|
reqSigs: reqSigs,
|
||||||
|
pubKeysEncrypted: pubKeys,
|
||||||
|
privKeysEncrypted: privKeys,
|
||||||
|
}
|
||||||
|
return serializeSeriesRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeserializeSeries wraps deserializeSeriesRow and returns a freshly-built
|
||||||
|
// SeriesRow.
|
||||||
|
func DeserializeSeries(serializedSeries []byte) (*SeriesRow, error) {
|
||||||
|
row, err := deserializeSeriesRow(serializedSeries)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SeriesRow{
|
||||||
|
Version: row.version,
|
||||||
|
Active: row.active,
|
||||||
|
ReqSigs: row.reqSigs,
|
||||||
|
PubKeysEncrypted: row.pubKeysEncrypted,
|
||||||
|
PrivKeysEncrypted: row.privKeysEncrypted,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var TstValidateAndDecryptKeys = validateAndDecryptKeys
|
620
votingpool/pool.go
Normal file
620
votingpool/pool.go
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package votingpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/conformal/btcscript"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcutil/hdkeychain"
|
||||||
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minSeriesPubKeys = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// SeriesData represents a Series for a given Pool.
|
||||||
|
type SeriesData struct {
|
||||||
|
version uint32
|
||||||
|
// Whether or not a series is active. This is serialized/deserialized but
|
||||||
|
// for now there's no way to deactivate a series.
|
||||||
|
active bool
|
||||||
|
// A.k.a. "m" in "m of n signatures needed".
|
||||||
|
reqSigs uint32
|
||||||
|
publicKeys []*hdkeychain.ExtendedKey
|
||||||
|
privateKeys []*hdkeychain.ExtendedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool represents an arrangement of notary servers to securely
|
||||||
|
// store and account for customer cryptocurrency deposits and to redeem
|
||||||
|
// valid withdrawals. For details about how the arrangement works, see
|
||||||
|
// http://opentransactions.org/wiki/index.php?title=Category:Voting_Pools
|
||||||
|
type Pool struct {
|
||||||
|
ID []byte
|
||||||
|
seriesLookup map[uint32]*SeriesData
|
||||||
|
manager *waddrmgr.Manager
|
||||||
|
namespace walletdb.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new entry in the database with the given ID
|
||||||
|
// and returns the Pool representing it.
|
||||||
|
func Create(namespace walletdb.Namespace, m *waddrmgr.Manager, poolID []byte) (*Pool, error) {
|
||||||
|
err := namespace.Update(
|
||||||
|
func(tx walletdb.Tx) error {
|
||||||
|
return putPool(tx, poolID)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("unable to add voting pool %v to db", poolID)
|
||||||
|
return nil, managerError(waddrmgr.ErrVotingPoolAlreadyExists, str, err)
|
||||||
|
}
|
||||||
|
return newPool(namespace, m, poolID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load fetches the entry in the database with the given ID and returns the Pool
|
||||||
|
// representing it.
|
||||||
|
func Load(namespace walletdb.Namespace, m *waddrmgr.Manager, poolID []byte) (*Pool, error) {
|
||||||
|
err := namespace.View(
|
||||||
|
func(tx walletdb.Tx) error {
|
||||||
|
if exists := existsPool(tx, poolID); !exists {
|
||||||
|
str := fmt.Sprintf("unable to find voting pool %v in db", poolID)
|
||||||
|
return managerError(waddrmgr.ErrVotingPoolNotExists, str, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vp := newPool(namespace, m, poolID)
|
||||||
|
if err = vp.LoadAllSeries(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPool creates a new Pool instance.
|
||||||
|
func newPool(namespace walletdb.Namespace, m *waddrmgr.Manager, poolID []byte) *Pool {
|
||||||
|
return &Pool{
|
||||||
|
ID: poolID,
|
||||||
|
seriesLookup: make(map[uint32]*SeriesData),
|
||||||
|
manager: m,
|
||||||
|
namespace: namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndGetDepositScript generates and returns a deposit script for the given seriesID,
|
||||||
|
// branch and index of the Pool identified by poolID.
|
||||||
|
func LoadAndGetDepositScript(namespace walletdb.Namespace, m *waddrmgr.Manager, poolID string, seriesID, branch, index uint32) ([]byte, error) {
|
||||||
|
pid := []byte(poolID)
|
||||||
|
vp, err := Load(namespace, m, pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
script, err := vp.DepositScript(seriesID, branch, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndCreateSeries loads the Pool with the given ID, creating a new one if it doesn't
|
||||||
|
// yet exist, and then creates and returns a Series with the given seriesID, rawPubKeys
|
||||||
|
// and reqSigs. See CreateSeries for the constraints enforced on rawPubKeys and reqSigs.
|
||||||
|
func LoadAndCreateSeries(namespace walletdb.Namespace, m *waddrmgr.Manager, version uint32,
|
||||||
|
poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error {
|
||||||
|
pid := []byte(poolID)
|
||||||
|
vp, err := Load(namespace, m, pid)
|
||||||
|
if err != nil {
|
||||||
|
managerErr := err.(waddrmgr.ManagerError)
|
||||||
|
if managerErr.ErrorCode == waddrmgr.ErrVotingPoolNotExists {
|
||||||
|
vp, err = Create(namespace, m, pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vp.CreateSeries(version, seriesID, reqSigs, rawPubKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndReplaceSeries loads the voting pool with the given ID and calls ReplaceSeries,
|
||||||
|
// passing the given series ID, public keys and reqSigs to it.
|
||||||
|
func LoadAndReplaceSeries(namespace walletdb.Namespace, m *waddrmgr.Manager, version uint32,
|
||||||
|
poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error {
|
||||||
|
pid := []byte(poolID)
|
||||||
|
vp, err := Load(namespace, m, pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return vp.ReplaceSeries(version, seriesID, reqSigs, rawPubKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndEmpowerSeries loads the voting pool with the given ID and calls EmpowerSeries,
|
||||||
|
// passing the given series ID and private key to it.
|
||||||
|
func LoadAndEmpowerSeries(namespace walletdb.Namespace, m *waddrmgr.Manager,
|
||||||
|
poolID string, seriesID uint32, rawPrivKey string) error {
|
||||||
|
pid := []byte(poolID)
|
||||||
|
pool, err := Load(namespace, m, pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pool.EmpowerSeries(seriesID, rawPrivKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSeries returns the series with the given ID, or nil if it doesn't
|
||||||
|
// exist.
|
||||||
|
func (vp *Pool) GetSeries(seriesID uint32) *SeriesData {
|
||||||
|
series, exists := vp.seriesLookup[seriesID]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return series
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveSeriesToDisk stores the given series ID and data in the database,
|
||||||
|
// first encrypting the public/private extended keys.
|
||||||
|
func (vp *Pool) saveSeriesToDisk(seriesID uint32, data *SeriesData) error {
|
||||||
|
var err error
|
||||||
|
encryptedPubKeys := make([][]byte, len(data.publicKeys))
|
||||||
|
for i, pubKey := range data.publicKeys {
|
||||||
|
encryptedPubKeys[i], err = vp.manager.Encrypt(
|
||||||
|
waddrmgr.CKTPublic, []byte(pubKey.String()))
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("key %v failed encryption", pubKey)
|
||||||
|
return managerError(waddrmgr.ErrCrypto, str, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encryptedPrivKeys := make([][]byte, len(data.privateKeys))
|
||||||
|
for i, privKey := range data.privateKeys {
|
||||||
|
if privKey == nil {
|
||||||
|
encryptedPrivKeys[i] = nil
|
||||||
|
} else {
|
||||||
|
encryptedPrivKeys[i], err = vp.manager.Encrypt(
|
||||||
|
waddrmgr.CKTPrivate, []byte(privKey.String()))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("key %v failed encryption", privKey)
|
||||||
|
return managerError(waddrmgr.ErrCrypto, str, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vp.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
return putSeries(tx, vp.ID, data.version, seriesID, data.active,
|
||||||
|
data.reqSigs, encryptedPubKeys, encryptedPrivKeys)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot put series #%d into db", seriesID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesStorage, str, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalKeyOrder will return a copy of the input canonically
|
||||||
|
// ordered which is defined to be lexicographical.
|
||||||
|
func CanonicalKeyOrder(keys []string) []string {
|
||||||
|
orderedKeys := make([]string, len(keys))
|
||||||
|
copy(orderedKeys, keys)
|
||||||
|
sort.Sort(sort.StringSlice(orderedKeys))
|
||||||
|
return orderedKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the given slice of strings into a slice of ExtendedKeys,
|
||||||
|
// checking that all of them are valid public (and not private) keys,
|
||||||
|
// and that there are no duplicates.
|
||||||
|
func convertAndValidatePubKeys(rawPubKeys []string) ([]*hdkeychain.ExtendedKey, error) {
|
||||||
|
seenKeys := make(map[string]bool)
|
||||||
|
keys := make([]*hdkeychain.ExtendedKey, len(rawPubKeys))
|
||||||
|
for i, rawPubKey := range rawPubKeys {
|
||||||
|
if _, seen := seenKeys[rawPubKey]; seen {
|
||||||
|
str := fmt.Sprintf("duplicated public key: %v", rawPubKey)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyDuplicate, str, nil)
|
||||||
|
}
|
||||||
|
seenKeys[rawPubKey] = true
|
||||||
|
|
||||||
|
key, err := hdkeychain.NewKeyFromString(rawPubKey)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("invalid extended public key %v", rawPubKey)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.IsPrivate() {
|
||||||
|
str := fmt.Sprintf("private keys not accepted: %v", rawPubKey)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyIsPrivate, str, nil)
|
||||||
|
}
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putSeries creates a new seriesData with the given arguments, ordering the
|
||||||
|
// given public keys (using CanonicalKeyOrder), validating and converting them
|
||||||
|
// to hdkeychain.ExtendedKeys, saves that to disk and adds it to this voting
|
||||||
|
// pool's seriesLookup map. It also ensures inRawPubKeys has at least
|
||||||
|
// minSeriesPubKeys items and reqSigs is not greater than the number of items in
|
||||||
|
// inRawPubKeys.
|
||||||
|
func (vp *Pool) putSeries(version, seriesID, reqSigs uint32, inRawPubKeys []string) error {
|
||||||
|
if len(inRawPubKeys) < minSeriesPubKeys {
|
||||||
|
str := fmt.Sprintf("need at least %d public keys to create a series", minSeriesPubKeys)
|
||||||
|
return managerError(waddrmgr.ErrTooFewPublicKeys, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqSigs > uint32(len(inRawPubKeys)) {
|
||||||
|
str := fmt.Sprintf(
|
||||||
|
"the number of required signatures cannot be more than the number of keys")
|
||||||
|
return managerError(waddrmgr.ErrTooManyReqSignatures, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawPubKeys := CanonicalKeyOrder(inRawPubKeys)
|
||||||
|
|
||||||
|
keys, err := convertAndValidatePubKeys(rawPubKeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &SeriesData{
|
||||||
|
version: version,
|
||||||
|
active: false,
|
||||||
|
reqSigs: reqSigs,
|
||||||
|
publicKeys: keys,
|
||||||
|
privateKeys: make([]*hdkeychain.ExtendedKey, len(keys)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vp.saveSeriesToDisk(seriesID, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vp.seriesLookup[seriesID] = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSeries will create and return a new non-existing series.
|
||||||
|
//
|
||||||
|
// - rawPubKeys has to contain three or more public keys;
|
||||||
|
// - reqSigs has to be less or equal than the number of public keys in rawPubKeys.
|
||||||
|
func (vp *Pool) CreateSeries(version, seriesID, reqSigs uint32, rawPubKeys []string) error {
|
||||||
|
if series := vp.GetSeries(seriesID); series != nil {
|
||||||
|
str := fmt.Sprintf("series #%d already exists", seriesID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesAlreadyExists, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vp.putSeries(version, seriesID, reqSigs, rawPubKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceSeries will replace an already existing series.
|
||||||
|
//
|
||||||
|
// - rawPubKeys has to contain three or more public keys
|
||||||
|
// - reqSigs has to be less or equal than the number of public keys in rawPubKeys.
|
||||||
|
func (vp *Pool) ReplaceSeries(version, seriesID, reqSigs uint32, rawPubKeys []string) error {
|
||||||
|
series := vp.GetSeries(seriesID)
|
||||||
|
if series == nil {
|
||||||
|
str := fmt.Sprintf("series #%d does not exist, cannot replace it", seriesID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesNotExists, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if series.IsEmpowered() {
|
||||||
|
str := fmt.Sprintf("series #%d has private keys and cannot be replaced", seriesID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesAlreadyEmpowered, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vp.putSeries(version, seriesID, reqSigs, rawPubKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptExtendedKey uses Manager.Decrypt() to decrypt the encrypted byte slice and return
|
||||||
|
// an extended (public or private) key representing it.
|
||||||
|
func (vp *Pool) decryptExtendedKey(keyType waddrmgr.CryptoKeyType, encrypted []byte) (*hdkeychain.ExtendedKey, error) {
|
||||||
|
decrypted, err := vp.manager.Decrypt(keyType, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot decrypt key %v", encrypted)
|
||||||
|
return nil, managerError(waddrmgr.ErrCrypto, str, err)
|
||||||
|
}
|
||||||
|
result, err := hdkeychain.NewKeyFromString(string(decrypted))
|
||||||
|
zero(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot get key from string %v", decrypted)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAndDecryptSeriesKeys checks that the length of the public and private key
|
||||||
|
// slices is the same, decrypts them, ensures the non-nil private keys have a matching
|
||||||
|
// public key and returns them.
|
||||||
|
func validateAndDecryptKeys(rawPubKeys, rawPrivKeys [][]byte, vp *Pool) (pubKeys, privKeys []*hdkeychain.ExtendedKey, err error) {
|
||||||
|
pubKeys = make([]*hdkeychain.ExtendedKey, len(rawPubKeys))
|
||||||
|
privKeys = make([]*hdkeychain.ExtendedKey, len(rawPrivKeys))
|
||||||
|
if len(pubKeys) != len(privKeys) {
|
||||||
|
return nil, nil, managerError(waddrmgr.ErrKeysPrivatePublicMismatch,
|
||||||
|
"the pub key and priv key arrays should have the same number of elements",
|
||||||
|
nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, encryptedPub := range rawPubKeys {
|
||||||
|
pubKey, err := vp.decryptExtendedKey(waddrmgr.CKTPublic, encryptedPub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pubKeys[i] = pubKey
|
||||||
|
|
||||||
|
encryptedPriv := rawPrivKeys[i]
|
||||||
|
var privKey *hdkeychain.ExtendedKey
|
||||||
|
if encryptedPriv == nil {
|
||||||
|
privKey = nil
|
||||||
|
} else {
|
||||||
|
privKey, err = vp.decryptExtendedKey(waddrmgr.CKTPrivate, encryptedPriv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
privKeys[i] = privKey
|
||||||
|
|
||||||
|
if privKey != nil {
|
||||||
|
checkPubKey, err := privKey.Neuter()
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("cannot neuter key %v", privKey)
|
||||||
|
return nil, nil, managerError(waddrmgr.ErrKeyNeuter, str, err)
|
||||||
|
}
|
||||||
|
if pubKey.String() != checkPubKey.String() {
|
||||||
|
str := fmt.Sprintf("public key %v different than expected %v",
|
||||||
|
pubKey, checkPubKey)
|
||||||
|
return nil, nil, managerError(waddrmgr.ErrKeyMismatch, str, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pubKeys, privKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAllSeries fetches all series (decrypting their public and private
|
||||||
|
// extended keys) for this Pool from the database and populates the
|
||||||
|
// seriesLookup map with them. If there are any private extended keys for
|
||||||
|
// a series, it will also ensure they have a matching extended public key
|
||||||
|
// in that series.
|
||||||
|
func (vp *Pool) LoadAllSeries() error {
|
||||||
|
var series map[uint32]*dbSeriesRow
|
||||||
|
err := vp.namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
var err error
|
||||||
|
series, err = loadAllSeries(tx, vp.ID)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for id, series := range series {
|
||||||
|
pubKeys, privKeys, err := validateAndDecryptKeys(
|
||||||
|
series.pubKeysEncrypted, series.privKeysEncrypted, vp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vp.seriesLookup[id] = &SeriesData{
|
||||||
|
publicKeys: pubKeys,
|
||||||
|
privateKeys: privKeys,
|
||||||
|
reqSigs: series.reqSigs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// existsSeries checks whether a series is stored in the database.
|
||||||
|
// Used solely by the series creation test.
|
||||||
|
func (vp *Pool) existsSeries(seriesID uint32) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := vp.namespace.View(
|
||||||
|
func(tx walletdb.Tx) error {
|
||||||
|
bucket := tx.RootBucket().Bucket(vp.ID)
|
||||||
|
if bucket == nil {
|
||||||
|
exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
exists = bucket.Get(uint32ToBytes(seriesID)) != nil
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the order of the pubkeys based on branch number.
|
||||||
|
// Given the three pubkeys ABC, this would mean:
|
||||||
|
// - branch 0: CBA (reversed)
|
||||||
|
// - branch 1: ABC (first key priority)
|
||||||
|
// - branch 2: BAC (second key priority)
|
||||||
|
// - branch 3: CAB (third key priority)
|
||||||
|
func branchOrder(pks []*hdkeychain.ExtendedKey, branch uint32) ([]*hdkeychain.ExtendedKey, error) {
|
||||||
|
if pks == nil {
|
||||||
|
// This really shouldn't happen, but we want to be good citizens, so we
|
||||||
|
// return an error instead of crashing.
|
||||||
|
return nil, managerError(waddrmgr.ErrInvalidValue, "pks cannot be nil", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if branch > uint32(len(pks)) {
|
||||||
|
return nil, managerError(waddrmgr.ErrInvalidBranch, "branch number is bigger than number of public keys", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if branch == 0 {
|
||||||
|
numKeys := len(pks)
|
||||||
|
res := make([]*hdkeychain.ExtendedKey, numKeys)
|
||||||
|
copy(res, pks)
|
||||||
|
// reverse pk
|
||||||
|
for i, j := 0, numKeys-1; i < j; i, j = i+1, j-1 {
|
||||||
|
res[i], res[j] = res[j], res[i]
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := make([]*hdkeychain.ExtendedKey, len(pks))
|
||||||
|
tmp[0] = pks[branch-1]
|
||||||
|
j := 1
|
||||||
|
for i := 0; i < len(pks); i++ {
|
||||||
|
if i != int(branch-1) {
|
||||||
|
tmp[j] = pks[i]
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositScriptAddress constructs a multi-signature redemption script using DepositScript
|
||||||
|
// and returns the pay-to-script-hash-address for that script.
|
||||||
|
func (vp *Pool) DepositScriptAddress(seriesID, branch, index uint32) (btcutil.Address, error) {
|
||||||
|
script, err := vp.DepositScript(seriesID, branch, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
scriptHash := btcutil.Hash160(script)
|
||||||
|
|
||||||
|
return btcutil.NewAddressScriptHashFromHash(scriptHash, vp.manager.Net())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositScript constructs and returns a multi-signature redemption script where
|
||||||
|
// a certain number (Series.reqSigs) of the public keys belonging to the series
|
||||||
|
// with the given ID are required to sign the transaction for it to be successful.
|
||||||
|
func (vp *Pool) DepositScript(seriesID, branch, index uint32) ([]byte, error) {
|
||||||
|
series := vp.GetSeries(seriesID)
|
||||||
|
if series == nil {
|
||||||
|
str := fmt.Sprintf("series #%d does not exist", seriesID)
|
||||||
|
return nil, managerError(waddrmgr.ErrSeriesNotExists, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeys, err := branchOrder(series.publicKeys, branch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pks := make([]*btcutil.AddressPubKey, len(pubKeys))
|
||||||
|
for i, key := range pubKeys {
|
||||||
|
child, err := key.Child(index)
|
||||||
|
// TODO: implement getting the next index until we find a valid one,
|
||||||
|
// in case there is a hdkeychain.ErrInvalidChild.
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("child #%d for this pubkey %d does not exist", index, i)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
pubkey, err := child.ECPubKey()
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("child #%d for this pubkey %d does not exist", index, i)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
pks[i], err = btcutil.NewAddressPubKey(pubkey.SerializeCompressed(), vp.manager.Net())
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf(
|
||||||
|
"child #%d for this pubkey %d could not be converted to an address",
|
||||||
|
index, i)
|
||||||
|
return nil, managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := btcscript.MultiSigScript(pks, int(series.reqSigs))
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("error while making multisig script hash, %d", len(pks))
|
||||||
|
return nil, managerError(waddrmgr.ErrScriptCreation, str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmpowerSeries adds the given extended private key (in raw format) to the
|
||||||
|
// series with the given ID, thus allowing it to sign deposit/withdrawal
|
||||||
|
// scripts. The series with the given ID must exist, the key must be a valid
|
||||||
|
// private extended key and must match one of the series' extended public keys.
|
||||||
|
func (vp *Pool) EmpowerSeries(seriesID uint32, rawPrivKey string) error {
|
||||||
|
// make sure this series exists
|
||||||
|
series := vp.GetSeries(seriesID)
|
||||||
|
if series == nil {
|
||||||
|
str := fmt.Sprintf("series %d does not exist for this voting pool",
|
||||||
|
seriesID)
|
||||||
|
return managerError(waddrmgr.ErrSeriesNotExists, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the private key is valid.
|
||||||
|
privKey, err := hdkeychain.NewKeyFromString(rawPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("invalid extended private key %v", rawPrivKey)
|
||||||
|
return managerError(waddrmgr.ErrKeyChain, str, err)
|
||||||
|
}
|
||||||
|
if !privKey.IsPrivate() {
|
||||||
|
str := fmt.Sprintf(
|
||||||
|
"to empower a series you need the extended private key, not an extended public key %v",
|
||||||
|
privKey)
|
||||||
|
return managerError(waddrmgr.ErrKeyIsPublic, str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := privKey.Neuter()
|
||||||
|
if err != nil {
|
||||||
|
str := fmt.Sprintf("invalid extended private key %v, can't convert to public key",
|
||||||
|
rawPrivKey)
|
||||||
|
return managerError(waddrmgr.ErrKeyNeuter, str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lookingFor := pubKey.String()
|
||||||
|
found := false
|
||||||
|
|
||||||
|
// Make sure the private key has the corresponding public key in the series,
|
||||||
|
// to be able to empower it.
|
||||||
|
for i, publicKey := range series.publicKeys {
|
||||||
|
if publicKey.String() == lookingFor {
|
||||||
|
found = true
|
||||||
|
series.privateKeys[i] = privKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
str := fmt.Sprintf(
|
||||||
|
"private Key does not have a corresponding public key in this series")
|
||||||
|
return managerError(waddrmgr.ErrKeysPrivatePublicMismatch, str, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vp.saveSeriesToDisk(seriesID, series)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpowered returns true if this series is empowered (i.e. if it has
|
||||||
|
// at least one private key loaded).
|
||||||
|
func (s *SeriesData) IsEmpowered() bool {
|
||||||
|
for _, key := range s.privateKeys {
|
||||||
|
if key != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// managerError creates a waddrmgr.ManagerError given a set of arguments.
|
||||||
|
// XXX(lars): We should probably make our own votingpoolError function.
|
||||||
|
func managerError(c waddrmgr.ErrorCode, desc string, err error) waddrmgr.ManagerError {
|
||||||
|
return waddrmgr.ManagerError{ErrorCode: c, Description: desc, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero sets all bytes in the passed slice to zero. This is used to
|
||||||
|
// explicitly clear private key material from memory.
|
||||||
|
//
|
||||||
|
// XXX(lars) there exists currently around 4-5 other zero functions
|
||||||
|
// with at least 3 different implementations. We should try to
|
||||||
|
// consolidate these.
|
||||||
|
func zero(b []byte) {
|
||||||
|
for i := range b {
|
||||||
|
b[i] ^= b[i]
|
||||||
|
}
|
||||||
|
}
|
1385
votingpool/pool_test.go
Normal file
1385
votingpool/pool_test.go
Normal file
File diff suppressed because it is too large
Load diff
92
votingpool/test_coverage.txt
Normal file
92
votingpool/test_coverage.txt
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go serializeSeriesRow 100.00% (29/29)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go branchOrder 100.00% (19/19)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go convertAndValidatePubKeys 100.00% (16/16)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credits.Less 100.00% (12/12)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.decryptExtendedKey 100.00% (10/10)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.ReplaceSeries 100.00% (8/8)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go AddressRange.NumAddresses 100.00% (7/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Create 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go putPool 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.DepositScriptAddress 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go currentTx.addTxIn 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go currentTx.addTxOut 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go CanonicalKeyOrder 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go @81:3 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.GetSeries 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go seriesData.IsEmpowered 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.CreateSeries 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.existsSeries 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go uint32ToBytes 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go @398:27 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go zero 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go putSeries 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go existsPool 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go Ntxid 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go NewOutputRequest 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go votingPoolAddress.Index 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go init 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go estimateSize 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go calculateFee 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go bytesToUint32 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go currentTx.isTooBig 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/error.go newError 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credit.TxSha 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credit.OutputIndex 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credit.Address 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go newCredit 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credits.Len 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Credits.Swap 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalOutput.Outpoints 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalOutput.Address 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Pool.isCharterOutput 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go @67:3 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go newPool 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalOutput.Status 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalOutput.addOutpoint 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalStatus.Outputs 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go WithdrawalOutput.Amount 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go ChangeAddress.Next 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go @205:28 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go votingPoolAddress.Addr 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go votingPoolAddress.SeriesID 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go managerError 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go votingPoolAddress.Branch 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.EmpowerSeries 96.43% (27/28)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go deserializeSeriesRow 94.87% (37/39)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.putSeries 93.75% (15/16)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go validateAndDecryptKeys 92.31% (24/26)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Pool.getEligibleInputsFromSeries 86.36% (19/22)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Pool.getEligibleInputs 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Load 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go Pool.isCreditEligible 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go withdrawal.finalizeCurrentTx 84.21% (16/19)
|
||||||
|
github.com/conformal/btcwallet/votingpool/input_selection.go groupCreditsByAddr 83.33% (10/12)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go loadAllSeries 83.33% (5/6)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go Pool.ChangeAddress 83.33% (5/6)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go LoadAndCreateSeries 80.00% (8/10)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.LoadAllSeries 80.00% (8/10)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go LoadAndEmpowerSeries 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go LoadAndReplaceSeries 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go Pool.WithdrawalAddress 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go withdrawal.sign 75.76% (25/33)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go LoadAndGetDepositScript 75.00% (6/8)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go OutputRequest.pkScript 75.00% (3/4)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.DepositScript 73.08% (19/26)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go ValidateSigScripts 72.73% (8/11)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go withdrawal.fulfilNextOutput 72.41% (21/29)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go SignMultiSigUTXO 71.43% (15/21)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go @77:3 71.43% (5/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go getRedeemScript 71.43% (5/7)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go Pool.saveSeriesToDisk 70.00% (14/20)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go withdrawal.fulfilOutputs 70.00% (7/10)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go getPrivKey 70.00% (7/10)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go Pool.Withdrawal 66.67% (12/18)
|
||||||
|
github.com/conformal/btcwallet/votingpool/pool.go @426:3 66.67% (4/6)
|
||||||
|
github.com/conformal/btcwallet/votingpool/error.go ErrorCode.String 66.67% (2/3)
|
||||||
|
github.com/conformal/btcwallet/votingpool/db.go putSeriesRow 53.85% (7/13)
|
||||||
|
github.com/conformal/btcwallet/votingpool/error.go Error.Error 0.00% (0/3)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go withdrawal.updateStatusFor 0.00% (0/0)
|
||||||
|
github.com/conformal/btcwallet/votingpool/withdrawal.go currentTx.rollBackLastOutput 0.00% (0/0)
|
||||||
|
github.com/conformal/btcwallet/votingpool -------------------------------- 85.36% (554/649)
|
||||||
|
|
|
@ -125,7 +125,7 @@ type managedAddress struct {
|
||||||
privKeyMutex sync.Mutex
|
privKeyMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce mangedAddress satisfies the ManagedPubKeyAddress interface.
|
// Enforce managedAddress satisfies the ManagedPubKeyAddress interface.
|
||||||
var _ ManagedPubKeyAddress = (*managedAddress)(nil)
|
var _ ManagedPubKeyAddress = (*managedAddress)(nil)
|
||||||
|
|
||||||
// unlock decrypts and stores a pointer to the associated private key. It will
|
// unlock decrypts and stores a pointer to the associated private key. It will
|
||||||
|
|
|
@ -302,17 +302,17 @@ func putWatchingOnly(tx walletdb.Tx, watchingOnly bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bucket.Put(watchingOnlyName, []byte{encoded}); err != nil {
|
if err := bucket.Put(watchingOnlyName, []byte{encoded}); err != nil {
|
||||||
str := "failed to store wathcing only flag"
|
str := "failed to store watching only flag"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountKey returns the account key to use in the database for a given account
|
// uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in
|
||||||
// number.
|
// little-endian order: 1 -> [1 0 0 0].
|
||||||
func accountKey(account uint32) []byte {
|
func uint32ToBytes(number uint32) []byte {
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(buf, account)
|
binary.LittleEndian.PutUint32(buf, number)
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +404,6 @@ func deserializeBIP0044AccountRow(accountID []byte, row *dbAccountRow) (*dbBIP00
|
||||||
func serializeBIP0044AccountRow(encryptedPubKey,
|
func serializeBIP0044AccountRow(encryptedPubKey,
|
||||||
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
|
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
|
||||||
name string) []byte {
|
name string) []byte {
|
||||||
|
|
||||||
// The serialized BIP0044 account raw data format is:
|
// The serialized BIP0044 account raw data format is:
|
||||||
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey><nextextidx>
|
// <encpubkeylen><encpubkey><encprivkeylen><encprivkey><nextextidx>
|
||||||
// <nextintidx><namelen><name>
|
// <nextintidx><namelen><name>
|
||||||
|
@ -438,7 +437,7 @@ func serializeBIP0044AccountRow(encryptedPubKey,
|
||||||
func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) {
|
func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) {
|
||||||
bucket := tx.RootBucket().Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
accountID := accountKey(account)
|
accountID := uint32ToBytes(account)
|
||||||
serializedRow := bucket.Get(accountID)
|
serializedRow := bucket.Get(accountID)
|
||||||
if serializedRow == nil {
|
if serializedRow == nil {
|
||||||
str := fmt.Sprintf("account %d not found", account)
|
str := fmt.Sprintf("account %d not found", account)
|
||||||
|
@ -465,7 +464,7 @@ func putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error {
|
||||||
bucket := tx.RootBucket().Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
// Write the serialized value keyed by the account number.
|
// Write the serialized value keyed by the account number.
|
||||||
err := bucket.Put(accountKey(account), serializeAccountRow(row))
|
err := bucket.Put(uint32ToBytes(account), serializeAccountRow(row))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := fmt.Sprintf("failed to store account %d", account)
|
str := fmt.Sprintf("failed to store account %d", account)
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
|
@ -781,12 +780,13 @@ func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
|
||||||
|
|
||||||
// Update the next index for the appropriate internal or external
|
// Update the next index for the appropriate internal or external
|
||||||
// branch.
|
// branch.
|
||||||
accountID := accountKey(account)
|
accountID := uint32ToBytes(account)
|
||||||
bucket := tx.RootBucket().Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
serializedAccount := bucket.Get(accountID)
|
serializedAccount := bucket.Get(accountID)
|
||||||
|
|
||||||
// Deserialize the account row.
|
// Deserialize the account row.
|
||||||
row, err := deserializeAccountRow(accountID, serializedAccount)
|
row, err := deserializeAccountRow(accountID, serializedAccount)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1228,7 +1228,7 @@ func upgradeManager(namespace walletdb.Namespace) error {
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the most recent manager version if it isn't already
|
// Save the most recent database version if it isn't already
|
||||||
// there, otherwise keep track of it for potential upgrades.
|
// there, otherwise keep track of it for potential upgrades.
|
||||||
verBytes := mainBucket.Get(mgrVersionName)
|
verBytes := mainBucket.Get(mgrVersionName)
|
||||||
if verBytes == nil {
|
if verBytes == nil {
|
||||||
|
|
|
@ -117,7 +117,7 @@ Requesting Existing Addresses
|
||||||
|
|
||||||
In addition to generating new addresses, access to old addresses is often
|
In addition to generating new addresses, access to old addresses is often
|
||||||
required. Most notably, to sign transactions in order to redeem them. The
|
required. Most notably, to sign transactions in order to redeem them. The
|
||||||
Address function provides this capability and returns a ManagedAddress
|
Address function provides this capability and returns a ManagedAddress.
|
||||||
|
|
||||||
Importing Addresses
|
Importing Addresses
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ const (
|
||||||
ErrDatabase ErrorCode = iota
|
ErrDatabase ErrorCode = iota
|
||||||
|
|
||||||
// ErrKeyChain indicates an error with the key chain typically either
|
// ErrKeyChain indicates an error with the key chain typically either
|
||||||
// due to the inability to create and extended key or deriving a child
|
// due to the inability to create an extended key or deriving a child
|
||||||
// extended key. When this error code is set, the Err field of the
|
// extended key. When this error code is set, the Err field of the
|
||||||
// ManagerError will be set to the underlying error.
|
// ManagerError will be set to the underlying error.
|
||||||
ErrKeyChain
|
ErrKeyChain
|
||||||
|
@ -74,54 +74,54 @@ const (
|
||||||
// key type has been selected.
|
// key type has been selected.
|
||||||
ErrInvalidKeyType
|
ErrInvalidKeyType
|
||||||
|
|
||||||
// ErrNoExist indicates the manager does not exist.
|
// ErrNoExist indicates that the specified database does not exist.
|
||||||
ErrNoExist
|
ErrNoExist
|
||||||
|
|
||||||
// ErrAlreadyExists indicates the specified manager already exists.
|
// ErrAlreadyExists indicates that the specified database already exists.
|
||||||
ErrAlreadyExists
|
ErrAlreadyExists
|
||||||
|
|
||||||
// ErrCoinTypeTooHigh indicates the coin type specified in the provided
|
// ErrCoinTypeTooHigh indicates that the coin type specified in the provided
|
||||||
// network parameters is higher than the max allowed value as defined
|
// network parameters is higher than the max allowed value as defined
|
||||||
// by the maxCoinType constant.
|
// by the maxCoinType constant.
|
||||||
ErrCoinTypeTooHigh
|
ErrCoinTypeTooHigh
|
||||||
|
|
||||||
// ErrAccountNumTooHigh indicates the specified account number is higher
|
// ErrAccountNumTooHigh indicates that the specified account number is higher
|
||||||
// than the max allowed value as defined by the MaxAccountNum constant.
|
// than the max allowed value as defined by the MaxAccountNum constant.
|
||||||
ErrAccountNumTooHigh
|
ErrAccountNumTooHigh
|
||||||
|
|
||||||
// ErrLocked indicates the an operation which requires the address
|
// ErrLocked indicates that an operation, which requires the account
|
||||||
// manager to be unlocked was requested on a locked address manager.
|
// manager to be unlocked, was requested on a locked account manager.
|
||||||
ErrLocked
|
ErrLocked
|
||||||
|
|
||||||
// ErrWatchingOnly indicates the an operation which requires the address
|
// ErrWatchingOnly indicates that an operation, which requires the
|
||||||
// manager to have access to private data was requested on a
|
// account manager to have access to private data, was requested on
|
||||||
// watching-only address manager.
|
// a watching-only account manager.
|
||||||
ErrWatchingOnly
|
ErrWatchingOnly
|
||||||
|
|
||||||
// ErrInvalidAccount indicates the requested account is not valid.
|
// ErrInvalidAccount indicates that the requested account is not valid.
|
||||||
ErrInvalidAccount
|
ErrInvalidAccount
|
||||||
|
|
||||||
// ErrAddressNotFound indicates the requested address is not known to
|
// ErrAddressNotFound indicates that the requested address is not known to
|
||||||
// the address manager.
|
// the account manager.
|
||||||
ErrAddressNotFound
|
ErrAddressNotFound
|
||||||
|
|
||||||
// ErrAccountNotFound indicates the requested account is not known to
|
// ErrAccountNotFound indicates that the requested account is not known to
|
||||||
// the address manager.
|
// the account manager.
|
||||||
ErrAccountNotFound
|
ErrAccountNotFound
|
||||||
|
|
||||||
// ErrDuplicate indicates an address already exists.
|
// ErrDuplicate indicates that an address already exists.
|
||||||
ErrDuplicate
|
ErrDuplicate
|
||||||
|
|
||||||
// ErrTooManyAddresses indicates more than the maximum allowed number of
|
// ErrTooManyAddresses indicates that more than the maximum allowed number of
|
||||||
// addresses per account have been requested.
|
// addresses per account have been requested.
|
||||||
ErrTooManyAddresses
|
ErrTooManyAddresses
|
||||||
|
|
||||||
// ErrWrongPassphrase inidicates the specified password is incorrect.
|
// ErrWrongPassphrase indicates that the specified passphrase is incorrect.
|
||||||
// This could be for either the public and private master keys.
|
// This could be for either public or private master keys.
|
||||||
ErrWrongPassphrase
|
ErrWrongPassphrase
|
||||||
|
|
||||||
// ErrWrongNet indicates the private key to be imported is not for the
|
// ErrWrongNet indicates that the private key to be imported is not for the
|
||||||
// the same network the account mangaer is configured for.
|
// the same network the account manager is configured for.
|
||||||
ErrWrongNet
|
ErrWrongNet
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -144,6 +144,26 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||||
ErrTooManyAddresses: "ErrTooManyAddresses",
|
ErrTooManyAddresses: "ErrTooManyAddresses",
|
||||||
ErrWrongPassphrase: "ErrWrongPassphrase",
|
ErrWrongPassphrase: "ErrWrongPassphrase",
|
||||||
ErrWrongNet: "ErrWrongNet",
|
ErrWrongNet: "ErrWrongNet",
|
||||||
|
|
||||||
|
// The following error codes are defined in pool_error.go.
|
||||||
|
ErrSeriesStorage: "ErrSeriesStorage",
|
||||||
|
ErrSeriesVersion: "ErrSeriesVersion",
|
||||||
|
ErrSeriesNotExists: "ErrSeriesNotExists",
|
||||||
|
ErrSeriesAlreadyExists: "ErrSeriesAlreadyExists",
|
||||||
|
ErrSeriesAlreadyEmpowered: "ErrSeriesAlreadyEmpowered",
|
||||||
|
ErrKeyIsPrivate: "ErrKeyIsPrivate",
|
||||||
|
ErrKeyIsPublic: "ErrKeyIsPublic",
|
||||||
|
ErrKeyNeuter: "ErrKeyNeuter",
|
||||||
|
ErrKeyMismatch: "ErrKeyMismatch",
|
||||||
|
ErrKeysPrivatePublicMismatch: "ErrKeysPrivatePublicMismatch",
|
||||||
|
ErrKeyDuplicate: "ErrKeyDuplicate",
|
||||||
|
ErrTooFewPublicKeys: "ErrTooFewPublicKeys",
|
||||||
|
ErrVotingPoolAlreadyExists: "ErrVotingPoolAlreadyExists",
|
||||||
|
ErrVotingPoolNotExists: "ErrVotingPoolNotExists",
|
||||||
|
ErrScriptCreation: "ErrScriptCreation",
|
||||||
|
ErrTooManyReqSignatures: "ErrTooManyReqSignatures",
|
||||||
|
ErrInvalidBranch: "ErrInvalidBranch",
|
||||||
|
ErrInvalidValue: "ErrInvalidValue",
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the ErrorCode as a human-readable name.
|
// String returns the ErrorCode as a human-readable name.
|
||||||
|
|
|
@ -46,6 +46,22 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||||
{waddrmgr.ErrTooManyAddresses, "ErrTooManyAddresses"},
|
{waddrmgr.ErrTooManyAddresses, "ErrTooManyAddresses"},
|
||||||
{waddrmgr.ErrWrongPassphrase, "ErrWrongPassphrase"},
|
{waddrmgr.ErrWrongPassphrase, "ErrWrongPassphrase"},
|
||||||
{waddrmgr.ErrWrongNet, "ErrWrongNet"},
|
{waddrmgr.ErrWrongNet, "ErrWrongNet"},
|
||||||
|
|
||||||
|
// The following error codes are defined in pool_error.go.
|
||||||
|
{waddrmgr.ErrSeriesStorage, "ErrSeriesStorage"},
|
||||||
|
{waddrmgr.ErrSeriesNotExists, "ErrSeriesNotExists"},
|
||||||
|
{waddrmgr.ErrSeriesAlreadyExists, "ErrSeriesAlreadyExists"},
|
||||||
|
{waddrmgr.ErrSeriesAlreadyEmpowered, "ErrSeriesAlreadyEmpowered"},
|
||||||
|
{waddrmgr.ErrKeyIsPrivate, "ErrKeyIsPrivate"},
|
||||||
|
{waddrmgr.ErrKeyNeuter, "ErrKeyNeuter"},
|
||||||
|
{waddrmgr.ErrKeyMismatch, "ErrKeyMismatch"},
|
||||||
|
{waddrmgr.ErrKeysPrivatePublicMismatch, "ErrKeysPrivatePublicMismatch"},
|
||||||
|
{waddrmgr.ErrKeyDuplicate, "ErrKeyDuplicate"},
|
||||||
|
{waddrmgr.ErrTooFewPublicKeys, "ErrTooFewPublicKeys"},
|
||||||
|
{waddrmgr.ErrVotingPoolNotExists, "ErrVotingPoolNotExists"},
|
||||||
|
{waddrmgr.ErrScriptCreation, "ErrScriptCreation"},
|
||||||
|
{waddrmgr.ErrTooManyReqSignatures, "ErrTooManyReqSignatures"},
|
||||||
|
|
||||||
{0xffff, "Unknown ErrorCode (65535)"},
|
{0xffff, "Unknown ErrorCode (65535)"},
|
||||||
}
|
}
|
||||||
t.Logf("Running %d tests", len(tests))
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
|
|
@ -47,7 +47,7 @@ func TstRunWithReplacedNewSecretKey(callback func()) {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TstCheckPublicPassphrase return true if the provided public passphrase is
|
// TstCheckPublicPassphrase returns true if the provided public passphrase is
|
||||||
// correct for the manager.
|
// correct for the manager.
|
||||||
func (m *Manager) TstCheckPublicPassphrase(pubPassphrase []byte) bool {
|
func (m *Manager) TstCheckPublicPassphrase(pubPassphrase []byte) bool {
|
||||||
secretKey := snacl.SecretKey{Key: &snacl.CryptoKey{}}
|
secretKey := snacl.SecretKey{Key: &snacl.CryptoKey{}}
|
||||||
|
|
92
waddrmgr/pool_error.go
Normal file
92
waddrmgr/pool_error.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package waddrmgr
|
||||||
|
|
||||||
|
// XXX: All errors defined here will soon be moved to the votingpool package, where they
|
||||||
|
// belong.
|
||||||
|
|
||||||
|
// Constants that identify voting pool-related errors.
|
||||||
|
// The codes start from 1000 to avoid confusion with the ones in error.go.
|
||||||
|
const (
|
||||||
|
// ErrSeriesStorage indicates that an error occurred while serializing
|
||||||
|
// or deserializing one or more series for storing into database.
|
||||||
|
ErrSeriesStorage ErrorCode = iota + 1000
|
||||||
|
|
||||||
|
// ErrSeriesVersion indicates that we've been asked to deal with
|
||||||
|
// a series whose version is unsupported
|
||||||
|
ErrSeriesVersion
|
||||||
|
|
||||||
|
// ErrSeriesNotExists indicates that an attempt has been made to access
|
||||||
|
// a series that does not exist.
|
||||||
|
ErrSeriesNotExists
|
||||||
|
|
||||||
|
// ErrSeriesAlreadyExists indicates that an attempt has been made to create
|
||||||
|
// a series that already exists.
|
||||||
|
ErrSeriesAlreadyExists
|
||||||
|
|
||||||
|
// ErrSeriesAlreadyEmpowered indicates that an already empowered series
|
||||||
|
// was used where a not empowered one was expected.
|
||||||
|
ErrSeriesAlreadyEmpowered
|
||||||
|
|
||||||
|
// ErrKeyIsPrivate indicates that a private key was used where a public
|
||||||
|
// one was expected.
|
||||||
|
ErrKeyIsPrivate
|
||||||
|
|
||||||
|
// ErrKeyIsPublic indicates that a public key was used where a private
|
||||||
|
// one was expected.
|
||||||
|
ErrKeyIsPublic
|
||||||
|
|
||||||
|
// ErrKeyNeuter indicates a problem when trying to neuter a private key.
|
||||||
|
ErrKeyNeuter
|
||||||
|
|
||||||
|
// ErrKeyMismatch indicates that the key is not the expected one.
|
||||||
|
ErrKeyMismatch
|
||||||
|
|
||||||
|
// ErrKeysPrivatePublicMismatch indicates that the number of private and
|
||||||
|
// public keys is not the same.
|
||||||
|
ErrKeysPrivatePublicMismatch
|
||||||
|
|
||||||
|
// ErrKeyDuplicate indicates that a key is duplicated.
|
||||||
|
ErrKeyDuplicate
|
||||||
|
|
||||||
|
// ErrTooFewPublicKeys indicates that a required minimum of public
|
||||||
|
// keys was not met.
|
||||||
|
ErrTooFewPublicKeys
|
||||||
|
|
||||||
|
// ErrVotingPoolAlreadyExists indicates that an attempt has been made to
|
||||||
|
// create a voting pool that already exists.
|
||||||
|
ErrVotingPoolAlreadyExists
|
||||||
|
|
||||||
|
// ErrVotingPoolNotExists indicates that an attempt has been made to access
|
||||||
|
// a voting pool that does not exist.
|
||||||
|
ErrVotingPoolNotExists
|
||||||
|
|
||||||
|
// ErrScriptCreation indicates that the creation of a deposit script failed.
|
||||||
|
ErrScriptCreation
|
||||||
|
|
||||||
|
// ErrTooManyReqSignatures indicates that too many required
|
||||||
|
// signatures are requested.
|
||||||
|
ErrTooManyReqSignatures
|
||||||
|
|
||||||
|
// ErrInvalidBranch indicates that the given branch number is not valid
|
||||||
|
// for a given set of public keys.
|
||||||
|
ErrInvalidBranch
|
||||||
|
|
||||||
|
// ErrInvalidValue indicates that the value of a given function argument
|
||||||
|
// is invalid.
|
||||||
|
ErrInvalidValue
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue