Documentation ¶
Overview ¶
Package votingpool provides voting pool functionality for bchwallet.
Overview ¶
The purpose of the voting pool package is to make it possible to store bitcoins using m-of-n multisig transactions. A pool can have multiple series, each of them with a set of pubkeys (one for each of the members in that pool's series) and the minimum number of required signatures (m) needed to spend the pool's coins. Each member will hold a private key matching one of the series' public keys, and at least m members will need to be in agreement when spending the pool's coins.
More details about voting pools as well as some of its use cases can be found at http://opentransactions.org/wiki/index.php?title=Category:Voting_Pools
This package depends on the waddrmgr and walletdb packages.
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 related to that pool under a bucket whose key is the pool's ID.
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 returns a series-specific P2SH address from the multi-sig script constructed with the index-th child of the series' public keys and sorted according to the given branch. The procedure to construct multi-sig deposit addresses is described in detail at http://opentransactions.org/wiki/index.php/Deposit_Address_(voting_pools)
Replacing a series ¶
A series can be replaced via the ReplaceSeries method. It accepts the same parameters as the CreateSeries method.
Empowering a series ¶
For security reasons, most private keys will be maintained offline and only brought online when they're needed. In order to bring a key online, one must use the EmpowerSeries method, which takes just the series ID and a raw private key matching one of the series' public keys.
Starting withdrawals ¶
When withdrawing coins from the pool, we employ a deterministic process in order to minimise the cost of coordinating transaction signing. For this to work, members of the pool have to perform an out-of-band consensus process (<http://opentransactions.org/wiki/index.php/Consensus_Process_(voting_pools)>) to define the following parameters, that should be passed to the StartWithdrawal method:
roundID: the unique identifier of a given consensus round requests: a list with outputs requested by users of the voting pool startAddress: the seriesID, branch and indes where we should start looking for inputs lastSeriesID: the ID of the last series where we should take inputs from changeStart: the first change address to use dustThreshold: the minimum amount of satoshis an input needs to be considered eligible
StartWithdrawal will then select all eligible inputs in the given address range (following the algorithim at <http://opentransactions.org/wiki/index.php/Input_Selection_Algorithm_(voting_pools)>) and use them to construct transactions (<http://opentransactions.org/wiki/index.php/Category:Transaction_Construction_Algorithm_(voting_pools)>) that fulfill the output requests. It returns a WithdrawalStatus containing the state of every requested output, the raw signatures for the constructed transactions, the network fees included in those transactions and the input range to use in the next withdrawal.
Example (DepositAddress) ¶
This example demonstrates how to create a voting pool with one series and get a deposit address for that series.
package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/dcrlabs/bchwallet/votingpool" "github.com/dcrlabs/bchwallet/waddrmgr" "github.com/dcrlabs/bchwallet/walletdb" "github.com/gcash/bchd/chaincfg" _ "github.com/dcrlabs/bchwallet/walletdb/bdb" ) var ( pubPassphrase = []byte("pubPassphrase") privPassphrase = []byte("privPassphrase") seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1} ) func createWaddrmgr(ns walletdb.ReadWriteBucket, params *chaincfg.Params) (*waddrmgr.Manager, error) { err := waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, params, fastScrypt, time.Now()) if err != nil { return nil, err } return waddrmgr.Open(ns, pubPassphrase, params) } func main() { // Create the address manager and votingpool DB namespace. See the example // for the Create() function for more info on how this is done. teardown, db, mgr := exampleCreateDBAndMgr() defer teardown() err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := votingpoolNamespace(tx) // Create the voting pool. pool, err := votingpool.Create(ns, mgr, []byte{0x00}) if err != nil { return err } // Create a 2-of-3 series. seriesID := uint32(1) requiredSignatures := uint32(2) pubKeys := []string{ "xpub661MyMwAqRbcFDDrR5jY7LqsRioFDwg3cLjc7tML3RRcfYyhXqqgCH5SqMSQdpQ1Xh8EtVwcfm8psD8zXKPcRaCVSY4GCqbb3aMEs27GitE", "xpub661MyMwAqRbcGsxyD8hTmJFtpmwoZhy4NBBVxzvFU8tDXD2ME49A6JjQCYgbpSUpHGP1q4S2S1Pxv2EqTjwfERS5pc9Q2yeLkPFzSgRpjs9", "xpub661MyMwAqRbcEbc4uYVXvQQpH9L3YuZLZ1gxCmj59yAhNy33vXxbXadmRpx5YZEupNSqWRrR7PqU6duS2FiVCGEiugBEa5zuEAjsyLJjKCh", } err = pool.CreateSeries(ns, votingpool.CurrentVersion, seriesID, requiredSignatures, pubKeys) if err != nil { return err } // Create a deposit address. addr, err := pool.DepositScriptAddress(seriesID, votingpool.Branch(0), votingpool.Index(1)) if err != nil { return err } fmt.Println("Generated deposit address:", addr.EncodeAddress()) return nil }) if err != nil { fmt.Println(err) return } } func createWalletDB() (walletdb.DB, func(), error) { dir, err := ioutil.TempDir("", "votingpool_example") if err != nil { return nil, nil, err } db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"), true) if err != nil { return nil, nil, err } dbTearDown := func() { db.Close() os.RemoveAll(dir) } return db, dbTearDown, nil } var ( addrmgrNamespaceKey = []byte("addrmgr") txmgrNamespaceKey = []byte("txmgr") votingpoolNamespaceKey = []byte("votingpool") ) func votingpoolNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(votingpoolNamespaceKey) } func exampleCreateDBAndMgr() (teardown func(), db walletdb.DB, mgr *waddrmgr.Manager) { db, dbTearDown, err := createWalletDB() if err != nil { dbTearDown() panic(err) } err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { addrmgrNs, err := tx.CreateTopLevelBucket(addrmgrNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(votingpoolNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(txmgrNamespaceKey) if err != nil { return err } mgr, err = createWaddrmgr(addrmgrNs, &chaincfg.MainNetParams) return err }) if err != nil { dbTearDown() panic(err) } teardown = func() { mgr.Close() dbTearDown() } return teardown, db, mgr }
Output: Generated deposit address: pruafy9cc8kmpt86wcewzw6u55ckjrmnhspuk2fxvz
Example (EmpowerSeries) ¶
This example demonstrates how to empower a series by loading the private key for one of the series' public keys.
package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/dcrlabs/bchwallet/votingpool" "github.com/dcrlabs/bchwallet/waddrmgr" "github.com/dcrlabs/bchwallet/walletdb" "github.com/gcash/bchd/chaincfg" _ "github.com/dcrlabs/bchwallet/walletdb/bdb" ) var ( pubPassphrase = []byte("pubPassphrase") privPassphrase = []byte("privPassphrase") seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1} ) func createWaddrmgr(ns walletdb.ReadWriteBucket, params *chaincfg.Params) (*waddrmgr.Manager, error) { err := waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, params, fastScrypt, time.Now()) if err != nil { return nil, err } return waddrmgr.Open(ns, pubPassphrase, params) } func main() { // Create the address manager and votingpool DB namespace. See the example // for the Create() function for more info on how this is done. teardown, db, mgr := exampleCreateDBAndMgr() defer teardown() // Create a pool and a series. See the DepositAddress example for more info // on how this is done. pool, seriesID := exampleCreatePoolAndSeries(db, mgr) err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := votingpoolNamespace(tx) addrmgrNs := addrmgrNamespace(tx) // Now empower the series with one of its private keys. Notice that in order // to do that we need to unlock the address manager. err := mgr.Unlock(addrmgrNs, privPassphrase) if err != nil { return err } defer mgr.Lock() privKey := "xprv9s21ZrQH143K2j9PK4CXkCu8sgxkpUxCF7p1KVwiV5tdnkeYzJXReUkxz5iB2FUzTXC1L15abCDG4RMxSYT5zhm67uvsnLYxuDhZfoFcB6a" return pool.EmpowerSeries(ns, seriesID, privKey) }) if err != nil { fmt.Println(err) return } } func createWalletDB() (walletdb.DB, func(), error) { dir, err := ioutil.TempDir("", "votingpool_example") if err != nil { return nil, nil, err } db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"), true) if err != nil { return nil, nil, err } dbTearDown := func() { db.Close() os.RemoveAll(dir) } return db, dbTearDown, nil } var ( addrmgrNamespaceKey = []byte("addrmgr") txmgrNamespaceKey = []byte("txmgr") votingpoolNamespaceKey = []byte("votingpool") ) func addrmgrNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(addrmgrNamespaceKey) } func votingpoolNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(votingpoolNamespaceKey) } func exampleCreateDBAndMgr() (teardown func(), db walletdb.DB, mgr *waddrmgr.Manager) { db, dbTearDown, err := createWalletDB() if err != nil { dbTearDown() panic(err) } err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { addrmgrNs, err := tx.CreateTopLevelBucket(addrmgrNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(votingpoolNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(txmgrNamespaceKey) if err != nil { return err } mgr, err = createWaddrmgr(addrmgrNs, &chaincfg.MainNetParams) return err }) if err != nil { dbTearDown() panic(err) } teardown = func() { mgr.Close() dbTearDown() } return teardown, db, mgr } func exampleCreatePoolAndSeries(db walletdb.DB, mgr *waddrmgr.Manager) (pool *votingpool.Pool, seriesID uint32) { err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := votingpoolNamespace(tx) var err error pool, err = votingpool.Create(ns, mgr, []byte{0x00}) if err != nil { return err } seriesID = uint32(1) requiredSignatures := uint32(2) pubKeys := []string{ "xpub661MyMwAqRbcFDDrR5jY7LqsRioFDwg3cLjc7tML3RRcfYyhXqqgCH5SqMSQdpQ1Xh8EtVwcfm8psD8zXKPcRaCVSY4GCqbb3aMEs27GitE", "xpub661MyMwAqRbcGsxyD8hTmJFtpmwoZhy4NBBVxzvFU8tDXD2ME49A6JjQCYgbpSUpHGP1q4S2S1Pxv2EqTjwfERS5pc9Q2yeLkPFzSgRpjs9", "xpub661MyMwAqRbcEbc4uYVXvQQpH9L3YuZLZ1gxCmj59yAhNy33vXxbXadmRpx5YZEupNSqWRrR7PqU6duS2FiVCGEiugBEa5zuEAjsyLJjKCh", } err = pool.CreateSeries(ns, votingpool.CurrentVersion, seriesID, requiredSignatures, pubKeys) if err != nil { return err } return pool.ActivateSeries(ns, seriesID) }) if err != nil { panic(err) } return pool, seriesID }
Output:
Example (StartWithdrawal) ¶
This example demonstrates how to use the Pool.StartWithdrawal method.
package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/dcrlabs/bchwallet/votingpool" "github.com/dcrlabs/bchwallet/waddrmgr" "github.com/dcrlabs/bchwallet/walletdb" "github.com/dcrlabs/bchwallet/wtxmgr" "github.com/gcash/bchd/chaincfg" "github.com/gcash/bchd/txscript" "github.com/gcash/bchutil" _ "github.com/dcrlabs/bchwallet/walletdb/bdb" ) var ( pubPassphrase = []byte("pubPassphrase") privPassphrase = []byte("privPassphrase") seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1} ) func createWaddrmgr(ns walletdb.ReadWriteBucket, params *chaincfg.Params) (*waddrmgr.Manager, error) { err := waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, params, fastScrypt, time.Now()) if err != nil { return nil, err } return waddrmgr.Open(ns, pubPassphrase, params) } func main() { // Create the address manager and votingpool DB namespace. See the example // for the Create() function for more info on how this is done. teardown, db, mgr := exampleCreateDBAndMgr() defer teardown() // Create a pool and a series. See the DepositAddress example for more info // on how this is done. pool, seriesID := exampleCreatePoolAndSeries(db, mgr) err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := votingpoolNamespace(tx) addrmgrNs := addrmgrNamespace(tx) txmgrNs := txmgrNamespace(tx) // Create the transaction store for later use. txstore := exampleCreateTxStore(txmgrNs) // Unlock the manager err := mgr.Unlock(addrmgrNs, privPassphrase) if err != nil { return err } defer mgr.Lock() addr, _ := bchutil.DecodeAddress("1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", mgr.ChainParams()) pkScript, _ := txscript.PayToAddrScript(addr) requests := []votingpool.OutputRequest{ { PkScript: pkScript, Address: addr, Amount: 1e6, Server: "server-id", Transaction: 123, }, } changeStart, err := pool.ChangeAddress(seriesID, votingpool.Index(0)) if err != nil { return err } // This is only needed because we have not used any deposit addresses from // the series, and we cannot create a WithdrawalAddress for an unused // branch/idx pair. err = pool.EnsureUsedAddr(ns, addrmgrNs, seriesID, votingpool.Branch(1), votingpool.Index(0)) if err != nil { return err } startAddr, err := pool.WithdrawalAddress(ns, addrmgrNs, seriesID, votingpool.Branch(1), votingpool.Index(0)) if err != nil { return err } lastSeriesID := seriesID dustThreshold := bchutil.Amount(1e4) currentBlock := int32(19432) roundID := uint32(0) _, err = pool.StartWithdrawal(ns, addrmgrNs, roundID, requests, *startAddr, lastSeriesID, *changeStart, txstore, txmgrNs, currentBlock, dustThreshold) return err }) if err != nil { fmt.Println(err) return } } func createWalletDB() (walletdb.DB, func(), error) { dir, err := ioutil.TempDir("", "votingpool_example") if err != nil { return nil, nil, err } db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"), true) if err != nil { return nil, nil, err } dbTearDown := func() { db.Close() os.RemoveAll(dir) } return db, dbTearDown, nil } var ( addrmgrNamespaceKey = []byte("addrmgr") txmgrNamespaceKey = []byte("txmgr") votingpoolNamespaceKey = []byte("votingpool") ) func addrmgrNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(addrmgrNamespaceKey) } func txmgrNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(txmgrNamespaceKey) } func votingpoolNamespace(dbtx walletdb.ReadWriteTx) walletdb.ReadWriteBucket { return dbtx.ReadWriteBucket(votingpoolNamespaceKey) } func exampleCreateDBAndMgr() (teardown func(), db walletdb.DB, mgr *waddrmgr.Manager) { db, dbTearDown, err := createWalletDB() if err != nil { dbTearDown() panic(err) } err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { addrmgrNs, err := tx.CreateTopLevelBucket(addrmgrNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(votingpoolNamespaceKey) if err != nil { return err } _, err = tx.CreateTopLevelBucket(txmgrNamespaceKey) if err != nil { return err } mgr, err = createWaddrmgr(addrmgrNs, &chaincfg.MainNetParams) return err }) if err != nil { dbTearDown() panic(err) } teardown = func() { mgr.Close() dbTearDown() } return teardown, db, mgr } func exampleCreatePoolAndSeries(db walletdb.DB, mgr *waddrmgr.Manager) (pool *votingpool.Pool, seriesID uint32) { err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := votingpoolNamespace(tx) var err error pool, err = votingpool.Create(ns, mgr, []byte{0x00}) if err != nil { return err } seriesID = uint32(1) requiredSignatures := uint32(2) pubKeys := []string{ "xpub661MyMwAqRbcFDDrR5jY7LqsRioFDwg3cLjc7tML3RRcfYyhXqqgCH5SqMSQdpQ1Xh8EtVwcfm8psD8zXKPcRaCVSY4GCqbb3aMEs27GitE", "xpub661MyMwAqRbcGsxyD8hTmJFtpmwoZhy4NBBVxzvFU8tDXD2ME49A6JjQCYgbpSUpHGP1q4S2S1Pxv2EqTjwfERS5pc9Q2yeLkPFzSgRpjs9", "xpub661MyMwAqRbcEbc4uYVXvQQpH9L3YuZLZ1gxCmj59yAhNy33vXxbXadmRpx5YZEupNSqWRrR7PqU6duS2FiVCGEiugBEa5zuEAjsyLJjKCh", } err = pool.CreateSeries(ns, votingpool.CurrentVersion, seriesID, requiredSignatures, pubKeys) if err != nil { return err } return pool.ActivateSeries(ns, seriesID) }) if err != nil { panic(err) } return pool, seriesID } func exampleCreateTxStore(ns walletdb.ReadWriteBucket) *wtxmgr.Store { err := wtxmgr.Create(ns) if err != nil { panic(err) } s, err := wtxmgr.Open(ns, &chaincfg.MainNetParams) if err != nil { panic(err) } return s }
Output:
Index ¶
- Constants
- func CanonicalKeyOrder(keys []string) []string
- func DisableLog()
- func LoadAndCreateSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32, ...) error
- func LoadAndEmpowerSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, poolID string, ...) error
- func LoadAndGetDepositScript(ns walletdb.ReadBucket, m *waddrmgr.Manager, poolID string, seriesID uint32, ...) ([]byte, error)
- func LoadAndReplaceSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32, ...) error
- func SignTx(msgtx *wire.MsgTx, inputAmounts InputAmounts, sigs TxSigs, ...) error
- func UseLogger(logger bchlog.Logger)
- type Branch
- type ChangeAddress
- type Credit
- type Error
- type ErrorCode
- type Index
- type InputAmounts
- type Ntxid
- type OutBailmentID
- type OutBailmentOutpoint
- type OutputRequest
- type Pool
- func (p *Pool) ActivateSeries(ns walletdb.ReadWriteBucket, seriesID uint32) error
- func (p *Pool) ChangeAddress(seriesID uint32, index Index) (*ChangeAddress, error)
- func (p *Pool) CreateSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, ...) error
- func (p *Pool) DepositScript(seriesID uint32, branch Branch, index Index) ([]byte, error)
- func (p *Pool) DepositScriptAddress(seriesID uint32, branch Branch, index Index) (bchutil.Address, error)
- func (p *Pool) EmpowerSeries(ns walletdb.ReadWriteBucket, seriesID uint32, rawPrivKey string) error
- func (p *Pool) EnsureUsedAddr(ns, addrmgrNs walletdb.ReadWriteBucket, seriesID uint32, branch Branch, ...) error
- func (p *Pool) LoadAllSeries(ns walletdb.ReadBucket) error
- func (p *Pool) Manager() *waddrmgr.Manager
- func (p *Pool) ReplaceSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, ...) error
- func (p *Pool) Series(seriesID uint32) *SeriesData
- func (p *Pool) StartWithdrawal(ns walletdb.ReadWriteBucket, addrmgrNs walletdb.ReadBucket, roundID uint32, ...) (*WithdrawalStatus, error)
- func (p *Pool) WithdrawalAddress(ns, addrmgrNs walletdb.ReadBucket, seriesID uint32, branch Branch, index Index) (*WithdrawalAddress, error)
- type PoolAddress
- type RawSig
- type SeriesData
- type TxSigs
- type WithdrawalAddress
- type WithdrawalOutput
- type WithdrawalStatus
- func (s *WithdrawalStatus) Fees() bchutil.Amount
- func (s *WithdrawalStatus) InputAmounts() map[Ntxid]InputAmounts
- func (s *WithdrawalStatus) NextChangeAddr() ChangeAddress
- func (s *WithdrawalStatus) NextInputAddr() WithdrawalAddress
- func (s *WithdrawalStatus) Outputs() map[OutBailmentID]*WithdrawalOutput
- func (s *WithdrawalStatus) Sigs() map[Ntxid]TxSigs
Examples ¶
Constants ¶
const (
// CurrentVersion is the version used for newly created Series.
CurrentVersion = 1
)
Variables ¶
This section is empty.
Functions ¶
func CanonicalKeyOrder ¶
CanonicalKeyOrder will return a copy of the input canonically ordered which is defined to be lexicographical.
func DisableLog ¶
func DisableLog()
DisableLog disables all library log output. Logging output is disabled by default until either UseLogger or SetLogWriter are called.
func LoadAndCreateSeries ¶
func LoadAndCreateSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32, poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error
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 LoadAndEmpowerSeries ¶
func LoadAndEmpowerSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, poolID string, seriesID uint32, rawPrivKey string) error
LoadAndEmpowerSeries loads the voting pool with the given ID and calls EmpowerSeries, passing the given series ID and private key to it.
func LoadAndGetDepositScript ¶
func LoadAndGetDepositScript(ns walletdb.ReadBucket, m *waddrmgr.Manager, poolID string, seriesID uint32, branch Branch, index Index) ([]byte, error)
LoadAndGetDepositScript generates and returns a deposit script for the given seriesID, branch and index of the Pool identified by poolID.
func LoadAndReplaceSeries ¶
func LoadAndReplaceSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32, poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error
LoadAndReplaceSeries loads the voting pool with the given ID and calls ReplaceSeries, passing the given series ID, public keys and reqSigs to it.
func SignTx ¶
func SignTx(msgtx *wire.MsgTx, inputAmounts InputAmounts, sigs TxSigs, mgr *waddrmgr.Manager, addrmgrNs walletdb.ReadBucket, store *wtxmgr.Store, txmgrNs walletdb.ReadBucket) error
SignTx signs every input of the given MsgTx by looking up (on the addr manager) the redeem script for each of them and constructing the signature script using that and the given raw signatures. This function must be called with the manager unlocked.
Types ¶
type ChangeAddress ¶
type ChangeAddress struct {
// contains filtered or unexported fields
}
ChangeAddress is a votingpool address meant to be used on transaction change outputs. All change addresses have branch==0.
type Credit ¶
Credit is an abstraction over wtxmgr.Credit used in the construction of voting pool withdrawal transactions.
type Error ¶
type Error struct { ErrorCode ErrorCode // Describes the kind of error Description string // Human readable description of the issue Err error // Underlying error }
Error is a typed error for all errors arising during the operation of the voting pool.
type ErrorCode ¶
type ErrorCode int
ErrorCode identifies a kind of error
const ( // ErrInputSelection indicates an error in the input selection // algorithm. ErrInputSelection ErrorCode = iota // ErrWithdrawalProcessing indicates an internal error when processing a // withdrawal request. ErrWithdrawalProcessing // ErrUnknownPubKey indicates a pubkey that does not belong to a given // series. ErrUnknownPubKey // ErrSeriesSerialization indicates that an error occurred while // serializing or deserializing one or more series for storing into // the database. ErrSeriesSerialization // 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 // ErrSeriesNotActive indicates that an active series was needed but the // selected one is not. ErrSeriesNotActive // 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 // ErrPoolAlreadyExists indicates that an attempt has been made to // create a voting pool that already exists. ErrPoolAlreadyExists // ErrPoolNotExists indicates that an attempt has been made to access // a voting pool that does not exist. ErrPoolNotExists // 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 // ErrDatabase indicates an error with the underlying database. ErrDatabase // ErrKeyChain indicates an error with the key chain typically either // due to the inability to create an extended key or deriving a child // extended key. ErrKeyChain // ErrCrypto indicates an error with the cryptography related operations // such as decrypting or encrypting data, parsing an EC public key, // or deriving a secret key from a password. ErrCrypto // ErrRawSigning indicates an error in the process of generating raw // signatures for a transaction input. ErrRawSigning // ErrPreconditionNotMet indicates a programming error since a // preconditon has not been met. ErrPreconditionNotMet // ErrTxSigning indicates an error when signing a transaction. ErrTxSigning // ErrSeriesIDNotSequential indicates an attempt to create a series with // an ID that is not sequantial. ErrSeriesIDNotSequential // ErrInvalidScriptHash indicates an invalid P2SH. ErrInvalidScriptHash // ErrWithdrawFromUnusedAddr indicates an attempt to withdraw funds from // an address which has not been used before. ErrWithdrawFromUnusedAddr // ErrSeriesIDInvalid indicates an attempt to create a series with an // invalid ID. ErrSeriesIDInvalid // ErrWithdrawalTxStorage indicates an error when storing withdrawal // transactions. ErrWithdrawalTxStorage // ErrWithdrawalStorage indicates an error occurred when serializing or // deserializing withdrawal information. ErrWithdrawalStorage )
type InputAmounts ¶
InputAmounts is a slice of input amounts that is used to sign each input in a transaction.
type Ntxid ¶
type Ntxid string
Ntxid is the normalized ID of a given bitcoin transaction, which is generated by hashing the serialized tx with blank sig scripts on all inputs.
type OutBailmentID ¶
type OutBailmentID string
OutBailmentID is the unique ID of a user's outbailment, comprising the name of the server the user connected to, and the transaction number, internal to that server.
type OutBailmentOutpoint ¶
type OutBailmentOutpoint struct {
// contains filtered or unexported fields
}
OutBailmentOutpoint represents one of the outpoints created to fulfill an OutputRequest.
func (OutBailmentOutpoint) Amount ¶
func (o OutBailmentOutpoint) Amount() bchutil.Amount
Amount returns the amount (in satoshis) in this OutBailmentOutpoint.
type OutputRequest ¶
type OutputRequest struct { Address bchutil.Address Amount bchutil.Amount PkScript []byte // The notary server that received the outbailment request. Server string // The server-specific transaction number for the outbailment request. Transaction uint32 // contains filtered or unexported fields }
OutputRequest represents one of the outputs (address/amount) requested by a withdrawal, and includes information about the user's outbailment request.
func (OutputRequest) String ¶
func (r OutputRequest) String() string
String makes OutputRequest satisfy the Stringer interface.
type Pool ¶
type Pool struct { ID []byte // contains filtered or unexported fields }
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
func Create ¶
Create creates a new entry in the database with the given ID and returns the Pool representing it.
Example ¶
package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/dcrlabs/bchwallet/votingpool" "github.com/dcrlabs/bchwallet/waddrmgr" "github.com/dcrlabs/bchwallet/walletdb" "github.com/gcash/bchd/chaincfg" _ "github.com/dcrlabs/bchwallet/walletdb/bdb" ) var ( pubPassphrase = []byte("pubPassphrase") privPassphrase = []byte("privPassphrase") seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1} ) func createWaddrmgr(ns walletdb.ReadWriteBucket, params *chaincfg.Params) (*waddrmgr.Manager, error) { err := waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, params, fastScrypt, time.Now()) if err != nil { return nil, err } return waddrmgr.Open(ns, pubPassphrase, params) } func main() { // Create a new walletdb.DB. See the walletdb docs for instructions on how // to do that. db, dbTearDown, err := createWalletDB() if err != nil { fmt.Println(err) return } defer dbTearDown() dbtx, err := db.BeginReadWriteTx() if err != nil { fmt.Println(err) return } defer dbtx.Commit() // Create a new walletdb namespace for the address manager. mgrNamespace, err := dbtx.CreateTopLevelBucket([]byte("waddrmgr")) if err != nil { fmt.Println(err) return } // Create the address manager. mgr, err := createWaddrmgr(mgrNamespace, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } // Create a walletdb namespace for votingpools. vpNamespace, err := dbtx.CreateTopLevelBucket([]byte("votingpool")) if err != nil { fmt.Println(err) return } // Create a voting pool. _, err = votingpool.Create(vpNamespace, mgr, []byte{0x00}) if err != nil { fmt.Println(err) return } } func createWalletDB() (walletdb.DB, func(), error) { dir, err := ioutil.TempDir("", "votingpool_example") if err != nil { return nil, nil, err } db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"), true) if err != nil { return nil, nil, err } dbTearDown := func() { db.Close() os.RemoveAll(dir) } return db, dbTearDown, nil }
Output:
func Load ¶
Load fetches the entry in the database with the given ID and returns the Pool representing it.
func (*Pool) ActivateSeries ¶
func (p *Pool) ActivateSeries(ns walletdb.ReadWriteBucket, seriesID uint32) error
ActivateSeries marks the series with the given ID as active.
func (*Pool) ChangeAddress ¶
func (p *Pool) ChangeAddress(seriesID uint32, index Index) (*ChangeAddress, error)
ChangeAddress returns a new votingpool address for the given seriesID and index, on the 0th branch (which is reserved for change addresses). The series with the given ID must be active.
func (*Pool) CreateSeries ¶
func (p *Pool) CreateSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, rawPubKeys []string) error
CreateSeries will create and return a new non-existing series.
- seriesID must be greater than or equal 1; - 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 (*Pool) DepositScript ¶
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 (*Pool) DepositScriptAddress ¶
func (p *Pool) DepositScriptAddress(seriesID uint32, branch Branch, index Index) (bchutil.Address, error)
DepositScriptAddress calls DepositScript to get a multi-signature redemption script and returns the pay-to-script-hash-address for that script.
func (*Pool) EmpowerSeries ¶
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.
This method must be called with the Pool's manager unlocked.
func (*Pool) EnsureUsedAddr ¶
func (p *Pool) EnsureUsedAddr(ns, addrmgrNs walletdb.ReadWriteBucket, seriesID uint32, branch Branch, index Index) error
EnsureUsedAddr ensures we have entries in our used addresses DB for the given seriesID, branch and all indices up to the given one. It must be called with the manager unlocked.
func (*Pool) LoadAllSeries ¶
func (p *Pool) LoadAllSeries(ns walletdb.ReadBucket) error
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.
This method must be called with the Pool's manager unlocked. FIXME: We should be able to get rid of this (and loadAllSeries/seriesLookup) by making Series() load the series data directly from the DB.
func (*Pool) ReplaceSeries ¶
func (p *Pool) ReplaceSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, rawPubKeys []string) error
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 (*Pool) Series ¶
func (p *Pool) Series(seriesID uint32) *SeriesData
Series returns the series with the given ID, or nil if it doesn't exist.
func (*Pool) StartWithdrawal ¶
func (p *Pool) StartWithdrawal(ns walletdb.ReadWriteBucket, addrmgrNs walletdb.ReadBucket, roundID uint32, requests []OutputRequest, startAddress WithdrawalAddress, lastSeriesID uint32, changeStart ChangeAddress, txStore *wtxmgr.Store, txmgrNs walletdb.ReadBucket, chainHeight int32, dustThreshold bchutil.Amount) ( *WithdrawalStatus, error)
StartWithdrawal uses a fully deterministic algorithm to construct transactions fulfilling as many of the given output requests as possible. It returns a WithdrawalStatus containing the outpoints fulfilling the requested outputs and a map of normalized transaction IDs (ntxid) to signature lists (one for every private key available to this wallet) for each of those transaction's inputs. More details about the actual algorithm can be found at http://opentransactions.org/wiki/index.php/Startwithdrawal This method must be called with the address manager unlocked.
func (*Pool) WithdrawalAddress ¶
func (p *Pool) WithdrawalAddress(ns, addrmgrNs walletdb.ReadBucket, seriesID uint32, branch Branch, index Index) ( *WithdrawalAddress, error)
WithdrawalAddress queries the address manager for the P2SH address of the redeem script generated with the given series/branch/index and uses that to populate the returned WithdrawalAddress. This is done because we should only withdraw from previously used addresses but also because when processing withdrawals we may iterate over a huge number of addresses and it'd be too expensive to re-generate the redeem script for all of them. This method must be called with the manager unlocked.
type PoolAddress ¶
PoolAddress represents a voting pool P2SH address, generated by deriving public HD keys from the series' master keys using the given branch/index and constructing a M-of-N multi-sig script.
type RawSig ¶
type RawSig []byte
RawSig represents one of the signatures included in the unlocking script of inputs spending from P2SH UTXOs.
type SeriesData ¶
type SeriesData struct {
// contains filtered or unexported fields
}
SeriesData represents a Series for a given Pool.
func (*SeriesData) IsEmpowered ¶
func (s *SeriesData) IsEmpowered() bool
IsEmpowered returns true if this series is empowered (i.e. if it has at least one private key loaded).
type TxSigs ¶
type TxSigs [][]RawSig
TxSigs is list of raw signatures (one for every pubkey in the multi-sig script) for a given transaction input. They should match the order of pubkeys in the script and an empty RawSig should be used when the private key for a pubkey is not known.
type WithdrawalAddress ¶
type WithdrawalAddress struct {
// contains filtered or unexported fields
}
WithdrawalAddress is a votingpool address that may contain unspent outputs available for use in a withdrawal.
type WithdrawalOutput ¶
type WithdrawalOutput struct {
// contains filtered or unexported fields
}
WithdrawalOutput represents a possibly fulfilled OutputRequest.
func (*WithdrawalOutput) Address ¶
func (o *WithdrawalOutput) Address() string
Address returns the string representation of this WithdrawalOutput's address.
func (*WithdrawalOutput) Outpoints ¶
func (o *WithdrawalOutput) Outpoints() []OutBailmentOutpoint
Outpoints returns a slice containing the OutBailmentOutpoints created to fulfill this output.
func (*WithdrawalOutput) Status ¶
func (o *WithdrawalOutput) Status() string
Status returns the status of this WithdrawalOutput.
func (*WithdrawalOutput) String ¶
func (o *WithdrawalOutput) String() string
type WithdrawalStatus ¶
type WithdrawalStatus struct {
// contains filtered or unexported fields
}
WithdrawalStatus contains the details of a processed withdrawal, including the status of each requested output, the total amount of network fees and the next input and change addresses to use in a subsequent withdrawal request.
func (*WithdrawalStatus) Fees ¶
func (s *WithdrawalStatus) Fees() bchutil.Amount
Fees returns the total amount of network fees included in all transactions generated as part of a withdrawal.
func (*WithdrawalStatus) InputAmounts ¶
func (s *WithdrawalStatus) InputAmounts() map[Ntxid]InputAmounts
InputAmounts returns a map of ntxids to input amounts for every input in the tx with that ntxid.
func (*WithdrawalStatus) NextChangeAddr ¶
func (s *WithdrawalStatus) NextChangeAddr() ChangeAddress
NextChangeAddr returns the votingpool address that should be used as the changeStart of subsequent withdrawals.
func (*WithdrawalStatus) NextInputAddr ¶
func (s *WithdrawalStatus) NextInputAddr() WithdrawalAddress
NextInputAddr returns the votingpool address that should be used as the startAddress of subsequent withdrawals.
func (*WithdrawalStatus) Outputs ¶
func (s *WithdrawalStatus) Outputs() map[OutBailmentID]*WithdrawalOutput
Outputs returns a map of outbailment IDs to WithdrawalOutputs for all outputs requested in this withdrawal.
func (*WithdrawalStatus) Sigs ¶
func (s *WithdrawalStatus) Sigs() map[Ntxid]TxSigs
Sigs returns a map of ntxids to signature lists for every input in the tx with that ntxid.