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

enckeys.gno

5.02 Kb · 153 lines
  1// Package enckeys_v1 is an ownerless public encryption-key directory.
  2// Keys are announcements by accounts, not attestations of identity, custody,
  3// recovery strength or successful decryption. There is no fee, admin or pause.
  4package enckeys_v1
  5
  6import (
  7	"chain"
  8	"chain/runtime"
  9	"crypto/bech32"
 10	"encoding/base64"
 11	"encoding/hex"
 12	"strconv"
 13
 14	"gno.land/p/samcrew/avl"
 15)
 16
 17const SuiteXWingV1 = "xwing-v1"
 18const PublicKeyBytes = 1216
 19
 20type keyRecord struct {
 21	Generation uint64
 22	PublicKey  []byte
 23	Operation  [16]byte
 24	Height     int64
 25	Revoked    bool
 26}
 27
 28var keys = avl.NewTree()
 29
 30// RegisterKey replaces the caller's announcement using an optimistic lock.
 31// expectedGeneration is zero only for first registration. A revoked generation
 32// is retained forever; re-registering cannot reuse an old identity generation.
 33func RegisterKey(cur realm, suite string, expectedGeneration uint64, publicKey []byte, opID [16]byte) uint64 {
 34	caller := user(cur)
 35	checkSuite(suite)
 36	if len(publicKey) != PublicKeyBytes {
 37		panic("enckeys: bad public key length")
 38	}
 39	old := current(caller)
 40	checkUpdate(old, expectedGeneration, opID)
 41	rec := &keyRecord{Generation: expectedGeneration + 1, PublicKey: clone(publicKey), Operation: opID, Height: runtime.ChainHeight()}
 42	keys.Set(string(caller), rec)
 43	emit(caller, rec, "registered")
 44	return rec.Generation
 45}
 46
 47// RevokeKey removes the active key but retains the generation and receipt.
 48// Historical announcements and wraps remain public chain history.
 49func RevokeKey(cur realm, suite string, expectedGeneration uint64, opID [16]byte) uint64 {
 50	caller := user(cur)
 51	checkSuite(suite)
 52	old := current(caller)
 53	checkUpdate(old, expectedGeneration, opID)
 54	if old == nil || old.Revoked {
 55		panic("enckeys: no active key")
 56	}
 57	rec := &keyRecord{Generation: expectedGeneration + 1, Operation: opID, Height: runtime.ChainHeight(), Revoked: true}
 58	keys.Set(string(caller), rec)
 59	emit(caller, rec, "revoked")
 60	return rec.Generation
 61}
 62
 63// GetKey returns a defensive copy; generation is still returned after revoke.
 64func GetKey(addr address, suite string) (publicKey []byte, generation uint64, active bool) {
 65	checkSuite(suite)
 66	rec := current(CanonicalAddress(addr))
 67	if rec == nil {
 68		return nil, 0, false
 69	}
 70	return clone(rec.PublicKey), rec.Generation, !rec.Revoked
 71}
 72
 73// KeyJSON serializes all chain integers as decimal strings. op_id and height
 74// identify the last successful mutation for recovery from an unknown outcome.
 75func KeyJSON(addr address, suite string) string {
 76	checkSuite(suite)
 77	who := CanonicalAddress(addr)
 78	rec := current(who)
 79	prefix := `{"address":` + strconv.Quote(string(who)) + `,"suite":"xwing-v1"`
 80	if rec == nil {
 81		return prefix + `,"generation":"0","active":false,"public_key":"","op_id":"","height":"0"}`
 82	}
 83	return prefix + `,"generation":` + strconv.Quote(strconv.FormatUint(rec.Generation, 10)) + `,"active":` + strconv.FormatBool(!rec.Revoked) + `,"public_key":` + strconv.Quote(base64.StdEncoding.EncodeToString(rec.PublicKey)) + `,"op_id":` + strconv.Quote(hex.EncodeToString(rec.Operation[:])) + `,"height":` + strconv.Quote(strconv.FormatInt(rec.Height, 10)) + `}`
 84}
 85
 86func Render(path string) string {
 87	if path == "" {
 88		return "# Encryption key directory\n\nAccount announcements for the xwing-v1 suite. Revocation does not erase chain history.\n"
 89	}
 90	return "````json\n" + KeyJSON(address(path), SuiteXWingV1) + "\n````"
 91}
 92
 93// CanonicalAddress accepts the chain's four checksum/case spellings but always
 94// returns the lowercase original bech32 spelling that realm callers use.
 95func CanonicalAddress(addr address) address {
 96	if len(string(addr)) != 40 {
 97		panic("enckeys: invalid address")
 98	}
 99	hrp, raw, err := bech32.DecodeToBase256(string(addr))
100	if err != nil || hrp != "g" || len(raw) != 20 {
101		panic("enckeys: invalid address")
102	}
103	out, err := bech32.EncodeFromBase256("g", raw)
104	if err != nil {
105		panic("enckeys: invalid address")
106	}
107	return address(out)
108}
109
110func user(cur realm) address {
111	if !cur.IsCurrent() {
112		panic("enckeys: stale realm")
113	}
114	if !cur.Previous().IsUserCall() {
115		panic("enckeys: call directly from your account")
116	}
117	assertNoCoins()
118	return cur.Previous().Address()
119}
120func checkSuite(suite string) {
121	if suite != SuiteXWingV1 {
122		panic("enckeys: unknown suite")
123	}
124}
125func current(addr address) *keyRecord {
126	v, ok := keys.Get(string(addr))
127	if !ok {
128		return nil
129	}
130	return v.(*keyRecord)
131}
132func checkUpdate(old *keyRecord, expected uint64, op [16]byte) {
133	generation := uint64(0)
134	if old != nil {
135		generation = old.Generation
136	}
137	if generation != expected {
138		panic("enckeys: stale generation")
139	}
140	if expected == ^uint64(0) {
141		panic("enckeys: generation exhausted")
142	}
143	if op == ([16]byte{}) {
144		panic("enckeys: empty operation id")
145	}
146	if old != nil && old.Operation == op {
147		panic("enckeys: operation id reused")
148	}
149}
150func clone(b []byte) []byte { return append([]byte(nil), b...) }
151func emit(addr address, rec *keyRecord, action string) {
152	chain.Emit("EncryptionKey", "address", string(addr), "suite", SuiteXWingV1, "generation", strconv.FormatUint(rec.Generation, 10), "action", action, "op_id", hex.EncodeToString(rec.Operation[:]))
153}