Skip to content

Conformance Test Vectors

s402 ships 161 machine-readable JSON test vectors that define correct protocol behavior. Both the TypeScript and Python implementations pass all 161 vectors — producing byte-identical wire output. Use these vectors to verify any new implementation (Go, Rust, Java) without reading a single line of TypeScript or Python.

Why This Matters

Most protocols define behavior in prose. Prose is ambiguous. s402 defines behavior in executable JSON vectors that any language can load and run. If your implementation passes all 161 vectors, it's s402-compliant.

The vectors live in spec/vectors/ at the root of the monorepo. Both the TypeScript and Python test suites read from this single directory — one source of truth.

The vectors cover:

  • Encode/decode — requirements, payloads, settle responses (header + body transport)
  • Key stripping — unknown fields MUST be stripped at decode (defense-in-depth)
  • Validation rejection — 39 malformed inputs that MUST be rejected
  • Roundtrip identity — encode → decode → re-encode = identical output
  • x402 compat — normalizing x402 V1/V2 input to s402 format
  • Receipt format/parse — the X-S402-Receipt wire format

Getting the Vectors

bash
npm pack s402
tar xzf s402-*.tgz
ls package/test/conformance/vectors/

The vectors ship in every npm install s402 — no separate download needed.

From source

bash
git clone https://github.com/s402-protocol/core.git
ls core/test/conformance/vectors/

Vector Format

Each JSON file is an array of test cases:

json
[
  {
    "description": "Human-readable test name",
    "input": { ... },
    "expected": { ... },
    "shouldReject": false
  }
]

Accept vectors (shouldReject: false): Your implementation MUST produce expected when given input.

Reject vectors (shouldReject: true): Your implementation MUST reject the input. The expectedErrorCode field tells you which error to expect.

Vector Files

FileCountWhat it tests
requirements-encode.json19Object → base64 header
requirements-decode.json21Base64 header → object (including key stripping)
payload-encode.json10Payment payload → base64 header
payload-decode.json9Base64 header → payment payload
settle-encode.json7Settle response → base64 header
settle-decode.json7Base64 header → settle response
body-transport.json6JSON body transport (no base64)
compat-normalize.json10x402 V1/V2 → s402 normalization
receipt-format.json6Receipt fields → header string
receipt-parse.json4Header string → receipt fields
settlement-verification.json7Settlement digest-binding verification
validation-reject.json46Malformed inputs that must fail
roundtrip.json9encode → decode → re-encode identity

Quick Start (Go example)

go
package s402test

import (
    "encoding/json"
    "os"
    "testing"
)

type Vector struct {
    Description     string         `json:"description"`
    Input           map[string]any `json:"input"`
    Expected        map[string]any `json:"expected"`
    ShouldReject    bool           `json:"shouldReject"`
    ExpectedErrCode string         `json:"expectedErrorCode"`
}

func TestRequirementsEncode(t *testing.T) {
    data, _ := os.ReadFile("vectors/requirements-encode.json")
    var vectors []Vector
    json.Unmarshal(data, &vectors)

    for _, v := range vectors {
        t.Run(v.Description, func(t *testing.T) {
            result := EncodePaymentRequired(v.Input) // your implementation
            if result != v.Expected["header"] {
                t.Errorf("got %s, want %s", result, v.Expected["header"])
            }
        })
    }
}

Quick Start (Python example)

python
import json, pytest

def load_vectors(name):
    with open(f"vectors/{name}.json") as f:
        return json.load(f)

@pytest.mark.parametrize("v", load_vectors("requirements-encode"))
def test_requirements_encode(v):
    if v["shouldReject"]:
        with pytest.raises(S402Error):
            encode_payment_required(v["input"])
    else:
        result = encode_payment_required(v["input"])
        assert result == v["expected"]["header"]

Key Implementation Notes

Encoding scheme

s402 uses Unicode-safe base64:

  1. JSON.stringify(obj) — serialize to JSON string
  2. UTF-8 encode the string
  3. Base64 encode the bytes

For ASCII-only content (the common case), this matches plain btoa(json).

Key stripping on decode

All decode functions MUST strip unknown keys. Only known fields survive. This is a security measure — untrusted HTTP input cannot inject arbitrary fields into your application.

The full list of known keys per decode path is documented in test/conformance/README.md.

JSON key ordering

Vectors use JavaScript's JSON.stringify key ordering (insertion order). Your serializer must produce keys in the same order to get identical base64 output. See the conformance README for language-specific guidance.

Regenerating Vectors

If you modify s402's encoding logic:

bash
npx tsx test/conformance/generate-vectors.ts
pnpm test  # verify conformance tests still pass

Vectors are generated from source — never hand-written. The generator runs the actual encode/decode functions and captures their output.

Released under the Apache 2.0 License.