func (p *Pool) ChangeAddress(seriesID uint32, index Index) (*ChangeAddress, error) {
	series := p.Series(seriesID)
	if series == nil {
		return nil, newError(ErrSeriesNotExists,
			fmt.Sprintf("series %d does not exist", seriesID), nil)
	}
	if !series.active {
		str := fmt.Sprintf("ChangeAddress must be on active series; series #%d is not", seriesID)
		return nil, newError(ErrSeriesNotActive, str, nil)
	}
	script, err := p.DepositScript(seriesID, Branch(0), index)
	if err != nil {
		return nil, err
	}
	pAddr, err := p.poolAddress(seriesID, Branch(0), index, script)
	if err != nil {
		return nil, err
	}
	return &ChangeAddress{poolAddress: pAddr}, nil
}
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.
package votingpool
import (
	"fmt"
	"sort"
	"github.com/gcash/bchd/txscript"
	"github.com/gcash/bchutil"
	"github.com/gcash/bchutil/hdkeychain"
	"github.com/gcash/bchwallet/internal/zero"
	"github.com/gcash/bchwallet/waddrmgr"
	"github.com/gcash/bchwallet/walletdb"
)
const (
	minSeriesPubKeys = 3
	CurrentVersion = 1
)
type Branch uint32
type Index uint32
type SeriesData struct {
	version uint32
	active bool
	reqSigs     uint32
	publicKeys  []*hdkeychain.ExtendedKey
	privateKeys []*hdkeychain.ExtendedKey
}
type Pool struct {
	ID           []byte
	seriesLookup map[uint32]*SeriesData
	manager      *waddrmgr.Manager
}
type PoolAddress interface {
	SeriesID() uint32
	Branch() Branch
	Index() Index
}
type poolAddress struct {
	pool     *Pool
	addr     bchutil.Address
	script   []byte
	seriesID uint32
	branch   Branch
	index    Index
}
type ChangeAddress struct {
	*poolAddress
}
type WithdrawalAddress struct {
	*poolAddress
}
func Create(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, poolID []byte) (*Pool, error) {
	err := putPool(ns, poolID)
	if err != nil {
		str := fmt.Sprintf("unable to add voting pool %v to db", poolID)
		return nil, newError(ErrPoolAlreadyExists, str, err)
	}
	return newPool(m, poolID), nil
}
func Load(ns walletdb.ReadBucket, m *waddrmgr.Manager, poolID []byte) (*Pool, error) {
	if !existsPool(ns, poolID) {
		str := fmt.Sprintf("unable to find voting pool %v in db", poolID)
		return nil, newError(ErrPoolNotExists, str, nil)
	}
	p := newPool(m, poolID)
	if err := p.LoadAllSeries(ns); err != nil {
		return nil, err
	}
	return p, nil
}
func newPool(m *waddrmgr.Manager, poolID []byte) *Pool {
	return &Pool{
		ID:           poolID,
		seriesLookup: make(map[uint32]*SeriesData),
		manager:      m,
	}
}
func LoadAndGetDepositScript(ns walletdb.ReadBucket, m *waddrmgr.Manager, poolID string, seriesID uint32, branch Branch, index Index) ([]byte, error) {
	pid := []byte(poolID)
	p, err := Load(ns, m, pid)
	if err != nil {
		return nil, err
	}
	script, err := p.DepositScript(seriesID, branch, index)
	if err != nil {
		return nil, err
	}
	return script, nil
}
func LoadAndCreateSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32,
	poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error {
	pid := []byte(poolID)
	p, err := Load(ns, m, pid)
	if err != nil {
		vpErr := err.(Error)
		if vpErr.ErrorCode == ErrPoolNotExists {
			p, err = Create(ns, m, pid)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}
	return p.CreateSeries(ns, version, seriesID, reqSigs, rawPubKeys)
}
func LoadAndReplaceSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager, version uint32,
	poolID string, seriesID, reqSigs uint32, rawPubKeys []string) error {
	pid := []byte(poolID)
	p, err := Load(ns, m, pid)
	if err != nil {
		return err
	}
	return p.ReplaceSeries(ns, version, seriesID, reqSigs, rawPubKeys)
}
func LoadAndEmpowerSeries(ns walletdb.ReadWriteBucket, m *waddrmgr.Manager,
	poolID string, seriesID uint32, rawPrivKey string) error {
	pid := []byte(poolID)
	pool, err := Load(ns, m, pid)
	if err != nil {
		return err
	}
	return pool.EmpowerSeries(ns, seriesID, rawPrivKey)
}
func (p *Pool) Series(seriesID uint32) *SeriesData {
	series, exists := p.seriesLookup[seriesID]
	if !exists {
		return nil
	}
	return series
}
func (p *Pool) Manager() *waddrmgr.Manager {
	return p.manager
}
func (p *Pool) saveSeriesToDisk(ns walletdb.ReadWriteBucket, seriesID uint32, data *SeriesData) error {
	var err error
	encryptedPubKeys := make([][]byte, len(data.publicKeys))
	for i, pubKey := range data.publicKeys {
		encryptedPubKeys[i], err = p.manager.Encrypt(
			waddrmgr.CKTPublic, []byte(pubKey.String()))
		if err != nil {
			str := fmt.Sprintf("key %v failed encryption", pubKey)
			return newError(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 = p.manager.Encrypt(
				waddrmgr.CKTPrivate, []byte(privKey.String()))
		}
		if err != nil {
			str := fmt.Sprintf("key %v failed encryption", privKey)
			return newError(ErrCrypto, str, err)
		}
	}
	err = putSeries(ns, p.ID, data.version, seriesID, data.active,
		data.reqSigs, encryptedPubKeys, encryptedPrivKeys)
	if err != nil {
		str := fmt.Sprintf("cannot put series #%d into db", seriesID)
		return newError(ErrSeriesSerialization, str, err)
	}
	return nil
}
func CanonicalKeyOrder(keys []string) []string {
	orderedKeys := make([]string, len(keys))
	copy(orderedKeys, keys)
	sort.Strings(orderedKeys)
	return orderedKeys
}
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, newError(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, newError(ErrKeyChain, str, err)
		}
		if key.IsPrivate() {
			str := fmt.Sprintf("private keys not accepted: %v", rawPubKey)
			return nil, newError(ErrKeyIsPrivate, str, nil)
		}
		keys[i] = key
	}
	return keys, nil
}
func (p *Pool) putSeries(ns walletdb.ReadWriteBucket, 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 newError(ErrTooFewPublicKeys, str, nil)
	}
	if reqSigs > uint32(len(inRawPubKeys)) {
		str := "the number of required signatures cannot be more than the number of keys"
		return newError(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 = p.saveSeriesToDisk(ns, seriesID, data)
	if err != nil {
		return err
	}
	p.seriesLookup[seriesID] = data
	return nil
}
func (p *Pool) CreateSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, rawPubKeys []string) error {
	if seriesID == 0 {
		return newError(ErrSeriesIDInvalid, "series ID cannot be 0", nil)
	}
	if series := p.Series(seriesID); series != nil {
		str := fmt.Sprintf("series #%d already exists", seriesID)
		return newError(ErrSeriesAlreadyExists, str, nil)
	}
	if seriesID != 1 {
		if _, ok := p.seriesLookup[seriesID-1]; !ok {
			str := fmt.Sprintf("series #%d cannot be created because series #%d does not exist",
				seriesID, seriesID-1)
			return newError(ErrSeriesIDNotSequential, str, nil)
		}
	}
	return p.putSeries(ns, version, seriesID, reqSigs, rawPubKeys)
}
func (p *Pool) ActivateSeries(ns walletdb.ReadWriteBucket, seriesID uint32) error {
	series := p.Series(seriesID)
	if series == nil {
		str := fmt.Sprintf("series #%d does not exist, cannot activate it", seriesID)
		return newError(ErrSeriesNotExists, str, nil)
	}
	series.active = true
	err := p.saveSeriesToDisk(ns, seriesID, series)
	if err != nil {
		return err
	}
	p.seriesLookup[seriesID] = series
	return nil
}
func (p *Pool) ReplaceSeries(ns walletdb.ReadWriteBucket, version, seriesID, reqSigs uint32, rawPubKeys []string) error {
	series := p.Series(seriesID)
	if series == nil {
		str := fmt.Sprintf("series #%d does not exist, cannot replace it", seriesID)
		return newError(ErrSeriesNotExists, str, nil)
	}
	if series.IsEmpowered() {
		str := fmt.Sprintf("series #%d has private keys and cannot be replaced", seriesID)
		return newError(ErrSeriesAlreadyEmpowered, str, nil)
	}
	return p.putSeries(ns, version, seriesID, reqSigs, rawPubKeys)
}
func (p *Pool) decryptExtendedKey(keyType waddrmgr.CryptoKeyType, encrypted []byte) (*hdkeychain.ExtendedKey, error) {
	decrypted, err := p.manager.Decrypt(keyType, encrypted)
	if err != nil {
		str := fmt.Sprintf("cannot decrypt key %v", encrypted)
		return nil, newError(ErrCrypto, str, err)
	}
	result, err := hdkeychain.NewKeyFromString(string(decrypted))
	zero.Bytes(decrypted)
	if err != nil {
		str := fmt.Sprintf("cannot get key from string %v", decrypted)
		return nil, newError(ErrKeyChain, str, err)
	}
	return result, nil
}
func validateAndDecryptKeys(rawPubKeys, rawPrivKeys [][]byte, p *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, newError(ErrKeysPrivatePublicMismatch,
			"the pub key and priv key arrays should have the same number of elements",
			nil)
	}
	for i, encryptedPub := range rawPubKeys {
		pubKey, err := p.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 = p.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, newError(ErrKeyNeuter, str, err)
			}
			if pubKey.String() != checkPubKey.String() {
				str := fmt.Sprintf("public key %v different than expected %v",
					pubKey, checkPubKey)
				return nil, nil, newError(ErrKeyMismatch, str, nil)
			}
		}
	}
	return pubKeys, privKeys, nil
}
func (p *Pool) LoadAllSeries(ns walletdb.ReadBucket) error {
	series, err := loadAllSeries(ns, p.ID)
	if err != nil {
		return err
	}
	for id, series := range series {
		pubKeys, privKeys, err := validateAndDecryptKeys(
			series.pubKeysEncrypted, series.privKeysEncrypted, p)
		if err != nil {
			return err
		}
		p.seriesLookup[id] = &SeriesData{
			publicKeys:  pubKeys,
			privateKeys: privKeys,
			reqSigs:     series.reqSigs,
		}
	}
	return nil
}
func branchOrder(pks []*hdkeychain.ExtendedKey, branch Branch) ([]*hdkeychain.ExtendedKey, error) {
	if pks == nil {
		return nil, newError(ErrInvalidValue, "pks cannot be nil", nil)
	}
	if branch > Branch(len(pks)) {
		return nil, newError(
			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)
		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
}
func (p *Pool) DepositScriptAddress(seriesID uint32, branch Branch, index Index) (bchutil.Address, error) {
	script, err := p.DepositScript(seriesID, branch, index)
	if err != nil {
		return nil, err
	}
	return p.addressFor(script)
}
func (p *Pool) addressFor(script []byte) (bchutil.Address, error) {
	scriptHash := bchutil.Hash160(script)
	return bchutil.NewAddressScriptHashFromHash(scriptHash, p.manager.ChainParams())
}
func (p *Pool) DepositScript(seriesID uint32, branch Branch, index Index) ([]byte, error) {
	series := p.Series(seriesID)
	if series == nil {
		str := fmt.Sprintf("series #%d does not exist", seriesID)
		return nil, newError(ErrSeriesNotExists, str, nil)
	}
	pubKeys, err := branchOrder(series.publicKeys, branch)
	if err != nil {
		return nil, err
	}
	pks := make([]*bchutil.AddressPubKey, len(pubKeys))
	for i, key := range pubKeys {
		child, err := key.Child(uint32(index))
		if err != nil {
			str := fmt.Sprintf("child #%d for this pubkey %d does not exist", index, i)
			return nil, newError(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, newError(ErrKeyChain, str, err)
		}
		pks[i], err = bchutil.NewAddressPubKey(pubkey.SerializeCompressed(),
			p.manager.ChainParams())
		if err != nil {
			str := fmt.Sprintf(
				"child #%d for this pubkey %d could not be converted to an address",
				index, i)
			return nil, newError(ErrKeyChain, str, err)
		}
	}
	script, err := txscript.MultiSigScript(pks, int(series.reqSigs))
	if err != nil {
		str := fmt.Sprintf("error while making multisig script hash, %d", len(pks))
		return nil, newError(ErrScriptCreation, str, err)
	}
	return script, nil
}