api.gno
3.36 Kb · 96 lines
1package namereg
2
3import (
4 "errors"
5 "regexp"
6 "strings"
7)
8
9// Open Nym Tier username format. Anchored.
10// - literal `nym-` prefix (4 chars)
11// - 5-13 lowercase ASCII letters (the alpha stem)
12// - exactly 3 trailing decimal digits
13//
14// Total length 12-20 chars. Distinct by length from `g1...` addresses
15// which are always 40 chars.
16const reNymFormat = `^nym-[a-z]{5,13}\d{3}$`
17
18var reNym = regexp.MustCompile(reNymFormat)
19
20// Reserved alpha-stem prefixes. Names whose stem starts with one of
21// these are rejected at format-validation time with ErrReservedPrefix
22// (clearer than ErrCanonicalCollision after the fact).
23//
24// `gi` is intentionally NOT listed: in most rendering targets `i` is
25// visually distinct enough from `1`/`l` that legitimate `gi*` names
26// (giggles, gimbal, gift, etc.) should remain registerable. Phishing
27// protection for the visual class is still enforced by canonical-
28// collision detection in r/sys/users — once any `gi*` or `gl*` name
29// is registered, all variants under the {l,i,1}→i canonicalization
30// collide.
31//
32// `gl` and `g1` remain listed because they're more visually
33// confusable with the bech32 address prefix `g1`. `g1` itself is
34// unreachable through the alpha-only stem regex; defense-in-depth
35// for any future regex relaxation.
36var reservedPrefixes = []string{"gl", "g1", "gno", "atom", "atone", "photon", "cosmos"}
37
38// Exported error sentinels returned by ValidateNymFormat. Use
39// errors.Is or direct equality; do not string-match.
40//
41// ErrReservedPrefix's message is built from reservedPrefixes at package
42// init time so the surfaced list never drifts from the actual policy.
43//
44// Canonical-collision detection moved to r/sys/users in Option B.
45// Consumers that previously caught namereg.ErrCanonicalCollision
46// should switch to susers.ErrCanonicalCollision.
47var (
48 ErrInvalidFormat = errors.New("namereg: name must match nym-[a-z]{5,13}\\d{3}")
49 ErrReservedPrefix = errors.New("namereg: stem starts with a reserved prefix (" + strings.Join(reservedPrefixes, "/") + ")")
50 ErrBlacklisted = errors.New("namereg: stem matches a reserved role name")
51)
52
53// IsReserved reports whether the given alpha stem matches a reserved
54// role name (with implicit `s`-suffix expansion). The check is
55// canonicalized — so `vital1k`-style l-substituted variants of a
56// reserved name are also caught. O(1) backed by `reservedSet` built
57// in init().
58func IsReserved(stem string) bool {
59 _, found := reservedSet[Canonicalize(stem)]
60 return found
61}
62
63// ValidateNymFormat checks the regex, prefix-exclusion, and reserved-
64// name rules in that order. Returns one of the exported sentinel
65// errors per failure mode, or nil on success.
66//
67// Does NOT run the canonical-collision check — that lives in r/sys/users
68// (susers.IsCanonicalTaken or, atomically with the write, inside
69// susers.RegisterUser).
70func ValidateNymFormat(username string) error {
71 if !reNym.MatchString(username) {
72 return ErrInvalidFormat
73 }
74
75 // Stem is everything between `nym-` (4 chars) and the trailing
76 // 3 digits. Regex guarantees 5..13 alpha chars in this slice.
77 stem := username[4 : len(username)-3]
78
79 for _, p := range reservedPrefixes {
80 if strings.HasPrefix(stem, p) {
81 return ErrReservedPrefix
82 }
83 }
84
85 if IsReserved(stem) {
86 return ErrBlacklisted
87 }
88
89 return nil
90}
91
92// IsPaused exposes the realm's pause flag for cross-controller
93// coordination.
94func IsPaused() bool {
95 return paused
96}