1package record
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"sync"
8
9	"github.com/libp2p/go-libp2p-core/crypto"
10	pb "github.com/libp2p/go-libp2p-core/record/pb"
11
12	pool "github.com/libp2p/go-buffer-pool"
13
14	"github.com/gogo/protobuf/proto"
15	"github.com/multiformats/go-varint"
16)
17
18// Envelope contains an arbitrary []byte payload, signed by a libp2p peer.
19//
20// Envelopes are signed in the context of a particular "domain", which is a
21// string specified when creating and verifying the envelope. You must know the
22// domain string used to produce the envelope in order to verify the signature
23// and access the payload.
24type Envelope struct {
25	// The public key that can be used to verify the signature and derive the peer id of the signer.
26	PublicKey crypto.PubKey
27
28	// A binary identifier that indicates what kind of data is contained in the payload.
29	// TODO(yusef): enforce multicodec prefix
30	PayloadType []byte
31
32	// The envelope payload.
33	RawPayload []byte
34
35	// The signature of the domain string :: type hint :: payload.
36	signature []byte
37
38	// the unmarshalled payload as a Record, cached on first access via the Record accessor method
39	cached         Record
40	unmarshalError error
41	unmarshalOnce  sync.Once
42}
43
44var ErrEmptyDomain = errors.New("envelope domain must not be empty")
45var ErrEmptyPayloadType = errors.New("payloadType must not be empty")
46var ErrInvalidSignature = errors.New("invalid signature or incorrect domain")
47
48// Seal marshals the given Record, places the marshaled bytes inside an Envelope,
49// and signs with the given private key.
50func Seal(rec Record, privateKey crypto.PrivKey) (*Envelope, error) {
51	payload, err := rec.MarshalRecord()
52	if err != nil {
53		return nil, fmt.Errorf("error marshaling record: %v", err)
54	}
55
56	domain := rec.Domain()
57	payloadType := rec.Codec()
58	if domain == "" {
59		return nil, ErrEmptyDomain
60	}
61
62	if len(payloadType) == 0 {
63		return nil, ErrEmptyPayloadType
64	}
65
66	unsigned, err := makeUnsigned(domain, payloadType, payload)
67	if err != nil {
68		return nil, err
69	}
70	defer pool.Put(unsigned)
71
72	sig, err := privateKey.Sign(unsigned)
73	if err != nil {
74		return nil, err
75	}
76
77	return &Envelope{
78		PublicKey:   privateKey.GetPublic(),
79		PayloadType: payloadType,
80		RawPayload:  payload,
81		signature:   sig,
82	}, nil
83}
84
85// ConsumeEnvelope unmarshals a serialized Envelope and validates its
86// signature using the provided 'domain' string. If validation fails, an error
87// is returned, along with the unmarshalled envelope so it can be inspected.
88//
89// On success, ConsumeEnvelope returns the Envelope itself, as well as the inner payload,
90// unmarshalled into a concrete Record type. The actual type of the returned Record depends
91// on what has been registered for the Envelope's PayloadType (see RegisterType for details).
92//
93// You can type assert on the returned Record to convert it to an instance of the concrete
94// Record type:
95//
96//    envelope, rec, err := ConsumeEnvelope(envelopeBytes, peer.PeerRecordEnvelopeDomain)
97//    if err != nil {
98//      handleError(envelope, err)  // envelope may be non-nil, even if errors occur!
99//      return
100//    }
101//    peerRec, ok := rec.(*peer.PeerRecord)
102//    if ok {
103//      doSomethingWithPeerRecord(peerRec)
104//    }
105//
106// Important: you MUST check the error value before using the returned Envelope. In some error
107// cases, including when the envelope signature is invalid, both the Envelope and an error will
108// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result,
109// you must not assume that any non-nil Envelope returned from this function is valid.
110//
111// If the Envelope signature is valid, but no Record type is registered for the Envelope's
112// PayloadType, ErrPayloadTypeNotRegistered will be returned, along with the Envelope and
113// a nil Record.
114func ConsumeEnvelope(data []byte, domain string) (envelope *Envelope, rec Record, err error) {
115	e, err := UnmarshalEnvelope(data)
116	if err != nil {
117		return nil, nil, fmt.Errorf("failed when unmarshalling the envelope: %w", err)
118	}
119
120	err = e.validate(domain)
121	if err != nil {
122		return e, nil, fmt.Errorf("failed to validate envelope: %w", err)
123	}
124
125	rec, err = e.Record()
126	if err != nil {
127		return e, nil, fmt.Errorf("failed to unmarshal envelope payload: %w", err)
128	}
129	return e, rec, nil
130}
131
132// ConsumeTypedEnvelope unmarshals a serialized Envelope and validates its
133// signature. If validation fails, an error is returned, along with the unmarshalled
134// envelope so it can be inspected.
135//
136// Unlike ConsumeEnvelope, ConsumeTypedEnvelope does not try to automatically determine
137// the type of Record to unmarshal the Envelope's payload into. Instead, the caller provides
138// a destination Record instance, which will unmarshal the Envelope payload. It is the caller's
139// responsibility to determine whether the given Record type is able to unmarshal the payload
140// correctly.
141//
142//    rec := &MyRecordType{}
143//    envelope, err := ConsumeTypedEnvelope(envelopeBytes, rec)
144//    if err != nil {
145//      handleError(envelope, err)
146//    }
147//    doSomethingWithRecord(rec)
148//
149// Important: you MUST check the error value before using the returned Envelope. In some error
150// cases, including when the envelope signature is invalid, both the Envelope and an error will
151// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result,
152// you must not assume that any non-nil Envelope returned from this function is valid.
153func ConsumeTypedEnvelope(data []byte, destRecord Record) (envelope *Envelope, err error) {
154	e, err := UnmarshalEnvelope(data)
155	if err != nil {
156		return nil, fmt.Errorf("failed when unmarshalling the envelope: %w", err)
157	}
158
159	err = e.validate(destRecord.Domain())
160	if err != nil {
161		return e, fmt.Errorf("failed to validate envelope: %w", err)
162	}
163
164	err = destRecord.UnmarshalRecord(e.RawPayload)
165	if err != nil {
166		return e, fmt.Errorf("failed to unmarshal envelope payload: %w", err)
167	}
168	e.cached = destRecord
169	return e, nil
170}
171
172// UnmarshalEnvelope unmarshals a serialized Envelope protobuf message,
173// without validating its contents. Most users should use ConsumeEnvelope.
174func UnmarshalEnvelope(data []byte) (*Envelope, error) {
175	var e pb.Envelope
176	if err := proto.Unmarshal(data, &e); err != nil {
177		return nil, err
178	}
179
180	key, err := crypto.PublicKeyFromProto(e.PublicKey)
181	if err != nil {
182		return nil, err
183	}
184
185	return &Envelope{
186		PublicKey:   key,
187		PayloadType: e.PayloadType,
188		RawPayload:  e.Payload,
189		signature:   e.Signature,
190	}, nil
191}
192
193// Marshal returns a byte slice containing a serialized protobuf representation
194// of a Envelope.
195func (e *Envelope) Marshal() ([]byte, error) {
196	key, err := crypto.PublicKeyToProto(e.PublicKey)
197	if err != nil {
198		return nil, err
199	}
200
201	msg := pb.Envelope{
202		PublicKey:   key,
203		PayloadType: e.PayloadType,
204		Payload:     e.RawPayload,
205		Signature:   e.signature,
206	}
207	return proto.Marshal(&msg)
208}
209
210// Equal returns true if the other Envelope has the same public key,
211// payload, payload type, and signature. This implies that they were also
212// created with the same domain string.
213func (e *Envelope) Equal(other *Envelope) bool {
214	if other == nil {
215		return e == nil
216	}
217	return e.PublicKey.Equals(other.PublicKey) &&
218		bytes.Compare(e.PayloadType, other.PayloadType) == 0 &&
219		bytes.Compare(e.signature, other.signature) == 0 &&
220		bytes.Compare(e.RawPayload, other.RawPayload) == 0
221}
222
223// Record returns the Envelope's payload unmarshalled as a Record.
224// The concrete type of the returned Record depends on which Record
225// type was registered for the Envelope's PayloadType - see record.RegisterType.
226//
227// Once unmarshalled, the Record is cached for future access.
228func (e *Envelope) Record() (Record, error) {
229	e.unmarshalOnce.Do(func() {
230		if e.cached != nil {
231			return
232		}
233		e.cached, e.unmarshalError = unmarshalRecordPayload(e.PayloadType, e.RawPayload)
234	})
235	return e.cached, e.unmarshalError
236}
237
238// TypedRecord unmarshals the Envelope's payload to the given Record instance.
239// It is the caller's responsibility to ensure that the Record type is capable
240// of unmarshalling the Envelope payload. Callers can inspect the Envelope's
241// PayloadType field to determine the correct type of Record to use.
242//
243// This method will always unmarshal the Envelope payload even if a cached record
244// exists.
245func (e *Envelope) TypedRecord(dest Record) error {
246	return dest.UnmarshalRecord(e.RawPayload)
247}
248
249// validate returns nil if the envelope signature is valid for the given 'domain',
250// or an error if signature validation fails.
251func (e *Envelope) validate(domain string) error {
252	unsigned, err := makeUnsigned(domain, e.PayloadType, e.RawPayload)
253	if err != nil {
254		return err
255	}
256	defer pool.Put(unsigned)
257
258	valid, err := e.PublicKey.Verify(unsigned, e.signature)
259	if err != nil {
260		return fmt.Errorf("failed while verifying signature: %w", err)
261	}
262	if !valid {
263		return ErrInvalidSignature
264	}
265	return nil
266}
267
268// makeUnsigned is a helper function that prepares a buffer to sign or verify.
269// It returns a byte slice from a pool. The caller MUST return this slice to the
270// pool.
271func makeUnsigned(domain string, payloadType []byte, payload []byte) ([]byte, error) {
272	var (
273		fields = [][]byte{[]byte(domain), payloadType, payload}
274
275		// fields are prefixed with their length as an unsigned varint. we
276		// compute the lengths before allocating the sig buffer so we know how
277		// much space to add for the lengths
278		flen = make([][]byte, len(fields))
279		size = 0
280	)
281
282	for i, f := range fields {
283		l := len(f)
284		flen[i] = varint.ToUvarint(uint64(l))
285		size += l + len(flen[i])
286	}
287
288	b := pool.Get(size)
289
290	var s int
291	for i, f := range fields {
292		s += copy(b[s:], flen[i])
293		s += copy(b[s:], f)
294	}
295
296	return b[:s], nil
297}
298