github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/types/signatures_test.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"SiaPrime/crypto"
     8  )
     9  
    10  // TestEd25519PublicKey tests the Ed25519PublicKey function.
    11  func TestEd25519PublicKey(t *testing.T) {
    12  	_, pk := crypto.GenerateKeyPair()
    13  	spk := Ed25519PublicKey(pk)
    14  	if spk.Algorithm != SignatureEd25519 {
    15  		t.Error("Ed25519PublicKey created key with wrong algorithm specifier:", spk.Algorithm)
    16  	}
    17  	if !bytes.Equal(spk.Key, pk[:]) {
    18  		t.Error("Ed25519PublicKey created key with wrong data")
    19  	}
    20  }
    21  
    22  // TestUnlockHash runs the UnlockHash code.
    23  func TestUnlockHash(t *testing.T) {
    24  	uc := UnlockConditions{
    25  		Timelock: 1,
    26  		PublicKeys: []SiaPublicKey{
    27  			{
    28  				Algorithm: SignatureEntropy,
    29  				Key:       []byte{'f', 'a', 'k', 'e'},
    30  			},
    31  		},
    32  		SignaturesRequired: 3,
    33  	}
    34  
    35  	_ = uc.UnlockHash()
    36  }
    37  
    38  // TestSigHash runs the SigHash function of the transaction type.
    39  func TestSigHash(t *testing.T) {
    40  	txn := Transaction{
    41  		SiacoinInputs:         []SiacoinInput{{}},
    42  		SiacoinOutputs:        []SiacoinOutput{{}},
    43  		FileContracts:         []FileContract{{}},
    44  		FileContractRevisions: []FileContractRevision{{}},
    45  		StorageProofs:         []StorageProof{{}},
    46  		SiafundInputs:         []SiafundInput{{}},
    47  		SiafundOutputs:        []SiafundOutput{{}},
    48  		MinerFees:             []Currency{{}},
    49  		ArbitraryData:         [][]byte{{'o'}, {'t'}},
    50  		TransactionSignatures: []TransactionSignature{
    51  			{
    52  				CoveredFields: CoveredFields{
    53  					WholeTransaction: true,
    54  				},
    55  			},
    56  			{
    57  				CoveredFields: CoveredFields{
    58  					SiacoinInputs:         []uint64{0},
    59  					SiacoinOutputs:        []uint64{0},
    60  					FileContracts:         []uint64{0},
    61  					FileContractRevisions: []uint64{0},
    62  					StorageProofs:         []uint64{0},
    63  					SiafundInputs:         []uint64{0},
    64  					SiafundOutputs:        []uint64{0},
    65  					MinerFees:             []uint64{0},
    66  					ArbitraryData:         []uint64{0},
    67  					TransactionSignatures: []uint64{0},
    68  				},
    69  			},
    70  		},
    71  	}
    72  
    73  	_ = txn.SigHash(0, 0)
    74  	_ = txn.SigHash(1, 0)
    75  
    76  }
    77  
    78  // TestSortedUnique probes the sortedUnique function.
    79  func TestSortedUnique(t *testing.T) {
    80  	su := []uint64{3, 5, 6, 8, 12}
    81  	if !sortedUnique(su, 13) {
    82  		t.Error("sortedUnique rejected a valid array")
    83  	}
    84  	if sortedUnique(su, 12) {
    85  		t.Error("sortedUnique accepted an invalid max")
    86  	}
    87  	if sortedUnique(su, 11) {
    88  		t.Error("sortedUnique accepted an invalid max")
    89  	}
    90  
    91  	unsorted := []uint64{3, 5, 3}
    92  	if sortedUnique(unsorted, 6) {
    93  		t.Error("sortedUnique accepted an unsorted array")
    94  	}
    95  
    96  	repeats := []uint64{2, 4, 4, 7}
    97  	if sortedUnique(repeats, 8) {
    98  		t.Error("sortedUnique accepted an array with repeats")
    99  	}
   100  
   101  	bothFlaws := []uint64{2, 3, 4, 5, 6, 6, 4}
   102  	if sortedUnique(bothFlaws, 7) {
   103  		t.Error("Sorted unique accetped array with multiple flaws")
   104  	}
   105  }
   106  
   107  // TestTransactionValidCoveredFields probes the validCoveredFields method of
   108  // the transaction type.
   109  func TestTransactionValidCoveredFields(t *testing.T) {
   110  	if testing.Short() {
   111  		t.SkipNow()
   112  	}
   113  
   114  	// Create a transaction with all fields filled in minimally. The first
   115  	// check has a legal CoveredFields object with 'WholeTransaction' set.
   116  	txn := Transaction{
   117  		SiacoinInputs:         []SiacoinInput{{}},
   118  		SiacoinOutputs:        []SiacoinOutput{{}},
   119  		FileContracts:         []FileContract{{}},
   120  		FileContractRevisions: []FileContractRevision{{}},
   121  		StorageProofs:         []StorageProof{{}},
   122  		SiafundInputs:         []SiafundInput{{}},
   123  		SiafundOutputs:        []SiafundOutput{{}},
   124  		MinerFees:             []Currency{{}},
   125  		ArbitraryData:         [][]byte{{'o'}, {'t'}},
   126  		TransactionSignatures: []TransactionSignature{{
   127  			CoveredFields: CoveredFields{WholeTransaction: true},
   128  		}},
   129  	}
   130  	err := txn.validCoveredFields()
   131  	if err != nil {
   132  		t.Error(err)
   133  	}
   134  
   135  	// Second check has CoveredFields object where 'WholeTransaction' is not
   136  	// set.
   137  	txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{
   138  		CoveredFields: CoveredFields{
   139  			SiacoinOutputs:        []uint64{0},
   140  			MinerFees:             []uint64{0},
   141  			ArbitraryData:         []uint64{0},
   142  			FileContractRevisions: []uint64{0},
   143  		},
   144  	})
   145  	err = txn.validCoveredFields()
   146  	if err != nil {
   147  		t.Error(err)
   148  	}
   149  
   150  	// Add signature coverage to the first signature. This should not violate
   151  	// any rules.
   152  	txn.TransactionSignatures[0].CoveredFields.TransactionSignatures = []uint64{1}
   153  	err = txn.validCoveredFields()
   154  	if err != nil {
   155  		t.Error(err)
   156  	}
   157  
   158  	// Add siacoin output coverage to the first signature. This should violate
   159  	// rules, as the fields are not allowed to be set when 'WholeTransaction'
   160  	// is set.
   161  	txn.TransactionSignatures[0].CoveredFields.SiacoinOutputs = []uint64{0}
   162  	err = txn.validCoveredFields()
   163  	if err != ErrWholeTransactionViolation {
   164  		t.Error("Expecting ErrWholeTransactionViolation, got", err)
   165  	}
   166  
   167  	// Create a SortedUnique violation instead of a WholeTransactionViolation.
   168  	txn.TransactionSignatures[0].CoveredFields.SiacoinOutputs = nil
   169  	txn.TransactionSignatures[0].CoveredFields.TransactionSignatures = []uint64{1, 2}
   170  	err = txn.validCoveredFields()
   171  	if err != ErrSortedUniqueViolation {
   172  		t.Error("Expecting ErrSortedUniqueViolation, got", err)
   173  	}
   174  
   175  	// Clear the CoveredFields completely.
   176  	txn.TransactionSignatures[0].CoveredFields = CoveredFields{}
   177  	err = txn.validCoveredFields()
   178  	if err != ErrWholeTransactionViolation {
   179  		t.Error("Expecting ErrWholeTransactionViolation, got", err)
   180  	}
   181  }
   182  
   183  // TestTransactionValidSignatures probes the validSignatures method of the
   184  // Transaction type.
   185  func TestTransactionValidSignatures(t *testing.T) {
   186  	// Create keys for use in signing and verifying.
   187  	sk, pk := crypto.GenerateKeyPair()
   188  
   189  	// Create UnlockConditions with 3 keys, 2 of which are required. The first
   190  	// possible key is a standard signature. The second key is an unknown
   191  	// signature type, which should always be accepted. The final type is an
   192  	// entropy type, which should never be accepted.
   193  	uc := UnlockConditions{
   194  		PublicKeys: []SiaPublicKey{
   195  			{Algorithm: SignatureEd25519, Key: pk[:]},
   196  			{},
   197  			{Algorithm: SignatureEntropy},
   198  		},
   199  		SignaturesRequired: 2,
   200  	}
   201  
   202  	// Create a transaction with each type of unlock condition.
   203  	txn := Transaction{
   204  		SiacoinInputs: []SiacoinInput{
   205  			{UnlockConditions: uc},
   206  		},
   207  		FileContractRevisions: []FileContractRevision{
   208  			{UnlockConditions: uc},
   209  		},
   210  		SiafundInputs: []SiafundInput{
   211  			{UnlockConditions: uc},
   212  		},
   213  	}
   214  	txn.FileContractRevisions[0].ParentID[0] = 1 // can't overlap with other objects
   215  	txn.SiafundInputs[0].ParentID[0] = 2         // can't overlap with other objects
   216  
   217  	// Create the signatures that spend the output.
   218  	txn.TransactionSignatures = []TransactionSignature{
   219  		// First signatures use cryptography.
   220  		{
   221  			Timelock:      5,
   222  			CoveredFields: CoveredFields{WholeTransaction: true},
   223  		},
   224  		{
   225  			CoveredFields: CoveredFields{WholeTransaction: true},
   226  		},
   227  		{
   228  			CoveredFields: CoveredFields{WholeTransaction: true},
   229  		},
   230  
   231  		// The second signatures should always work for being unrecognized
   232  		// types.
   233  		{PublicKeyIndex: 1, CoveredFields: CoveredFields{WholeTransaction: true}},
   234  		{PublicKeyIndex: 1, CoveredFields: CoveredFields{WholeTransaction: true}},
   235  		{PublicKeyIndex: 1, CoveredFields: CoveredFields{WholeTransaction: true}},
   236  	}
   237  	txn.TransactionSignatures[1].ParentID[0] = 1
   238  	txn.TransactionSignatures[2].ParentID[0] = 2
   239  	txn.TransactionSignatures[4].ParentID[0] = 1
   240  	txn.TransactionSignatures[5].ParentID[0] = 2
   241  	sigHash0 := txn.SigHash(0, 10)
   242  	sigHash1 := txn.SigHash(1, 10)
   243  	sigHash2 := txn.SigHash(2, 10)
   244  	sig0 := crypto.SignHash(sigHash0, sk)
   245  	sig1 := crypto.SignHash(sigHash1, sk)
   246  	sig2 := crypto.SignHash(sigHash2, sk)
   247  	txn.TransactionSignatures[0].Signature = sig0[:]
   248  	txn.TransactionSignatures[1].Signature = sig1[:]
   249  	txn.TransactionSignatures[2].Signature = sig2[:]
   250  
   251  	// Check that the signing was successful.
   252  	err := txn.validSignatures(10)
   253  	if err != nil {
   254  		t.Error(err)
   255  	}
   256  
   257  	// Corrupt one of the signatures.
   258  	sig0[0]++
   259  	txn.TransactionSignatures[0].Signature = sig0[:]
   260  	err = txn.validSignatures(10)
   261  	if err == nil {
   262  		t.Error("Corrupted a signature but the txn was still accepted as valid!")
   263  	}
   264  	sig0[0]--
   265  	txn.TransactionSignatures[0].Signature = sig0[:]
   266  
   267  	// Fail the validCoveredFields check.
   268  	txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = []uint64{33}
   269  	err = txn.validSignatures(10)
   270  	if err == nil {
   271  		t.Error("failed to flunk the validCoveredFields check")
   272  	}
   273  	txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = nil
   274  
   275  	// Double spend a SiacoinInput, FileContractTermination, and SiafundInput.
   276  	txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{UnlockConditions: UnlockConditions{}})
   277  	err = txn.validSignatures(10)
   278  	if err == nil {
   279  		t.Error("failed to double spend a siacoin input")
   280  	}
   281  	txn.SiacoinInputs = txn.SiacoinInputs[:len(txn.SiacoinInputs)-1]
   282  	txn.FileContractRevisions = append(txn.FileContractRevisions, FileContractRevision{UnlockConditions: UnlockConditions{}})
   283  	err = txn.validSignatures(10)
   284  	if err == nil {
   285  		t.Error("failed to double spend a file contract termination")
   286  	}
   287  	txn.FileContractRevisions = txn.FileContractRevisions[:len(txn.FileContractRevisions)-1]
   288  	txn.SiafundInputs = append(txn.SiafundInputs, SiafundInput{UnlockConditions: UnlockConditions{}})
   289  	err = txn.validSignatures(10)
   290  	if err == nil {
   291  		t.Error("failed to double spend a siafund input")
   292  	}
   293  	txn.SiafundInputs = txn.SiafundInputs[:len(txn.SiafundInputs)-1]
   294  
   295  	// Add a frivolous signature
   296  	txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{CoveredFields: CoveredFields{WholeTransaction: true}})
   297  	err = txn.validSignatures(10)
   298  	if err != ErrFrivolousSignature {
   299  		t.Error(err)
   300  	}
   301  	txn.TransactionSignatures = txn.TransactionSignatures[:len(txn.TransactionSignatures)-1]
   302  
   303  	// Replace one of the cryptography signatures with an always-accepted
   304  	// signature. This should get rejected because the always-accepted
   305  	// signature has already been used.
   306  	tmpTxn0 := txn.TransactionSignatures[0]
   307  	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 1, CoveredFields: CoveredFields{WholeTransaction: true}}
   308  	err = txn.validSignatures(10)
   309  	if err != ErrPublicKeyOveruse {
   310  		t.Error(err)
   311  	}
   312  	txn.TransactionSignatures[0] = tmpTxn0
   313  
   314  	// Fail the timelock check for signatures.
   315  	err = txn.validSignatures(4)
   316  	if err != ErrPrematureSignature {
   317  		t.Error(err)
   318  	}
   319  
   320  	// Try to spend an entropy signature.
   321  	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 2, CoveredFields: CoveredFields{WholeTransaction: true}}
   322  	err = txn.validSignatures(10)
   323  	if err != ErrEntropyKey {
   324  		t.Error(err)
   325  	}
   326  	txn.TransactionSignatures[0] = tmpTxn0
   327  
   328  	// Try to point to a nonexistent public key.
   329  	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 5, CoveredFields: CoveredFields{WholeTransaction: true}}
   330  	err = txn.validSignatures(10)
   331  	if err != ErrInvalidPubKeyIndex {
   332  		t.Error(err)
   333  	}
   334  	txn.TransactionSignatures[0] = tmpTxn0
   335  
   336  	// Insert a malformed public key into the transaction.
   337  	txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = []byte{'b', 'a', 'd'}
   338  	err = txn.validSignatures(10)
   339  	if err == nil {
   340  		t.Error(err)
   341  	}
   342  	txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = pk[:]
   343  
   344  	// Insert a malformed signature into the transaction.
   345  	txn.TransactionSignatures[0].Signature = []byte{'m', 'a', 'l'}
   346  	err = txn.validSignatures(10)
   347  	if err == nil {
   348  		t.Error(err)
   349  	}
   350  	txn.TransactionSignatures[0] = tmpTxn0
   351  
   352  	// Try to spend a transaction when not every required signature is
   353  	// available.
   354  	txn.TransactionSignatures = txn.TransactionSignatures[1:]
   355  	err = txn.validSignatures(10)
   356  	if err != ErrMissingSignatures {
   357  		t.Error(err)
   358  	}
   359  }