gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/negotiaterecentrevision.go (about)

     1  package host
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	bolt "github.com/coreos/bbolt"
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    11  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/SiaPrime/types"
    14  )
    15  
    16  var (
    17  	// errRevisionWrongPublicKeyCount is returned when a stored file contract
    18  	// revision does not have enough public keys - such a situation should
    19  	// never happen, and is a critical / developer error.
    20  	errRevisionWrongPublicKeyCount = errors.New("wrong number of public keys in the unlock conditions of the file contract revision")
    21  
    22  	// errVerifyChallenge is returned to renter instead of any error
    23  	// returned by managedVerifyChallengeResponse. It is used instead
    24  	// of the original error not to leak if the host has the contract
    25  	// with the ID sent by renter.
    26  	errVerifyChallenge = errors.New("bad signature from renter or no such contract")
    27  )
    28  
    29  // managedVerifyChallengeResponse will verify that the renter's response to the
    30  // challenge provided by the host is correct. In the process, the storage
    31  // obligation and file contract revision will be loaded and returned.
    32  //
    33  // The storage obligation is returned under a storage obligation lock.
    34  func (h *Host) managedVerifyChallengeResponse(fcid types.FileContractID, challenge crypto.Hash, challengeResponse crypto.Signature) (so storageObligation, recentRevision types.FileContractRevision, revisionSigs []types.TransactionSignature, err error) {
    35  	// Grab a lock before it is possible to perform any operations on the
    36  	// storage obligation. Defer a call to unlock in the event of an error. If
    37  	// there is no error, the storage obligation will be returned with a lock.
    38  	err = h.managedTryLockStorageObligation(fcid, obligationLockTimeout)
    39  	if err != nil {
    40  		err = extendErr("could not get "+fcid.String()+" lock: ", ErrorInternal(err.Error()))
    41  		return storageObligation{}, types.FileContractRevision{}, nil, err
    42  	}
    43  	defer func() {
    44  		if err != nil {
    45  			h.managedUnlockStorageObligation(fcid)
    46  		}
    47  	}()
    48  
    49  	// Fetch the storage obligation, which has the revision, which has the
    50  	// renter's public key.
    51  	h.mu.RLock()
    52  	defer h.mu.RUnlock()
    53  	err = h.db.View(func(tx *bolt.Tx) error {
    54  		so, err = getStorageObligation(tx, fcid)
    55  		return err
    56  	})
    57  	if err != nil {
    58  		err = extendErr("could not fetch "+fcid.String()+": ", ErrorInternal(err.Error()))
    59  		return storageObligation{}, types.FileContractRevision{}, nil, err
    60  	}
    61  
    62  	// Pull out the file contract revision and the revision's signatures from
    63  	// the transaction.
    64  	revisionTxn := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1]
    65  	recentRevision = revisionTxn.FileContractRevisions[0]
    66  	for _, sig := range revisionTxn.TransactionSignatures {
    67  		// Checking for just the parent id is sufficient, an over-signed file
    68  		// contract is invalid.
    69  		if sig.ParentID == crypto.Hash(fcid) {
    70  			revisionSigs = append(revisionSigs, sig)
    71  		}
    72  	}
    73  
    74  	// Verify that the challegne response matches the public key.
    75  	var renterPK crypto.PublicKey
    76  	// Sanity check - there should be two public keys.
    77  	if len(recentRevision.UnlockConditions.PublicKeys) != 2 {
    78  		// The error has to be set here so that the defered error check will
    79  		// unlock the storage obligation.
    80  		h.log.Critical("wrong public key count in file contract revision")
    81  		err = errRevisionWrongPublicKeyCount
    82  		err = extendErr("wrong public key count for "+fcid.String()+": ", ErrorInternal(err.Error()))
    83  		return storageObligation{}, types.FileContractRevision{}, nil, err
    84  	}
    85  	copy(renterPK[:], recentRevision.UnlockConditions.PublicKeys[0].Key)
    86  	err = crypto.VerifyHash(challenge, renterPK, challengeResponse)
    87  	if err != nil {
    88  		err = extendErr("bad signature from renter: ", ErrorCommunication(err.Error()))
    89  		return storageObligation{}, types.FileContractRevision{}, nil, err
    90  	}
    91  	return so, recentRevision, revisionSigs, nil
    92  }
    93  
    94  // managedRPCRecentRevision sends the most recent known file contract
    95  // revision, including signatures, to the renter, for the file contract with
    96  // the id given by the renter.
    97  //
    98  // The storage obligation is returned under a storage obligation lock.
    99  func (h *Host) managedRPCRecentRevision(conn net.Conn) (types.FileContractID, storageObligation, error) {
   100  	// Set the negotiation deadline.
   101  	conn.SetDeadline(time.Now().Add(modules.NegotiateRecentRevisionTime))
   102  
   103  	// Receive the file contract id from the renter.
   104  	var fcid types.FileContractID
   105  	err := encoding.ReadObject(conn, &fcid, uint64(len(fcid)))
   106  	if err != nil {
   107  		return types.FileContractID{}, storageObligation{}, extendErr("could not read file contract id: ", ErrorConnection(err.Error()))
   108  	}
   109  
   110  	// Send a challenge to the renter to verify that the renter has write
   111  	// access to the revision being opened.
   112  	var challenge crypto.Hash
   113  	fastrand.Read(challenge[16:])
   114  	err = encoding.WriteObject(conn, challenge)
   115  	if err != nil {
   116  		return types.FileContractID{}, storageObligation{}, extendErr("cound not write challenge: ", ErrorConnection(err.Error()))
   117  	}
   118  
   119  	// Read the signed response from the renter.
   120  	var challengeResponse crypto.Signature
   121  	err = encoding.ReadObject(conn, &challengeResponse, uint64(len(challengeResponse)))
   122  	if err != nil {
   123  		return types.FileContractID{}, storageObligation{}, extendErr("could not read challenge response: ", ErrorConnection(err.Error()))
   124  	}
   125  	// Verify the response. In the process, fetch the related storage
   126  	// obligation, file contract revision, and transaction signatures.
   127  	so, recentRevision, revisionSigs, err := h.managedVerifyChallengeResponse(fcid, challenge, challengeResponse)
   128  	if err != nil {
   129  		// Do not disclose the original error to renter not to leak
   130  		// if the host has the contract with the ID sent by renter.
   131  		modules.WriteNegotiationRejection(conn, errVerifyChallenge)
   132  		return types.FileContractID{}, storageObligation{}, extendErr("challenge failed: ", err)
   133  	}
   134  	// Defer a call to unlock the storage obligation in the event of an error.
   135  	defer func() {
   136  		if err != nil {
   137  			h.managedUnlockStorageObligation(fcid)
   138  		}
   139  	}()
   140  
   141  	// Send the file contract revision and the corresponding signatures to the
   142  	// renter.
   143  	err = modules.WriteNegotiationAcceptance(conn)
   144  	if err != nil {
   145  		err = extendErr("failed to write challenge acceptance: ", ErrorConnection(err.Error()))
   146  		return types.FileContractID{}, storageObligation{}, err
   147  	}
   148  	err = encoding.WriteObject(conn, recentRevision)
   149  	if err != nil {
   150  		err = extendErr("failed to write recent revision: ", ErrorConnection(err.Error()))
   151  		return types.FileContractID{}, storageObligation{}, err
   152  	}
   153  	err = encoding.WriteObject(conn, revisionSigs)
   154  	if err != nil {
   155  		err = extendErr("failed to write recent revision signatures: ", ErrorConnection(err.Error()))
   156  		return types.FileContractID{}, storageObligation{}, err
   157  	}
   158  	return fcid, so, nil
   159  }