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