mirror of https://github.com/poanetwork/gecko.git
184 lines
5.2 KiB
Go
184 lines
5.2 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package spdagvm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/ava-labs/gecko/ids"
|
|
"github.com/ava-labs/gecko/utils/crypto"
|
|
"github.com/ava-labs/gecko/utils/formatting"
|
|
)
|
|
|
|
var (
|
|
errLockedFunds = errors.New("funds currently locked")
|
|
errCantSpend = errors.New("utxo couldn't be spent")
|
|
)
|
|
|
|
// Keychain is a collection of keys that can be used to spend utxos
|
|
type Keychain struct {
|
|
factory crypto.FactorySECP256K1R
|
|
networkID uint32
|
|
chainID ids.ID
|
|
|
|
// Key: The id of a private key (namely, [privKey].PublicKey().Address().Key())
|
|
// Value: The index in Keys of that private key
|
|
keyMap map[[20]byte]int
|
|
|
|
// Each element is an address controlled by a key in [Keys]
|
|
// This can be used to iterate over. It should not be modified externally.
|
|
Addrs ids.ShortSet
|
|
|
|
// List of keys this keychain manages
|
|
// This can be used to iterate over. It should not be modified externally.
|
|
Keys []*crypto.PrivateKeySECP256K1R
|
|
}
|
|
|
|
// NewKeychain creates a new keychain for a chain
|
|
func NewKeychain(networkID uint32, chainID ids.ID) *Keychain {
|
|
return &Keychain{
|
|
networkID: networkID,
|
|
chainID: chainID,
|
|
keyMap: make(map[[20]byte]int),
|
|
}
|
|
}
|
|
|
|
// Add a new key to the key chain.
|
|
// If [key] is already in the keychain, does nothing.
|
|
func (kc *Keychain) Add(key *crypto.PrivateKeySECP256K1R) {
|
|
addr := key.PublicKey().Address() // The address controlled by [key]
|
|
addrHash := addr.Key()
|
|
if _, ok := kc.keyMap[addrHash]; !ok {
|
|
kc.keyMap[addrHash] = len(kc.Keys)
|
|
kc.Keys = append(kc.Keys, key)
|
|
kc.Addrs.Add(addr)
|
|
}
|
|
}
|
|
|
|
// Get a key from the keychain. If the key is unknown, the second return value is false.
|
|
func (kc *Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) {
|
|
if i, ok := kc.keyMap[id.Key()]; ok {
|
|
return kc.Keys[i], true
|
|
}
|
|
return &crypto.PrivateKeySECP256K1R{}, false
|
|
}
|
|
|
|
// Addresses returns a list of addresses this keychain manages
|
|
func (kc *Keychain) Addresses() ids.ShortSet { return kc.Addrs }
|
|
|
|
// New returns a newly generated private key.
|
|
// The key and the address it controls are added to
|
|
// [kc.Keys] and [kc.Addrs], respectively
|
|
func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) {
|
|
skGen, err := kc.factory.NewPrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sk := skGen.(*crypto.PrivateKeySECP256K1R)
|
|
kc.Add(sk)
|
|
return sk, nil
|
|
}
|
|
|
|
// Spend attempts to create an input
|
|
func (kc *Keychain) Spend(utxo *UTXO, time uint64) (Input, *InputSigner, error) {
|
|
builder := Builder{
|
|
NetworkID: kc.networkID,
|
|
ChainID: kc.chainID,
|
|
}
|
|
|
|
switch out := utxo.Out().(type) {
|
|
case *OutputPayment:
|
|
if time < out.Locktime() { // [UTXO] may not be spent yet
|
|
return nil, nil, errLockedFunds
|
|
}
|
|
// Get [threshold] of the keys needed to spend [UTXO]
|
|
if sigs, keys, able := kc.GetSigsAndKeys(out.Addresses(), int(out.Threshold())); able {
|
|
sourceID, sourceIndex := utxo.Source()
|
|
return builder.NewInputPayment(
|
|
sourceID,
|
|
sourceIndex,
|
|
out.Amount(),
|
|
sigs,
|
|
),
|
|
&InputSigner{Keys: keys},
|
|
nil
|
|
}
|
|
case *OutputTakeOrLeave:
|
|
if time < out.Locktime1() {
|
|
return nil, nil, errLockedFunds
|
|
}
|
|
if sigs, keys, able := kc.GetSigsAndKeys(out.Addresses1(), int(out.Threshold1())); able {
|
|
sourceID, sourceIndex := utxo.Source()
|
|
return builder.NewInputPayment(
|
|
sourceID,
|
|
sourceIndex,
|
|
out.Amount(),
|
|
sigs,
|
|
),
|
|
&InputSigner{Keys: keys},
|
|
nil
|
|
}
|
|
if time < out.Locktime2() {
|
|
return nil, nil, errLockedFunds
|
|
}
|
|
if sigs, keys, able := kc.GetSigsAndKeys(out.Addresses2(), int(out.Threshold2())); able {
|
|
sourceID, sourceIndex := utxo.Source()
|
|
return builder.NewInputPayment(
|
|
sourceID,
|
|
sourceIndex,
|
|
out.Amount(),
|
|
sigs,
|
|
),
|
|
&InputSigner{Keys: keys},
|
|
nil
|
|
}
|
|
}
|
|
return nil, nil, errCantSpend
|
|
}
|
|
|
|
// GetSigsAndKeys returns:
|
|
// 1) A list of *Sig where [Sig].Index is the index of an address in [addresses]
|
|
// such that a key in this keychain that controls the address
|
|
// 2) A list of private keys such that each key controls an address in [addresses]
|
|
// 3) true iff this keychain contains at least [threshold] keys that control an address
|
|
// in [addresses]
|
|
func (kc *Keychain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*Sig, []*crypto.PrivateKeySECP256K1R, bool) {
|
|
sigs := []*Sig{}
|
|
keys := []*crypto.PrivateKeySECP256K1R{}
|
|
builder := Builder{
|
|
NetworkID: kc.networkID,
|
|
ChainID: kc.chainID,
|
|
}
|
|
for i := uint32(0); i < uint32(len(addresses)) && len(keys) < threshold; i++ {
|
|
if key, exists := kc.Get(addresses[i]); exists {
|
|
sigs = append(sigs, builder.NewSig(i))
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
return sigs, keys, len(keys) == threshold
|
|
}
|
|
|
|
// PrefixedString returns the key chain as a string representation with [prefix]
|
|
// added before every line.
|
|
func (kc *Keychain) PrefixedString(prefix string) string {
|
|
s := strings.Builder{}
|
|
|
|
format := fmt.Sprintf("%%sKey[%s]: Key: %%s Address: %%s\n",
|
|
formatting.IntFormat(len(kc.Keys)-1))
|
|
for i, key := range kc.Keys {
|
|
s.WriteString(fmt.Sprintf(format,
|
|
prefix,
|
|
i,
|
|
formatting.CB58{Bytes: key.Bytes()},
|
|
key.PublicKey().Address()))
|
|
}
|
|
|
|
return strings.TrimSuffix(s.String(), "\n")
|
|
}
|
|
|
|
func (kc *Keychain) String() string { return kc.PrefixedString("") }
|