/
quorum_certificate.go
129 lines (115 loc) · 3.78 KB
/
quorum_certificate.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
package types
import (
"bytes"
gocrypto "crypto"
"errors"
"fmt"
"hash"
"sort"
"github.com/alphabill-org/alphabill/crypto"
"github.com/alphabill-org/alphabill/types"
)
var (
errVoteInfoIsNil = errors.New("vote info is nil")
errLedgerCommitInfoIsNil = errors.New("ledger commit info is nil")
errInvalidRoundInfoHash = errors.New("round info has is missing")
)
type QuorumCert struct {
_ struct{} `cbor:",toarray"`
VoteInfo *RoundInfo `json:"vote_info,omitempty"` // Consensus data
LedgerCommitInfo *types.UnicitySeal `json:"ledger_commit_info,omitempty"` // Commit info
Signatures map[string][]byte `json:"signatures,omitempty"` // Node identifier to signature map (NB! aggregated signature schema in spec)
}
func NewQuorumCertificateFromVote(voteInfo *RoundInfo, commitInfo *types.UnicitySeal, signatures map[string][]byte) *QuorumCert {
return &QuorumCert{
VoteInfo: voteInfo,
LedgerCommitInfo: commitInfo,
Signatures: signatures,
}
}
func NewQuorumCertificate(voteInfo *RoundInfo, commitHash []byte) *QuorumCert {
return &QuorumCert{
VoteInfo: voteInfo,
LedgerCommitInfo: &types.UnicitySeal{PreviousHash: voteInfo.Hash(gocrypto.SHA256), Hash: commitHash},
Signatures: map[string][]byte{},
}
}
func (x *QuorumCert) GetRound() uint64 {
if x != nil {
return x.VoteInfo.GetRound()
}
return 0
}
func (x *QuorumCert) GetParentRound() uint64 {
if x != nil {
return x.VoteInfo.GetParentRound()
}
return 0
}
func (x *QuorumCert) GetCommitRound() uint64 {
if x.LedgerCommitInfo != nil && x.LedgerCommitInfo.Hash != nil {
return x.LedgerCommitInfo.RootChainRoundNumber
}
return 0
}
func (x *QuorumCert) IsValid() error {
if x.VoteInfo == nil {
return errVoteInfoIsNil
}
if err := x.VoteInfo.IsValid(); err != nil {
return fmt.Errorf("invalid vote info: %w", err)
}
// and must have valid ledger commit info
if x.LedgerCommitInfo == nil {
return errLedgerCommitInfoIsNil
}
// todo: should call x.LedgerCommitInfo.IsValid but that requires some refactoring
// not to require trustbase parameter?
// PreviousHash must not be empty, it always contains vote info hash (name is misleading)
if len(x.LedgerCommitInfo.PreviousHash) < 1 {
return errInvalidRoundInfoHash
}
return nil
}
func (x *QuorumCert) Verify(quorum uint32, rootTrust map[string]crypto.Verifier) error {
if err := x.IsValid(); err != nil {
return fmt.Errorf("invalid quorum certificate: %w", err)
}
// check vote info hash
if !bytes.Equal(x.VoteInfo.Hash(gocrypto.SHA256), x.LedgerCommitInfo.PreviousHash) {
return fmt.Errorf("vote info hash verification failed")
}
/* todo: call LedgerCommitInfo.Verify but first refactor it so that it takes quorum param?
if err := x.LedgerCommitInfo.Verify(rootTrust); err != nil {
return fmt.Errorf("invalid commit info: %w", err)
}*/
// Check quorum, if not fail without checking signatures
if uint32(len(x.Signatures)) < quorum {
return fmt.Errorf("quorum requires %d signatures but certificate has %d", quorum, len(x.Signatures))
}
// Verify all signatures
for author, sig := range x.Signatures {
ver, f := rootTrust[author]
if !f {
return fmt.Errorf("signer %q is not part of trustbase", author)
}
if err := ver.VerifyBytes(sig, x.LedgerCommitInfo.Bytes()); err != nil {
return fmt.Errorf("signer %q signature is not valid: %w", author, err)
}
}
return nil
}
func (x *QuorumCert) AddSignersToHasher(hasher hash.Hash) {
if x != nil {
// From QC signers (in the alphabetical order of signer ID!) must be included
signatures := x.Signatures
authors := make([]string, 0, len(signatures))
for k := range signatures {
authors = append(authors, k)
}
sort.Strings(authors)
for _, author := range authors {
hasher.Write(signatures[author])
}
}
}