votingpool

package
v0.0.0-...-0e95005 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2024 License: ISC Imports: 21 Imported by: 0

README

votingpool

[Build Status] (https://travis-ci.org/gcash/bchwallet)

Package votingpool provides voting pool functionality for bchwallet as described here: 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

[GoDoc] (http://godoc.org/github.com/gcash/bchwallet/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/gcash/bchwallet/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/gcash/bchwallet/votingpool

Package votingpool is licensed under the copyfree ISC License.

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

Examples

Constants

View Source
const (

	// CurrentVersion is the version used for newly created Series.
	CurrentVersion = 1
)

Variables

This section is empty.

Functions

func CanonicalKeyOrder

func CanonicalKeyOrder(keys []string) []string

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.

func UseLogger

func UseLogger(logger bchlog.Logger)

UseLogger uses a specified Logger to output package logging info. This should be used in preference to SetLogWriter if the caller is also using bchlog.

Types

type Branch

type Branch uint32

Branch is the type used to represent a branch number in a series.

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.

func (ChangeAddress) Branch

func (a ChangeAddress) Branch() Branch

func (ChangeAddress) Index

func (a ChangeAddress) Index() Index

func (ChangeAddress) SeriesID

func (a ChangeAddress) SeriesID() uint32

func (ChangeAddress) String

func (a ChangeAddress) String() string

String returns a string encoding of the underlying bitcoin payment address.

type Credit

type Credit struct {
	wtxmgr.Credit
	// contains filtered or unexported fields
}

Credit is an abstraction over wtxmgr.Credit used in the construction of voting pool withdrawal transactions.

func (*Credit) String

func (c *Credit) String() string

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.

func (Error) Error

func (e Error) Error() string

Error satisfies the error interface and prints human-readable errors.

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
)

func (ErrorCode) String

func (e ErrorCode) String() string

String returns the ErrorCode as a human-readable name.

type Index

type Index uint32

Index is the type used to represent an index number in a series.

type InputAmounts

type InputAmounts []bchutil.Amount

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

func Create(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, poolID []byte) (*Pool, error)

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

func Load(ns walletdb.ReadBucket, m *waddrmgr.Manager, poolID []byte) (*Pool, error)

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

func (p *Pool) DepositScript(seriesID uint32, branch Branch, index Index) ([]byte, error)

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

func (p *Pool) EmpowerSeries(ns walletdb.ReadWriteBucket, seriesID uint32, rawPrivKey string) error

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

func (p *Pool) Manager() *waddrmgr.Manager

Manager returns the waddrmgr.Manager used by this Pool.

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

type PoolAddress interface {
	SeriesID() uint32
	Branch() Branch
	Index() Index
}

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.

func (WithdrawalAddress) Branch

func (a WithdrawalAddress) Branch() Branch

func (WithdrawalAddress) Index

func (a WithdrawalAddress) Index() Index

func (WithdrawalAddress) SeriesID

func (a WithdrawalAddress) SeriesID() uint32

func (WithdrawalAddress) String

func (a WithdrawalAddress) String() string

String returns a string encoding of the underlying bitcoin payment address.

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

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL