niko_trust/internal/transport/view.go
Niko Marmeladkov 20cc52c3a5 feat: network layer — PoW, checkpoint chain, gossip, light node, WS, delegation, rotation, BFT
- BLAKE3 keyed proof-of-work on object storage and auth challenges,
  with frozen vectors cross-checked against an independent Python
  reference implementing the single-block hash it needs.
- Sparse Merkle trie over object IDs: order-independent roots,
  inclusion and absence proofs (internal/smt).
- Signed checkpoint chain per relay: transport key amendment to INV-1,
  /v1/checkpoint/* and inclusion/absence proof endpoints, restart-safe
  epoch continuity (internal/checkpoint).
- Head gossip with TOFU pinning and equivocation detection; light node
  (cmd/lightnode) that stores no history: quorum of pinned relays,
  every served object proven against the agreed root, LRU disk cache.
- WebSocket streaming on relay and light node (coder/websocket):
  scoped channels mirroring REST, raw envelopes verified client-side;
  light node marks streamed objects unproven until checkpoint coverage.
- Protocol v1 additions: DelegationClaim tag 0x07 with deterministic
  chain resolution in verify.Graph, KeyRotationRequest/Confirm tags
  0x08/0x09 with hash-bound two-sided consent and Policy.RotationMaxAge;
  spec sections, frozen vectors appended byte-identically, Python
  reference extended.
- Optional permissioned BFT finality over gossip (internal/bft):
  prevote/precommit with quorum certificates verifiable offline.
- Quick wins: Policy.TrustedIssuers, per-type stored metrics,
  batch fetch, lexicographic lists with stable cursor pagination.
- Security review of the network layer (docs/SECURITY-REVIEW.md) with
  findings F-01..F-09; hub send/close race and unstable pagination
  fixed under review.

12 packages green, vet/gofmt clean, protocol fuzzing stable.
2026-08-25 20:38:40 +03:00

232 lines
6 KiB
Go

package transport
import (
"encoding/hex"
"encoding/json"
"git.n1ko.dev/Niko/niko_trust/internal/identity"
"git.n1ko.dev/Niko/niko_trust/internal/protocol"
"git.n1ko.dev/Niko/niko_trust/internal/tce"
)
func hexStr(b []byte) string { return hex.EncodeToString(b) }
// AddrOf renders a public key as its trust address text.
func AddrOf(pub []byte) string {
id, err := identity.FromPubKey(pub)
if err != nil {
return ""
}
return id.Address().String()
}
func addrOf(pub []byte) string { return AddrOf(pub) }
// valueView renders a TCE value for the JSON `object` convenience view.
func valueView(v tce.Value) any {
switch v.Tag() {
case tce.ValNull:
return nil
case tce.ValTrue:
return true
case tce.ValFalse:
return false
case tce.ValString:
s, _ := v.Str()
return s
case tce.ValNumber:
tok, _ := v.NumberToken()
return json.Number(tok)
}
return nil
}
func objectTypeOf(obj any) string {
switch obj.(type) {
case *protocol.Identity:
return "identity"
case *protocol.Claim:
return "claim"
case *protocol.Revocation:
return "revocation"
case *protocol.ApprovalRequest:
return "request"
case *protocol.ApprovalResponse:
return "response"
case *protocol.AuthAssertion:
return "auth"
case *protocol.DelegationClaim:
return "delegation"
case *protocol.KeyRotationRequest:
return "key_rotation_request"
case *protocol.KeyRotationConfirm:
return "key_rotation_confirm"
}
return ""
}
// ObjectTypeName returns the type name of a decoded protocol object.
func ObjectTypeName(obj any) string { return objectTypeOf(obj) }
// DecodeObject strict-decodes TCE bytes into the typed protocol object and
// returns its type name.
func DecodeObject(b []byte) (string, any, error) {
d := tce.NewDecoder(b)
tag, err := d.Header()
if err != nil {
return "", nil, err
}
switch tag {
case tce.TagIdentity:
o, e := protocol.DecodeIdentity(b)
return "identity", o, e
case tce.TagClaim:
o, e := protocol.DecodeClaim(b)
return "claim", o, e
case tce.TagRevocation:
o, e := protocol.DecodeRevocation(b)
return "revocation", o, e
case tce.TagApprovalRequest:
o, e := protocol.DecodeApprovalRequest(b)
return "request", o, e
case tce.TagApprovalResponse:
o, e := protocol.DecodeApprovalResponse(b)
return "response", o, e
case tce.TagAuthAssertion:
o, e := protocol.DecodeAuthAssertion(b)
return "auth", o, e
case tce.TagDelegation:
o, e := protocol.DecodeDelegationClaim(b)
return "delegation", o, e
case tce.TagKeyRotation:
o, e := protocol.DecodeKeyRotationRequest(b)
return "key_rotation_request", o, e
case tce.TagKeyRotationConf:
o, e := protocol.DecodeKeyRotationConfirm(b)
return "key_rotation_confirm", o, e
default:
return "", nil, tce.ErrObjectTag
}
}
// BuildView decodes the TCE bytes and produces the `object` convenience view of
// PROTOCOL.md section 10. It fails if the bytes do not strict-decode.
func BuildView(b []byte) (objectType string, view json.RawMessage, err error) {
typ, obj, decErr := DecodeObject(b)
if decErr != nil {
return "", nil, decErr
}
var v any
switch o := obj.(type) {
case *protocol.Identity:
v = map[string]any{
"type": "identity",
"version": 1,
"identity": addrOf(o.PubKey),
"alias": o.Alias,
"created_at": o.CreatedAt,
}
case *protocol.Claim:
claims := make(map[string]any, len(o.Claims))
for k, val := range o.Claims {
claims[k] = valueView(val)
}
v = map[string]any{
"type": "claim",
"version": 1,
"issuer": addrOf(o.Issuer),
"subject": addrOf(o.Subject),
"claims": claims,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"serial": o.Serial,
"nonce": hexStr(o.Nonce),
}
case *protocol.Revocation:
v = map[string]any{
"type": "revocation",
"version": 1,
"issuer": addrOf(o.Issuer),
"claim_id": o.ClaimID.String(),
"reason": o.Reason,
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalRequest:
payload := make(map[string]any, len(o.Payload))
for k, val := range o.Payload {
payload[k] = valueView(val)
}
v = map[string]any{
"type": "request",
"version": 1,
"sender": addrOf(o.Sender),
"recipient": addrOf(o.Recipient),
"action": o.Action,
"payload": payload,
"message": o.Message,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalResponse:
v = map[string]any{
"type": "response",
"version": 1,
"request_hash": o.RequestHash.String(),
"responder": addrOf(o.Responder),
"decision": o.Decision.String(),
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.AuthAssertion:
v = map[string]any{
"type": "auth",
"version": 1,
"identity": addrOf(o.PubKey),
"challenge": hexStr(o.Challenge),
"scope": o.Scope,
"audience": o.Audience,
"created_at": o.CreatedAt,
}
case *protocol.DelegationClaim:
predicates := make(map[string]any, len(o.Predicates))
for k, val := range o.Predicates {
predicates[k] = valueView(val)
}
v = map[string]any{
"type": "delegation",
"version": 1,
"granter": addrOf(o.Granter),
"grantee": addrOf(o.Grantee),
"predicates": predicates,
"max_depth": o.MaxDepth,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"serial": o.Serial,
"nonce": hexStr(o.Nonce),
}
case *protocol.KeyRotationRequest:
v = map[string]any{
"type": "key_rotation_request",
"version": 1,
"successor": addrOf(o.Successor),
"predecessor": addrOf(o.Predecessor),
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
}
case *protocol.KeyRotationConfirm:
v = map[string]any{
"type": "key_rotation_confirm",
"version": 1,
"rotation_hash": o.RotationHash.String(),
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
}
raw, err := json.Marshal(v)
if err != nil {
return "", nil, err
}
return typ, raw, nil
}