github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/negotiate.go (about)

     1  package proto
     2  
     3  import (
     4  	"net"
     5  	"time"
     6  
     7  	"SiaPrime/build"
     8  	"SiaPrime/crypto"
     9  	"SiaPrime/encoding"
    10  	"SiaPrime/modules"
    11  	"SiaPrime/types"
    12  	"gitlab.com/NebulousLabs/errors"
    13  )
    14  
    15  // extendDeadline is a helper function for extending the connection timeout.
    16  func extendDeadline(conn net.Conn, d time.Duration) { _ = conn.SetDeadline(time.Now().Add(d)) }
    17  
    18  // startRevision is run at the beginning of each revision iteration. It reads
    19  // the host's settings confirms that the values are acceptable, and writes an acceptance.
    20  func startRevision(conn net.Conn, host modules.HostDBEntry) error {
    21  	// verify the host's settings and confirm its identity
    22  	_, err := verifySettings(conn, host)
    23  	if err != nil {
    24  		return err
    25  	}
    26  	return modules.WriteNegotiationAcceptance(conn)
    27  }
    28  
    29  // startDownload is run at the beginning of each download iteration. It reads
    30  // the host's settings confirms that the values are acceptable, and writes an acceptance.
    31  func startDownload(conn net.Conn, host modules.HostDBEntry) error {
    32  	// verify the host's settings and confirm its identity
    33  	_, err := verifySettings(conn, host)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	return modules.WriteNegotiationAcceptance(conn)
    38  }
    39  
    40  // verifySettings reads a signed HostSettings object from conn, validates the
    41  // signature, and checks for discrepancies between the known settings and the
    42  // received settings. If there is a discrepancy, the hostDB is notified. The
    43  // received settings are returned.
    44  func verifySettings(conn net.Conn, host modules.HostDBEntry) (modules.HostDBEntry, error) {
    45  	// convert host key (types.SiaPublicKey) to a crypto.PublicKey
    46  	if host.PublicKey.Algorithm != types.SignatureEd25519 || len(host.PublicKey.Key) != crypto.PublicKeySize {
    47  		build.Critical("hostdb did not filter out host with wrong signature algorithm:", host.PublicKey.Algorithm)
    48  		return modules.HostDBEntry{}, errors.New("host used unsupported signature algorithm")
    49  	}
    50  	var pk crypto.PublicKey
    51  	copy(pk[:], host.PublicKey.Key)
    52  
    53  	// read signed host settings
    54  	var recvSettings modules.HostExternalSettings
    55  	if err := crypto.ReadSignedObject(conn, &recvSettings, modules.NegotiateMaxHostExternalSettingsLen, pk); err != nil {
    56  		return modules.HostDBEntry{}, errors.New("couldn't read host's settings: " + err.Error())
    57  	}
    58  	// TODO: check recvSettings against host.HostExternalSettings. If there is
    59  	// a discrepancy, write the error to conn.
    60  	if recvSettings.NetAddress != host.NetAddress {
    61  		// for now, just overwrite the NetAddress, since we know that
    62  		// host.NetAddress works (it was the one we dialed to get conn)
    63  		recvSettings.NetAddress = host.NetAddress
    64  	}
    65  	host.HostExternalSettings = recvSettings
    66  	return host, nil
    67  }
    68  
    69  // verifyRecentRevision confirms that the host and contractor agree upon the current
    70  // state of the contract being revised.
    71  func verifyRecentRevision(conn net.Conn, contract *SafeContract, hostVersion string) error {
    72  	// send contract ID
    73  	if err := encoding.WriteObject(conn, contract.header.ID()); err != nil {
    74  		return errors.New("couldn't send contract ID: " + err.Error())
    75  	}
    76  	// read challenge
    77  	var challenge crypto.Hash
    78  	if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
    79  		return errors.New("couldn't read challenge: " + err.Error())
    80  	}
    81  	if build.VersionCmp(hostVersion, "1.3.0") >= 0 {
    82  		crypto.SecureWipe(challenge[:16])
    83  	}
    84  	// sign and return
    85  	sig := crypto.SignHash(challenge, contract.header.SecretKey)
    86  	if err := encoding.WriteObject(conn, sig); err != nil {
    87  		return errors.New("couldn't send challenge response: " + err.Error())
    88  	}
    89  	// read acceptance
    90  	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
    91  		return errors.New("host did not accept revision request: " + err.Error())
    92  	}
    93  	// read last revision and signatures
    94  	var lastRevision types.FileContractRevision
    95  	var hostSignatures []types.TransactionSignature
    96  	if err := encoding.ReadObject(conn, &lastRevision, 2048); err != nil {
    97  		return errors.New("couldn't read last revision: " + err.Error())
    98  	}
    99  	if err := encoding.ReadObject(conn, &hostSignatures, 2048); err != nil {
   100  		return errors.New("couldn't read host signatures: " + err.Error())
   101  	}
   102  	// Check that the unlock hashes match; if they do not, something is
   103  	// seriously wrong. Otherwise, check that the revision numbers match.
   104  	ourRev := contract.header.LastRevision()
   105  	if lastRevision.UnlockConditions.UnlockHash() != ourRev.UnlockConditions.UnlockHash() {
   106  		return errors.New("unlock conditions do not match")
   107  	} else if lastRevision.NewRevisionNumber != ourRev.NewRevisionNumber {
   108  		// If the revision number doesn't match try to commit potential
   109  		// unapplied transactions and check again.
   110  		if err := contract.commitTxns(); err != nil {
   111  			return errors.AddContext(err, "failed to commit transactions")
   112  		}
   113  		ourRev = contract.header.LastRevision()
   114  		if lastRevision.NewRevisionNumber != ourRev.NewRevisionNumber {
   115  			return &recentRevisionError{ourRev.NewRevisionNumber, lastRevision.NewRevisionNumber}
   116  		}
   117  	}
   118  	// NOTE: we can fake the blockheight here because it doesn't affect
   119  	// verification; it just needs to be above the fork height and below the
   120  	// contract expiration (which was checked earlier).
   121  	return modules.VerifyFileContractRevisionTransactionSignatures(lastRevision, hostSignatures, contract.header.EndHeight()-1)
   122  }
   123  
   124  // negotiateRevision sends a revision and actions to the host for approval,
   125  // completing one iteration of the revision loop.
   126  func negotiateRevision(conn net.Conn, rev types.FileContractRevision, secretKey crypto.SecretKey, height types.BlockHeight) (types.Transaction, error) {
   127  	// create transaction containing the revision
   128  	signedTxn := types.Transaction{
   129  		FileContractRevisions: []types.FileContractRevision{rev},
   130  		TransactionSignatures: []types.TransactionSignature{{
   131  			ParentID:       crypto.Hash(rev.ParentID),
   132  			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   133  			PublicKeyIndex: 0, // renter key is always first -- see formContract
   134  		}},
   135  	}
   136  	// sign the transaction
   137  	encodedSig := crypto.SignHash(signedTxn.SigHash(0, height), secretKey)
   138  	signedTxn.TransactionSignatures[0].Signature = encodedSig[:]
   139  
   140  	// send the revision
   141  	if err := encoding.WriteObject(conn, rev); err != nil {
   142  		return types.Transaction{}, errors.New("couldn't send revision: " + err.Error())
   143  	}
   144  	// read acceptance
   145  	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
   146  		return types.Transaction{}, errors.New("host did not accept revision: " + err.Error())
   147  	}
   148  
   149  	// send the new transaction signature
   150  	if err := encoding.WriteObject(conn, signedTxn.TransactionSignatures[0]); err != nil {
   151  		return types.Transaction{}, errors.New("couldn't send transaction signature: " + err.Error())
   152  	}
   153  	// read the host's acceptance and transaction signature
   154  	// NOTE: if the host sends ErrStopResponse, we should continue processing
   155  	// the revision, but return the error anyway.
   156  	responseErr := modules.ReadNegotiationAcceptance(conn)
   157  	if responseErr != nil && responseErr != modules.ErrStopResponse {
   158  		return types.Transaction{}, errors.New("host did not accept transaction signature: " + responseErr.Error())
   159  	}
   160  	var hostSig types.TransactionSignature
   161  	if err := encoding.ReadObject(conn, &hostSig, 16e3); err != nil {
   162  		return types.Transaction{}, errors.New("couldn't read host's signature: " + err.Error())
   163  	}
   164  
   165  	// add the signature to the transaction and verify it
   166  	// NOTE: we can fake the blockheight here because it doesn't affect
   167  	// verification; it just needs to be above the fork height and below the
   168  	// contract expiration (which was checked earlier).
   169  	verificationHeight := rev.NewWindowStart - 1
   170  	signedTxn.TransactionSignatures = append(signedTxn.TransactionSignatures, hostSig)
   171  	if err := signedTxn.StandaloneValid(verificationHeight); err != nil {
   172  		return types.Transaction{}, err
   173  	}
   174  
   175  	// if the host sent ErrStopResponse, return it
   176  	return signedTxn, responseErr
   177  }
   178  
   179  // newRevision creates a copy of current with its revision number incremented,
   180  // and with cost transferred from the renter to the host.
   181  func newRevision(current types.FileContractRevision, cost types.Currency) types.FileContractRevision {
   182  	rev := current
   183  
   184  	// need to manually copy slice memory
   185  	rev.NewValidProofOutputs = make([]types.SiacoinOutput, 2)
   186  	rev.NewMissedProofOutputs = make([]types.SiacoinOutput, 3)
   187  	copy(rev.NewValidProofOutputs, current.NewValidProofOutputs)
   188  	copy(rev.NewMissedProofOutputs, current.NewMissedProofOutputs)
   189  
   190  	// move valid payout from renter to host
   191  	rev.NewValidProofOutputs[0].Value = current.NewValidProofOutputs[0].Value.Sub(cost)
   192  	rev.NewValidProofOutputs[1].Value = current.NewValidProofOutputs[1].Value.Add(cost)
   193  
   194  	// move missed payout from renter to void
   195  	rev.NewMissedProofOutputs[0].Value = current.NewMissedProofOutputs[0].Value.Sub(cost)
   196  	rev.NewMissedProofOutputs[2].Value = current.NewMissedProofOutputs[2].Value.Add(cost)
   197  
   198  	// increment revision number
   199  	rev.NewRevisionNumber++
   200  
   201  	return rev
   202  }
   203  
   204  // newDownloadRevision revises the current revision to cover the cost of
   205  // downloading data.
   206  func newDownloadRevision(current types.FileContractRevision, downloadCost types.Currency) types.FileContractRevision {
   207  	return newRevision(current, downloadCost)
   208  }
   209  
   210  // newUploadRevision revises the current revision to cover the cost of
   211  // uploading a sector.
   212  func newUploadRevision(current types.FileContractRevision, merkleRoot crypto.Hash, price, collateral types.Currency) types.FileContractRevision {
   213  	rev := newRevision(current, price)
   214  
   215  	// move collateral from host to void
   216  	rev.NewMissedProofOutputs[1].Value = rev.NewMissedProofOutputs[1].Value.Sub(collateral)
   217  	rev.NewMissedProofOutputs[2].Value = rev.NewMissedProofOutputs[2].Value.Add(collateral)
   218  
   219  	// set new filesize and Merkle root
   220  	rev.NewFileSize += modules.SectorSize
   221  	rev.NewFileMerkleRoot = merkleRoot
   222  	return rev
   223  }
   224  
   225  // newDeleteRevision revises the current revision to cover the cost of
   226  // deleting a sector.
   227  func newDeleteRevision(current types.FileContractRevision, merkleRoot crypto.Hash) types.FileContractRevision {
   228  	rev := newRevision(current, types.ZeroCurrency)
   229  	rev.NewFileSize -= modules.SectorSize
   230  	rev.NewFileMerkleRoot = merkleRoot
   231  	return rev
   232  }
   233  
   234  // newModifyRevision revises the current revision to cover the cost of
   235  // modifying a sector.
   236  func newModifyRevision(current types.FileContractRevision, merkleRoot crypto.Hash, uploadCost types.Currency) types.FileContractRevision {
   237  	rev := newRevision(current, uploadCost)
   238  	rev.NewFileMerkleRoot = merkleRoot
   239  	return rev
   240  }