niko_trust/pkg/protocol/rejects_test.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

110 lines
3.3 KiB
Go

package protocol_test
import (
"errors"
"testing"
"git.n1ko.dev/Niko/niko_trust/pkg/protocol"
"git.n1ko.dev/Niko/niko_trust/pkg/tce"
)
// TestRejectVectors runs the frozen malformed-encoding vectors from
// PROTOCOL.md section 12.3. Every one must be refused by the strict decoder;
// a few also pin the exact reason.
func TestRejectVectors(t *testing.T) {
vf := loadVectors(t)
exact := map[string]error{
"empty": tce.ErrTruncated,
"magic_truncated": tce.ErrTruncated,
"magic_wrong_version": tce.ErrMagic,
"unknown_object_tag": tce.ErrObjectTag,
"object_tag_zero": tce.ErrObjectTag,
"non_minimal_uvarint": tce.ErrNonMinimal,
"trailing_byte": tce.ErrTrailing,
}
checked := 0
for _, r := range vf.Rejects {
if r.TCEHex == nil {
// Constructed cases (unsorted/duplicate map keys, reserved value
// tag) are exercised separately; they have no serialised bytes in
// the file.
continue
}
checked++
t.Run(r.Name, func(t *testing.T) {
b := mustHex(t, *r.TCEHex)
_, err := protocol.DecodeClaim(b)
if err == nil {
t.Fatalf("accepted a vector the reference implementation refuses")
}
if want, ok := exact[r.Name]; ok && !errors.Is(err, want) {
t.Fatalf("err = %v, want %v", err, want)
}
})
}
if checked == 0 {
t.Fatal("no vector rejects with tce_hex found")
}
}
// twoKeyClaimBytes builds a valid two-key claim, whose map occupies bytes
// [91, 98): count(1) | len(1) key value | len(1) key value.
func twoKeyClaimBytes(t *testing.T, fx *signerFixtures) []byte {
t.Helper()
c := &protocol.Claim{
Issuer: fx.alice.Public(), Subject: fx.bob.Public(),
Claims: map[string]tce.Value{"a": tce.Bool(true), "b": tce.Bool(false)},
CreatedAt: 1_700_000_000, ExpiresAt: 1_700_086_400, Serial: 1, Nonce: nonce(1),
}
b, err := protocol.EncodeClaim(c)
if err != nil {
t.Fatal(err)
}
const wantLen = 91 + 7 + 5 + 5 + 1 + 1 + 16
if len(b) != wantLen {
t.Fatalf("unexpected claim length %d, want %d; offset assumptions are stale", len(b), wantLen)
}
return b
}
func TestRejectUnsortedAndDuplicateMapKeys(t *testing.T) {
fx := newSignerFixtures(t)
base := twoKeyClaimBytes(t, fx)
t.Run("unsorted map keys", func(t *testing.T) {
bad := append([]byte{}, base...)
copy(bad[91:98], []byte{0x02, 0x01, 'b', 0x01, 0x01, 'a', 0x02})
if _, err := protocol.DecodeClaim(bad); !errors.Is(err, tce.ErrKeyOrder) {
t.Fatalf("err = %v, want ErrKeyOrder", err)
}
})
t.Run("duplicate map key", func(t *testing.T) {
bad := append([]byte{}, base...)
copy(bad[91:98], []byte{0x02, 0x01, 'a', 0x02, 0x01, 'a', 0x01})
if _, err := protocol.DecodeClaim(bad); !errors.Is(err, tce.ErrDuplicateKey) {
t.Fatalf("err = %v, want ErrDuplicateKey", err)
}
})
}
func TestRejectReservedValueTag(t *testing.T) {
// The frozen claim/boolean vector has a single value tag at offset 105.
vf := loadVectors(t)
for _, v := range vf.Vectors {
if v.Name != "claim/boolean" {
continue
}
b := mustHex(t, v.TCEHex)
for _, tag := range []byte{0x05, 0x06, 0x07, 0x08, 0xff} {
bad := append([]byte{}, b...)
bad[105] = tag
if _, err := protocol.DecodeClaim(bad); !errors.Is(err, tce.ErrValueTag) {
t.Fatalf("value tag 0x%02x: err = %v, want ErrValueTag", tag, err)
}
}
return
}
t.Fatal("claim/boolean vector not found")
}