niko_trust/pkg/tce/decoder.go
Niko Marmeladkov 3bf13fa488 Public SDK packages, proxy-aware rate limits, service login recipe
- 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
2026-08-26 12:49:54 +03:00

287 lines
7.2 KiB
Go

package tce
import (
"unicode/utf8"
)
// Decoder reads canonical TCE bytes.
//
// The decoder is strict: it accepts only the exact byte sequence the encoder
// would produce for a given object, and treats every other input as an error.
// In particular it never skips a field it does not understand. Skipping would
// mean two implementations computed different meanings for the same signed
// bytes while both saw a valid signature, so an old verifier could approve an
// object whose actual content it never examined.
//
// Every length prefix is checked against the remaining input and against the
// field's maximum before any allocation, so a hostile length cannot cause a
// large allocation or a long loop.
type Decoder struct {
buf []byte
off int
}
// NewDecoder returns a decoder reading b. The slice is not copied; the caller
// must not modify it while decoding.
func NewDecoder(b []byte) *Decoder { return &Decoder{buf: b} }
// Offset returns the current read position.
func (d *Decoder) Offset() int { return d.off }
// Remaining returns the number of unread bytes.
func (d *Decoder) Remaining() int { return len(d.buf) - d.off }
// End asserts that the input is fully consumed.
//
// Trailing bytes are an error rather than ignored data: an object with extra
// bytes appended has a different byte string, and therefore a different object
// ID and a different signature, from the object it appears to contain.
func (d *Decoder) End() error {
if d.off != len(d.buf) {
return ErrTrailing
}
return nil
}
// Header reads and validates the magic, object tag and object version.
func (d *Decoder) Header() (ObjectTag, error) {
if d.Remaining() < MagicLen {
return TagReserved, ErrTruncated
}
if string(d.buf[d.off:d.off+MagicLen]) != Magic {
return TagReserved, ErrMagic
}
d.off += MagicLen
if d.Remaining() < 1 {
return TagReserved, ErrTruncated
}
tag := ObjectTag(d.buf[d.off])
d.off++
if !knownTag(tag) {
return TagReserved, ErrObjectTag
}
ver, err := d.Uvarint()
if err != nil {
return TagReserved, fieldErr("version", err)
}
if ver != Version {
return TagReserved, ErrVersion
}
return tag, nil
}
// Uvarint reads a canonical LEB128 unsigned varint.
//
// A multi-byte encoding whose final group is zero is a longer spelling of a
// shorter value and is rejected, because permitting it would give the same
// number two encodings and therefore the same object two byte strings.
func (d *Decoder) Uvarint() (uint64, error) {
var n uint64
var shift uint
start := d.off
for {
if d.off >= len(d.buf) {
return 0, ErrTruncated
}
if d.off-start >= MaxUvarintBytes {
return 0, ErrUvarint
}
b := d.buf[d.off]
d.off++
if shift >= 64 || (shift == 63 && b > 1) {
return 0, ErrOverflow
}
n |= uint64(b&0x7f) << shift
if b&0x80 == 0 {
if d.off-start > 1 && b == 0x00 {
return 0, ErrNonMinimal
}
return n, nil
}
shift += 7
}
}
// RawBytes reads a length-prefixed byte string, enforcing maxLen.
//
// The returned slice aliases the decoder's buffer. Callers that retain the
// data must copy it; the object constructors in the protocol package do.
func (d *Decoder) RawBytes(maxLen int) ([]byte, error) {
n, err := d.Uvarint()
if err != nil {
return nil, err
}
// Compare against the remaining input first so that an absurd length is
// rejected before it is compared with anything else.
if n > uint64(d.Remaining()) {
return nil, ErrTruncated
}
if n > uint64(maxLen) {
return nil, ErrTooLong
}
b := d.buf[d.off : d.off+int(n)]
d.off += int(n)
return b, nil
}
// FixedBytes reads a length-prefixed byte string of exactly want bytes.
func (d *Decoder) FixedBytes(want int) ([]byte, error) {
b, err := d.RawBytes(want)
if err != nil {
return nil, err
}
if len(b) != want {
return nil, ErrFieldSize
}
return b, nil
}
// String reads a length-prefixed UTF-8 string and validates it.
func (d *Decoder) String(maxLen int) (string, error) {
b, err := d.RawBytes(maxLen)
if err != nil {
return "", err
}
if !utf8.Valid(b) {
return "", ErrUTF8
}
s := string(b)
for _, r := range s {
if r <= 0x1f || r == 0x7f || (r >= 0x80 && r <= 0x9f) {
return "", ErrControlChar
}
}
return s, nil
}
// Identity reads an identity field and returns a copy of the public key.
//
// The key is copied so that a decoded object does not alias the input buffer,
// which means a caller cannot alter an identity after the object containing it
// has been verified.
func (d *Decoder) Identity() ([]byte, error) {
ver, err := d.Uvarint()
if err != nil {
return nil, err
}
if ver != AddressVersion {
return nil, ErrAddressVersion
}
b, err := d.FixedBytes(PubKeySize)
if err != nil {
return nil, err
}
out := make([]byte, PubKeySize)
copy(out, b)
return out, nil
}
// Timestamp reads a timestamp and validates its range.
func (d *Decoder) Timestamp(allowZero bool) (uint64, error) {
ts, err := d.Uvarint()
if err != nil {
return 0, err
}
if err := ValidateTimestamp(ts, allowZero); err != nil {
return 0, err
}
return ts, nil
}
// Value reads a typed value.
//
// Reserved and unknown tags are rejected. This is the opposite of the
// "ignore what you do not understand" rule common in extensible formats, and
// it is deliberate: in a signed protocol, skipping an unrecognised value means
// the verifier's view of the object differs from the signer's.
func (d *Decoder) Value() (Value, error) {
if d.Remaining() < 1 {
return Value{}, ErrTruncated
}
tag := ValueTag(d.buf[d.off])
d.off++
switch tag {
case ValNull:
return Null(), nil
case ValFalse:
return Bool(false), nil
case ValTrue:
return Bool(true), nil
case ValString:
s, err := d.String(MaxStringValue)
if err != nil {
return Value{}, err
}
return String(s), nil
case ValNumber:
b, err := d.RawBytes(MaxNumberToken)
if err != nil {
return Value{}, err
}
tok := string(b)
// A number must arrive in canonical form. Accepting "1.0" here would
// mean two byte strings encoded the same value.
if !IsCanonicalNumber(tok) {
return Value{}, ErrNumberFormat
}
return Number(tok), nil
default:
return Value{}, ErrValueTag
}
}
// Map reads a map, enforcing ascending key order, key uniqueness, the key
// grammar and the entry limit.
func (d *Decoder) Map(minEntries int) (map[string]Value, error) {
n, err := d.Uvarint()
if err != nil {
return nil, err
}
if n > MaxMapEntries {
return nil, ErrTooLong
}
if n < uint64(minEntries) {
return nil, ErrEmptyMap
}
// Each entry costs at least two bytes, so a count larger than the
// remaining input cannot be satisfied. Checking this before allocating
// prevents a small input from reserving a large map.
if n > uint64(d.Remaining()) {
return nil, ErrTruncated
}
m := make(map[string]Value, n)
prev := ""
for i := uint64(0); i < n; i++ {
kb, err := d.RawBytes(MaxKeyLen)
if err != nil {
return nil, err
}
key := string(kb)
if err := ValidateKey(key); err != nil {
return nil, err
}
if i > 0 {
switch {
case key == prev:
return nil, ErrDuplicateKey
case key < prev:
// Out-of-order entries parse, but would give one map two
// encodings, so they are rejected.
return nil, ErrKeyOrder
}
}
v, err := d.Value()
if err != nil {
return nil, err
}
m[key] = v
prev = key
}
return m, nil
}