Closer/docs/crypto/sealed-answer-test-vectors....

81 lines
3.6 KiB
JSON
Raw Normal View History

{
"_comment": "Cross-platform test vectors for the sealed-answer crypto protocol.",
"_note": "Values are stable inputs and commitments. The ciphertext fields are illustrative — run the regeneration script to produce real ciphertexts for CI.",
"_regenerate": "cd app && ./gradlew :app:testDebugUnitTest --tests 'app.closer.crypto.SealedAnswerTestVectorGenerator'",
"protocol_version": "v1",
"commitment": {
"_spec": "SHA-256(\"v1|{coupleId}|{questionId}|{userId}|{canonicalJson}\"), urlsafe-base64-no-padding",
"_canonical_json_key_order": ["scaleValue", "selectedOptionIds", "writtenText"],
"_selectedOptionIds_order": "lexicographic ascending",
"vectors": [
{
"id": "commit-001",
"input": {
"coupleId": "couple-abc",
"questionId": "q-001",
"userId": "user-alice",
"writtenText": "I love rainy evenings at home.",
"selectedOptionIds": ["opt-b", "opt-a"],
"scaleValue": 7
},
"canonical_json": "{\"scaleValue\":7,\"selectedOptionIds\":[\"opt-a\",\"opt-b\"],\"writtenText\":\"I love rainy evenings at home.\"}",
"commitment_hash": "sha256:REGENERATE_WITH_SCRIPT"
},
{
"id": "commit-002-nulls",
"input": {
"coupleId": "couple-abc",
"questionId": "q-002",
"userId": "user-bob",
"writtenText": null,
"selectedOptionIds": [],
"scaleValue": null
},
"canonical_json": "{\"scaleValue\":null,\"selectedOptionIds\":[],\"writtenText\":null}",
"commitment_hash": "sha256:REGENERATE_WITH_SCRIPT"
},
{
"id": "commit-003-special-chars",
"input": {
"coupleId": "couple-xyz",
"questionId": "q-003",
"userId": "user-alice",
"writtenText": "she said \"hello\"\nand left.\ttab here",
"selectedOptionIds": [],
"scaleValue": null
},
"canonical_json": "{\"scaleValue\":null,\"selectedOptionIds\":[],\"writtenText\":\"she said \\\"hello\\\"\\nand left.\\ttab here\"}",
"commitment_hash": "sha256:REGENERATE_WITH_SCRIPT"
}
]
},
"sealed_payload": {
"_spec": "AES-256-GCM via Tink AEAD. AAD = UTF-8('{coupleId}|{questionId}|{userId}'). Wire format: 'sealed:v1:{urlsafe-base64-no-padding}'.",
"_note": "Ciphertext is non-deterministic (random nonce per encrypt call). Test by decrypt-round-trip, not by fixed ciphertext.",
"aad_format": "{coupleId}|{questionId}|{userId}",
"payload_format": "{\"scaleValue\":{int_or_null},\"selectedOptionIds\":[...sorted...],\"writtenText\":{string_or_null}}"
},
"release_key": {
"_spec": "ECIES-P256-HKDF-HMAC-SHA256-AES128-GCM via Tink HybridEncrypt. Context info = UTF-8('{coupleId}|{questionId}|{senderUserId}|{recipientUserId}'). Wire format: 'keybox:v1:{urlsafe-base64-no-padding}'.",
"context_info_format": "{coupleId}|{questionId}|{senderUserId}|{recipientUserId}"
},
"public_key": {
"_spec": "Tink ECIES public keyset serialised as JSON, then urlsafe-base64-no-padding. Wire format: 'pub:v1:{base64}'.",
"algorithm": "ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM"
},
"ios_compat_notes": [
"Use urlsafe Base64 (no padding) for all wire formats.",
"Canonical JSON key order is fixed: scaleValue, selectedOptionIds, writtenText.",
"selectedOptionIds must be sorted lexicographically before hashing and before encryption.",
"String encoding is always UTF-8.",
"SHA-256 input is UTF-8 bytes of the full prefix+canonical-json string.",
"ECIES ciphertext prefix format is Tink's standard; iOS must use Tink or a compatible library."
]
}