Commit f53f7e42 authored by Luke Champine's avatar Luke Champine

clean up signatures.go

parent b955c88c
...@@ -21,106 +21,72 @@ type inputSignatures struct { ...@@ -21,106 +21,72 @@ type inputSignatures struct {
index int index int
} }
// sortedUnique checks that all of the elements in a []unit64 are sorted and // sortedUnique checks that 'elems' is sorted, contains no repeats, and that no
// without repeates, and also checks that the largest element is less than or // element is an invalid index of 'elems'.
// equal to the biggest allowed element. func sortedUnique(elems []uint64) bool {
func sortedUnique(elems []uint64, biggestAllowed int) (err error) {
if len(elems) == 0 { if len(elems) == 0 {
return return true
} }
biggest := elems[0] biggest := elems[0]
for _, elem := range elems[1:] { for _, elem := range elems[1:] {
if elem <= biggest { if elem <= biggest {
err = errors.New("covered fields sorting violation") return false
return
} }
} }
if int(biggest) > biggestAllowed { if biggest >= uint64(len(elems)) {
err = errors.New("covered fields indexing violation") return false
return
} }
return return true
} }
// validCoveredFields makes sure that all covered fields objects in the // validCoveredFields makes sure that all covered fields objects in the
// signatures follow the rules. This means that if `WholeTransaction` is set to // 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 // true, all fields except for 'Signatures' must be empty. All fields must be
// sorted numerically, and there can be no repeats. // sorted numerically, and there can be no repeats.
func (t Transaction) validCoveredFields() (err error) { func (t Transaction) validCoveredFields() error {
for _, sig := range t.Signatures { for _, sig := range t.Signatures {
// Check that all fields are empty if `WholeTransaction` is set. // convenience variables
cf := sig.CoveredFields cf := sig.CoveredFields
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.
if cf.WholeTransaction { if cf.WholeTransaction {
if len(cf.SiacoinInputs) != 0 || for _, field := range fields {
len(cf.MinerFees) != 0 || if len(field) != 0 {
len(cf.SiacoinOutputs) != 0 || return errors.New("whole transaction flag is set, but not all fields besides signatures are empty")
len(cf.FileContracts) != 0 || }
len(cf.FileContractTerminations) != 0 ||
len(cf.StorageProofs) != 0 ||
len(cf.SiafundInputs) != 0 ||
len(cf.SiafundOutputs) != 0 ||
len(cf.ArbitraryData) != 0 {
err = errors.New("whole transaction flag is set but not all fields besides signatures are empty")
return
} }
} }
// Check that all fields are sorted, and without repeat values, and // Check that all fields are sorted, and without repeat values, and
// that all elements point to objects that exists within the // that all elements point to objects that exists within the
// transaction. // transaction.
err = sortedUnique(cf.SiacoinInputs, len(cf.SiacoinInputs)-1) for _, field := range fields {
if err != nil { if !sortedUnique(field) {
return return errors.New("field does not satisfy 'sorted and unique' requirement")
} }
err = sortedUnique(cf.MinerFees, len(cf.MinerFees)-1)
if err != nil {
return
}
err = sortedUnique(cf.SiacoinOutputs, len(cf.SiacoinOutputs)-1)
if err != nil {
return
}
err = sortedUnique(cf.FileContracts, len(cf.FileContracts)-1)
if err != nil {
return
}
err = sortedUnique(cf.FileContractTerminations, len(cf.FileContractTerminations)-1)
if err != nil {
return
}
err = sortedUnique(cf.StorageProofs, len(cf.StorageProofs)-1)
if err != nil {
return
}
err = sortedUnique(cf.SiafundInputs, len(cf.SiafundInputs)-1)
if err != nil {
return
}
err = sortedUnique(cf.SiafundOutputs, len(cf.SiafundOutputs)-1)
if err != nil {
return
}
err = sortedUnique(cf.ArbitraryData, len(cf.ArbitraryData)-1)
if err != nil {
return
}
err = sortedUnique(cf.Signatures, len(cf.Signatures)-1)
if err != nil {
return
} }
} }
return return nil
} }
// validSignatures takes a transaction and returns an error if the signatures // validSignatures checks the validaty of all signatures in a transaction.
// are not all valid, or if any are missing. func (s *State) validSignatures(t Transaction) error {
func (s *State) validSignatures(t Transaction) (err error) {
// Check that all covered fields objects follow the rules. // Check that all covered fields objects follow the rules.
err = t.validCoveredFields() err := t.validCoveredFields()
if err != nil { if err != nil {
return return err
} }
// Create the inputSignatures object for each input. // Create the inputSignatures object for each input.
...@@ -132,12 +98,11 @@ func (s *State) validSignatures(t Transaction) (err error) { ...@@ -132,12 +98,11 @@ func (s *State) validSignatures(t Transaction) (err error) {
return errors.New("siacoin output spent twice in the same transaction.") return errors.New("siacoin output spent twice in the same transaction.")
} }
inSig := &inputSignatures{ sigMap[id] = &inputSignatures{
remainingSignatures: input.UnlockConditions.NumSignatures, remainingSignatures: input.UnlockConditions.NumSignatures,
possibleKeys: input.UnlockConditions.PublicKeys, possibleKeys: input.UnlockConditions.PublicKeys,
index: i, index: i,
} }
sigMap[id] = inSig
} }
for i, termination := range t.FileContractTerminations { for i, termination := range t.FileContractTerminations {
id := crypto.Hash(termination.ParentID) id := crypto.Hash(termination.ParentID)
...@@ -146,12 +111,11 @@ func (s *State) validSignatures(t Transaction) (err error) { ...@@ -146,12 +111,11 @@ func (s *State) validSignatures(t Transaction) (err error) {
return errors.New("file contract terminated twice in the same transaction.") return errors.New("file contract terminated twice in the same transaction.")
} }
inSig := &inputSignatures{ sigMap[id] = &inputSignatures{
remainingSignatures: termination.TerminationConditions.NumSignatures, remainingSignatures: termination.TerminationConditions.NumSignatures,
possibleKeys: termination.TerminationConditions.PublicKeys, possibleKeys: termination.TerminationConditions.PublicKeys,
index: i, index: i,
} }
sigMap[id] = inSig
} }
for i, input := range t.SiafundInputs { for i, input := range t.SiafundInputs {
id := crypto.Hash(input.ParentID) id := crypto.Hash(input.ParentID)
...@@ -160,67 +124,64 @@ func (s *State) validSignatures(t Transaction) (err error) { ...@@ -160,67 +124,64 @@ func (s *State) validSignatures(t Transaction) (err error) {
return errors.New("siafund output spent twice in the same transaction.") return errors.New("siafund output spent twice in the same transaction.")
} }
inSig := &inputSignatures{ sigMap[id] = &inputSignatures{
remainingSignatures: input.UnlockConditions.NumSignatures, remainingSignatures: input.UnlockConditions.NumSignatures,
possibleKeys: input.UnlockConditions.PublicKeys, possibleKeys: input.UnlockConditions.PublicKeys,
index: i, index: i,
} }
sigMap[id] = inSig
} }
// Check all of the signatures for validity. // Check all of the signatures for validity.
for i, sig := range t.Signatures { for i, sig := range t.Signatures {
id := crypto.Hash(sig.ParentID) // check that sig corresponds to an entry in sigMap
inSig, exists := sigMap[crypto.Hash(sig.ParentID)]
// Check that each signature signs a unique pubkey where if !exists || inSig.remainingSignatures == 0 {
// remainingSignatures > 0.
if sigMap[id].remainingSignatures == 0 {
return errors.New("frivolous signature in transaction") return errors.New("frivolous signature in transaction")
} }
_, exists := sigMap[id].usedKeys[sig.PublicKeyIndex] // check that sig's key hasn't already been used
_, exists = inSig.usedKeys[sig.PublicKeyIndex]
if exists { if exists {
return errors.New("one public key was used twice while signing an input") return errors.New("one public key was used twice while signing an input")
} }
// Check that the timelock has expired. // Check that the timelock has expired.
if sig.Timelock > s.height() { if sig.Timelock > s.height() {
return errors.New("signature used before timelock expiration") return errors.New("signature used before timelock expiration")
} }
// Check that the signature verifies. Sia is built to support multiple // Check that the signature verifies. Multiple signature schemes are
// types of signature algorithms, this is handled by the switch // supported.
// statement. publicKey := inSig.possibleKeys[sig.PublicKeyIndex]
publicKey := sigMap[id].possibleKeys[sig.PublicKeyIndex]
switch publicKey.Algorithm { switch publicKey.Algorithm {
case SignatureEntropy: case SignatureEntropy:
return crypto.ErrInvalidSignature return crypto.ErrInvalidSignature
case SignatureEd25519: case SignatureEd25519:
// Decode the public key and signature. // Decode the public key and signature.
var decodedPK crypto.PublicKey var edPK crypto.PublicKey
err := encoding.Unmarshal([]byte(publicKey.Key), &decodedPK) err := encoding.Unmarshal([]byte(publicKey.Key), &edPK)
if err != nil { if err != nil {
return err return err
} }
var decodedSig [crypto.SignatureSize]byte var edSig [crypto.SignatureSize]byte
err = encoding.Unmarshal([]byte(sig.Signature), &decodedSig) err = encoding.Unmarshal([]byte(sig.Signature), &edSig)
if err != nil { if err != nil {
return err return err
} }
cryptoSig := crypto.Signature(&decodedSig) cryptoSig := crypto.Signature(&edSig)
sigHash := t.SigHash(i) sigHash := t.SigHash(i)
err = crypto.VerifyHash(sigHash, decodedPK, cryptoSig) err = crypto.VerifyHash(sigHash, edPK, cryptoSig)
if err != nil { if err != nil {
return err return err
} }
default: default:
// If we don't recognize the identifier, assume that the signature // If we don't recognize the identifier, assume that the signature
// is valid; do nothing. This allows more signature types to be // is valid. This allows more signature types to be added via soft
// added through soft forking. // forking.
} }
// Subtract the number of signatures remaining for this input. inSig.remainingSignatures--
sigMap[id].remainingSignatures -= 1
} }
// Check that all inputs have been sufficiently signed. // Check that all inputs have been sufficiently signed.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment