crypto.gno
4.40 Kb · 139 lines
1package memba_notes_v1
2
3import (
4 "bytes"
5 "chain/runtime"
6 "crypto/bech32"
7 "crypto/sha256"
8 "encoding/binary"
9 keys "gno.land/r/samcrew/enckeys_v1"
10 "strconv"
11)
12
13const ManifestEntryBytes = 1299
14const MaxManifestBytes = 2 + MaxReaders*ManifestEntryBytes
15const MaxCipherCommentBytes = 5687
16
17type recipient struct {
18 Who address
19 Writer bool
20 Generation uint64
21}
22
23func encrypted(mode uint8) bool { return mode <= ModeLink }
24func contains(addrs []address, who address) bool {
25 for _, a := range addrs {
26 if a == who {
27 return true
28 }
29 }
30 return false
31}
32func parseManifest(data []byte, epoch uint32, owner address, mode uint8) []recipient {
33 return parseManifestFor(data, epoch, owner, mode, "")
34}
35
36// A self refresh checks the new caller identity while preserving every other
37// already-stored envelope, including revoked or superseded identities.
38func parseManifestFor(data []byte, epoch uint32, owner address, mode uint8, currentOnly address) []recipient {
39 if len(data) < 2 || len(data) > MaxManifestBytes || data[0] != 1 || data[1] < 1 || data[1] > MaxReaders || len(data) != 2+int(data[1])*ManifestEntryBytes {
40 panic("notes: manifest header/length")
41 }
42 result := []recipient{}
43 previous := []byte(nil)
44 hasOwner := false
45 hasWriter := false
46 for i := 0; i < int(data[1]); i++ {
47 off := 2 + i*ManifestEntryBytes
48 raw := data[off : off+20]
49 if i > 0 && bytes.Compare(raw, previous) <= 0 || data[off+20] > 1 {
50 panic("notes: manifest order/role")
51 }
52 previous = raw
53 encoded, err := bech32.EncodeFromBase256("g", raw)
54 if err != nil {
55 panic("notes: recipient address")
56 }
57 who := address(encoded)
58 generation := binary.BigEndian.Uint64(data[off+21 : off+29])
59 if generation == 0 {
60 panic("notes: zero identity generation")
61 }
62 wr := off + 61
63 if data[wr] != 1 || data[wr+1] != 1 || binary.BigEndian.Uint32(data[wr+2:wr+6]) != epoch {
64 panic("notes: wrap suite/epoch")
65 }
66 if binary.BigEndian.Uint64(data[wr+6:wr+14]) != generation || !bytes.Equal(data[off+29:off+61], data[wr+14:wr+46]) {
67 panic("notes: wrap identity mismatch")
68 }
69 if currentOnly == "" || currentOnly == who {
70 pk, gen, active := keys.GetKey(who, keys.SuiteXWingV1)
71 if !active || gen != generation {
72 panic("notes: recipient key inactive/stale")
73 }
74 hash := sha256.Sum256(pk)
75 if !bytes.Equal(hash[:], data[off+29:off+61]) {
76 panic("notes: recipient public key hash")
77 }
78 }
79 isWriter := data[off+20] == 1
80 if isWriter {
81 hasWriter = true
82 }
83 if who == owner {
84 if !isWriter {
85 panic("notes: owner must write")
86 }
87 hasOwner = true
88 }
89 if mode == ModeLink && !isWriter {
90 panic("notes: link wraps are for writers")
91 }
92 result = append(result, recipient{who, isWriter, generation})
93 }
94 if owner != "" && (!hasOwner || !hasWriter) || mode == ModePrivate && (len(result) != 1 || owner == "") {
95 panic("notes: missing owner/private readers")
96 }
97 return result
98}
99func validateCipher(blob []byte, epoch uint32, field uint8, revision uint64, cap int) {
100 if len(blob) < 59 || len(blob) > cap || blob[0] != 1 || blob[1] != 1 || binary.BigEndian.Uint32(blob[2:6]) != epoch || blob[6] != field || binary.BigEndian.Uint64(blob[7:15]) != revision {
101 panic("notes: invalid cipher envelope")
102 }
103}
104func validateCommitment(kc []byte) {
105 if len(kc) != 32 || bytes.Equal(kc, make([]byte, 32)) {
106 panic("notes: invalid epoch commitment")
107 }
108}
109func appendText(b []byte, s string) []byte {
110 size := make([]byte, 4)
111 binary.BigEndian.PutUint32(size, uint32(len(s)))
112 b = append(b, size...)
113 return append(b, []byte(s)...)
114}
115func epochCommitment(id [16]byte, epoch uint32, key []byte) []byte {
116 b := appendText(nil, "memba-notes/v1/epoch-commitment")
117 b = appendText(b, runtime.ChainID())
118 b = appendText(b, RealmPath)
119 b = append(b, id[:]...)
120 ep := make([]byte, 4)
121 binary.BigEndian.PutUint32(ep, epoch)
122 b = append(b, ep...)
123 b = append(b, key...)
124 h := sha256.Sum256(b)
125 return h[:]
126}
127
128func manifestAddress(raw []byte) address {
129 s, err := bech32.EncodeFromBase256("g", raw)
130 if err != nil {
131 panic("notes: manifest address")
132 }
133 return address(s)
134}
135
136func canonicalRecipient(who address) address { return keys.CanonicalAddress(who) }
137func wrapJSON(entry []byte, epoch uint32, who address) string {
138 return `{"address":` + q(string(who)) + `,"epoch":` + u(uint64(epoch)) + `,"writer":` + strconv.FormatBool(entry[20] == 1) + `,"generation":` + u(binary.BigEndian.Uint64(entry[21:29])) + `,"public_key_hash":` + blob(entry[29:61]) + `,"wrap":` + blob(entry[61:]) + `}`
139}