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 }