gecko/vms/spdagvm/output.go

309 lines
9.2 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spdagvm
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
)
// Output describes what functions every output must implement
type Output interface {
formatting.PrefixedStringer
Unlock(Input, uint64) error
Verify() error
}
// OutputPayment represents an output that transfers value
// OutputPayment implements Output
type OutputPayment struct {
// The amount of this output
amount uint64
// The earliest time at which this output may be spent
// Measured in Unix time
locktime uint64
// The number of signatures required to spend this output
threshold uint32
// The addresses that can produce signatures to spend this output
addresses []ids.ShortID
}
// Amount of value this output creates
func (op *OutputPayment) Amount() uint64 { return op.amount }
// Locktime is the time that this output should be able to be spent
func (op *OutputPayment) Locktime() uint64 { return op.locktime }
// Threshold is the number of signatures this output will require to be spent
func (op *OutputPayment) Threshold() uint32 { return op.threshold }
// Addresses are the representations of keys that can produce signatures to
// spend this output
func (op *OutputPayment) Addresses() []ids.ShortID { return op.addresses }
// Unlock returns true if the input has the correct signatures to spend this
// output
func (op *OutputPayment) Unlock(in Input, time uint64) error {
if op.locktime > time {
return errTimelocked
}
switch i := in.(type) {
case *InputPayment:
switch {
case op.amount != i.amount:
return errInvalidAmount
case !checkRawAddresses(op.threshold, op.addresses, i.sigs):
return errSpendFailed
}
default:
return errTypeMismatch
}
return nil
}
// Verify that this output is syntactically correct
func (op *OutputPayment) Verify() error {
switch {
case op == nil:
return errNilOutput
case op.amount == 0:
return errOutputHasNoValue
case op.threshold > uint32(len(op.addresses)):
return errOutputUnspendable
case op.threshold == 0 && len(op.addresses) > 0:
return errOutputUnoptimized
case !ids.IsSortedAndUniqueShortIDs(op.addresses): // TODO: Should we allow duplicated addresses
return errAddrsNotSortedUnique
default:
return nil
}
}
// PrefixedString converts this input to a string representation with a prefix
// for each newline
func (op *OutputPayment) PrefixedString(prefix string) string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("OutputPayment(\n"+
"%s Amount = %d\n"+
"%s Locktime = %d\n"+
"%s Threshold = %d\n"+
"%s NumAddrs = %d\n",
prefix, op.amount,
prefix, op.locktime,
prefix, op.threshold,
prefix, len(op.addresses)))
addrFormat := fmt.Sprintf("%%s Addrs[%s]: %%s\n",
formatting.IntFormat(len(op.addresses)-1))
for i, addr := range op.addresses {
s.WriteString(fmt.Sprintf(addrFormat,
prefix, i, addr,
))
}
s.WriteString(fmt.Sprintf("%s)", prefix))
return s.String()
}
func (op *OutputPayment) String() string { return op.PrefixedString("") }
// OutputTakeOrLeave is a take-or-leave transaction. It implements Output.
// After time [locktime1], it can be spent using [threshold1] signatures, where each
// signature is from [addresses1]
// After time [locktime2], it can also be spent using [threshold2] signatures, where each
// signature is from [addresses2]
type OutputTakeOrLeave struct {
// The amount of this output
amount uint64
// The time (Unix time) after which this output may be spent
// using [threshold1] signatures from [addresses1]
locktime1 uint64
// The time (Unix time) after which this output may be spent
// using [threshold1] signatures from [addresses1]
// Must be greater than [locktime1]
locktime2 uint64
// The number of signatures from [addresses1] required to spend
// this output
threshold1 uint32
// The number of signatures from [addresses2] required to spend
// this output
threshold2 uint32
// The addresses that may spend this output after [locktime1]
addresses1 []ids.ShortID
// The addresses that may spend this output after [locktime2]
addresses2 []ids.ShortID
}
// Amount returns the value this output produces
func (otol *OutputTakeOrLeave) Amount() uint64 { return otol.amount }
// Locktime1 returns the time after which the first set of addresses
// may spend this output are unlocked
func (otol *OutputTakeOrLeave) Locktime1() uint64 { return otol.locktime1 }
// Threshold1 returns the number of signatures the first set of addresses need to
// produce to be able to spend this output
func (otol *OutputTakeOrLeave) Threshold1() uint32 { return otol.threshold1 }
// Addresses1 are the addresses controlled by keys that can produce signatures to
// spend this output
func (otol *OutputTakeOrLeave) Addresses1() []ids.ShortID { return otol.addresses1 }
// Locktime2 returns when the second set of addresses are unlocked
func (otol *OutputTakeOrLeave) Locktime2() uint64 { return otol.locktime2 }
// Threshold2 returns the number of signatures the second set of addresses
// need to produce to be able to spend this output
func (otol *OutputTakeOrLeave) Threshold2() uint32 { return otol.threshold2 }
// Addresses2 are the addresses controlled by keys that can produce signatures to
// spend this output
func (otol *OutputTakeOrLeave) Addresses2() []ids.ShortID { return otol.addresses2 }
// Unlock returns true if the input has the correct signatures to spend this
// output at time [time]
func (otol *OutputTakeOrLeave) Unlock(in Input, time uint64) error {
switch i := in.(type) {
case *InputPayment:
switch {
case otol.amount != i.amount:
return errInvalidAmount
case otol.locktime2 > time:
return errTimelocked
case (otol.locktime1 > time ||
!checkRawAddresses(otol.threshold1, otol.addresses1, i.sigs)) &&
!checkRawAddresses(otol.threshold2, otol.addresses2, i.sigs):
return errSpendFailed
}
default:
return errTypeMismatch
}
return nil
}
// Verify that this output is syntactically correct
func (otol *OutputTakeOrLeave) Verify() error {
switch {
case otol == nil:
return errNilOutput
case otol.amount == 0:
return errOutputHasNoValue
case otol.threshold1 > uint32(len(otol.addresses1)) ||
otol.threshold2 > uint32(len(otol.addresses2)):
return errOutputUnspendable
case (otol.threshold1 == 0 && len(otol.addresses1) > 0) ||
(otol.threshold2 == 0 && len(otol.addresses2) > 0):
return errOutputUnoptimized
case otol.locktime1 >= otol.locktime2:
return errTimesNotSortedUnique
case !ids.IsSortedAndUniqueShortIDs(otol.addresses1) || // TODO: Should we allow duplicated addresses
!ids.IsSortedAndUniqueShortIDs(otol.addresses2):
return errAddrsNotSortedUnique
default:
return nil
}
}
// PrefixedString converts this input to a string representation with a prefix
// for each newline
func (otol *OutputTakeOrLeave) PrefixedString(prefix string) string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("OutputTakeOrLeave(\n"+
"%s Amount = %d\n"+
"%s Locktime = %d\n"+
"%s Threshold = %d\n"+
"%s NumAddrs = %d\n",
prefix, otol.amount,
prefix, otol.locktime1,
prefix, otol.threshold1,
prefix, len(otol.addresses1)))
addrFormat := fmt.Sprintf("%%s Addrs[%s]: %%s\n",
formatting.IntFormat(len(otol.addresses1)-1))
for i, addr := range otol.addresses1 {
s.WriteString(fmt.Sprintf(addrFormat,
prefix, i, addr,
))
}
s.WriteString(fmt.Sprintf("%s FallLocktime = %d\n"+
"%s FallThreshold = %d\n"+
"%s FallNumAddrs = %d\n",
prefix, otol.locktime2,
prefix, otol.threshold2,
prefix, len(otol.addresses2)))
fallAddrFormat := fmt.Sprintf("%%s FallAddrs[%s]: %%s\n",
formatting.IntFormat(len(otol.addresses2)-1))
for i, addr := range otol.addresses2 {
s.WriteString(fmt.Sprintf(fallAddrFormat,
prefix, i, addr,
))
}
s.WriteString(fmt.Sprintf("%s)", prefix))
return s.String()
}
func (otol *OutputTakeOrLeave) String() string { return otol.PrefixedString("") }
// checkRange returns true if [index] is in the range [l, u).
func checkRange(index, l, u int) bool {
return l <= index && index < u
}
// checkRawAddresses checks that the signatures match with the addresses and
// that the threshold is the expected value.
func checkRawAddresses(threshold uint32, addrs []ids.ShortID, sigs []*Sig) bool {
if !crypto.EnableCrypto {
return true
}
if uint32(len(sigs)) != threshold {
return false
}
for _, sig := range sigs {
i := int(sig.index)
if !checkRange(i, 0, len(addrs)) || !bytes.Equal(addrs[i].Bytes(), hashing.PubkeyBytesToAddress(sig.parsedPubKey)) {
return false
}
}
return true
}
type sortOutsData []Output
func (outs sortOutsData) Less(i, j int) bool {
c := Codec{}
iBytes, _ := c.MarshalOutput(outs[i])
jBytes, _ := c.MarshalOutput(outs[j])
return bytes.Compare(iBytes, jBytes) == -1
}
func (outs sortOutsData) Len() int { return len(outs) }
func (outs sortOutsData) Swap(i, j int) { outs[j], outs[i] = outs[i], outs[j] }
// SortOuts sorts the tx output list by byte representation
func SortOuts(outs []Output) { sort.Sort(sortOutsData(outs)) }
func isSortedOuts(outs []Output) bool { return sort.IsSorted(sortOutsData(outs)) }