Commit 23dfd269 authored by David Vorick's avatar David Vorick

enforce covered fields requirements

parent e9783459
......@@ -14,8 +14,7 @@ principles.
TODO: Write the formal specification for encoding things.
TODO: contract ids, storage proof ids, siafund output ids, siafund claim output
ids.
TODO: siafund output ids, siafund claim output ids.
TODO: Document which crypto is used in consensus. (Hash algorithm, signature
algorithm)
......@@ -178,8 +177,9 @@ Outputs contain a value and a spend hash (also called a coin address). The
spend hash is a hash of the spend conditions that must be met to spend the
output.
The id of an output is obtained by marshalling and hashing all of the fields in
the transaction that contained the output, except for the signatures.
The id of a contract is determined by marshalling all of the transaction fields
except for the signatures and then appending the string "siacoin output" and
the index of the output within the transaction, and then taking the hash.
File Contracts
--------------
......@@ -202,6 +202,10 @@ All contracts must have a non-zero payout, 'Start' must be before 'End', and
'Start' must be greater than the current height of the state. A storage proof
is acceptible if it is submitted in the block of height 'End'.
The id of a contract is determined by marshalling all of the transaction fields
except for the signatures and then appending the string "file contract" and the
index of the contract within the transaction, and then taking the hash.
Storage Proofs
--------------
......@@ -229,6 +233,10 @@ hashes required to fill out the remaining tree. The total size of the proof
will be 64 bytes + 32 bytes * log(num segments), and can be verified by anybody
who knows the root hash and the file size.
The id for a storage proof output is found by taking the id of the contract and
concatenating a bool set to 'true' if the proof was submitted and valid, and
set to 'false' if a valid proof was not submitted by the end of the contract.
Storage proof transactions are not allowed to have siacoin outputs, siafund
outputs, or contracts. All outputs created by the storage proofs cannot be
spent for 100 blocks.
......
package consensus
// TODO: when testing the covered fields stuff, antagonistically try to cause
// seg faults by throwing covered fields objects at the state which point to
// nonexistent objects in the transaction.
import (
"errors"
......@@ -16,35 +20,119 @@ type InputSignatures struct {
Index int
}
// OutputSum returns the sum of all the outputs in the transaction, which must
// match the sum of all the inputs. Outputs created by storage proofs are not
// considered, as they were already considered when the contract was created.
func (t Transaction) OutputSum() (sum Currency) {
// Add the miner fees.
for _, fee := range t.MinerFees {
sum += fee
}
// Add the contract payouts
for _, contract := range t.FileContracts {
sum += contract.Payout
}
// validCoveredFields makes sure that all covered fields objects in the
// 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
// sorted numerically, and there can be no repeats.
//
// TODO: Enable the siafund stuff.
func (t Transaction) validCoveredFields() (err error) {
for _, sig := range t.Signatures {
// Check that all fields are empty if `WholeTransaction` is set.
cf := sig.CoveredFields
if cf.WholeTransaction {
if len(cf.Inputs) != 0 ||
len(cf.MinerFees) != 0 ||
len(cf.Outputs) != 0 ||
len(cf.FileContracts) != 0 ||
len(cf.StorageProofs) != 0 ||
// len(cf.SiafundInputs) != 0 ||
// len(cf.SiafundOutputs) != 0 ||
len(cf.ArbitraryData) != 0 {
err = errors.New("whole transaction is set but not all fields besides signatures are empty")
return
}
}
// Add the outputs
for _, output := range t.Outputs {
sum += output.Value
// Check that all fields are sorted, and without repeat values, and
// that all elements point to objects that exists within the
// transaction.
biggest := -1
for _, elem := range cf.Inputs {
if int(elem) <= biggest || int(elem) >= len(cf.Inputs) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.MinerFees {
if int(elem) <= biggest || int(elem) >= len(cf.MinerFees) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.Outputs {
if int(elem) <= biggest || int(elem) >= len(cf.Outputs) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.FileContracts {
if int(elem) <= biggest || int(elem) >= len(cf.FileContracts) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.StorageProofs {
if int(elem) <= biggest || int(elem) >= len(cf.StorageProofs) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
/*
biggest = -1
for _, elem := range cf.SiafundInputs {
if int(elem) <= biggest || int(elem) >= len(cf.SiafundInputs) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.SiafundOutputs {
if int(elem) <= biggest || int(elem) >= len(cf.SiafundOutputs) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
*/
biggest = -1
for _, elem := range cf.ArbitraryData {
if int(elem) <= biggest || int(elem) >= len(cf.ArbitraryData) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
biggest = -1
for _, elem := range cf.Signatures {
if int(elem) <= biggest || int(elem) >= len(cf.Signatures) {
err = errors.New("covered fields violation")
return
}
biggest = int(elem)
}
}
return
}
func (s *State) ValidSignatures(t Transaction) error {
s.mu.RLock()
defer s.mu.RUnlock()
return s.validSignatures(t)
}
func (s *State) validSignatures(t Transaction) (err error) {
// Check that all covered fields objects follow the rules.
err = t.validCoveredFields()
if err != nil {
return
}
func (s *State) validSignatures(t Transaction) error {
// Create the InputSignatures object for each input.
sigMap := make(map[OutputID]*InputSignatures)
for i, input := range t.Inputs {
......@@ -225,6 +313,34 @@ func (s *State) applyTransaction(t Transaction) (outputDiffs []OutputDiff, contr
return
}
// OutputSum returns the sum of all the outputs in the transaction, which must
// match the sum of all the inputs. Outputs created by storage proofs are not
// considered, as they were already considered when the contract was created.
func (t Transaction) OutputSum() (sum Currency) {
// Add the miner fees.
for _, fee := range t.MinerFees {
sum += fee
}
// Add the contract payouts
for _, contract := range t.FileContracts {
sum += contract.Payout
}
// Add the outputs
for _, output := range t.Outputs {
sum += output.Value
}
return
}
func (s *State) ValidSignatures(t Transaction) error {
s.mu.RLock()
defer s.mu.RUnlock()
return s.validSignatures(t)
}
func (s *State) ValidTransaction(t Transaction) (err error) {
s.mu.RLock()
defer s.mu.RUnlock()
......
package consensus
// TODO: Convert all string literals to byte arrays, or get rid of them entirely.
// TODO: Swtich to 128 bit Currency, which is overflow-safe. Then update
// CalculateCoinbase.
......@@ -142,20 +140,18 @@ type TransactionSignature struct {
Signature crypto.Signature
}
// TODO: If `WholeTransaction` is set to true, then all other fields except for
// Signatures should be empty, this should be a consensus rule.
//
// TODO: Signature can include each element at most once. If there are repeat
// elements, everything is invalid.
//
// TODO: We might, for speed reasons, want to also force the fields to be
// sorted already.
// The CoveredFields portion of a signature indicates which fields in the
// transaction have been covered by the signature. Each slice of elements in a
// transaction is represented by a slice of indices. The indicies must be
// sorted, must not repeat, and must point to elements that exist within the
// transaction. If 'WholeTransaction' is set to true, all other fields must be
// empty except for the Signatures field.
type CoveredFields struct {
WholeTransaction bool
Inputs []uint64 // each element indicates an index which is signed.
Inputs []uint64
MinerFees []uint64
Outputs []uint64
Contracts []uint64
FileContracts []uint64
StorageProofs []uint64
// SiafundInputs []uint64
// SiafundOutputs []unit64
......@@ -207,21 +203,28 @@ func (b Block) MinerPayoutID(i int) OutputID {
return OutputID(hash.HashBytes(encoding.MarshalAll(b.ID(), i)))
}
// FileContractID returns the id of a file contract given the index of the contract.
//
// TODO: Reconsider how file contract ids are derived
// FileContractID returns the id of a file contract given the index of the
// contract. The id is derived by marshalling all of the fields in the
// transaction except for the signatures and then appending the string "file
// contract" and the index of the contract.
func (t Transaction) FileContractID(i int) ContractID {
return ContractID(hash.HashBytes(encoding.MarshalAll(
t.Outputs[0],
t.FileContracts[i],
"contract",
t.Inputs,
t.MinerFees,
t.Outputs,
t.FileContracts,
t.StorageProofs,
// t.SiafundInputs,
// t.SiafundOutputs,
t.ArbitraryData,
"file contract",
i,
)))
}
// OutputID gets the id of an output in the transaction, which is derived from
// marshalling all of the fields in the transaction except for the signatures
// and then appending the index of the output.
// and then appending the string "siacoin output" and the index of the output.
func (t Transaction) OutputID(i int) OutputID {
return OutputID(hash.HashBytes(encoding.MarshalAll(
t.Inputs,
......@@ -232,6 +235,7 @@ func (t Transaction) OutputID(i int) OutputID {
// t.SiafundInputs,
// t.SiafundOutputs,
t.ArbitraryData,
"siacoin output",
i,
)))
}
......@@ -272,7 +276,7 @@ func (t Transaction) SigHash(i int) hash.Hash {
for _, output := range t.Signatures[i].CoveredFields.Outputs {
signedData = append(signedData, encoding.Marshal(t.Outputs[output])...)
}
for _, contract := range t.Signatures[i].CoveredFields.Contracts {
for _, contract := range t.Signatures[i].CoveredFields.FileContracts {
signedData = append(signedData, encoding.Marshal(t.FileContracts[contract])...)
}
for _, storageProof := range t.Signatures[i].CoveredFields.StorageProofs {
......@@ -295,13 +299,10 @@ func (t Transaction) SigHash(i int) hash.Hash {
// StorageProofOutputID returns the OutputID of the output created during the
// window index that was active at height 'height'.
//
// TODO: Reconsider how the StorageProofOutputID is determined.
func (fcID ContractID) StorageProofOutputID(proofValid bool) (outputID OutputID) {
proofString := proofString(proofValid)
outputID = OutputID(hash.HashBytes(encoding.MarshalAll(
fcID,
proofString,
proofValid,
)))
return
}
......@@ -319,14 +320,3 @@ func (sc SpendConditions) CoinAddress() CoinAddress {
leaves = append(leaves, hash.HashObject(sc.NumSignatures))
return CoinAddress(hash.MerkleRoot(leaves))
}
// proofString returns the string to be used when generating the output id of a
// valid proof if bool is set to true, and it returns the string to be used in
// a missed proof if the bool is set to false.
func proofString(proofValid bool) string {
if proofValid {
return "validproof"
} else {
return "missedproof"
}
}
......@@ -182,7 +182,7 @@ func (w *Wallet) SignTransaction(id string, wholeTransaction bool) (txn consensu
coveredFields.Outputs = append(coveredFields.Outputs, uint64(i))
}
for i := range txn.FileContracts {
coveredFields.Contracts = append(coveredFields.Contracts, uint64(i))
coveredFields.FileContracts = append(coveredFields.FileContracts, uint64(i))
}
for i := range txn.StorageProofs {
coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i))
......
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