-
Notifications
You must be signed in to change notification settings - Fork 2
/
spec.go
137 lines (111 loc) · 3.63 KB
/
spec.go
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package access_ticket
import (
"context"
"fmt"
storetypes "cosmossdk.io/store/types"
abcitypes "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/merkle"
"github.com/sourcenetwork/sourcehub/x/acp/did"
"github.com/sourcenetwork/sourcehub/x/acp/types"
)
func NewAccessTicketSpec(serv *abciService, registry did.Registry) AccessTicketSpec {
return AccessTicketSpec{
abciService: serv,
registry: registry,
marshaler: Marshaler{},
}
}
type AccessTicketSpec struct {
abciService *abciService
registry did.Registry
keyBuilder keyBuilder
signer signer
marshaler Marshaler
}
func (s *AccessTicketSpec) Satisfies(ctx context.Context, ticket string) error {
tkt, err := s.marshaler.Unmarshal(ticket)
if err != nil {
return err
}
return s.SatisfiesRaw(ctx, tkt)
}
// Satisfies inspects an AccessTicket and verifies whether it meets specification and is valid.
// Returns a non-nill error if the Ticket is invalid
func (s *AccessTicketSpec) SatisfiesRaw(ctx context.Context, ticket *types.AccessTicket) error {
err := s.verifyProof(ctx, ticket.DecisionId, ticket.DecisionProof)
if err != nil {
return err
}
did := ticket.Decision.ActorDid
pkey, err := s.registry.ResolveKey(did)
if err != nil {
return err
}
err = s.signer.Verify(pkey, ticket)
if err != nil {
return err
}
err = s.validateDecision(ticket)
if err != nil {
return err
}
heightInt, err := s.abciService.GetCurrentHeight(ctx)
height := uint64(heightInt)
if err != nil {
return err
}
decisionExpiration := s.computeDecisionExpiration(ticket.Decision)
proofExpiration := s.computeProofExpiration(ticket.Decision)
ticketExpiration := s.computeTicketExpiration(ticket.Decision)
if height > decisionExpiration {
return ErrExpiredDecision
}
if height > proofExpiration {
return ErrExpiredDecision
}
if height > ticketExpiration {
return ErrExpiredTicket
}
return nil
}
func (s *AccessTicketSpec) computeDecisionExpiration(decision *types.AccessDecision) uint64 {
return decision.IssuedHeight + decision.Params.DecisionExpirationDelta
}
func (s *AccessTicketSpec) computeTicketExpiration(decision *types.AccessDecision) uint64 {
return decision.IssuedHeight + decision.Params.TicketExpirationDelta
}
func (s *AccessTicketSpec) computeProofExpiration(decision *types.AccessDecision) uint64 {
return decision.IssuedHeight + decision.Params.ProofExpirationDelta
}
func (s *AccessTicketSpec) verifyProof(ctx context.Context, decisionId string, decisionProof []byte) error {
abciQuery := &abcitypes.ResponseQuery{}
err := abciQuery.Unmarshal(decisionProof)
if err != nil {
return ErrInvalidDecisionProof
}
height := abciQuery.Height + 1
header, err := s.abciService.GetBlockHeader(ctx, height)
if err != nil {
return err
}
root := header.AppHash.Bytes()
key := string(s.keyBuilder.KVKey(decisionId))
runtime := merkle.NewProofRuntime()
runtime.RegisterOpDecoder(storetypes.ProofOpIAVLCommitment, storetypes.CommitmentOpDecoder)
runtime.RegisterOpDecoder(storetypes.ProofOpSimpleMerkleCommitment, storetypes.CommitmentOpDecoder)
err = runtime.VerifyValue(abciQuery.ProofOps, root, key, abciQuery.Value)
if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidDecisionProof, err)
}
return nil
}
func (s *AccessTicketSpec) validateDecision(ticket *types.AccessTicket) error {
decisionHash := ticket.Decision.ProduceId()
if decisionHash != ticket.DecisionId {
return fmt.Errorf("expected %v got %v: %w", ticket.DecisionId, decisionHash, ErrDecisionTampered)
}
if decisionHash != ticket.Decision.Id {
return fmt.Errorf("expected %v got %v: %w", ticket.DecisionId, decisionHash, ErrDecisionTampered)
}
return nil
}