- 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
154 lines
4 KiB
Go
154 lines
4 KiB
Go
package tce
|
|
|
|
// Value is a claim or approval payload value.
|
|
//
|
|
// The zero Value is null, which is a deliberate choice: a forgotten field
|
|
// encodes as an explicit null rather than as something that fails to encode or
|
|
// silently disappears.
|
|
//
|
|
// Value is an opaque struct rather than an interface so that the set of
|
|
// representable values is exactly the set the specification defines. There is
|
|
// no way to construct a value with a reserved tag, and no way for a caller to
|
|
// supply a type the encoder does not know how to canonicalize.
|
|
type Value struct {
|
|
tag ValueTag
|
|
str string // string contents, or the canonical number token
|
|
}
|
|
|
|
// Null returns the null value.
|
|
func Null() Value { return Value{tag: ValNull} }
|
|
|
|
// Bool returns a boolean value.
|
|
func Bool(b bool) Value {
|
|
if b {
|
|
return Value{tag: ValTrue}
|
|
}
|
|
return Value{tag: ValFalse}
|
|
}
|
|
|
|
// String returns a string value. Validation happens at encode time.
|
|
func String(s string) Value { return Value{tag: ValString, str: s} }
|
|
|
|
// Number returns a numeric value from its exact decimal source token.
|
|
//
|
|
// The token is taken as text rather than as a float64 because a JSON number
|
|
// is an arbitrary-precision decimal literal: converting through a binary float
|
|
// loses precision above 2^53 and makes the signed bytes depend on the
|
|
// implementation's rounding. Callers decoding JSON should use json.Number,
|
|
// which preserves the source token.
|
|
//
|
|
// The token is canonicalized at encode time, so Number("1.0") and Number("1")
|
|
// produce identical bytes.
|
|
func Number(token string) Value { return Value{tag: ValNumber, str: token} }
|
|
|
|
// Int returns a numeric value from an integer.
|
|
func Int(n int64) Value { return Value{tag: ValNumber, str: formatInt(n)} }
|
|
|
|
// Tag returns the value's type tag.
|
|
func (v Value) Tag() ValueTag { return v.tag }
|
|
|
|
// IsNull reports whether v is null.
|
|
func (v Value) IsNull() bool { return v.tag == ValNull }
|
|
|
|
// Bool returns the boolean contents and whether v is a boolean.
|
|
func (v Value) Bool() (bool, bool) {
|
|
switch v.tag {
|
|
case ValTrue:
|
|
return true, true
|
|
case ValFalse:
|
|
return false, true
|
|
default:
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
// Str returns the string contents and whether v is a string.
|
|
func (v Value) Str() (string, bool) {
|
|
if v.tag == ValString {
|
|
return v.str, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// NumberToken returns the number's decimal token and whether v is a number.
|
|
//
|
|
// For a decoded value the token is always in canonical form, because the
|
|
// decoder rejects any other spelling.
|
|
func (v Value) NumberToken() (string, bool) {
|
|
if v.tag == ValNumber {
|
|
return v.str, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// Equal reports whether two values are identical after canonicalization.
|
|
//
|
|
// Numbers compare by canonical form, so Number("1.0") equals Number("1").
|
|
// A value that cannot be canonicalized never compares equal to anything,
|
|
// including itself, because it has no defined meaning.
|
|
func (v Value) Equal(o Value) bool {
|
|
if v.tag != o.tag {
|
|
return false
|
|
}
|
|
switch v.tag {
|
|
case ValNull, ValFalse, ValTrue:
|
|
return true
|
|
case ValString:
|
|
return v.str == o.str
|
|
case ValNumber:
|
|
a, err1 := CanonicalNumber(v.str)
|
|
b, err2 := CanonicalNumber(o.str)
|
|
if err1 != nil || err2 != nil {
|
|
return false
|
|
}
|
|
return a == b
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// GoString renders a value for test failure messages.
|
|
func (v Value) GoString() string {
|
|
switch v.tag {
|
|
case ValNull:
|
|
return "null"
|
|
case ValTrue:
|
|
return "true"
|
|
case ValFalse:
|
|
return "false"
|
|
case ValString:
|
|
return "string(" + v.str + ")"
|
|
case ValNumber:
|
|
return "number(" + v.str + ")"
|
|
default:
|
|
return "invalid"
|
|
}
|
|
}
|
|
|
|
// formatInt renders an int64 in plain decimal without importing strconv's
|
|
// float machinery.
|
|
func formatInt(n int64) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
neg := n < 0
|
|
var buf [20]byte
|
|
i := len(buf)
|
|
// Accumulate using uint64 so that math.MinInt64 negates correctly.
|
|
var u uint64
|
|
if neg {
|
|
u = uint64(-(n + 1)) + 1
|
|
} else {
|
|
u = uint64(n)
|
|
}
|
|
for u > 0 {
|
|
i--
|
|
buf[i] = byte('0' + u%10)
|
|
u /= 10
|
|
}
|
|
if neg {
|
|
i--
|
|
buf[i] = '-'
|
|
}
|
|
return string(buf[i:])
|
|
}
|