signatures.go 5.36 KB
Newer Older
1 2 3 4 5 6
package consensus

import (
	"errors"

	"github.com/NebulousLabs/Sia/crypto"
7
	"github.com/NebulousLabs/Sia/encoding"
8 9
)

10
var (
11
	ErrMissingSignatures = errors.New("transaction has inputs with missing signatures")
12 13
)

14
// Each input has a list of public keys and a required number of signatures.
Luke Champine's avatar
Luke Champine committed
15
// inputSignatures keeps track of which public keys have been used and how many
16
// more signatures are needed.
Luke Champine's avatar
Luke Champine committed
17 18 19 20 21
type inputSignatures struct {
	remainingSignatures uint64
	possibleKeys        []SiaPublicKey
	usedKeys            map[uint64]struct{}
	index               int
22 23
}

Luke Champine's avatar
Luke Champine committed
24 25 26
// sortedUnique checks that 'elems' is sorted, contains no repeats, and that no
// element is an invalid index of 'elems'.
func sortedUnique(elems []uint64) bool {
David Vorick's avatar
David Vorick committed
27
	if len(elems) == 0 {
Luke Champine's avatar
Luke Champine committed
28
		return true
David Vorick's avatar
David Vorick committed
29 30
	}

31 32 33
	biggest := elems[0]
	for _, elem := range elems[1:] {
		if elem <= biggest {
Luke Champine's avatar
Luke Champine committed
34
			return false
35 36
		}
	}
Luke Champine's avatar
Luke Champine committed
37 38
	if biggest >= uint64(len(elems)) {
		return false
39
	}
Luke Champine's avatar
Luke Champine committed
40
	return true
41 42 43
}

// validCoveredFields makes sure that all covered fields objects in the
Luke Champine's avatar
Luke Champine committed
44 45
// signatures follow the rules. This means that if 'WholeTransaction' is set to
// true, all fields except for 'Signatures' must be empty. All fields must be
46
// sorted numerically, and there can be no repeats.
Luke Champine's avatar
Luke Champine committed
47
func (t Transaction) validCoveredFields() error {
48
	for _, sig := range t.Signatures {
Luke Champine's avatar
Luke Champine committed
49
		// convenience variables
50
		cf := sig.CoveredFields
Luke Champine's avatar
Luke Champine committed
51 52 53 54 55 56 57 58 59 60 61 62
		fields := [][]uint64{
			cf.SiacoinInputs,
			cf.MinerFees,
			cf.FileContracts,
			cf.FileContractTerminations,
			cf.StorageProofs,
			cf.SiafundInputs,
			cf.SiafundOutputs,
			cf.ArbitraryData,
		}

		// Check that all fields are empty if 'WholeTransaction' is set.
63
		if cf.WholeTransaction {
Luke Champine's avatar
Luke Champine committed
64 65 66 67
			for _, field := range fields {
				if len(field) != 0 {
					return errors.New("whole transaction flag is set, but not all fields besides signatures are empty")
				}
68 69 70 71 72 73
			}
		}

		// Check that all fields are sorted, and without repeat values, and
		// that all elements point to objects that exists within the
		// transaction.
Luke Champine's avatar
Luke Champine committed
74 75 76 77
		for _, field := range fields {
			if !sortedUnique(field) {
				return errors.New("field does not satisfy 'sorted and unique' requirement")
			}
78 79 80
		}
	}

Luke Champine's avatar
Luke Champine committed
81
	return nil
82 83
}

Luke Champine's avatar
Luke Champine committed
84 85
// validSignatures checks the validaty of all signatures in a transaction.
func (s *State) validSignatures(t Transaction) error {
86
	// Check that all covered fields objects follow the rules.
Luke Champine's avatar
Luke Champine committed
87
	err := t.validCoveredFields()
88
	if err != nil {
Luke Champine's avatar
Luke Champine committed
89
		return err
90 91
	}

Luke Champine's avatar
Luke Champine committed
92 93
	// Create the inputSignatures object for each input.
	sigMap := make(map[crypto.Hash]*inputSignatures)
94
	for i, input := range t.SiacoinInputs {
95 96
		id := crypto.Hash(input.ParentID)
		_, exists := sigMap[id]
97
		if exists {
98
			return errors.New("siacoin output spent twice in the same transaction.")
99
		}
100

Luke Champine's avatar
Luke Champine committed
101
		sigMap[id] = &inputSignatures{
Luke Champine's avatar
Luke Champine committed
102 103 104
			remainingSignatures: input.UnlockConditions.NumSignatures,
			possibleKeys:        input.UnlockConditions.PublicKeys,
			index:               i,
105
		}
106
	}
David Vorick's avatar
David Vorick committed
107
	for i, termination := range t.FileContractTerminations {
108 109
		id := crypto.Hash(termination.ParentID)
		_, exists := sigMap[id]
110
		if exists {
David Vorick's avatar
David Vorick committed
111
			return errors.New("file contract terminated twice in the same transaction.")
112 113
		}

Luke Champine's avatar
Luke Champine committed
114
		sigMap[id] = &inputSignatures{
Luke Champine's avatar
Luke Champine committed
115 116 117
			remainingSignatures: termination.TerminationConditions.NumSignatures,
			possibleKeys:        termination.TerminationConditions.PublicKeys,
			index:               i,
118 119
		}
	}
David Vorick's avatar
David Vorick committed
120
	for i, input := range t.SiafundInputs {
121 122
		id := crypto.Hash(input.ParentID)
		_, exists := sigMap[id]
123
		if exists {
David Vorick's avatar
David Vorick committed
124
			return errors.New("siafund output spent twice in the same transaction.")
125 126
		}

Luke Champine's avatar
Luke Champine committed
127
		sigMap[id] = &inputSignatures{
Luke Champine's avatar
Luke Champine committed
128 129 130
			remainingSignatures: input.UnlockConditions.NumSignatures,
			possibleKeys:        input.UnlockConditions.PublicKeys,
			index:               i,
131
		}
132 133 134 135
	}

	// Check all of the signatures for validity.
	for i, sig := range t.Signatures {
Luke Champine's avatar
Luke Champine committed
136 137 138
		// check that sig corresponds to an entry in sigMap
		inSig, exists := sigMap[crypto.Hash(sig.ParentID)]
		if !exists || inSig.remainingSignatures == 0 {
139 140
			return errors.New("frivolous signature in transaction")
		}
Luke Champine's avatar
Luke Champine committed
141 142
		// check that sig's key hasn't already been used
		_, exists = inSig.usedKeys[sig.PublicKeyIndex]
143 144 145
		if exists {
			return errors.New("one public key was used twice while signing an input")
		}
146
		// Check that the timelock has expired.
147
		if sig.Timelock > s.height() {
148 149 150
			return errors.New("signature used before timelock expiration")
		}

Luke Champine's avatar
Luke Champine committed
151 152 153
		// Check that the signature verifies. Multiple signature schemes are
		// supported.
		publicKey := inSig.possibleKeys[sig.PublicKeyIndex]
154
		switch publicKey.Algorithm {
David Vorick's avatar
David Vorick committed
155
		case SignatureEntropy:
156
			return crypto.ErrInvalidSignature
Luke Champine's avatar
Luke Champine committed
157

158
		case SignatureEd25519:
David Vorick's avatar
David Vorick committed
159
			// Decode the public key and signature.
Luke Champine's avatar
Luke Champine committed
160 161
			var edPK crypto.PublicKey
			err := encoding.Unmarshal([]byte(publicKey.Key), &edPK)
David Vorick's avatar
David Vorick committed
162 163
			if err != nil {
				return err
164
			}
Luke Champine's avatar
Luke Champine committed
165 166
			var edSig [crypto.SignatureSize]byte
			err = encoding.Unmarshal([]byte(sig.Signature), &edSig)
David Vorick's avatar
David Vorick committed
167 168
			if err != nil {
				return err
169
			}
Luke Champine's avatar
Luke Champine committed
170
			cryptoSig := crypto.Signature(&edSig)
171 172

			sigHash := t.SigHash(i)
Luke Champine's avatar
Luke Champine committed
173
			err = crypto.VerifyHash(sigHash, edPK, cryptoSig)
174 175
			if err != nil {
				return err
176
			}
Luke Champine's avatar
Luke Champine committed
177

178
		default:
179
			// If we don't recognize the identifier, assume that the signature
Luke Champine's avatar
Luke Champine committed
180 181
			// is valid. This allows more signature types to be added via soft
			// forking.
182 183
		}

Luke Champine's avatar
Luke Champine committed
184
		inSig.remainingSignatures--
185 186 187 188
	}

	// Check that all inputs have been sufficiently signed.
	for _, reqSigs := range sigMap {
Luke Champine's avatar
Luke Champine committed
189
		if reqSigs.remainingSignatures != 0 {
190
			return ErrMissingSignatures
191 192 193 194 195
		}
	}

	return nil
}