negotiaterecentrevision.go 6.59 KB
Newer Older
1 2 3
package host

import (
4
	"errors"
5 6 7
	"net"
	"time"

8 9 10 11 12
	"gitlab.com/NebulousLabs/Sia/crypto"
	"gitlab.com/NebulousLabs/Sia/encoding"
	"gitlab.com/NebulousLabs/Sia/modules"
	"gitlab.com/NebulousLabs/Sia/types"
	"gitlab.com/NebulousLabs/fastrand"
13

14
	"github.com/coreos/bbolt"
15 16
)

17
var (
18
	// errRevisionWrongPublicKeyCount is returned when a stored file contract
19 20
	// revision does not have enough public keys - such a situation should
	// 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 24 25 26 27

	// errVerifyChallenge is returned to renter instead of any error
	// returned by managedVerifyChallengeResponse. It is used instead
	// of the original error not to leak if the host has the contract
	// with the ID sent by renter.
	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 32
// challenge provided by the host is correct. In the process, the storage
// obligation and file contract revision will be loaded and returned.
33 34
//
// The storage obligation is returned under a storage obligation lock.
David Vorick's avatar
David Vorick committed
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 37 38
	// Grab a lock before it is possible to perform any operations on the
	// storage obligation. Defer a call to unlock in the event of an error. If
	// there is no error, the storage obligation will be returned with a lock.
David Vorick's avatar
David Vorick committed
39
	err = h.managedTryLockStorageObligation(fcid)
40
	if err != nil {
41 42
		err = extendErr("could not get "+fcid.String()+" lock: ", ErrorInternal(err.Error()))
		return storageObligation{}, types.FileContractRevision{}, nil, err
43 44 45
	}
	defer func() {
		if err != nil {
David Vorick's avatar
David Vorick committed
46
			h.managedUnlockStorageObligation(fcid)
47 48 49
		}
	}()

50 51
	// Fetch the storage obligation, which has the revision, which has the
	// renter's public key.
52 53
	h.mu.RLock()
	defer h.mu.RUnlock()
54 55
	err = h.db.View(func(tx *bolt.Tx) error {
		so, err = getStorageObligation(tx, fcid)
56 57 58
		return err
	})
	if err != nil {
59 60
		err = extendErr("could not fetch "+fcid.String()+": ", ErrorInternal(err.Error()))
		return storageObligation{}, types.FileContractRevision{}, nil, err
61 62
	}

David Vorick's avatar
David Vorick committed
63 64
	// Pull out the file contract revision and the revision's signatures from
	// the transaction.
65
	revisionTxn := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1]
66
	recentRevision = revisionTxn.FileContractRevisions[0]
67
	for _, sig := range revisionTxn.TransactionSignatures {
68 69 70
		// Checking for just the parent id is sufficient, an over-signed file
		// contract is invalid.
		if sig.ParentID == crypto.Hash(fcid) {
71 72 73 74
			revisionSigs = append(revisionSigs, sig)
		}
	}

David Vorick's avatar
David Vorick committed
75
	// Verify that the challegne response matches the public key.
76 77 78
	var renterPK crypto.PublicKey
	// Sanity check - there should be two public keys.
	if len(recentRevision.UnlockConditions.PublicKeys) != 2 {
79 80
		// The error has to be set here so that the defered error check will
		// unlock the storage obligation.
David Vorick's avatar
David Vorick committed
81
		h.log.Critical("wrong public key count in file contract revision")
82
		err = errRevisionWrongPublicKeyCount
83 84
		err = extendErr("wrong public key count for "+fcid.String()+": ", ErrorInternal(err.Error()))
		return storageObligation{}, types.FileContractRevision{}, nil, err
85 86 87 88
	}
	copy(renterPK[:], recentRevision.UnlockConditions.PublicKeys[0].Key)
	err = crypto.VerifyHash(challenge, renterPK, challengeResponse)
	if err != nil {
89 90
		err = extendErr("bad signature from renter: ", ErrorCommunication(err.Error()))
		return storageObligation{}, types.FileContractRevision{}, nil, err
91 92 93 94 95 96
	}
	return so, recentRevision, revisionSigs, nil
}

// managedRPCRecentRevision sends the most recent known file contract
// revision, including signatures, to the renter, for the file contract with
97 98 99 100
// the id given by the renter.
//
// The storage obligation is returned under a storage obligation lock.
func (h *Host) managedRPCRecentRevision(conn net.Conn) (types.FileContractID, storageObligation, error) {
101 102 103 104 105 106 107
	// Set the negotiation deadline.
	conn.SetDeadline(time.Now().Add(modules.NegotiateRecentRevisionTime))

	// Receive the file contract id from the renter.
	var fcid types.FileContractID
	err := encoding.ReadObject(conn, &fcid, uint64(len(fcid)))
	if err != nil {
David Vorick's avatar
David Vorick committed
108
		return types.FileContractID{}, storageObligation{}, extendErr("could not read file contract id: ", ErrorConnection(err.Error()))
109 110
	}

111 112 113
	// Send a challenge to the renter to verify that the renter has write
	// access to the revision being opened.
	var challenge crypto.Hash
114
	fastrand.Read(challenge[16:])
115 116
	err = encoding.WriteObject(conn, challenge)
	if err != nil {
David Vorick's avatar
David Vorick committed
117
		return types.FileContractID{}, storageObligation{}, extendErr("cound not write challenge: ", ErrorConnection(err.Error()))
118 119 120 121
	}

	// Read the signed response from the renter.
	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 127
	}
	// Verify the response. In the process, fetch the related storage
	// obligation, file contract revision, and transaction signatures.
David Vorick's avatar
David Vorick committed
128
	so, recentRevision, revisionSigs, err := h.managedVerifyChallengeResponse(fcid, challenge, challengeResponse)
129
	if err != nil {
130 131 132
		// Do not disclose the original error to renter not to leak
		// if the host has the contract with the ID sent by renter.
		modules.WriteNegotiationRejection(conn, errVerifyChallenge)
David Vorick's avatar
David Vorick committed
133
		return types.FileContractID{}, storageObligation{}, extendErr("challenge failed: ", err)
134
	}
135 136 137
	// Defer a call to unlock the storage obligation in the event of an error.
	defer func() {
		if err != nil {
David Vorick's avatar
David Vorick committed
138
			h.managedUnlockStorageObligation(fcid)
139 140
		}
	}()
141

142 143
	// Send the file contract revision and the corresponding signatures to the
	// renter.
144 145
	err = modules.WriteNegotiationAcceptance(conn)
	if err != nil {
146 147
		err = extendErr("failed to write challenge acceptance: ", ErrorConnection(err.Error()))
		return types.FileContractID{}, storageObligation{}, err
148
	}
David Vorick's avatar
David Vorick committed
149
	err = encoding.WriteObject(conn, recentRevision)
150
	if err != nil {
151 152
		err = extendErr("failed to write recent revision: ", ErrorConnection(err.Error()))
		return types.FileContractID{}, storageObligation{}, err
153 154 155
	}
	err = encoding.WriteObject(conn, revisionSigs)
	if err != nil {
156
		err = extendErr("failed to write recent revision signatures: ", ErrorConnection(err.Error()))
157
		return types.FileContractID{}, storageObligation{}, err
158
	}
David Vorick's avatar
David Vorick committed
159
	return fcid, so, nil
160
}