- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
external Go projects can import the verified core; invariant tests
updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
sits behind a reverse proxy (off by default, header never trusted
otherwise)
- examples/service + examples/approve: complete passwordless login round
trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
309 lines
7.7 KiB
Go
309 lines
7.7 KiB
Go
package tce
|
|
|
|
import (
|
|
"sort"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Encoder builds canonical TCE bytes.
|
|
//
|
|
// The encoder is append-only and records the first error it encounters,
|
|
// after which every further operation is a no-op. This means a caller writes
|
|
// a straight sequence of field writes and checks once at the end, without a
|
|
// partially built object ever escaping.
|
|
type Encoder struct {
|
|
buf []byte
|
|
err error
|
|
}
|
|
|
|
// NewEncoder returns an encoder with space reserved for a typical object.
|
|
func NewEncoder() *Encoder {
|
|
return &Encoder{buf: make([]byte, 0, 256)}
|
|
}
|
|
|
|
// Err returns the first error recorded, if any.
|
|
func (e *Encoder) Err() error { return e.err }
|
|
|
|
// fail records the first error.
|
|
func (e *Encoder) fail(field string, err error) {
|
|
if e.err == nil {
|
|
e.err = fieldErr(field, err)
|
|
}
|
|
}
|
|
|
|
// Bytes returns the encoded object, or an error if any write failed.
|
|
//
|
|
// The returned slice is a copy, so a caller cannot alter the encoder's buffer
|
|
// afterwards and no two callers share backing storage.
|
|
func (e *Encoder) Bytes() ([]byte, error) {
|
|
if e.err != nil {
|
|
return nil, e.err
|
|
}
|
|
out := make([]byte, len(e.buf))
|
|
copy(out, e.buf)
|
|
return out, nil
|
|
}
|
|
|
|
// Header writes the magic, the object tag and the object version. It must be
|
|
// the first call on an encoder.
|
|
func (e *Encoder) Header(tag ObjectTag) {
|
|
if !knownTag(tag) {
|
|
e.fail("object tag", ErrObjectTag)
|
|
return
|
|
}
|
|
e.buf = append(e.buf, Magic...)
|
|
e.buf = append(e.buf, byte(tag))
|
|
e.Uvarint(Version)
|
|
}
|
|
|
|
// Uvarint appends a canonical LEB128 unsigned varint.
|
|
func (e *Encoder) Uvarint(n uint64) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
e.buf = AppendUvarint(e.buf, n)
|
|
}
|
|
|
|
// AppendUvarint appends the canonical LEB128 encoding of n to dst.
|
|
//
|
|
// Canonical means shortest: the loop emits a continuation byte only while
|
|
// bits remain, so no encoding ever ends in a redundant 0x00 group.
|
|
func AppendUvarint(dst []byte, n uint64) []byte {
|
|
for n >= 0x80 {
|
|
dst = append(dst, byte(n)|0x80)
|
|
n >>= 7
|
|
}
|
|
return append(dst, byte(n))
|
|
}
|
|
|
|
// UvarintLen returns the number of bytes AppendUvarint would write.
|
|
func UvarintLen(n uint64) int {
|
|
l := 1
|
|
for n >= 0x80 {
|
|
n >>= 7
|
|
l++
|
|
}
|
|
return l
|
|
}
|
|
|
|
// RawBytes appends a length-prefixed byte string with no length limit of its
|
|
// own. Callers use FixedBytes or the field-specific helpers instead where a
|
|
// limit applies.
|
|
func (e *Encoder) RawBytes(field string, b []byte) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
e.buf = AppendUvarint(e.buf, uint64(len(b)))
|
|
e.buf = append(e.buf, b...)
|
|
}
|
|
|
|
// FixedBytes appends a length-prefixed byte string that must have exactly the
|
|
// given length.
|
|
//
|
|
// The length prefix is written even though the width is fixed, so that every
|
|
// field stays self-delimiting and a decoder never depends on out-of-band
|
|
// knowledge of a field's size.
|
|
func (e *Encoder) FixedBytes(field string, b []byte, want int) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if len(b) != want {
|
|
e.fail(field, ErrFieldSize)
|
|
return
|
|
}
|
|
e.RawBytes(field, b)
|
|
}
|
|
|
|
// String appends a length-prefixed UTF-8 string after validating it.
|
|
func (e *Encoder) String(field, s string, maxLen int) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if err := ValidateString(s, maxLen); err != nil {
|
|
e.fail(field, err)
|
|
return
|
|
}
|
|
e.RawBytes(field, []byte(s))
|
|
}
|
|
|
|
// Identity appends an identity field: the address version, then the
|
|
// length-prefixed raw public key.
|
|
//
|
|
// The raw key is encoded rather than the bech32m address text. The address is
|
|
// a presentation format; the key is the identity. Signing the key means a
|
|
// change to address rendering cannot invalidate existing signatures.
|
|
func (e *Encoder) Identity(field string, pubkey []byte) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if len(pubkey) != PubKeySize {
|
|
e.fail(field, ErrFieldSize)
|
|
return
|
|
}
|
|
e.Uvarint(AddressVersion)
|
|
e.RawBytes(field, pubkey)
|
|
}
|
|
|
|
// Timestamp appends a timestamp, enforcing the protocol's range.
|
|
//
|
|
// allowZero permits the single exception where 0 means "does not expire".
|
|
func (e *Encoder) Timestamp(field string, ts uint64, allowZero bool) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if err := ValidateTimestamp(ts, allowZero); err != nil {
|
|
e.fail(field, err)
|
|
return
|
|
}
|
|
e.Uvarint(ts)
|
|
}
|
|
|
|
// ValidateTimestamp checks a timestamp against the specification's bounds.
|
|
func ValidateTimestamp(ts uint64, allowZero bool) error {
|
|
if allowZero && ts == 0 {
|
|
return nil
|
|
}
|
|
if ts < MinTimestamp || ts > MaxTimestamp {
|
|
return ErrTimestamp
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateString applies the protocol's string rules.
|
|
//
|
|
// No normalisation is performed. The bytes are signed exactly as supplied,
|
|
// because silently rewriting a user's text before signing it would mean the
|
|
// user signs something other than what they reviewed.
|
|
func ValidateString(s string, maxLen int) error {
|
|
if len(s) > maxLen {
|
|
return ErrTooLong
|
|
}
|
|
if !utf8.ValidString(s) {
|
|
return ErrUTF8
|
|
}
|
|
for _, r := range s {
|
|
// utf8.ValidString already rejects surrogates and overlong forms.
|
|
if r <= 0x1f || r == 0x7f || (r >= 0x80 && r <= 0x9f) {
|
|
return ErrControlChar
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Value appends a typed claim or payload value.
|
|
func (e *Encoder) Value(field string, v Value) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
switch v.tag {
|
|
case ValNull, ValFalse, ValTrue:
|
|
e.buf = append(e.buf, byte(v.tag))
|
|
case ValString:
|
|
if err := ValidateString(v.str, MaxStringValue); err != nil {
|
|
e.fail(field, err)
|
|
return
|
|
}
|
|
e.buf = append(e.buf, byte(ValString))
|
|
e.RawBytes(field, []byte(v.str))
|
|
case ValNumber:
|
|
canon, err := CanonicalNumber(v.str)
|
|
if err != nil {
|
|
e.fail(field, err)
|
|
return
|
|
}
|
|
e.buf = append(e.buf, byte(ValNumber))
|
|
e.RawBytes(field, []byte(canon))
|
|
default:
|
|
e.fail(field, ErrValueTag)
|
|
}
|
|
}
|
|
|
|
// Map appends a map of keys to values in canonical order.
|
|
//
|
|
// Entries are sorted by raw key bytes, unsigned bytewise ascending. Duplicate
|
|
// keys are an error rather than a last-one-wins situation, because leaving
|
|
// the winner to the implementation would mean two conforming encoders
|
|
// disagreed about the meaning of the same input.
|
|
func (e *Encoder) Map(field string, m map[string]Value, minEntries int) {
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
if len(m) < minEntries {
|
|
e.fail(field, ErrEmptyMap)
|
|
return
|
|
}
|
|
if len(m) > MaxMapEntries {
|
|
e.fail(field, ErrTooLong)
|
|
return
|
|
}
|
|
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
if err := ValidateKey(k); err != nil {
|
|
e.fail(field, err)
|
|
return
|
|
}
|
|
keys = append(keys, k)
|
|
}
|
|
// Go string comparison is bytewise on the underlying bytes, which is the
|
|
// ordering the specification requires.
|
|
sort.Strings(keys)
|
|
|
|
// A Go map cannot hold duplicate keys, but the check is kept so that the
|
|
// invariant is enforced here as well as in the decoder.
|
|
for i := 1; i < len(keys); i++ {
|
|
if keys[i] == keys[i-1] {
|
|
e.fail(field, ErrDuplicateKey)
|
|
return
|
|
}
|
|
}
|
|
|
|
e.Uvarint(uint64(len(keys)))
|
|
for _, k := range keys {
|
|
e.RawBytes(field, []byte(k))
|
|
e.Value(field, m[k])
|
|
if e.err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ValidateKey applies the map key grammar from PROTOCOL.md section 6.2:
|
|
//
|
|
// [a-z][a-z0-9]*([._-][a-z0-9]+)*
|
|
//
|
|
// This is a lexical rule with no semantics attached. The protocol never
|
|
// interprets a key; the restriction exists so that keys sort predictably and
|
|
// cannot carry homoglyphs or bidirectional overrides.
|
|
func ValidateKey(k string) error {
|
|
if len(k) == 0 || len(k) > MaxKeyLen {
|
|
return ErrTooLong
|
|
}
|
|
if k[0] < 'a' || k[0] > 'z' {
|
|
return ErrKeyGrammar
|
|
}
|
|
prevSep := false
|
|
for i := 1; i < len(k); i++ {
|
|
c := k[i]
|
|
switch {
|
|
case c >= 'a' && c <= 'z', c >= '0' && c <= '9':
|
|
prevSep = false
|
|
case c == '.' || c == '_' || c == '-':
|
|
// No repeated separators and none at the end.
|
|
if prevSep || i == len(k)-1 {
|
|
return ErrKeyGrammar
|
|
}
|
|
prevSep = true
|
|
default:
|
|
return ErrKeyGrammar
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkSize enforces the per-object maximum size.
|
|
func (e *Encoder) checkSize(limit int) {
|
|
if e.err == nil && len(e.buf) > limit {
|
|
e.err = ErrObjectTooLarge
|
|
}
|
|
}
|