Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}