- 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
110 lines
3.3 KiB
Go
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")
|
|
}
|