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 }