daoauth.gno
6.27 Kb · 224 lines
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.
17package daoauth
18
19import (
20 "errors"
21 "strconv"
22 "strings"
23)
24
25const (
26 MaxArgs = 1024 // bytes of one encoded argument string
27 MaxText = 512 // bytes of one string field
28)
29
30var (
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.
39type Args struct{ fields []string }
40
41func New() *Args { return &Args{} }
42
43func (a *Args) Uint(v uint64) *Args { return a.add("u:" + strconv.FormatUint(v, 10)) }
44func (a *Args) Int(v int64) *Args { return a.add("i:" + strconv.FormatInt(v, 10)) }
45
46func (a *Args) Bool(v bool) *Args {
47 if v {
48 return a.add("b:1")
49 }
50 return a.add("b:0")
51}
52
53func (a *Args) Addr(v address) *Args {
54 if !ValidAddress(v) {
55 panic(ErrAddress.Error())
56 }
57 return a.add("a:" + string(v))
58}
59
60func (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
67func (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.
73func (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.
83type 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.
90func 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
136func 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.
165func 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.
186var 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.
194var 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).
206const bech32PrefixState uint32 = 0x8c07
207
208// ValidAddress reports whether a is "g1" and 38 lowercase bech32 characters
209// whose checksum is the original bech32 one.
210func 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}