~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
117 lines · 4.3 KB Raw History
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
}