| 1 | { |
| 2 | "format": "contra-golden-vectors", |
| 3 | "version": 1, |
| 4 | "spec": "PROTOCOL.md; wire formats pinned in discussion 19", |
| 5 | "generator": "vectors/gen/gen.c against OpenSSL 3.5.6 7 Apr 2026", |
| 6 | "note": "Every input here is generated: fixture(label) = SHA-256(\"contra/v1/vectors/\" || label). Nothing in this file is a real secret, and nothing in it may be reused as one.", |
| 7 | "binding": "The Go, JS and Swift crypto suites must all pass this file. Changing a value here is changing the protocol.", |
| 8 | "external_anchors": { |
| 9 | "note": "Published third-party vectors. gen.c recomputes each one and aborts on mismatch before generating anything below.", |
| 10 | "sources": ["PHC argon2 reference vectors", "RFC 5869 cases 1 and 3", "RFC 4231 case 1", "NIST CAVP AES-256-GCM"] |
| 11 | }, |
| 12 | "argon2id": { |
| 13 | "params": {"m_kib": 65536, "t": 3, "p": 1, "tag_len": 32}, |
| 14 | "floor": {"m_kib": 65536, "t": 3, "note": "clients refuse anything weaker a server offers"}, |
| 15 | "cases": [ |
| 16 | { |
| 17 | "name": "S_pw", |
| 18 | "passphrase_utf8": "contra-golden-vector-passphrase-do-not-use-8f3a1c", |
| 19 | "passphrase_hex": "636f6e7472612d676f6c64656e2d766563746f722d706173737068726173652d646f2d6e6f742d7573652d386633613163", |
| 20 | "salt_hex": "962c1e2ca2b4c0602e6ec706e76e3eac", |
| 21 | "out_hex": "6bf83f20da63fb1c818ce0f12e93294ba999b4ad156a7e5ae680f9f38fdd493b" |
| 22 | } |
| 23 | ] |
| 24 | }, |
| 25 | "hkdf": { |
| 26 | "construction": "RFC 5869 extract-then-expand, SHA-256; an absent salt is zero-length; info is the label's ASCII bytes with no NUL", |
| 27 | "cases": [ |
| 28 | { |
| 29 | "name": "seed_login", |
| 30 | "info": "contra/v1/login", |
| 31 | "ikm_hex": "6bf83f20da63fb1c818ce0f12e93294ba999b4ad156a7e5ae680f9f38fdd493b", |
| 32 | "salt_hex": "", |
| 33 | "l": 32, |
| 34 | "okm_hex": "4f2b905bcff9d01c15bbb82bb0a9d26a1267f35278928d1bd38e8c838ef68b8a" |
| 35 | }, |
| 36 | { |
| 37 | "name": "KEK_pw", |
| 38 | "info": "contra/v1/kek", |
| 39 | "ikm_hex": "6bf83f20da63fb1c818ce0f12e93294ba999b4ad156a7e5ae680f9f38fdd493b", |
| 40 | "salt_hex": "f60fb281e8c72f6725add4cbd40e7b08", |
| 41 | "l": 32, |
| 42 | "okm_hex": "e70a2f643ee0dc6123d042ca574b44994135d7776016e3d181fc32f93db40d82" |
| 43 | }, |
| 44 | { |
| 45 | "name": "seed_rc_login", |
| 46 | "info": "contra/v1/rc-login", |
| 47 | "ikm_hex": "70da5ffc983510bba4672030946b9eed", |
| 48 | "salt_hex": "", |
| 49 | "l": 32, |
| 50 | "okm_hex": "46b18c0c2a54c5ef6baeeda49ab42fd24ae8401912020cf94c02a51f1436240b" |
| 51 | }, |
| 52 | { |
| 53 | "name": "KEK_rc", |
| 54 | "info": "contra/v1/rc-kek", |
| 55 | "ikm_hex": "70da5ffc983510bba4672030946b9eed", |
| 56 | "salt_hex": "", |
| 57 | "l": 32, |
| 58 | "okm_hex": "58c5e4bd32d767d48a63d3f77ed67bad6c1fb4017d90a2f098cb32e8b87ff27b" |
| 59 | }, |
| 60 | { |
| 61 | "name": "KEK_prf", |
| 62 | "info": "contra/v1/prf-kek", |
| 63 | "ikm_hex": "6f926e3f71aec7803ffeeef8598e91de4195a42067688c8f4e3fd24c00c50c49", |
| 64 | "salt_hex": "ce414df9c26826e10c1c4f359823885eb5eb39f89ead15583cade016d317f276", |
| 65 | "l": 32, |
| 66 | "okm_hex": "25a832e73062849b768e245a28a1139f3a0171d41a05a34ce8d2615e7ecacad9" |
| 67 | } |
| 68 | ] |
| 69 | }, |
| 70 | "kcv": { |
| 71 | "construction": "HMAC-SHA-256(KEK, \"contra/v1/kcv\")[..16]", |
| 72 | "cases": [ |
| 73 | {"name": "KCV_pw", "kek_hex": "e70a2f643ee0dc6123d042ca574b44994135d7776016e3d181fc32f93db40d82", "kcv_hex": "fc6587261be8f190edd03ee21eda38b4"}, |
| 74 | {"name": "KCV_rc", "kek_hex": "58c5e4bd32d767d48a63d3f77ed67bad6c1fb4017d90a2f098cb32e8b87ff27b", "kcv_hex": "a4e31d3daede8f7051c4ff6a9cea876b"}, |
| 75 | {"name": "KCV_prf", "kek_hex": "25a832e73062849b768e245a28a1139f3a0171d41a05a34ce8d2615e7ecacad9", "kcv_hex": "a6a270fb61f6a4dc3c8378d76784c8bc"} |
| 76 | ] |
| 77 | }, |
| 78 | "p256_from_seed": { |
| 79 | "construction": "candidate_i = HKDF-SHA-256(ikm=seed, salt=\"\", info=\"contra/v1/ec-p256\" || byte(i)); accept when 1 <= OS2IP(candidate) <= n-1, else i += 1", |
| 80 | "public_key_encoding": "SEC1 uncompressed, 0x04 || X || Y", |
| 81 | "cases": [ |
| 82 | { |
| 83 | "name": "LK_pw", |
| 84 | "seed_hex": "4f2b905bcff9d01c15bbb82bb0a9d26a1267f35278928d1bd38e8c838ef68b8a", |
| 85 | "candidates_tried": 1, |
| 86 | "candidates": ["ae4fd50980acd2da69b6b3d77be8f4347b36d5429639f42e0753e784718cc05b"], |
| 87 | "d_hex": "ae4fd50980acd2da69b6b3d77be8f4347b36d5429639f42e0753e784718cc05b", |
| 88 | "public_uncompressed_hex": "0465b117b08f92789048944fced0df2343d93b77e8c92448a3f2be170ea91755066a7f789fdf979c17ed782cf9f24cdec565ad8fe752e9f24e974e4c5270cb908f" |
| 89 | }, |
| 90 | { |
| 91 | "name": "LK_rc", |
| 92 | "seed_hex": "46b18c0c2a54c5ef6baeeda49ab42fd24ae8401912020cf94c02a51f1436240b", |
| 93 | "candidates_tried": 1, |
| 94 | "candidates": ["5e849fc53c753f9bd0f76ad1b3d3d997c1b7837a1ee733666a69679f2fca9e01"], |
| 95 | "d_hex": "5e849fc53c753f9bd0f76ad1b3d3d997c1b7837a1ee733666a69679f2fca9e01", |
| 96 | "public_uncompressed_hex": "048b58c6f080a5fe442a996ad8c7104fc2ecc23fac14d5bfb834fbf949640dae7a4ff41f793d4d7ae97103fe8d5d13faf99b46c52f46a36ce5416d9346b8fac90f" |
| 97 | }, |
| 98 | { |
| 99 | "name": "rejects_at_i0", |
| 100 | "seed_hex": "636f6e7472612f76312f766563746f722d73656172636821000000028d6ed394", |
| 101 | "candidates_tried": 2, |
| 102 | "candidates": ["ffffffff664667834724f9a77479bf20190521865ac9486dcc6c225972783d8b", "5868322caaf160cdb5b9289459bd504c3c48f6397bade3c3bf07a7f2deefd8e8"], |
| 103 | "d_hex": "5868322caaf160cdb5b9289459bd504c3c48f6397bade3c3bf07a7f2deefd8e8", |
| 104 | "public_uncompressed_hex": "040fa92cfa882ba8b5d2977fcd7d5805751d54c3530daabce40f3f8ccfdc5715ddb75a2e638f11f685eebff0ac49872d0f8033d5c871202c796ec6400acf1c9afa" |
| 105 | } |
| 106 | ] |
| 107 | }, |
| 108 | "p256_rejecting_seed": { |
| 109 | "note": "A rejection has probability about 2^-32, so this seed was searched for. Without it the rejection branch is dead code in all three languages.", |
| 110 | "seed_recipe": "\"contra/v1/vector-search!\" || u64be(k)", |
| 111 | "k": 10962785172, |
| 112 | "seed_hex": "636f6e7472612f76312f766563746f722d73656172636821000000028d6ed394" |
| 113 | }, |
| 114 | "crockford": { |
| 115 | "alphabet": "0123456789ABCDEFGHJKMNPQRSTVWXYZ", |
| 116 | "packing": "MSB-first over the raw bytes, left-aligned; 16 bytes -> 26 symbols with two zero pad bits. Decoding rejects a non-zero pad, so every value has exactly one encoding.", |
| 117 | "normalize": "strip '-' and whitespace, uppercase, O->0, I->1, L->1; U is not in the alphabet and is an error", |
| 118 | "encode_cases": [ |
| 119 | {"bytes_hex": "00000000000000000000000000000000", "symbols": "00000000000000000000000000"}, |
| 120 | {"bytes_hex": "ffffffffffffffffffffffffffffffff", "symbols": "ZZZZZZZZZZZZZZZZZZZZZZZZZW"}, |
| 121 | {"bytes_hex": "f60fb281e8c72f6725add4cbd40e7b08", "symbols": "YR7V50F8RWQPE9DDTK5X83KV10"}, |
| 122 | {"bytes_hex": "70da5ffc983510bba4672030946b9eed", "symbols": "E3D5ZZ4R6M8BQ93740R98TWYXM"} |
| 123 | ], |
| 124 | "normalize_cases": [ |
| 125 | {"input": "c1-oi1l-0O1I", "normalized": "C101110011"}, |
| 126 | {"input": " A1 oL Li ", "normalized": "A10111"}, |
| 127 | {"input": "c1-abc-def", "normalized": "C1ABCDEF"}, |
| 128 | {"input": "C1-UUUU", "error": "character outside the Crockford alphabet"}, |
| 129 | {"input": "c1?abc", "error": "character outside the Crockford alphabet"} |
| 130 | ], |
| 131 | "decode_cases": [ |
| 132 | {"symbols": "E3D5ZZ4R6M8BQ93740R98TWYXM", "bytes_hex": "70da5ffc983510bba4672030946b9eed"}, |
| 133 | {"symbols": "E3D5ZZ4R6M8BQ93740R98TWYXN", "error": "non-zero pad bits in the final symbol", "rejected": true} |
| 134 | ] |
| 135 | }, |
| 136 | "printed_code": { |
| 137 | "form": "tag(2) || data(26) || checksum(4), printed as TAG-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", |
| 138 | "checksum": "first 20 bits, MSB-first, of SHA-256(payload_bytes) -- over the 16 raw bytes, not the string", |
| 139 | "cases": [ |
| 140 | {"name": "account_key", "tag": "A1", "payload_hex": "f60fb281e8c72f6725add4cbd40e7b08", "checksum20": 543112, "normalized": "A1YR7V50F8RWQPE9DDTK5X83KV10GJC8", "printed": "A1-YR7V5-0F8RW-QPE9D-DTK5X-83KV1-0GJC8"}, |
| 141 | {"name": "recovery_code", "tag": "C1", "payload_hex": "70da5ffc983510bba4672030946b9eed", "checksum20": 573888, "normalized": "C1E3D5ZZ4R6M8BQ93740R98TWYXMHGE0", "printed": "C1-E3D5Z-Z4R6M-8BQ93-740R9-8TWYX-MHGE0"} |
| 142 | ], |
| 143 | "repair": { |
| 144 | "note": "substitute one character, then brute-force all 30 payload positions x 32 symbols against the checksum; return every survivor in position-then-alphabet order for the caller to try against the KCV", |
| 145 | "correct_normalized": "C1E3D5ZZ4R6M8BQ93740R98TWYXMHGE0", |
| 146 | "typo_normalized": "C1E3D5Z84R6M8BQ93740R98TWYXMHGE0", |
| 147 | "typo_position": 7, |
| 148 | "candidates": ["C1E3D5ZZ4R6M8BQ93740R98TWYXMHGE0"], |
| 149 | "candidate_count": 1, |
| 150 | "correct_is_a_candidate": true |
| 151 | } |
| 152 | }, |
| 153 | "aead": { |
| 154 | "algorithm": "AES-256-GCM, 96-bit nonce, 128-bit tag", |
| 155 | "aad": "\"contra/v1/aad\" || 0x00 || u32be(len(scope_id)) || scope_id || u32be(len(object_type)) || object_type || u32be(len(object_id)) || object_id || u64be(version)", |
| 156 | "scope_id": "the account id for account-scoped objects, the vault id for vault-scoped ones", |
| 157 | "account_id_hex": "b22c6a5eae1be9d6a773f474fd0532b0", |
| 158 | "vault_id_hex": "d7331f78b34eb0cc299252e2624070db", |
| 159 | "rk_hex": "e3769479962198ef9dbcf21e4822b0e2d45f9918f1e7bf86aa6208fc3e3ad3c6", |
| 160 | "wraps": [ |
| 161 | { |
| 162 | "name": "W_pw", |
| 163 | "object_type": "wrap.pw", |
| 164 | "kek_hex": "e70a2f643ee0dc6123d042ca574b44994135d7776016e3d181fc32f93db40d82", |
| 165 | "kcv_hex": "fc6587261be8f190edd03ee21eda38b4", |
| 166 | "wrap_id_hex": "cc715de83e6bec8114d5478e1b2e13d6", |
| 167 | "version": 1, |
| 168 | "nonce_hex": "63ca9a59270f1867de916493", |
| 169 | "aad_hex": "636f6e7472612f76312f6161640000000010b22c6a5eae1be9d6a773f474fd0532b000000007777261702e707700000010cc715de83e6bec8114d5478e1b2e13d60000000000000001", |
| 170 | "ciphertext_hex": "54960d310960a2b99c8df592b1017490c7e89507caef806e00e32ea4263aba34", |
| 171 | "tag_hex": "b270200424ec37c3a06158abc13a2c00" |
| 172 | }, |
| 173 | { |
| 174 | "name": "W_rc", |
| 175 | "object_type": "wrap.rc", |
| 176 | "kek_hex": "58c5e4bd32d767d48a63d3f77ed67bad6c1fb4017d90a2f098cb32e8b87ff27b", |
| 177 | "kcv_hex": "a4e31d3daede8f7051c4ff6a9cea876b", |
| 178 | "wrap_id_hex": "7d4f2401a54f0af0f9c2123e89ce940e", |
| 179 | "version": 1, |
| 180 | "nonce_hex": "5758cf37dbe4daad674c8ce7", |
| 181 | "aad_hex": "636f6e7472612f76312f6161640000000010b22c6a5eae1be9d6a773f474fd0532b000000007777261702e7263000000107d4f2401a54f0af0f9c2123e89ce940e0000000000000001", |
| 182 | "ciphertext_hex": "3a0c3081efb8d7c4224e299d7aa4f1ed2e502b10f3b01925de4af09a48c61eed", |
| 183 | "tag_hex": "9bb37b5e3d79fd21d91907ca261aee75" |
| 184 | }, |
| 185 | { |
| 186 | "name": "W_prf", |
| 187 | "object_type": "wrap.prf", |
| 188 | "kek_hex": "25a832e73062849b768e245a28a1139f3a0171d41a05a34ce8d2615e7ecacad9", |
| 189 | "kcv_hex": "a6a270fb61f6a4dc3c8378d76784c8bc", |
| 190 | "wrap_id_hex": "be8b97ce6d27b2fb61229efc81f502b4", |
| 191 | "version": 1, |
| 192 | "nonce_hex": "2ff8fef97b48f6969b78b23f", |
| 193 | "aad_hex": "636f6e7472612f76312f6161640000000010b22c6a5eae1be9d6a773f474fd0532b000000008777261702e70726600000010be8b97ce6d27b2fb61229efc81f502b40000000000000001", |
| 194 | "ciphertext_hex": "0b5f5c20153730681bf09e0740b64b7f49f6d3180f1365dd3bead4fffcbe06ea", |
| 195 | "tag_hex": "0900826fc6463dbdf94fe5cacbf1c585" |
| 196 | } |
| 197 | ], |
| 198 | "item": { |
| 199 | "scope": "vault", |
| 200 | "vault_id_hex": "d7331f78b34eb0cc299252e2624070db", |
| 201 | "object_type": "item", |
| 202 | "item_id_hex": "4580c14ff986936588b14e66e247978d", |
| 203 | "version": 1, |
| 204 | "k_vault_hex": "576894008c1a94f71704db728d37bfa9f0023cca4fd8d2ae48f6af81a4b959a5", |
| 205 | "nonce_hex": "50e4e9c4a5166e560d453550", |
| 206 | "aad_hex": "636f6e7472612f76312f6161640000000010d7331f78b34eb0cc299252e2624070db000000046974656d000000104580c14ff986936588b14e66e247978d0000000000000001", |
| 207 | "plaintext_utf8": "{\"title\":\"Generated vector item\",\"username\":\"vector@example.invalid\",\"secret\":\"not-a-real-value\"}", |
| 208 | "plaintext_hex": "7b227469746c65223a2247656e65726174656420766563746f72206974656d222c22757365726e616d65223a22766563746f72406578616d706c652e696e76616c6964222c22736563726574223a226e6f742d612d7265616c2d76616c7565227d", |
| 209 | "ciphertext_hex": "47296118e291596d6ba9d4c6919abb042b01888060a0d6cf13bf224ccf7cc57678945ec6ae628deaff76f21a79463fcc6b3f31a222902be2a23497c86cad77864e40f5cf00db37bacf4580fe0e6f5a969eb4bd8a84fff06ef87b85ca2d9d86200d", |
| 210 | "tag_hex": "8c033da48e3a51c5a8e7618c95cba27f" |
| 211 | } |
| 212 | }, |
| 213 | "handoff": { |
| 214 | "note": "PROTOCOL.md rung 1: an enrolled device seals the Account Key to a fresh device's ephemeral public key, and the server relays a blob it cannot open", |
| 215 | "shared_secret": "X(d*P), big-endian at the field width; Y discarded, per SEC 1 3.3.1", |
| 216 | "key": "HKDF-SHA-256(ikm=shared_secret, salt=ephemeral_public || recipient_public, info=\"contra/v1/ecies\", L=32)", |
| 217 | "envelope": "ephemeral_public(65) || nonce(12) || ciphertext || tag(16)", |
| 218 | "recipient_seed_hex": "9b99cffdf8c8336ee1052a5d23d8c6a89233b6f4b8dce3abb67467797ff5f47c", |
| 219 | "recipient_scalar_hex": "7066a8a9a92ab829b8410497f8832950dd2c873d27c8e8719fd07b1bdb2c2689", |
| 220 | "recipient_public_hex": "04a3bd4385af0b7002d753e994b46b670b515961d7c937be9bd67bb32bfc195fbd00893a7972b680c821681c479e54bfc36b0bff3555883d36d836914bc942eef3", |
| 221 | "ephemeral_seed_hex": "80d827ebf9b6cbd89ebdc4e883f1f6604e55ee6639ff0523c89cd2ce4cbfd069", |
| 222 | "ephemeral_scalar_hex": "29bb73bd46d37937e6aec53e555e8121248dd20ca2276c7a62a80e1294fe0f12", |
| 223 | "ephemeral_public_hex": "04f32e19a10b4812c4b681abf4e0f695dfcd7c409de8bc3432f2678c6bba111cec0bcdd4499e5b0dbdf422e122ee3aa8de1bbbae9601cb8e724ff29d480c990da4", |
| 224 | "shared_secret_hex": "2c5a4aaf13ca4aceeeea13a4cb51992086b61d2a00fcd3c33e0c01baf69d9725", |
| 225 | "key_hex": "f7e866f04455d3ad3387fa20096c3d4a4762e16893390f7ce6b862c9857a34b9", |
| 226 | "account_id_hex": "b22c6a5eae1be9d6a773f474fd0532b0", |
| 227 | "object_type": "handoff", |
| 228 | "version": 1, |
| 229 | "nonce_hex": "f3d0c570acc6bb78bdb8f27b", |
| 230 | "aad_hex": "636f6e7472612f76312f6161640000000010b22c6a5eae1be9d6a773f474fd0532b00000000768616e646f66660000004104a3bd4385af0b7002d753e994b46b670b515961d7c937be9bd67bb32bfc195fbd00893a7972b680c821681c479e54bfc36b0bff3555883d36d836914bc942eef30000000000000001", |
| 231 | "plaintext_hex": "f60fb281e8c72f6725add4cbd40e7b08", |
| 232 | "ciphertext_hex": "37388e3ef836f43004b123b0322071d9", |
| 233 | "tag_hex": "e3855f79299f4270d9f5fb0572171be8" |
| 234 | }, |
| 235 | "account_chain": { |
| 236 | "note": "the whole of PROTOCOL.md's 'The shape', end to end, so a new implementation can bisect where it diverges", |
| 237 | "passphrase_utf8": "contra-golden-vector-passphrase-do-not-use-8f3a1c", |
| 238 | "salt_pw_hex": "962c1e2ca2b4c0602e6ec706e76e3eac", |
| 239 | "account_key_hex": "f60fb281e8c72f6725add4cbd40e7b08", |
| 240 | "recovery_code_hex": "70da5ffc983510bba4672030946b9eed", |
| 241 | "prf_output_hex": "6f926e3f71aec7803ffeeef8598e91de4195a42067688c8f4e3fd24c00c50c49", |
| 242 | "cred_salt_hex": "ce414df9c26826e10c1c4f359823885eb5eb39f89ead15583cade016d317f276", |
| 243 | "s_pw_hex": "6bf83f20da63fb1c818ce0f12e93294ba999b4ad156a7e5ae680f9f38fdd493b", |
| 244 | "seed_login_hex": "4f2b905bcff9d01c15bbb82bb0a9d26a1267f35278928d1bd38e8c838ef68b8a", |
| 245 | "lk_pw_public_hex": "0465b117b08f92789048944fced0df2343d93b77e8c92448a3f2be170ea91755066a7f789fdf979c17ed782cf9f24cdec565ad8fe752e9f24e974e4c5270cb908f", |
| 246 | "seed_rc_login_hex": "46b18c0c2a54c5ef6baeeda49ab42fd24ae8401912020cf94c02a51f1436240b", |
| 247 | "lk_rc_public_hex": "048b58c6f080a5fe442a996ad8c7104fc2ecc23fac14d5bfb834fbf949640dae7a4ff41f793d4d7ae97103fe8d5d13faf99b46c52f46a36ce5416d9346b8fac90f", |
| 248 | "kek_pw_hex": "e70a2f643ee0dc6123d042ca574b44994135d7776016e3d181fc32f93db40d82", |
| 249 | "kek_rc_hex": "58c5e4bd32d767d48a63d3f77ed67bad6c1fb4017d90a2f098cb32e8b87ff27b", |
| 250 | "kek_prf_hex": "25a832e73062849b768e245a28a1139f3a0171d41a05a34ce8d2615e7ecacad9", |
| 251 | "rk_hex": "e3769479962198ef9dbcf21e4822b0e2d45f9918f1e7bf86aa6208fc3e3ad3c6" |
| 252 | } |
| 253 | } |
| 254 | |