1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package market
import (
"context"
"fmt"
)
type ContractGraph struct {
Bid ResolvedRecord
RFP ResolvedRecord
RFPPayload ResolvedRecord
BidPayload ResolvedRecord
BidConfig *ResolvedRecord
}
type ContractVerifyOptions struct {
SkipSignatureVerification bool
KeysForDID KeysForDID
KeyResolver KeyResolver
KeyBoundByBadgeBlueKey func(ctx context.Context, did, key string) (bool, error)
}
func ResolveContractGraph(ctx context.Context, accept Accept, resolver RecordResolver, opts ContractVerifyOptions) (*ContractGraph, error) {
if resolver == nil {
return nil, fmt.Errorf("%w: no record resolver", ErrContractGraph)
}
bidRef := RecordRef{URI: accept.Bid.URI, CID: accept.Bid.CID}
bidRecord, err := resolver.Resolve(ctx, bidRef)
if err != nil {
return nil, &ContractGraphError{Reason: "resolve bid: " + err.Error()}
}
bid, err := ParseBid(bidRecord.Value)
if err != nil {
return nil, &ContractGraphError{Reason: "parse bid: " + err.Error()}
}
if !RefsEqual(RecordRef{URI: bid.RFP.URI, CID: bid.RFP.CID}, RecordRef{URI: accept.RFP.URI, CID: accept.RFP.CID}) {
return nil, &ContractGraphError{Reason: "Accept.rfp does not match Bid.rfp"}
}
rfpRecord, err := resolver.Resolve(ctx, RecordRef{URI: accept.RFP.URI, CID: accept.RFP.CID})
if err != nil {
return nil, &ContractGraphError{Reason: "resolve rfp: " + err.Error()}
}
rfp, err := ParseRFP(rfpRecord.Value)
if err != nil {
return nil, &ContractGraphError{Reason: "parse rfp: " + err.Error()}
}
if !opts.SkipSignatureVerification {
verifyOpts := VerifyRecordOptions{
KeysForDID: opts.KeysForDID,
KeyResolver: opts.KeyResolver,
KeyBoundByBadgeBlueKey: opts.KeyBoundByBadgeBlueKey,
}
if !VerifyRecordSignatures(ctx, bidRecord.Value, ATURIAuthority(bidRecord.URI), verifyOpts) {
return nil, &ContractGraphError{Reason: "Bid is missing a valid badge.blue signature"}
}
if !VerifyRecordSignatures(ctx, rfpRecord.Value, ATURIAuthority(rfpRecord.URI), verifyOpts) {
return nil, &ContractGraphError{Reason: "RFP is missing a valid badge.blue signature"}
}
}
rfpPayload, err := resolver.Resolve(ctx, RecordRef{URI: rfp.Payload.URI, CID: rfp.Payload.CID})
if err != nil {
return nil, &ContractGraphError{Reason: "resolve rfp payload: " + err.Error()}
}
bidPayload, err := resolver.Resolve(ctx, RecordRef{URI: bid.Payload.URI, CID: bid.Payload.CID})
if err != nil {
return nil, &ContractGraphError{Reason: "resolve bid payload: " + err.Error()}
}
var bidConfig *ResolvedRecord
if bid.Config != nil {
cfg, err := resolver.Resolve(ctx, RecordRef{URI: bid.Config.URI, CID: bid.Config.CID})
if err != nil {
return nil, &ContractGraphError{Reason: "resolve bid config: " + err.Error()}
}
bidConfig = &cfg
}
return &ContractGraph{
Bid: bidRecord,
RFP: rfpRecord,
RFPPayload: rfpPayload,
BidPayload: bidPayload,
BidConfig: bidConfig,
}, nil
}
type ReceiptVerification struct {
OK bool
SigOK bool
BindOK bool
}
func VerifyReceipt(ctx context.Context, receiptRecord map[string]any, receiptURI string, acceptRecord map[string]any, acceptURI string, opts VerifyRecordOptions) ReceiptVerification {
opts.KeysForDID = nil
sigOK := VerifyRecordSignatures(ctx, receiptRecord, ATURIAuthority(receiptURI), opts)
bindOK := VerifyRemoteProof(acceptRecord, ATURIAuthority(acceptURI), receiptRecord)
return ReceiptVerification{OK: sigOK && bindOK, SigOK: sigOK, BindOK: bindOK}
}
func BindEventPayload(event Event, eventURI, receiptURI, payloadNSID string) (RecordRef, error) {
if event.Type != EventNSID {
return RecordRef{}, fmt.Errorf("%w: envelope is not a market.event", ErrInvalidEvent)
}
if event.Receipt.URI != receiptURI {
return RecordRef{}, fmt.Errorf("%w: event names another receipt", ErrInvalidEvent)
}
bidder := ATURIAuthority(receiptURI)
if ATURIAuthority(eventURI) != bidder {
return RecordRef{}, fmt.Errorf("%w: event not authored by the receipt's bidder", ErrInvalidEvent)
}
payload := RecordRef{URI: event.Payload.URI, CID: event.Payload.CID}
if ATURIAuthority(payload.URI) != bidder {
return RecordRef{}, fmt.Errorf("%w: payload not in the bidder's repo", ErrInvalidEvent)
}
if NSIDFromURI(payload.URI) != payloadNSID {
return RecordRef{}, fmt.Errorf("%w: payload is not %s", ErrInvalidEvent, payloadNSID)
}
return payload, nil
}