Gno.land
Package daoauth writes the arguments of a governed action as one typed, canonical string. memba\_gov stores that string with the proposal and the target realm rebuilds it from its real arguments when it consumes the approval: the two must be equal byte for byte. Each value has exactly one spelling, so equal actions always encode to equal strings. Fields are joined by "|": u:<uint64> decimal, no sign, no leading zero i:<int64> decimal, "-" only when negative, no leading zero b:0 or b:1 bool a:<address> the lowercase bech32 text the chain gives a caller s:<len>:<text> length-prefixed printable ASCII (0x20-0x7E) Strings are compared as raw bytes, with no Unicode normalisation; text is printable ASCII so that what a voter reads is exactly what executes.
| 1 | // Package daoauth writes the arguments of a governed action as one typed, |
| 2 | // canonical string. memba_gov stores that string with the proposal and the |
| 3 | // target realm rebuilds it from its real arguments when it consumes the |
| 4 | // approval: the two must be equal byte for byte. Each value has exactly one |
| 5 | // spelling, so equal actions always encode to equal strings. |
| 6 | // |
| 7 | // Fields are joined by "|": |
| 8 | // |
| 9 | // u:<uint64> decimal, no sign, no leading zero |
| 10 | // i:<int64> decimal, "-" only when negative, no leading zero |
| 11 | // b:0 or b:1 bool |
| 12 | // a:<address> the lowercase bech32 text the chain gives a caller |
| 13 | // s:<len>:<text> length-prefixed printable ASCII (0x20-0x7E) |
| 14 | // |
| 15 | // Strings are compared as raw bytes, with no Unicode normalisation; text is |
| 16 | // printable ASCII so that what a voter reads is exactly what executes. |
| 17 | package daoauth |
| 18 | |
| 19 | import ( |
| 20 | "errors" |
| 21 | "strconv" |
| 22 | "strings" |
| 23 | ) |
| 24 | |
| 25 | const ( |
| 26 | MaxArgs = 1024 // bytes of one encoded argument string |
| 27 | MaxText = 512 // bytes of one string field |
| 28 | ) |
| 29 | |
| 30 | var ( |
| 31 | ErrSyntax = errors.New("daoauth: malformed argument string") |
| 32 | ErrAddress = errors.New("daoauth: address is not lowercase bech32") |
| 33 | ErrText = errors.New("daoauth: text must be printable ASCII of at most 512 bytes") |
| 34 | ErrLength = errors.New("daoauth: arguments exceed 1024 bytes") |
| 35 | ) |
| 36 | |
| 37 | // Args accumulates fields in order. Methods panic on a value that has no |
| 38 | // canonical form, so a realm can never build an approval nobody can match. |
| 39 | type Args struct{ fields []string } |
| 40 | |
| 41 | func New() *Args { return &Args{} } |
| 42 | |
| 43 | func (a *Args) Uint(v uint64) *Args { return a.add("u:" + strconv.FormatUint(v, 10)) } |
| 44 | func (a *Args) Int(v int64) *Args { return a.add("i:" + strconv.FormatInt(v, 10)) } |
| 45 | |
| 46 | func (a *Args) Bool(v bool) *Args { |
| 47 | if v { |
| 48 | return a.add("b:1") |
| 49 | } |
| 50 | return a.add("b:0") |
| 51 | } |
| 52 | |
| 53 | func (a *Args) Addr(v address) *Args { |
| 54 | if !ValidAddress(v) { |
| 55 | panic(ErrAddress.Error()) |
| 56 | } |
| 57 | return a.add("a:" + string(v)) |
| 58 | } |
| 59 | |
| 60 | func (a *Args) Str(v string) *Args { |
| 61 | if !ValidText(v) { |
| 62 | panic(ErrText.Error()) |
| 63 | } |
| 64 | return a.add("s:" + strconv.Itoa(len(v)) + ":" + v) |
| 65 | } |
| 66 | |
| 67 | func (a *Args) add(f string) *Args { |
| 68 | a.fields = append(a.fields, f) |
| 69 | return a |
| 70 | } |
| 71 | |
| 72 | // String returns the encoding. It panics past MaxArgs. |
| 73 | func (a *Args) String() string { |
| 74 | s := strings.Join(a.fields, "|") |
| 75 | if len(s) > MaxArgs { |
| 76 | panic(ErrLength.Error()) |
| 77 | } |
| 78 | return s |
| 79 | } |
| 80 | |
| 81 | // Field is one decoded value: Tag is 'u', 'i', 'b', 'a' or 's', and Value |
| 82 | // is its text, without the length prefix for strings. |
| 83 | type Field struct { |
| 84 | Tag byte |
| 85 | Value string |
| 86 | } |
| 87 | |
| 88 | // Parse decodes s and refuses any text that is not the canonical encoding of |
| 89 | // its own fields. The empty string is the encoding of no fields. |
| 90 | func Parse(s string) ([]Field, error) { |
| 91 | if len(s) > MaxArgs { |
| 92 | return nil, ErrLength |
| 93 | } |
| 94 | var out []Field |
| 95 | for len(s) > 0 { |
| 96 | if len(s) < 2 || s[1] != ':' { |
| 97 | return nil, ErrSyntax |
| 98 | } |
| 99 | tag, rest := s[0], s[2:] |
| 100 | var v string |
| 101 | if tag == 's' { |
| 102 | i := strings.IndexByte(rest, ':') |
| 103 | if i < 1 { |
| 104 | return nil, ErrSyntax |
| 105 | } |
| 106 | n, err := strconv.Atoi(rest[:i]) |
| 107 | if err != nil || n < 0 || strconv.Itoa(n) != rest[:i] || n > len(rest)-i-1 { |
| 108 | return nil, ErrSyntax |
| 109 | } |
| 110 | v, rest = rest[i+1:i+1+n], rest[i+1+n:] |
| 111 | if !ValidText(v) { |
| 112 | return nil, ErrText |
| 113 | } |
| 114 | } else { |
| 115 | i := strings.IndexByte(rest, '|') |
| 116 | if i < 0 { |
| 117 | i = len(rest) |
| 118 | } |
| 119 | v, rest = rest[:i], rest[i:] |
| 120 | if err := checkScalar(tag, v); err != nil { |
| 121 | return nil, err |
| 122 | } |
| 123 | } |
| 124 | out = append(out, Field{Tag: tag, Value: v}) |
| 125 | if len(rest) > 0 { |
| 126 | if rest[0] != '|' || len(rest) == 1 { |
| 127 | return nil, ErrSyntax |
| 128 | } |
| 129 | rest = rest[1:] |
| 130 | } |
| 131 | s = rest |
| 132 | } |
| 133 | return out, nil |
| 134 | } |
| 135 | |
| 136 | func checkScalar(tag byte, v string) error { |
| 137 | switch tag { |
| 138 | case 'u': |
| 139 | n, err := strconv.ParseUint(v, 10, 64) |
| 140 | if err != nil || strconv.FormatUint(n, 10) != v { |
| 141 | return ErrSyntax |
| 142 | } |
| 143 | case 'i': |
| 144 | n, err := strconv.ParseInt(v, 10, 64) |
| 145 | if err != nil || strconv.FormatInt(n, 10) != v { |
| 146 | return ErrSyntax |
| 147 | } |
| 148 | case 'b': |
| 149 | if v != "0" && v != "1" { |
| 150 | return ErrSyntax |
| 151 | } |
| 152 | case 'a': |
| 153 | if !ValidAddress(address(v)) { |
| 154 | return ErrAddress |
| 155 | } |
| 156 | default: |
| 157 | return ErrSyntax |
| 158 | } |
| 159 | return nil |
| 160 | } |
| 161 | |
| 162 | // ValidText reports whether s is at most MaxText bytes of printable ASCII |
| 163 | // (0x20 to 0x7E): no control, invisible, combining or direction-changing |
| 164 | // character can make a value read differently from what executes. |
| 165 | func ValidText(s string) bool { |
| 166 | if len(s) > MaxText { |
| 167 | return false |
| 168 | } |
| 169 | for i := 0; i < len(s); i++ { |
| 170 | if s[i] < 0x20 || s[i] > 0x7e { |
| 171 | return false |
| 172 | } |
| 173 | } |
| 174 | return true |
| 175 | } |
| 176 | |
| 177 | // The chain decodes four texts to one address: the lowercase bech32 text it |
| 178 | // gives a caller, that text in capitals, and the bech32m checksum in either |
| 179 | // case. Only the first ever equals cur.Previous().Address(). The check below |
| 180 | // is the one of launchpad meta/v1, copied rather than imported so governance |
| 181 | // does not depend on the Launchpad release. It reads the text instead of |
| 182 | // decoding it. |
| 183 | |
| 184 | // bech32Value holds, for each character of the bech32 alphabet, its 5-bit |
| 185 | // value plus one. Zero marks every other byte, capitals included. |
| 186 | var bech32Value = [256]byte{ |
| 187 | 'q': 1, 'p': 2, 'z': 3, 'r': 4, 'y': 5, '9': 6, 'x': 7, '8': 8, |
| 188 | 'g': 9, 'f': 10, '2': 11, 't': 12, 'v': 13, 'd': 14, 'w': 15, '0': 16, |
| 189 | 's': 17, '3': 18, 'j': 19, 'n': 20, '5': 21, '4': 22, 'k': 23, 'h': 24, |
| 190 | 'c': 25, 'e': 26, '6': 27, 'm': 28, 'u': 29, 'a': 30, '7': 31, 'l': 32, |
| 191 | } |
| 192 | |
| 193 | // bech32Gen[t] is the XOR of the BIP-173 generators selected by the bits of t. |
| 194 | var bech32Gen = [32]uint32{ |
| 195 | 0x00000000, 0x3b6a57b2, 0x26508e6d, 0x1d3ad9df, |
| 196 | 0x1ea119fa, 0x25cb4e48, 0x38f19797, 0x039bc025, |
| 197 | 0x3d4233dd, 0x0628646f, 0x1b12bdb0, 0x2078ea02, |
| 198 | 0x23e32a27, 0x18897d95, 0x05b3a44a, 0x3ed9f3f8, |
| 199 | 0x2a1462b3, 0x117e3501, 0x0c44ecde, 0x372ebb6c, |
| 200 | 0x34b57b49, 0x0fdf2cfb, 0x12e5f524, 0x298fa296, |
| 201 | 0x1756516e, 0x2c3c06dc, 0x3106df03, 0x0a6c88b1, |
| 202 | 0x09f74894, 0x329d1f26, 0x2fa7c6f9, 0x14cd914b, |
| 203 | } |
| 204 | |
| 205 | // bech32PrefixState is the checksum state after the prefix "g" (3, 0, 7). |
| 206 | const bech32PrefixState uint32 = 0x8c07 |
| 207 | |
| 208 | // ValidAddress reports whether a is "g1" and 38 lowercase bech32 characters |
| 209 | // whose checksum is the original bech32 one. |
| 210 | func ValidAddress(a address) bool { |
| 211 | s := string(a) |
| 212 | if len(s) != 40 || s[0] != 'g' || s[1] != '1' { |
| 213 | return false |
| 214 | } |
| 215 | chk := bech32PrefixState |
| 216 | for i := 2; i < 40; i++ { |
| 217 | v := bech32Value[s[i]] |
| 218 | if v == 0 { |
| 219 | return false |
| 220 | } |
| 221 | chk = (chk&0x1ffffff)<<5 ^ uint32(v-1) ^ bech32Gen[chk>>25] |
| 222 | } |
| 223 | return chk == 1 |
| 224 | } |
| Function | Parameters |
|---|
N/A