func (s *SeriesData) IsEmpowered() bool {
	for _, key := range s.privateKeys {
		if key != nil {
			return true
		}
	}
	return false
}
IsEmpowered returns true if this series is empowered (i.e. if it has
at least one private key loaded).
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
}
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
}
func (p *Pool) WithdrawalAddress(ns, addrmgrNs walletdb.ReadBucket, seriesID uint32, branch Branch, index Index) (
	*WithdrawalAddress, error) {
	addr, err := p.getUsedAddr(ns, addrmgrNs, seriesID, branch, index)
	if err != nil {
		return nil, err
	}
	if addr == nil {
		str := fmt.Sprintf("cannot withdraw from unused addr (series: %d, branch: %d, index: %d)",
			seriesID, branch, index)
		return nil, newError(ErrWithdrawFromUnusedAddr, str, nil)
	}
	script, err := addr.Script()
	if err != nil {
		return nil, err
	}
	pAddr, err := p.poolAddress(seriesID, branch, index, script)
	if err != nil {
		return nil, err
	}
	return &WithdrawalAddress{poolAddress: pAddr}, nil
}
func (p *Pool) poolAddress(seriesID uint32, branch Branch, index Index, script []byte) (
	*poolAddress, error) {
	addr, err := p.addressFor(script)
	if err != nil {
		return nil, err
	}
	return &poolAddress{
			pool: p, seriesID: seriesID, branch: branch, index: index, addr: addr,
			script: script},
		nil
}
func (p *Pool) EmpowerSeries(ns walletdb.ReadWriteBucket, seriesID uint32, rawPrivKey string) error {
	series := p.Series(seriesID)
	if series == nil {
		str := fmt.Sprintf("series %d does not exist for this voting pool",
			seriesID)
		return newError(ErrSeriesNotExists, str, nil)
	}
	privKey, err := hdkeychain.NewKeyFromString(rawPrivKey)
	if err != nil {
		str := fmt.Sprintf("invalid extended private key %v", rawPrivKey)
		return newError(ErrKeyChain, str, err)
	}
	if !privKey.IsPrivate() {
		str := fmt.Sprintf(
			"to empower a series you need the extended private key, not an extended public key %v",
			privKey)
		return newError(ErrKeyIsPublic, str, err)
	}
	pubKey, err := privKey.Neuter()
	if err != nil {
		str := fmt.Sprintf("invalid extended private key %v, can't convert to public key",
			rawPrivKey)
		return newError(ErrKeyNeuter, str, err)
	}
	lookingFor := pubKey.String()
	found := false
	for i, publicKey := range series.publicKeys {
		if publicKey.String() == lookingFor {
			found = true
			series.privateKeys[i] = privKey
		}
	}
	if !found {
		str := "private Key does not have a corresponding public key in this series"
		return newError(ErrKeysPrivatePublicMismatch, str, nil)
	}
	return p.saveSeriesToDisk(ns, seriesID, series)
}
func (p *Pool) EnsureUsedAddr(ns, addrmgrNs walletdb.ReadWriteBucket, seriesID uint32, branch Branch, index Index) error {
	lastIdx, err := p.highestUsedIndexFor(ns, seriesID, branch)
	if err != nil {
		return err
	}
	if lastIdx == 0 {
		if err := p.addUsedAddr(ns, addrmgrNs, seriesID, branch, lastIdx); err != nil {
			return err
		}
	}
	lastIdx++
	for lastIdx <= index {
		if err := p.addUsedAddr(ns, addrmgrNs, seriesID, branch, lastIdx); err != nil {
			return err
		}
		lastIdx++
	}
	return nil
}
func (p *Pool) addUsedAddr(ns, addrmgrNs walletdb.ReadWriteBucket, seriesID uint32, branch Branch, index Index) error {
	script, err := p.DepositScript(seriesID, branch, index)
	if err != nil {
		return err
	}
	manager, err := p.manager.FetchScopedKeyManager(waddrmgr.KeyScopeBIP0044)
	if err != nil {
		return err
	}
	_, err = manager.ImportScript(addrmgrNs, script, &waddrmgr.BlockStamp{})
	if err != nil && err.(waddrmgr.ManagerError).ErrorCode != waddrmgr.ErrDuplicateAddress {
		return err
	}
	encryptedHash, err := p.manager.Encrypt(waddrmgr.CKTPublic, bchutil.Hash160(script))
	if err != nil {
		return newError(ErrCrypto, "failed to encrypt script hash", err)
	}
	err = putUsedAddrHash(ns, p.ID, seriesID, branch, index, encryptedHash)
	if err != nil {
		return newError(ErrDatabase, "failed to store used addr script hash", err)
	}
	return nil
}
func (p *Pool) getUsedAddr(ns, addrmgrNs walletdb.ReadBucket, seriesID uint32, branch Branch, index Index) (
	waddrmgr.ManagedScriptAddress, error) {
	mgr := p.manager
	encryptedHash := getUsedAddrHash(ns, p.ID, seriesID, branch, index)
	if encryptedHash == nil {
		return nil, nil
	}
	hash, err := p.manager.Decrypt(waddrmgr.CKTPublic, encryptedHash)
	if err != nil {
		return nil, newError(ErrCrypto, "failed to decrypt stored script hash", err)
	}
	addr, err := bchutil.NewAddressScriptHashFromHash(hash, mgr.ChainParams())
	if err != nil {
		return nil, newError(ErrInvalidScriptHash, "failed to parse script hash", err)
	}
	mAddr, err := mgr.Address(addrmgrNs, addr)
	if err != nil {
		return nil, err
	}
	return mAddr.(waddrmgr.ManagedScriptAddress), nil
}
func (p *Pool) highestUsedIndexFor(ns walletdb.ReadBucket, seriesID uint32, branch Branch) (Index, error) {
	return getMaxUsedIdx(ns, p.ID, seriesID, branch)
}
func (a *poolAddress) String() string {
	return a.addr.EncodeAddress()
}
func (a *poolAddress) addrIdentifier() string {
	return fmt.Sprintf("PoolAddress seriesID:%d, branch:%d, index:%d", a.seriesID, a.branch,
		a.index)
}
func (a *poolAddress) redeemScript() []byte {
	return a.script
}
func (a *poolAddress) series() *SeriesData {
	return a.pool.Series(a.seriesID)
}
func (a *poolAddress) SeriesID() uint32 {
	return a.seriesID
}
func (a *poolAddress) Branch() Branch {
	return a.branch
}
func (a *poolAddress) Index() Index {
	return a.index
}