diff --git a/Cargo.toml b/Cargo.toml index 71e82b8..9b8706b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ thiserror = "1.0" [dev-dependencies] hex = "0.4" secp256k1 = { version = "0.28", features = [ "global-context" ] } +serde_json = "*" diff --git a/src/nip44.vectors.json b/src/nip44.vectors.json new file mode 100644 index 0000000..9ff2184 --- /dev/null +++ b/src/nip44.vectors.json @@ -0,0 +1,250 @@ +{ + "v2": { + "valid": { + "get_conversation_key": [ + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", + "pub2": "0000000000000000000000000000000000000000000000000000000000000002", + "conversation_key": "8b6392dbf2ec6a2b2d5b1477fc2be84d63ef254b667cadd31bd3f444c44ae6ba", + "note": "sec1 = n-2, pub2: random, 0x02" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeb", + "conversation_key": "be234f46f60a250bef52a5ee34c758800c4ca8e5030bf4cc1a31d37ba2104d43", + "note": "sec1 = 2, pub2: rand" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000001", + "pub2": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "conversation_key": "3b4610cb7189beb9cc29eb3716ecc6102f1247e8f3101a03a1787d8908aeb54e", + "note": "sec1 == pub2" + } + ], + "calc_padded_len": [ + [16, 32], + [32, 32], + [33, 64], + [37, 64], + [45, 64], + [49, 64], + [64, 64], + [65, 96], + [100, 128], + [111, 128], + [200, 224], + [250, 256], + [320, 320], + [383, 384], + [384, 384], + [400, 448], + [500, 512], + [512, 512], + [515, 640], + [700, 768], + [800, 896], + [900, 1024], + [1020, 1024], + [65536, 65536] + ], + "encrypt_decrypt": [ + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000001", + "sec2": "0000000000000000000000000000000000000000000000000000000000000002", + "conversation_key": "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d", + "nonce": "0000000000000000000000000000000000000000000000000000000000000001", + "plaintext": "a", + "ciphertext": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "sec2": "0000000000000000000000000000000000000000000000000000000000000001", + "conversation_key": "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d", + "nonce": "f00000000000000000000000000000f00000000000000000000000000000000f", + "plaintext": "🍕🫃", + "ciphertext": "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj" + }, + { + "sec1": "5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", + "sec2": "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", + "conversation_key": "3e2b52a63be47d34fe0a80e34e73d436d6963bc8f39827f327057a9986c20a45", + "nonce": "b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b", + "plaintext": "表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀", + "ciphertext": "ArY1I2xC2yDwIbuNHN/1ynXdGgzHLqdCrXUPMwELJPc7s7JqlCMJBAIIjfkpHReBPXeoMCyuClwgbT419jUWU1PwaNl4FEQYKCDKVJz+97Mp3K+Q2YGa77B6gpxB/lr1QgoqpDf7wDVrDmOqGoiPjWDqy8KzLueKDcm9BVP8xeTJIxs=" + }, + { + "sec1": "8f40e50a84a7462e2b8d24c28898ef1f23359fff50d8c509e6fb7ce06e142f9c", + "sec2": "b9b0a1e9cc20100c5faa3bbe2777303d25950616c4c6a3fa2e3e046f936ec2ba", + "conversation_key": "d5a2f879123145a4b291d767428870f5a8d9e5007193321795b40183d4ab8c2b", + "nonce": "b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf", + "plaintext": "ability🤝的 ȺȾ", + "ciphertext": "ArIJia3D3cQc0sQ1lSwNWakTFdjFIY1QQFc/w3SVQ6yvbG2S0x4Yu86QGwPTy7mP3961I1XqB6SFFTzqDZZavhxoWMj7mEVGMQIsh2RLWI5EYQaQDIePSnXPlzf7CIt+voTD" + }, + { + "sec1": "875adb475056aec0b4809bd2db9aa00cff53a649e7b59d8edcbf4e6330b0995c", + "sec2": "9c05781112d5b0a2a7148a222e50e0bd891d6b60c5483f03456e982185944aae", + "conversation_key": "3b15c977e20bfe4b8482991274635edd94f366595b1a3d2993515705ca3cedb8", + "nonce": "8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1", + "plaintext": "pepper👀їжак", + "ciphertext": "Ao1EQnE+udR5EXXLBA2Y1vxb6IZNbsL4nPCJWisrctGxY3AduCS+jTUgAAnfvKafkmpy15+i9YMwCdccisRa8SvzW671T2JO4LFSPX31K4kYUKelSAdSPwe9NwO6LhOsnoJ+" + }, + { + "sec1": "eba1687cab6a3101bfc68fd70f214aa4cc059e9ec1b79fdb9ad0a0a4e259829f", + "sec2": "dff20d262bef9dfd94666548f556393085e6ea421c8af86e9d333fa8747e94b3", + "conversation_key": "4f1538411098cf11c8af216836444787c462d47f97287f46cf7edb2c4915b8a5", + "nonce": "2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407", + "plaintext": "( ͡° ͜ʖ ͡°)", + "ciphertext": "AiGAtSrmRfz59QgNgbHwtdbyzXf/PJhogrtUkVhGLzQHv4qhKQwnFQ54OjVMgqCea/Vj0YqBSdhqNR777TJ4zIUk7R0fnizp6l1zwgzWv7+ee6u+0/89KIjY5q1wu6inyuiv" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68", + "plaintext": "مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،", + "ciphertext": "AuTNX3zk7qAkvHGxetRWqYanSsQmwsYrChXrXFyPiItoIBsWu1CB+sStla2M4VeANASHxM78i1CfHQQH1YbBy24Tng7emYW44ol6QkFD6D8Zq7QPl+8L1c47lx8RoODEQMvNCbOk5ffUV3/AhONHBXnffrI+0025c+uRGzfqpYki4lBqm9iYU+k3Tvjczq9wU0mkVDEaM34WiQi30MfkJdRbeeYaq6kNvGPunLb3xdjjs5DL720d61Flc5ZfoZm+CBhADy9D9XiVZYLKAlkijALJur9dATYKci6OBOoc2SJS2Clai5hOVzR0yVeyHRgRfH9aLSlWW5dXcUxTo7qqRjNf8W5+J4jF4gNQp5f5d0YA4vPAzjBwSP/5bGzNDslKfcAH" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023", + "plaintext": "الكل في المجمو عة (5)", + "ciphertext": "AjjRygq++eX1ZOiXYahs7gRXS2gl0+8gY7EK11iZ5LAjbOTrlfrxak5Lki42v2jMPpLSicy8eHjsWkkMtF0i925vOaKG/ZkMHh9ccQBdfTvgEGKzztedqDCAWb5TP1YwU1PsWaiiqG3+WgVvJiO4lUdMHXL7+zKKx8bgDtowzz4QAwI=" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0", + "plaintext": "𝖑𝖆𝖟𝖞 社會科學院語學研究所", + "ciphertext": "Ak8aMZCfNIOp5pyFSaVbvJryX6W77Pe9MtmJb4PvLhLgh/TsxPLFSANcT67EC1t/qxjru5ZoADjKVEt2ejdx+xGvH49mcdfbc+l+L7gJtkH7GLKpE9pQNQWNHMAmj043PAXJZ++fiJObMRR2mye5VHEANzZWkZXMrXF7YjuG10S1pOU=" + }, + { + "sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", + "sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", + "conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd", + "nonce": "a3e219242d85465e70adcd640b564b3feff57d2ef8745d5e7a0663b2dccceb54", + "plaintext": "🙈 🙉 🙊 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗", + "ciphertext": "AqPiGSQthUZecK3NZAtWSz/v9X0u+HRdXnoGY7LczOtUf05aMF89q1FLwJvaFJYICZoMYgRJHFLwPiOHce7fuAc40kX0wXJvipyBJ9HzCOj7CgtnC1/cmPCHR3s5AIORmroBWglm1LiFMohv1FSPEbaBD51VXxJa4JyWpYhreSOEjn1wd0lMKC9b+osV2N2tpbs+rbpQem2tRen3sWflmCqjkG5VOVwRErCuXuPb5+hYwd8BoZbfCrsiAVLd7YT44dRtKNBx6rkabWfddKSLtreHLDysOhQUVOp/XkE7OzSkWl6sky0Hva6qJJ/V726hMlomvcLHjE41iKmW2CpcZfOedg==" + } + ], + "encrypt_decrypt_long_msg": [ + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", + "pub2": "0000000000000000000000000000000000000000000000000000000000000002", + "conversation_key": "7a1ccf5ce5a08e380f590de0c02776623b85a61ae67cfb6a017317e505b7cb51", + "nonce": "a000000000000000000000000000000000000000000000000000000000000001", + "letter": "ф", + "repeat": 65535, + "ciphertext_checksum": "", + "note": "фффф... (65535 times)" + } + ] + }, + "invalid": { + "encrypt_msg_lengths": [0, 65536, 100000, 10000000], + "decrypt_msg_lengths": [0, 1, 2, 5, 10, 20, 32, 48, 64], + "get_conversation_key": [ + { + "sec1": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 higher than curve.n" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000000", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 is 0" + }, + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", + "pub2": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "note": "pub2 is invalid, no sqrt, all-ff" + }, + { + "sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "sec1 == curve.n" + }, + { + "sec1": "0000000000000000000000000000000000000000000000000000000000000002", + "pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "note": "pub2 is invalid, no sqrt" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "0000000000000000000000000000000000000000000000000000000000000000", + "note": "pub2 is point of order 3 on twist" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d", + "note": "pub2 is point of order 13 on twist" + }, + { + "sec1": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "pub2": "709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f", + "note": "pub2 is point of order 3319 on twist" + } + ], + "decrypt": [ + { + "conversation_key": "ca2527a037347b91bea0c8a30fc8d9600ffd81ec00038671e3a0f0cb0fc9f642", + "nonce": "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", + "plaintext": "n o b l e", + "ciphertext": "#Atqupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJdU0MIDf06CUvEvdnr1cp1fiMtlM/GrE92xAc1K5odTpCzUB+mjXgbaqtntBUbTToSUoT0ovrlPwzGjyp", + "note": "unknown encryption version" + }, + { + "conversation_key": "36f04e558af246352dcf73b692fbd3646a2207bd8abd4b1cd26b234db84d9481", + "nonce": "ad408d4be8616dc84bb0bf046454a2a102edac937c35209c43cd7964c5feb781", + "plaintext": "⚠️", + "ciphertext": "AK1AjUvoYW3IS7C/BGRUoqEC7ayTfDUgnEPNeWTF/reBZFaha6EAIRueE9D1B1RuoiuFScC0Q94yjIuxZD3JStQtE8JMNacWFs9rlYP+ZydtHhRucp+lxfdvFlaGV/sQlqZz", + "note": "unknown encryption version 0" + }, + { + "conversation_key": "ca2527a037347b91bea0c8a30fc8d9600ffd81ec00038671e3a0f0cb0fc9f642", + "nonce": "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", + "plaintext": "n o s t r", + "ciphertext": "Atфupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJZE0UICD06CUvEvdnr1cp1fiMtlM/GrE92xAc1EwsVCQEgWEu2gsHUVf4JAa3TpgkmFc3TWsax0v6n/Wq", + "note": "invalid base64" + }, + { + "conversation_key": "cff7bd6a3e29a450fd27f6c125d5edeb0987c475fd1e8d97591e0d4d8a89763c", + "nonce": "09ff97750b084012e15ecb84614ce88180d7b8ec0d468508a86b6d70c0361a25", + "plaintext": "¯\\_(ツ)_/¯", + "ciphertext": "Agn/l3ULCEAS4V7LhGFM6IGA17jsDUaFCKhrbXDANholyySBfeh+EN8wNB9gaLlg4j6wdBYh+3oK+mnxWu3NKRbSvQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "note": "invalid MAC" + }, + { + "conversation_key": "cfcc9cf682dfb00b11357f65bdc45e29156b69db424d20b3596919074f5bf957", + "nonce": "65b14b0b949aaa7d52c417eb753b390e8ad6d84b23af4bec6d9bfa3e03a08af4", + "plaintext": "🥎", + "ciphertext": "AmWxSwuUmqp9UsQX63U7OQ6K1thLI69L7G2b+j4DoIr0oRWQ8avl4OLqWZiTJ10vIgKrNqjoaX+fNhE9RqmR5g0f6BtUg1ijFMz71MO1D4lQLQfW7+UHva8PGYgQ1QpHlKgR", + "note": "invalid MAC" + }, + { + "conversation_key": "5254827d29177622d40a7b67cad014fe7137700c3c523903ebbe3e1b74d40214", + "nonce": "7ab65dbb8bbc2b8e35cafb5745314e1f050325a864d11d0475ef75b3660d91c1", + "plaintext": "elliptic-curve cryptography", + "ciphertext": "Anq2XbuLvCuONcr7V0UxTh8FAyWoZNEdBHXvdbNmDZHB573MI7R7rrTYftpqmvUpahmBC2sngmI14/L0HjOZ7lWGJlzdh6luiOnGPc46cGxf08MRC4CIuxx3i2Lm0KqgJ7vA", + "note": "invalid padding" + }, + { + "conversation_key": "fea39aca9aa8340c3a78ae1f0902aa7e726946e4efcd7783379df8096029c496", + "nonce": "7d4283e3b54c885d6afee881f48e62f0a3f5d7a9e1cb71ccab594a7882c39330", + "plaintext": "noble", + "ciphertext": "An1Cg+O1TIhdav7ogfSOYvCj9dep4ctxzKtZSniCw5MwRrrPJFyAQYZh5VpjC2QYzny5LIQ9v9lhqmZR4WBYRNJ0ognHVNMwiFV1SHpvUFT8HHZN/m/QarflbvDHAtO6pY16", + "note": "invalid padding" + }, + { + "conversation_key": "0c4cffb7a6f7e706ec94b2e879f1fc54ff8de38d8db87e11787694d5392d5b3f", + "nonce": "6f9fd72667c273acd23ca6653711a708434474dd9eb15c3edb01ce9a95743e9b", + "plaintext": "censorship-resistant and global social network", + "ciphertext": "Am+f1yZnwnOs0jymZTcRpwhDRHTdnrFcPtsBzpqVdD6b2NZDaNm/TPkZGr75kbB6tCSoq7YRcbPiNfJXNch3Tf+o9+zZTMxwjgX/nm3yDKR2kHQMBhVleCB9uPuljl40AJ8kXRD0gjw+aYRJFUMK9gCETZAjjmrsCM+nGRZ1FfNsHr6Z", + "note": "invalid padding" + } + ] + } + } +} \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs index 53c1fff..35f393e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,568 +1,270 @@ use crate::*; use secp256k1::{SecretKey, XOnlyPublicKey, SECP256K1}; -#[derive(Debug, Clone)] -struct ValidSec { - sec1: &'static str, - sec2: &'static str, - shared: &'static str, - salt: &'static str, - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, -} - -#[derive(Debug, Clone)] -struct ValidSecParsed { - sec1: SecretKey, - sec2: SecretKey, - shared: [u8; 32], - salt: [u8; 32], - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, -} - -impl ValidSec { - fn parsed(self) -> ValidSecParsed { - let sec1hex = hex::decode(self.sec1).unwrap(); - let sec1 = SecretKey::from_slice(&sec1hex).unwrap(); - let sec2hex = hex::decode(self.sec2).unwrap(); - let sec2 = SecretKey::from_slice(&sec2hex).unwrap(); - ValidSecParsed { - sec1, - sec2, - shared: hex::decode(self.shared).unwrap().try_into().unwrap(), - salt: hex::decode(self.salt).unwrap().try_into().unwrap(), - plaintext: self.plaintext, - ciphertext: self.ciphertext, - note: self.note, - } - } -} +// We use the test vectors from Paul Miller's javascript so we don't accidently +// mistype anything +const JSON_VECTORS: &'static str = include_str!("nip44.vectors.json"); #[test] -pub fn test_valid_sec_test_vectors() { - for vec in &VALID_SEC { - let vector = vec.clone().parsed(); +fn test_valid_get_conversation_key() { + let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); + + // v2.valid.get_conversation_key[] + for vectorobj in json + .as_object() + .unwrap() + .get("v2") + .unwrap() + .as_object() + .unwrap() + .get("valid") + .unwrap() + .as_object() + .unwrap() + .get("get_conversation_key") + .unwrap() + .as_array() + .unwrap() + { + let vector = vectorobj.as_object().unwrap(); + + let sec1 = { + let sec1hex = vector.get("sec1").unwrap().as_str().unwrap(); + let sec1bytes = hex::decode(sec1hex).unwrap(); + SecretKey::from_slice(&sec1bytes).unwrap() + }; + let pub2 = { + let pub2hex = vector.get("pub2").unwrap().as_str().unwrap(); + let pub2bytes = hex::decode(pub2hex).unwrap(); + XOnlyPublicKey::from_slice(&pub2bytes).unwrap() + }; + let conversation_key: [u8; 32] = { + let ckeyhex = vector.get("conversation_key").unwrap().as_str().unwrap(); + hex::decode(ckeyhex).unwrap().try_into().unwrap() + }; + let note = vector.get("note").unwrap().as_str().unwrap(); + + let computed_conversation_key = get_conversation_key(sec1, pub2); - // Test conversation key - let conversation_key = - get_conversation_key(vector.sec1, vector.sec2.x_only_public_key(&SECP256K1).0); assert_eq!( - conversation_key, vector.shared, + conversation_key, computed_conversation_key, "Conversation key failure on {}", - vector.note + note ); - - // Test encryption with an overridden salt - let ciphertext = - encrypt_inner(&conversation_key, &vector.plaintext, Some(&vector.salt)).unwrap(); - assert_eq!( - ciphertext, vector.ciphertext, - "Encryption does not match on {}", - vector.note - ); - - // Test decryption - let plaintext = decrypt(&conversation_key, &vector.ciphertext).unwrap(); - assert_eq!( - plaintext, vector.plaintext, - "Decryption does not match on {}", - vector.note - ); - } -} - -const VALID_SEC: [ValidSec; 10] = [ - ValidSec { - sec1: "0000000000000000000000000000000000000000000000000000000000000001", - sec2: "0000000000000000000000000000000000000000000000000000000000000002", - shared: "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", - salt: "0000000000000000000000000000000000000000000000000000000000000001", - plaintext: "a", - ciphertext: "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYNpT9ESckRbRUY7bUF5P+1rObpA4BNoksAUQ8myMDd9/37W/J2YHvBpRjvy9uC0+ovbpLc0WLaMFieqAMdIYqR14", - note: "sk1 = 1, sk2 = random, 0x02", - }, - ValidSec { - sec1: "0000000000000000000000000000000000000000000000000000000000000002", - sec2: "0000000000000000000000000000000000000000000000000000000000000001", - shared: "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", - salt: "f00000000000000000000000000000f00000000000000000000000000000000f", - plaintext: "🍕🫃", - ciphertext: "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPKY68BwdF7PIT205jBoaZHSs7OMpKsULW5F5ClOJWiy6XjZy7s2v85KugYmbBKgEC2LytbXbxkr7Jpgfk529K3/pP", - note: "sk1 = 1, sk2 = random, 0x02" - }, - ValidSec { - sec1: "5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", - sec2: "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", - shared: "94da47d851b9c1ed33b3b72f35434f56aa608d60e573e9c295f568011f4f50a4", - salt: "b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b", - plaintext: "表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀", - ciphertext: "ArY1I2xC2yDwIbuNHN/1ynXdGgzHLqdCrXUPMwELJPc7yuU7XwJ8wCYUrq4aXX86HLnkMx7fPFvNeMk0uek9ma01magfEBIf+vJvZdWKiv48eUu9Cv31plAJsH6kSIsGc5TVYBYipkrQUNRxxJA15QT+uCURF96v3XuSS0k2Pf108AI=", - note: "hard-unicode string" - }, - ValidSec { - sec1: "8f40e50a84a7462e2b8d24c28898ef1f23359fff50d8c509e6fb7ce06e142f9c", - sec2: "b9b0a1e9cc20100c5faa3bbe2777303d25950616c4c6a3fa2e3e046f936ec2ba", - shared: "ab99c122d4586cdd5c813058aa543d0e7233545dbf6874fc34a3d8d9a18fbbc3", - salt: "b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf", - plaintext: "ability🤝的 ȺȾ", - ciphertext: "ArIJia3D3cQc0sQ1lSwNWakTFdjFIY1QQFc/w3SVQ6yvPSc+7YCIFTmGk5OLuh1nhl6TvID7sGKLFUCWRW1eRfV/0a7sT46N3nTQzD7IE67zLWrYqGnE+0DDNz6sJ4hAaFrT", - note: "", - }, - ValidSec { - sec1: "875adb475056aec0b4809bd2db9aa00cff53a649e7b59d8edcbf4e6330b0995c", - sec2: "9c05781112d5b0a2a7148a222e50e0bd891d6b60c5483f03456e982185944aae", - shared: "a449f2a85c6d3db0f44c64554a05d11a3c0988d645e4b4b2592072f63662f422", - salt: "8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1", - plaintext: "pepper👀їжак", - ciphertext: "Ao1EQnE+udR5EXXLBA2Y1vxb6IZNbsL4nPCJWisrctGx1TkkMfiHJxEeSdQ/4Rlaghn0okDCNYLihBsHrDzBsNRC27APmH9mmZcpcg66Mb0exH9V5/lLBWdQW+fcY9GpvXv0", - note: "", - }, - ValidSec { - sec1: "eba1687cab6a3101bfc68fd70f214aa4cc059e9ec1b79fdb9ad0a0a4e259829f", - sec2: "dff20d262bef9dfd94666548f556393085e6ea421c8af86e9d333fa8747e94b3", - shared: "decde9938ffcb14fa7ff300105eb1bf239469af9baf376e69755b9070ae48c47", - salt: "2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407", - plaintext: "( ͡° ͜ʖ ͡°)", - ciphertext: "AiGAtSrmRfz59QgNgbHwtdbyzXf/PJhogrtUkVhGLzQHiR8Hljs6Nl/XsNDAmCz6U1Z3NUGhbCtczc3wXXxDzFkjjMimxsf/74OEzu7LphUadM9iSWvVKPrNXY7lTD0B2muz", - note: "", - }, - ValidSec { - sec1: "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", - sec2: "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", - shared: "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", - salt: "e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68", - plaintext: "مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،", - ciphertext: "AuTNX3zk7qAkvHGxetRWqYanSsQmwsYrChXrXFyPiItohfde4vHVRHUupr+Glh9JW4f9EY+w795hvRZbixs0EQgDZ7zwLlymVQI3NNvMqvemQzHUA1I5+9gSu8XSMwX9gDCUAjUJtntCkRt9+tjdy2Wa2ZrDYqCvgirvzbJTIC69Ve3YbKuiTQCKtVi0PA5ZLqVmnkHPIqfPqDOGj/a3dvJVzGSgeijcIpjuEgFF54uirrWvIWmTBDeTA+tlQzJHpB2wQnUndd2gLDb8+eKFUZPBifshD3WmgWxv8wRv6k3DeWuWEZQ70Z+YDpgpeOzuzHj0MDBwMAlY8Qq86Rx6pxY76PLDDfHh3rE2CHJEKl2MhDj7pGXao2o633vSRd9ueG8W", - note: "", - }, - ValidSec { - sec1: "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", - sec2: "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", - shared: "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", - salt: "38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023", - plaintext: "الكل في المجمو عة (5)", - ciphertext: "AjjRygq++eX1ZOiXYahs7gRXS2gl0+8gY7EK11iZ5LAjTHmhdBC3meTY4A7Lv8s8B86MnmlUBJ8ebzwxFQzDyVCcdSbWFaKe0gigEBdXew7TjrjH8BCpAbtYjoa4YHa8GNjj7zH314ApVnwoByHdLHLB9Vr6VdzkxcJgA6oL4MAsRLg=", - note: "", - }, - ValidSec { - sec1: "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", - sec2: "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", - shared: "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", - salt: "4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0", - plaintext: "𝖑𝖆𝖟𝖞 社會科學院語學研究所", - ciphertext: "Ak8aMZCfNIOp5pyFSaVbvJryX6W77Pe9MtmJb4PvLhLg/25Q5uBC88jl5ghtEREXX6o4QijPzM0uwmkeQ54/6aIqUyzGNVdryWKZ0mee2lmVVWhU+26X6XGFQ5DGRn+1v0POsFUCZ/REh35+beBNHnyvjxD/rbrMfhP2Blc8X5m8Xvk=", - note: "", - }, - ValidSec { - sec1: "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e", - sec2: "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214", - shared: "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", - salt: "a3e219242d85465e70adcd640b564b3feff57d2ef8745d5e7a0663b2dccceb54", - plaintext: "🙈 🙉 🙊 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗", - ciphertext: "AqPiGSQthUZecK3NZAtWSz/v9X0u+HRdXnoGY7LczOtU9bUC2ji2A2udRI2VCEQZ7IAmYRRgxodBtd5Yi/5htCUczf1jLHxIt9AhVAZLKuRgbWOuEMq5RBybkxPsSeAkxzXVOlWHZ1Febq5ogkjqY/6Xj8CwwmaZxfbx+d1BKKO3Wa+IFuXwuVAZa1Xo+fan+skyf+2R5QSj10QGAnGO7odAu/iZ9A28eMoSNeXsdxqy1+PRt5Zk4i019xmf7C4PDGSzgFZSvQ2EzusJN5WcsnRFmF1L5rXpX1AYo8HusOpWcGf9PjmFbO+8spUkX1W/T21GRm4o7dro1Y6ycgGOA9BsiQ==", - note: "emoji and lang 7" - }, -]; - -#[derive(Debug, Clone)] -struct ValidPub { - sec1: &'static str, - pub2: &'static str, - shared: &'static str, - salt: &'static str, - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, -} - -#[derive(Debug, Clone)] -struct ValidPubParsed { - sec1: SecretKey, - pub2: XOnlyPublicKey, - shared: [u8; 32], - salt: [u8; 32], - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, -} - -impl ValidPub { - fn parsed(self) -> ValidPubParsed { - let sec1hex = hex::decode(self.sec1).unwrap(); - let sec1 = SecretKey::from_slice(&sec1hex).unwrap(); - let pub2hex = hex::decode(self.pub2).unwrap(); - let pub2 = XOnlyPublicKey::from_slice(&pub2hex).unwrap(); - ValidPubParsed { - sec1, - pub2, - shared: hex::decode(self.shared).unwrap().try_into().unwrap(), - salt: hex::decode(self.salt).unwrap().try_into().unwrap(), - plaintext: self.plaintext, - ciphertext: self.ciphertext, - note: self.note, - } } } #[test] -pub fn test_valid_pub_test_vectors() { - for vec in &VALID_PUB { - let vector = vec.clone().parsed(); +fn test_valid_calc_padded_len() { + let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); - // Test conversation key - let conversation_key = get_conversation_key(vector.sec1, vector.pub2); - assert_eq!( - conversation_key, vector.shared, - "Conversation key failure on {}", - vector.note - ); - - // Test encryption with an overridden salt - let ciphertext = - encrypt_inner(&conversation_key, &vector.plaintext, Some(&vector.salt)).unwrap(); - assert_eq!( - ciphertext, vector.ciphertext, - "Encryption does not match on {}", - vector.note - ); - - // Test decryption - let plaintext = decrypt(&conversation_key, &vector.ciphertext).unwrap(); - assert_eq!( - plaintext, vector.plaintext, - "Decryption does not match on {}", - vector.note - ); - } -} - -const VALID_PUB: [ValidPub; 3] = [ - ValidPub { - sec1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", - pub2: "0000000000000000000000000000000000000000000000000000000000000002", - shared: "7a1ccf5ce5a08e380f590de0c02776623b85a61ae67cfb6a017317e505b7cb51", - salt: "a000000000000000000000000000000000000000000000000000000000000001", - plaintext: "⁰⁴⁵₀₁₂", - ciphertext: "AqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2+xmGnjIMPMqqJGmjdYAYZUDUyEEUO3/evHUaO40LePeR91VlMVZ7I+nKJPkaUiKZ3cQiQnA86Uwti2IxepmzOFN", - note: "sec1 = n-2, pub2: random, 0x02" - }, - ValidPub { - sec1: "0000000000000000000000000000000000000000000000000000000000000002", - pub2: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeb", - shared: "aa971537d741089885a0b48f2730a125e15b36033d089d4537a4e1204e76b39e", - salt: "b000000000000000000000000000000000000000000000000000000000000002", - plaintext: "A Peer-to-Peer Electronic Cash System", - ciphertext: "ArAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyuqG6RycuPyDPtwxzTcuMQu+is3N5XuWTlvCjligVaVBRydexaylXbsX592MEd3/Jt13BNL/GlpYpGDvLS4Tt/+2s9FX/16e/RDc+czdwXglc4DdSHiq+O06BvvXYfEQOPw=", - note: "sec1 = 2, pub2: " - }, - ValidPub { - sec1: "0000000000000000000000000000000000000000000000000000000000000001", - pub2: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - shared: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - salt: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - plaintext: "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending.", - ciphertext: "Anm+Zn753LusVaBilc6HCwcCm/zbLc4o2VnygVsW+BeYb9wHyKevpe7ohJ6OkpceFcb0pySY8TLGwT7Q3zWNDKxc9blXanxKborEXkQH8xNaB2ViJfgxpkutbwbYd0Grix34xzaZBASufdsNm7R768t51tI6sdS0nms6kWLVJpEGu6Ke4Bldv4StJtWBLaTcgsgN+4WxDbBhC/nhwjEQiBBbbmUrPWjaVZXjl8dzzPrYtkSoeBNJs/UNvDwym4+qrmhv4ASTvVflpZgLlSe4seqeu6dWoRqn8uRHZQnPs+XhqwbdCHpeKGB3AfGBykZY0RIr0tjarWdXNasGbIhGM3GiLasioJeabAZw0plCevDkKpZYDaNfMJdzqFVJ8UXRIpvDpQad0SOm8lLum/aBzUpLqTjr3RvSlhYdbuODpd9pR5K60k4L2N8nrPtBv08wlilQg2ymwQgKVE6ipxIzzKMetn8+f0nQ9bHjWFJqxetSuMzzArTUQl9c4q/DwZmCBhI2", - note: "sec1 == pub2 == salt" - } -]; - -#[derive(Debug, Clone)] -#[allow(dead_code)] -struct InvalidPub { - sec1: &'static str, - pub2: &'static str, - shared: &'static str, - salt: &'static str, - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, - error: Error, -} - -#[derive(Debug, Clone)] -struct InvalidPubParsed { - sec1: SecretKey, - pub2: XOnlyPublicKey, - shared: [u8; 32], - //salt: [u8; 32], - plaintext: &'static str, - ciphertext: &'static str, - note: &'static str, - error: Error, -} - -impl InvalidPub { - fn parsed(self) -> InvalidPubParsed { - let sec1hex = hex::decode(self.sec1).unwrap(); - let sec1 = SecretKey::from_slice(&sec1hex).unwrap(); - let pub2hex = hex::decode(self.pub2).unwrap(); - let pub2 = XOnlyPublicKey::from_slice(&pub2hex).unwrap(); - InvalidPubParsed { - sec1, - pub2, - shared: hex::decode(self.shared).unwrap().try_into().unwrap(), - //salt: hex::decode(self.salt).unwrap().try_into().unwrap(), - plaintext: self.plaintext, - ciphertext: self.ciphertext, - note: self.note, - error: self.error, - } - } -} - -#[test] -pub fn test_invalid_pub_test_vectors() { - for vec in &INVALID_PUB { - let vector = vec.clone().parsed(); - - // Test conversation key - let conversation_key = get_conversation_key(vector.sec1, vector.pub2); - assert_eq!( - conversation_key, vector.shared, - "Conversation key failure on {}", - vector.note - ); - - // Test decryption fails - let result = decrypt(&conversation_key, &vector.ciphertext); - assert!( - result.is_err(), - "Should not have decrypted: {}", - vector.note - ); - let err = result.unwrap_err(); - assert_eq!(err, vector.error, "Plaintext was {}", vector.plaintext); - } -} - -const INVALID_PUB: [InvalidPub; 8] = [ - InvalidPub { - sec1: "2573d1e9b9ac5de5d570f652cbb9e8d4f235e3d3d334181448e87c417f374e83", - pub2: "8348c2d35549098706e5bab7966d9a9c72fbf6554e918f41c2b6cb275f79ec13", - shared: "8673ec68393a997bfad7eab8661461daf8b3931b7e885d78312a3fb7fe17f41a", - salt: "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", - plaintext: "n o s t r", - ciphertext: "##Atqupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbOsrsqIEybscEwg5rnI/Cx03mDSmeweOLKD7dw5BDZQDxXSlCwX1LIcTJEZaJPTz98Ftu0zSE0d93ED7OtdlvNeZx", - note: "unknown encryption version", - error: Error::UnsupportedFutureVersion, - }, - InvalidPub { - sec1: "11063318c5cb3cd9cafcced42b4db5ea02ec976ed995962d2bc1fa1e9b52e29f", - pub2: "5c49873b6eac3dd363325250cc55d5dd4c7ce9a885134580405736d83506bb74", - shared: "e2aad10de00913088e5cb0f73fa526a6a17e95763cc5b2a127022f5ea5a73445", - salt: "ad408d4be8616dc84bb0bf046454a2a102edac937c35209c43cd7964c5feb781", - plaintext: "⚠️", - ciphertext: "AK1AjUvoYW3IS7C/BGRUoqEC7ayTfDUgnEPNeWTF/reBA4fZmoHrtrz5I5pCHuwWZ22qqL/Xt1VidEZGMLds0yaJ5VwUbeEifEJlPICOFt1ssZJxCUf43HvRwCVTFskbhSMh", - note: "unknown encryption version: 0", - error: Error::UnknownVersion, - }, - InvalidPub { - sec1: "2573d1e9b9ac5de5d570f652cbb9e8d4f235e3d3d334181448e87c417f374e83", - pub2: "8348c2d35549098706e5bab7966d9a9c72fbf6554e918f41c2b6cb275f79ec13", - shared: "8673ec68393a997bfad7eab8661461daf8b3931b7e885d78312a3fb7fe17f41a", - salt: "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db", - plaintext: "n o s t r", - ciphertext: "Atqupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbOsrsqIEybscEwg5rnI/Cx03mDSmeweOLKD,7dw5BDZQDxXSlCwX1LIcTJEZaJPTz98Ftu0zSE0d93ED7OtdlvNeZx", - note: "invalid base64", - error: Error::Base64Decode(base64::DecodeError::InvalidLength), - }, - InvalidPub { - sec1: "5a2f39347fed3883c9fe05868a8f6156a292c45f606bc610495fcc020ed158f7", - pub2: "775bbfeba58d07f9d1fbb862e306ac780f39e5418043dadb547c7b5900245e71", - shared: "2e70c0a1cde884b88392458ca86148d859b273a5695ede5bbe41f731d7d88ffd", - salt: "09ff97750b084012e15ecb84614ce88180d7b8ec0d468508a86b6d70c0361a25", - plaintext: "¯\\_(ツ)_/¯", - ciphertext: "Agn/l3ULCEAS4V7LhGFM6IGA17jsDUaFCKhrbXDANholdUejFZPARM22IvOqp1U/UmFSkeSyTBYbbwy5ykmi+mKiEcWL+nVmTOf28MMiC+rTpZys/8p1hqQFpn+XWZRPrVay", - note: "invalid MAC", - error: Error::InvalidMac, - }, - InvalidPub { - sec1: "067eda13c4a36090ad28a7a183e9df611186ca01f63cb30fcdfa615ebfd6fb6d", - pub2: "32c1ece2c5dd2160ad03b243f50eff12db605b86ac92da47eacc78144bf0cdd3", - shared: "a808915e31afc5b853d654d2519632dac7298ee2ecddc11695b8eba925935c2a", - salt: "65b14b0b949aaa7d52c417eb753b390e8ad6d84b23af4bec6d9bfa3e03a08af4", - plaintext: "🥎", - ciphertext: "AmWxSwuUmqp9UsQX63U7OQ6K1thLI69L7G2b+j4DoIr0U0P/M1/oKm95z8qz6Kg0zQawLzwk3DskvWA2drXP4zK+tzHpKvWq0KOdx5MdypboSQsP4NXfhh2KoUffjkyIOiMA", - note: "invalid MAC", - error: Error::InvalidMac, - }, - InvalidPub { - sec1: "3e7be560fb9f8c965c48953dbd00411d48577e200cf00d7cc427e49d0e8d9c01", - pub2: "e539e5fee58a337307e2a937ee9a7561b45876fb5df405c5e7be3ee564b239cc", - shared: "6ee3efc4255e3b8270e5dd3f7dc7f6b60878cda6218c8df34a3261cd48744931", - salt: "7ab65dbb8bbc2b8e35cafb5745314e1f050325a864d11d0475ef75b3660d91c1", - plaintext: "elliptic-curve cryptography", - ciphertext: "Anq2XbuLvCuONcr7V0UxTh8FAyWoZNEdBHXvdbNmDZHBu7F9m36yBd58mVUBB5ktBTOJREDaQT1KAyPmZidP+IRea1lNw5YAEK7+pbnpfCw8CD0i2n8Pf2IDWlKDhLiVvatw", - note: "invalid padding", - error: Error::MessageIsEmpty, - }, - InvalidPub { - sec1: "c22e1d4de967aa39dc143354d8f596cec1d7c912c3140831fff2976ce3e387c1", - pub2: "4e405be192677a2da95ffc733950777213bf880cf7c3b084eeb6f3fe5bd43705", - shared: "1675a773dbf6fbcbef6a293004a4504b6c856978be738b10584b0269d437c8d1", - salt: "7d4283e3b54c885d6afee881f48e62f0a3f5d7a9e1cb71ccab594a7882c39330", - plaintext: "Peer-to-Peer", - ciphertext: "An1Cg+O1TIhdav7ogfSOYvCj9dep4ctxzKtZSniCw5MwhT0hvSnF9Xjp9Lml792qtNbmAVvR6laukTe9eYEjeWPpZFxtkVpYTbbL9wDKFeplDMKsUKVa+roSeSvv0ela9seDVl2Sfso=", - note: "invalid padding", - error: Error::InvalidPadding, - }, - InvalidPub { - sec1: "be1edab14c5912e5c59084f197f0945242e969c363096cccb59af8898815096f", - pub2: "9eaf0775d971e4941c97189232542e1daefcdb7dddafc39bcea2520217710ba2", - shared: "1741a44c052d5ae363c7845441f73d2b6c28d9bfb3006190012bba12eb4c774b", - salt: "6f9fd72667c273acd23ca6653711a708434474dd9eb15c3edb01ce9a95743e9b", - plaintext: "censorship-resistant and global social network", - ciphertext: "Am+f1yZnwnOs0jymZTcRpwhDRHTdnrFcPtsBzpqVdD6bL9HUMo3Mjkz4bjQo/FJF2LWHmaCr9Byc3hU9D7we+EkNBWenBHasT1G52fZk9r3NKeOC1hLezNwBLr7XXiULh+NbMBDtJh9/aQh1uZ9EpAfeISOzbZXwYwf0P5M85g9XER8hZ2fgJDLb4qMOuQRG6CrPezhr357nS3UHwPC2qHo3uKACxhE+2td+965yDcvMTx4KYTQg1zNhd7PA5v/WPnWeq2B623yLxlevUuo/OvXplFho3QVy7s5QZVop6qV2g2/l/SIsvD0HIcv3V35sywOCBR0K4VHgduFqkx/LEF3NGgAbjONXQHX8ZKushsEeR4TxlFoRSovAyYjhWolz+Ok3KJL2Ertds3H+M/Bdl2WnZGT0IbjZjn3DS+b1Ke0R0X4Onww2ZG3+7o6ncIwTc+lh1O7YQn00V0HJ+EIp03heKV2zWdVSC615By/+Yt9KAiV56n5+02GAuNqA", - note: "invalid padding", - error: Error::InvalidPadding, - } -]; - -#[derive(Debug, Clone)] -struct InvalidKeys { - sec1: &'static str, - pub2: &'static str, - note: &'static str, -} - -#[test] -pub fn test_invalid_keys() { - for vector in &INVALID_KEYS { - let sec1hex = hex::decode(vector.sec1).unwrap(); - let sec1 = SecretKey::from_slice(&sec1hex); - let pub2hex = hex::decode(vector.pub2).unwrap(); - let pub2 = XOnlyPublicKey::from_slice(&pub2hex); - assert!( - sec1.is_err() || pub2.is_err(), - "One of the keys should have failed: {}", - vector.note - ); - } -} - -const INVALID_KEYS: [InvalidKeys; 5] = [ - InvalidKeys { - sec1: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - pub2: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - note: "sec1 higher than curve.n", - }, - InvalidKeys { - sec1: "0000000000000000000000000000000000000000000000000000000000000000", - pub2: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - note: "sec1 is 0", - }, - InvalidKeys { - sec1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", - pub2: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - note: "pub2 is invalid, no sqrt, all-ff", - }, - InvalidKeys { - sec1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - pub2: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - note: "sec1 == curve.n", - }, - InvalidKeys { - sec1: "0000000000000000000000000000000000000000000000000000000000000002", - pub2: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - note: "pub2 is invalid, no sqrt", - }, -]; - -#[test] -fn test_padding_length() { - for (len, pad) in PADDING { + for elem in json + .as_object() + .unwrap() + .get("v2") + .unwrap() + .as_object() + .unwrap() + .get("valid") + .unwrap() + .as_object() + .unwrap() + .get("calc_padded_len") + .unwrap() + .as_array() + .unwrap() + { + let len = elem[0].as_number().unwrap().as_u64().unwrap() as usize; + let pad = elem[1].as_number().unwrap().as_u64().unwrap() as usize; assert_eq!(calc_padding(len), pad); } } -const PADDING: [(usize, usize); 24] = [ - (16, 32), - (32, 32), - (33, 64), - (37, 64), - (45, 64), - (49, 64), - (64, 64), - (65, 96), - (100, 128), - (111, 128), - (200, 224), - (250, 256), - (320, 320), - (383, 384), - (384, 384), - (400, 448), - (500, 512), - (512, 512), - (515, 640), - (700, 768), - (800, 896), - (900, 1024), - (1020, 1024), - (74123, 81920), -]; +#[test] +fn test_valid_encrypt_decrypt() { + let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); + + for (i, vectorobj) in json + .as_object() + .unwrap() + .get("v2") + .unwrap() + .as_object() + .unwrap() + .get("valid") + .unwrap() + .as_object() + .unwrap() + .get("encrypt_decrypt") + .unwrap() + .as_array() + .unwrap() + .iter() + .enumerate() + { + let vector = vectorobj.as_object().unwrap(); + + let sec1 = { + let sec1hex = vector.get("sec1").unwrap().as_str().unwrap(); + let sec1bytes = hex::decode(sec1hex).unwrap(); + SecretKey::from_slice(&sec1bytes).unwrap() + }; + let sec2 = { + let sec2hex = vector.get("sec2").unwrap().as_str().unwrap(); + let sec2bytes = hex::decode(sec2hex).unwrap(); + SecretKey::from_slice(&sec2bytes).unwrap() + }; + let conversation_key: [u8; 32] = { + let ckeyhex = vector.get("conversation_key").unwrap().as_str().unwrap(); + hex::decode(ckeyhex).unwrap().try_into().unwrap() + }; + let nonce: [u8; 32] = { + let noncehex = vector.get("nonce").unwrap().as_str().unwrap(); + hex::decode(noncehex).unwrap().try_into().unwrap() + }; + let plaintext = vector.get("plaintext").unwrap().as_str().unwrap(); + let ciphertext = vector.get("ciphertext").unwrap().as_str().unwrap(); + + // Test conversation key + let computed_conversation_key = + get_conversation_key(sec1, sec2.x_only_public_key(&SECP256K1).0); + assert_eq!( + computed_conversation_key, conversation_key, + "Conversation key failure on ValidSec #{}", + i + ); + + // Test encryption with an overridden nonce + let computed_ciphertext = + encrypt_inner(&conversation_key, &plaintext, Some(&nonce)).unwrap(); + assert_eq!( + computed_ciphertext, ciphertext, + "Encryption does not match on ValidSec #{}", + i + ); + + // Test decryption + let computed_plaintext = decrypt(&conversation_key, &ciphertext).unwrap(); + assert_eq!( + computed_plaintext, plaintext, + "Decryption does not match on ValidSec #{}", + i + ); + } +} + +//TBD? +//#[test] +//fn test_valid_encrypt_decrypt_long_msg() { +//} + +//TBD? +//#[test] +//fn test_invalid_encrypt_msg_lengths() { +//} + +//TBD? +//#[test] +//fn test_invalid_decrypt_msg_lengths() { +//} #[test] -fn bench_encryption_inner() { - const SEC1HEX: &'static str = - "dc4b57c5fe856584b01aab34dad7454b0f715bdfab091bf0dbbe12f65c778838"; - const SEC2HEX: &'static str = - "3072ab28ed7d5c2e4f5efbdcde5fb11455ab7f976225d1779a1751eb6400411a"; +fn test_invalid_get_conversation_key() { + let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); - let sec1bytes = hex::decode(SEC1HEX).unwrap(); - let sec1 = SecretKey::from_slice(&sec1bytes).unwrap(); + for vectorobj in json + .as_object() + .unwrap() + .get("v2") + .unwrap() + .as_object() + .unwrap() + .get("invalid") + .unwrap() + .as_object() + .unwrap() + .get("get_conversation_key") + .unwrap() + .as_array() + .unwrap() + { + let vector = vectorobj.as_object().unwrap(); - let sec2bytes = hex::decode(SEC2HEX).unwrap(); - let sec2 = SecretKey::from_slice(&sec2bytes).unwrap(); + let sec1result = { + let sec1hex = vector.get("sec1").unwrap().as_str().unwrap(); + let sec1bytes = hex::decode(sec1hex).unwrap(); + SecretKey::from_slice(&sec1bytes) + }; + let pub2result = { + let pub2hex = vector.get("pub2").unwrap().as_str().unwrap(); + let pub2bytes = hex::decode(pub2hex).unwrap(); + XOnlyPublicKey::from_slice(&pub2bytes) + }; + let note = vector.get("note").unwrap().as_str().unwrap(); - let (pub2, _) = sec2.x_only_public_key(&SECP256K1); - - let shared = get_conversation_key(sec1, pub2); - - // Bench a maximum length message - let message: Vec = std::iter::repeat(0).take(65536 - 128).collect(); - let message = unsafe { String::from_utf8_unchecked(message) }; - let start = std::time::Instant::now(); - let rounds = 32768; - for _ in 0..rounds { - std::hint::black_box({ - let encrypted = encrypt(&shared, &*message).unwrap(); - let _decrypted = decrypt(&shared, &*encrypted).unwrap(); - }); + assert!( + sec1result.is_err() || pub2result.is_err(), + "One of the keys should have failed: {}", + note + ); + } +} + +#[test] +fn test_invalid_decrypt() { + let json: serde_json::Value = serde_json::from_str(JSON_VECTORS).unwrap(); + + let known_errors = [ + Error::UnsupportedFutureVersion, + Error::UnknownVersion, + Error::Base64Decode(base64::DecodeError::InvalidLength), + Error::InvalidMac, + Error::InvalidMac, + Error::InvalidPadding, + Error::MessageIsEmpty, + Error::InvalidPadding, + Error::InvalidPadding, + ]; + + for (i, vectorobj) in json + .as_object() + .unwrap() + .get("v2") + .unwrap() + .as_object() + .unwrap() + .get("invalid") + .unwrap() + .as_object() + .unwrap() + .get("decrypt") + .unwrap() + .as_array() + .unwrap() + .iter() + .enumerate() + { + let vector = vectorobj.as_object().unwrap(); + let conversation_key: [u8; 32] = { + let ckeyhex = vector.get("conversation_key").unwrap().as_str().unwrap(); + hex::decode(ckeyhex).unwrap().try_into().unwrap() + }; + //let nonce: [u8; 32] = { + // let noncehex = vector.get("nonce").unwrap().as_str().unwrap(); + // hex::decode(noncehex).unwrap().try_into().unwrap() + //}; + // let plaintext = vector.get("plaintext").unwrap().as_str().unwrap(); + let ciphertext = vector.get("ciphertext").unwrap().as_str().unwrap(); + let note = vector.get("note").unwrap().as_str().unwrap(); + + let result = decrypt(&conversation_key, &ciphertext); + assert!(result.is_err(), "Should not have decrypted: {}", note); + + let err = result.unwrap_err(); + assert_eq!( + err, known_errors[i], + "Unexpected error in invalid decrypt #{}", + i + ); } - let elapsed = start.elapsed(); - let total_nanos = elapsed.as_nanos(); - let nanos_per_roundtrip = total_nanos / rounds as u128; - let nanosx10_per_roundtrip_per_char_long = 10 * nanos_per_roundtrip / message.len() as u128; - - // Bench a minimal length message - let message = "a"; - let start = std::time::Instant::now(); - let rounds = 32768; - for _ in 0..rounds { - std::hint::black_box({ - let encrypted = encrypt(&shared, &*message).unwrap(); - let _decrypted = decrypt(&shared, &*encrypted).unwrap(); - }); - } - let elapsed = start.elapsed(); - let total_nanos = elapsed.as_nanos(); - let nanos_per_roundtrip = total_nanos / rounds as u128; - let nanosx10_per_roundtrip_per_char_short = 10 * nanos_per_roundtrip / message.len() as u128; - - // This is approximate math, assuming overhead is negligable on the long message, which - // is approximately true. - let percharx10 = nanosx10_per_roundtrip_per_char_long; - let overheadx10 = nanosx10_per_roundtrip_per_char_short - percharx10; - - println!( - "{}.{}ns plus {}.{}ns per character (encrypt and decrypt)", - overheadx10 / 10, - overheadx10 % 10, - percharx10 / 10, - percharx10 % 10 - ); }