Files
heicode-mananger/heicode/testdata/device_signature_vectors.json
T

98 lines
5.1 KiB
JSON

{
"_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" }
}
]
}