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

89 lines
2.7 KiB
Go

package protocol_test
import (
"bytes"
"fmt"
"git.n1ko.dev/Niko/niko_trust/pkg/identity/signer"
"git.n1ko.dev/Niko/niko_trust/pkg/protocol"
"git.n1ko.dev/Niko/niko_trust/pkg/tce"
)
// ExampleClaimLifecycle is the whole client-side story for one claim, with no
// server in the loop: generate keys, build the canonical bytes, sign them, and
// let any verifier decode and verify locally. The signature is computed over
// the exact TCE bytes, so it stays valid no matter which relay later serves
// them (PROTOCOL.md section 7.3).
func Example_claimLifecycle() {
// In production use signer.Generate(); fixed seeds keep this example
// deterministic so its output can be asserted.
issuer, _ := signer.FromSeed(bytes.Repeat([]byte{0x01}, 32))
subject, _ := signer.FromSeed(bytes.Repeat([]byte{0x02}, 32))
c := &protocol.Claim{
Issuer: issuer.Public(),
Subject: subject.Public(),
Claims: map[string]tce.Value{"example.flag": tce.Bool(true)},
CreatedAt: 1_700_000_000,
ExpiresAt: 1_700_086_400,
Serial: 1,
Nonce: bytes.Repeat([]byte{0x10}, tce.NonceSize),
}
// 1. Client builds and signs the canonical bytes.
tceBytes, err := protocol.EncodeClaim(c)
if err != nil {
fmt.Println("encode:", err)
return
}
sig := issuer.Sign(tceBytes)
// 2. Any verifier, anywhere, with only the bytes + signature.
got, err := protocol.DecodeClaim(tceBytes)
if err != nil {
fmt.Println("decode:", err)
return
}
if _, err := protocol.VerifyClaim(tceBytes, sig); err != nil {
fmt.Println("verify:", err)
return
}
v, ok := got.Claims["example.flag"].Bool()
fmt.Println("verified", got.CreatedAt == c.CreatedAt, ok && v)
// Output: verified true true
}
// ExampleAuthHandshake is the login flow (PROTOCOL.md section 8.6). The server
// mints a single-use challenge; the client proves possession of its key by
// signing an AuthAssertion bound to that challenge and to the server's
// audience. The server verifies the assertion but never sees a private key.
func Example_authHandshake() {
client, _ := signer.FromSeed(bytes.Repeat([]byte{0x03}, 32))
// Server side: issue a 32-byte challenge (CSPRNG in reality).
challenge := bytes.Repeat([]byte{0x07}, tce.ChallengeSize)
audience := "trust.n1ko.dev"
// Client side: build, sign, return.
a := &protocol.AuthAssertion{
PubKey: client.Public(),
Challenge: challenge,
Scope: "read:claims",
Audience: audience,
CreatedAt: 1_700_000_000,
}
b, err := protocol.EncodeAuthAssertion(a)
if err != nil {
fmt.Println("encode:", err)
return
}
sig := client.Sign(b)
// Server side: verify the assertion for this exact audience.
if _, err := protocol.VerifyAuthAssertion(b, sig, audience); err != nil {
fmt.Println("auth failed:", err)
return
}
fmt.Println("auth ok")
// Output: auth ok
}