github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/types/signatures_test.go (about)

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