{ "_comment": "Cross-language test vectors for device-signature canonical-string + Ed25519 signing. Both Manager (Go) and client (Rust+TS) tests load this file, compute the canonical string from the raw inputs in their own implementation, and assert the computed string is byte-identical to expected_canonical. The shared test key lets each side independently produce + verify a signature; because Ed25519 sign is deterministic, both implementations must produce the SAME signature for the same digest. The test seed below is RFC 8032 test vector 1's secret — published, not real. Never use these keys for anything beyond unit tests.", "test_keypair": { "_comment": "32-byte Ed25519 seed, hex-encoded. Public key is derived deterministically. RFC 8032 test vector 1.", "seed_hex": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", "public_key_b64": "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=", "public_key_hex": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" }, "canonical_format_spec": { "_comment": "EXACT byte layout that the canonical string must match. Any drift between Go and Rust/TS implementations here breaks signature verification end-to-end. Document mirror lives at: cc-haha/src/services/device/signRequest.ts (top-of-file comment) and heicode/middleware/device_signature.go (HeaderDeviceID block).", "fields_in_order": [ "method (uppercase ASCII, e.g. POST)", "path_with_query (RequestURI form: /v1/messages?stream=true)", "timestamp_ms (decimal integer, no thousands separator, no sign)", "nonce_hex (lowercase hex, 32 chars for 16 bytes)", "device_fingerprint (lowercase hex, 64 chars for sha256)", "sha256_hex(body_bytes) (lowercase hex, 64 chars)" ], "separator": "\\n (single line-feed, 0x0A, between each field; NOT included after the last field)", "digest_to_sign": "sha256(canonical_string)" }, "cases": [ { "name": "GET_empty_body", "input": { "method": "GET", "path_with_query": "/v1/models", "timestamp_ms": "1747680000000", "nonce_hex": "0123456789abcdef0123456789abcdef", "device_fingerprint": "a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789a", "body_text": "" }, "expected_body_sha256_hex": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "expected_canonical": "GET\n/v1/models\n1747680000000\n0123456789abcdef0123456789abcdef\na1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789a\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, { "name": "POST_small_json_body", "input": { "method": "POST", "path_with_query": "/v1/messages", "timestamp_ms": "1747680001234", "nonce_hex": "ffffffffffffffffffffffffffffffff", "device_fingerprint": "0000000000000000000000000000000000000000000000000000000000000000", "body_text": "{\"model\":\"claude-sonnet-4-6\",\"max_tokens\":1024}" }, "expected_body_sha256_hex": "a86e9f8fe1ec25a48b78f4d1d3df88b3dee3c5816fbc8ce26d8b2bc44b46d4b8", "expected_canonical_starts_with": "POST\n/v1/messages\n1747680001234\nffffffffffffffffffffffffffffffff\n0000000000000000000000000000000000000000000000000000000000000000\n", "_note_about_body_hash": "expected_body_sha256_hex above is illustrative; tests MUST recompute SHA256 of body_text bytes (UTF-8) and compare directly rather than relying on the precomputed value." }, { "name": "POST_streaming_request", "input": { "method": "POST", "path_with_query": "/v1/messages?stream=true", "timestamp_ms": "1747680002000", "nonce_hex": "abababababababababababababababab", "device_fingerprint": "11111111111111111111111111111111deadbeefdeadbeefdeadbeefdeadbeef", "body_text": "{\"model\":\"claude-sonnet-4-6\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"max_tokens\":256,\"stream\":true}" }, "expected_canonical_starts_with": "POST\n/v1/messages?stream=true\n1747680002000\n" }, { "name": "GET_query_with_special_chars", "input": { "method": "GET", "path_with_query": "/v1/dashboard/billing/usage?date=2026-05-20&filter=cost+desc", "timestamp_ms": "1747680003000", "nonce_hex": "deadbeefcafebabe0001020304050607", "device_fingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "body_text": "" }, "expected_canonical_starts_with": "GET\n/v1/dashboard/billing/usage?date=2026-05-20&filter=cost+desc\n" } ], "negative_cases": [ { "name": "tampered_timestamp", "_comment": "Sign canonical_1, then alter timestamp on the wire to canonical_2. Verify with pubkey + canonical_2 → must fail.", "fixture": "GET_empty_body", "tamper": { "timestamp_ms": "1747680000001" } }, { "name": "tampered_body", "fixture": "POST_small_json_body", "tamper": { "body_text": "{\"model\":\"claude-opus-4-7\",\"max_tokens\":1024}" } }, { "name": "wrong_method", "fixture": "POST_small_json_body", "tamper": { "method": "PUT" } } ] }