Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}