81 lines
3.6 KiB
JSON
81 lines
3.6 KiB
JSON
{
|
|
"_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."
|
|
]
|
|
}
|