// Command busvectors emits deterministic cross-language test vectors for the bus // protocol and its end-to-end crypto. The browser-native client (uniweb) ports the // protocol to TypeScript; these vectors are the contract that proves the port is // byte-for-byte compatible with this Go reference implementation (issue // uniweb/0001, Phase 0). // // Every input is fixed (hardcoded key material and messages) so the output is // stable across runs and can be committed as a golden file. The crypto primitives // are the SAME registry functions the bus uses (functions/cybersecurity), so the // vectors exercise the real path, not a test-only reimplementation. // // Coverage: // - endpoint_id : EndpointID(signPub) = base64url(sha256(signPub)) // - sign : Ed25519 signature over a fixed message (deterministic) // - aead : ChaCha20-Poly1305 seal with a FIXED nonce (deterministic, so // the TS port must reproduce the same ciphertext AND open it) // - keybox : sealed-box (X25519) of a room key for a recipient; the TS port // must OPEN it (the ephemeral sender key is random, so only the // open direction is a stable vector — the TS->Go seal direction // is covered by the live E2E test in Phase 3) // - frame : canonical JSON wire bytes of a Frame, and its SigningBytes // // Usage: // // go run ./cmd/busvectors > ../uniweb/web/src/bus/testdata/vectors.json package main import ( "crypto/ed25519" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "os" cs "fn-registry/functions/cybersecurity" "github.com/enmanuel/unibus/pkg/busauth" "github.com/enmanuel/unibus/pkg/frame" "github.com/enmanuel/unibus/pkg/membership" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" ) // Fixed key material. The bytes are arbitrary but stable: the point is a golden // file, not secrecy (these are test vectors, never real identities). var ( signSeed = mustHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") kexPriv = mustHex("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f") recipientKexPriv = mustHex("404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f") aeadKey = mustHex("606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f") aeadNonce = mustHex("808182838485868788898a8b") // 12 bytes (ChaCha20-Poly1305 IETF) roomKey = mustHex("a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf") signMessage = []byte("unibus parity vector message") aeadAAD = []byte("unibus-room-42") aeadPlaintext = []byte("hello from the bus") ) // vectors is the JSON document consumed by the TypeScript parity tests. Every field // is hex except the frame wire bytes, which are base64 (the frame is JSON, so the // TS side compares the exact UTF-8 bytes). type vectors struct { Note string `json:"note"` Endpoint endpointVector `json:"endpoint_id"` Nkey nkeyVector `json:"nkey"` Sign signVector `json:"sign"` AEAD aeadVector `json:"aead"` KeyBox keyboxVector `json:"keybox"` Frame frameVector `json:"frame"` CtrlReq controlReqVector `json:"control_request"` } type endpointVector struct { SignPubHex string `json:"sign_pub_hex"` EndpointID string `json:"endpoint_id"` // base64url(sha256(sign_pub)), unpadded } type nkeyVector struct { SignPubHex string `json:"sign_pub_hex"` NkeyPublic string `json:"nkey_public"` // NATS user nkey ("U...") from the Ed25519 pubkey } type controlReqVector struct { Method string `json:"method"` Path string `json:"path"` Ts string `json:"ts"` Nonce string `json:"nonce"` BodyHex string `json:"body_hex"` // raw request body (empty for GET) CanonicalHex string `json:"canonical_hex"` // bytes that get signed SigHex string `json:"sig_hex"` // Ed25519 over canonical, by the signer below SignPrivHex string `json:"sign_priv_hex"` } type signVector struct { SignPrivHex string `json:"sign_priv_hex"` SignPubHex string `json:"sign_pub_hex"` MessageHex string `json:"message_hex"` SigHex string `json:"sig_hex"` } type aeadVector struct { KeyHex string `json:"key_hex"` NonceHex string `json:"nonce_hex"` AADHex string `json:"aad_hex"` PlaintextHex string `json:"plaintext_hex"` CiphertextHex string `json:"ciphertext_hex"` // includes the 16-byte Poly1305 tag } type keyboxVector struct { RecipientKexPubHex string `json:"recipient_kex_pub_hex"` RecipientKexPrivHex string `json:"recipient_kex_priv_hex"` SecretHex string `json:"secret_hex"` SealedHex string `json:"sealed_hex"` } type frameVector struct { // The source fields, so the TS side can build the same Frame and compare. Type int `json:"type"` Subject string `json:"subject"` Sender string `json:"sender"` MsgID string `json:"msg_id"` Epoch int `json:"epoch"` NonceHex string `json:"nonce_hex"` PayloadHex string `json:"payload_hex"` WireB64 string `json:"wire_b64"` // base64(Marshal()) — full frame incl. sig SigningB64 string `json:"signing_bytes_b64"` // base64(SigningBytes()) — what gets signed SigHex string `json:"sig_hex"` // Ed25519 over SigningBytes } func main() { if err := run(os.Stdout); err != nil { fmt.Fprintln(os.Stderr, "busvectors:", err) os.Exit(1) } } func run(out *os.File) error { // Identity from the fixed seed: Go's ed25519 private key layout is seed||pub, the // same 64-byte layout cs.Identity and the TS wallet use. signPriv := ed25519.NewKeyFromSeed(signSeed) signPub := signPriv.Public().(ed25519.PublicKey) // X25519 public keys from the fixed private scalars (curve25519 clamps internally, // matching @noble/curves x25519.getPublicKey). kexPub, err := curve25519.X25519(kexPriv, curve25519.Basepoint) if err != nil { return fmt.Errorf("kex pub: %w", err) } recipientKexPub, err := curve25519.X25519(recipientKexPriv, curve25519.Basepoint) if err != nil { return fmt.Errorf("recipient kex pub: %w", err) } // AEAD with a FIXED nonce so the vector is deterministic. This is the same cipher // (ChaCha20-Poly1305 IETF, 12-byte nonce) that cs.SealAEAD uses; we set the nonce // explicitly only to make the vector reproducible. OpenAEAD verifies round-trip. aead, err := chacha20poly1305.New(aeadKey) if err != nil { return fmt.Errorf("aead cipher: %w", err) } ciphertext := aead.Seal(nil, aeadNonce, aeadPlaintext, aeadAAD) if _, err := cs.OpenAEAD(aeadKey, aeadNonce, ciphertext, aeadAAD); err != nil { return fmt.Errorf("aead self-check: %w", err) } // Sealed box of the room key for the recipient. The sender's ephemeral key is // random (anonymous sealed box), so SealedHex changes per run; the stable, useful // assertion for the TS port is that OpenKeyBox recovers the secret, which we // self-check here. The TS test opens SealedHex and compares to SecretHex. sealed, err := cs.SealKeyBox(recipientKexPub, roomKey) if err != nil { return fmt.Errorf("seal keybox: %w", err) } if got, err := cs.OpenKeyBox(recipientKexPub, recipientKexPriv, sealed); err != nil || hex.EncodeToString(got) != hex.EncodeToString(roomKey) { return fmt.Errorf("keybox self-check failed: %v", err) } // A representative encrypted-room frame, signed end-to-end. f := frame.Frame{ Type: frame.PUB, Subject: "room.parity", Sender: frame.EndpointID(signPub), MsgID: "01HZY0VECTORFIXEDULID0001", Epoch: 1, Nonce: aeadNonce, Payload: ciphertext, } f.Sig = ed25519.Sign(signPriv, f.SigningBytes()) wire, err := f.Marshal() if err != nil { return fmt.Errorf("marshal frame: %w", err) } // NATS user nkey derived from the Ed25519 public key (the browser must produce // the same "U..." string to authenticate on the data plane). nkeyPub, err := busauth.NkeyPublicFromSignPub(signPub) if err != nil { return fmt.Errorf("nkey public: %w", err) } // A signed control-plane request vector: the browser signs CanonicalRequest the // same way to authenticate every HTTP call to membershipd. A POST with a body // exercises the sha256(body) term. const ctrlMethod = "POST" const ctrlPath = "/rooms" const ctrlTs = "1700000000" const ctrlNonce = "Zm9vYmFyMTIzNDU2Nzg5MA==" ctrlBody := []byte(`{"subject":"room.parity"}`) canonical := membership.CanonicalRequest(ctrlMethod, ctrlPath, ctrlTs, ctrlNonce, ctrlBody) ctrlSig := ed25519.Sign(signPriv, canonical) v := vectors{ Note: "Deterministic cross-language vectors for the unibus protocol. Generated by " + "cmd/busvectors in the unibus repo; regenerate with `go run ./cmd/busvectors`. " + "sealed_hex varies per run (anonymous sealed box); assert via OpenKeyBox.", Endpoint: endpointVector{ SignPubHex: hex.EncodeToString(signPub), EndpointID: frame.EndpointID(signPub), }, Nkey: nkeyVector{ SignPubHex: hex.EncodeToString(signPub), NkeyPublic: nkeyPub, }, Sign: signVector{ SignPrivHex: hex.EncodeToString(signPriv), SignPubHex: hex.EncodeToString(signPub), MessageHex: hex.EncodeToString(signMessage), SigHex: hex.EncodeToString(ed25519.Sign(signPriv, signMessage)), }, AEAD: aeadVector{ KeyHex: hex.EncodeToString(aeadKey), NonceHex: hex.EncodeToString(aeadNonce), AADHex: hex.EncodeToString(aeadAAD), PlaintextHex: hex.EncodeToString(aeadPlaintext), CiphertextHex: hex.EncodeToString(ciphertext), }, KeyBox: keyboxVector{ RecipientKexPubHex: hex.EncodeToString(recipientKexPub), RecipientKexPrivHex: hex.EncodeToString(recipientKexPriv), SecretHex: hex.EncodeToString(roomKey), SealedHex: hex.EncodeToString(sealed), }, Frame: frameVector{ Type: int(f.Type), Subject: f.Subject, Sender: f.Sender, MsgID: f.MsgID, Epoch: f.Epoch, NonceHex: hex.EncodeToString(f.Nonce), PayloadHex: hex.EncodeToString(f.Payload), WireB64: base64.StdEncoding.EncodeToString(wire), SigningB64: base64.StdEncoding.EncodeToString(f.SigningBytes()), SigHex: hex.EncodeToString(f.Sig), }, CtrlReq: controlReqVector{ Method: ctrlMethod, Path: ctrlPath, Ts: ctrlTs, Nonce: ctrlNonce, BodyHex: hex.EncodeToString(ctrlBody), CanonicalHex: hex.EncodeToString(canonical), SigHex: hex.EncodeToString(ctrlSig), SignPrivHex: hex.EncodeToString(signPriv), }, // kexPub is unused in a vector field today but derived above to validate the // scalar; reference it so the intent is documented. } _ = kexPub enc := json.NewEncoder(out) enc.SetIndent("", " ") return enc.Encode(v) } func mustHex(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic("busvectors: bad fixed hex: " + s) } return b }