mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 08:47:33 +00:00
- Migrates Quartz to a KMP project
- Converts OpenTimestamps from java to Kotlin - Moves OTS OkHttp setup to Quartz
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"english": [
|
||||
[
|
||||
"00000000000000000000000000000000",
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
|
||||
],
|
||||
[
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
|
||||
"legal winner thank year wave sausage worth useful legal winner thank yellow",
|
||||
"2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"
|
||||
],
|
||||
[
|
||||
"80808080808080808080808080808080",
|
||||
"letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
|
||||
"d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8"
|
||||
],
|
||||
[
|
||||
"ffffffffffffffffffffffffffffffff",
|
||||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
|
||||
"ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069"
|
||||
],
|
||||
[
|
||||
"000000000000000000000000000000000000000000000000",
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
|
||||
"035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa"
|
||||
],
|
||||
[
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
|
||||
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
|
||||
"f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd"
|
||||
],
|
||||
[
|
||||
"808080808080808080808080808080808080808080808080",
|
||||
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
|
||||
"107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65"
|
||||
],
|
||||
[
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",
|
||||
"0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528"
|
||||
],
|
||||
[
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
"bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"
|
||||
],
|
||||
[
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
|
||||
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
|
||||
"bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87"
|
||||
],
|
||||
[
|
||||
"8080808080808080808080808080808080808080808080808080808080808080",
|
||||
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
|
||||
"c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f"
|
||||
],
|
||||
[
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
|
||||
"dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad"
|
||||
],
|
||||
[
|
||||
"77c2b00716cec7213839159e404db50d",
|
||||
"jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge",
|
||||
"b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff"
|
||||
],
|
||||
[
|
||||
"b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b",
|
||||
"renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap",
|
||||
"9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5"
|
||||
],
|
||||
[
|
||||
"3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982",
|
||||
"dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic",
|
||||
"ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67"
|
||||
],
|
||||
[
|
||||
"0460ef47585604c5660618db2e6a7e7f",
|
||||
"afford alter spike radar gate glance object seek swamp infant panel yellow",
|
||||
"65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4"
|
||||
],
|
||||
[
|
||||
"72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f",
|
||||
"indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left",
|
||||
"3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba"
|
||||
],
|
||||
[
|
||||
"2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416",
|
||||
"clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste",
|
||||
"fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449"
|
||||
],
|
||||
[
|
||||
"eaebabb2383351fd31d703840b32e9e2",
|
||||
"turtle front uncle idea crush write shrug there lottery flower risk shell",
|
||||
"bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c404f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c"
|
||||
],
|
||||
[
|
||||
"7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78",
|
||||
"kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment",
|
||||
"ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79"
|
||||
],
|
||||
[
|
||||
"4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef",
|
||||
"exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top",
|
||||
"095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c"
|
||||
],
|
||||
[
|
||||
"18ab19a9f54a9274f03e5209a2ac8a91",
|
||||
"board flee heavy tunnel powder denial science ski answer betray cargo cat",
|
||||
"6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8"
|
||||
],
|
||||
[
|
||||
"18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4",
|
||||
"board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief",
|
||||
"f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9"
|
||||
],
|
||||
[
|
||||
"15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419",
|
||||
"beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut",
|
||||
"b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
|
||||
]
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,648 @@
|
||||
{
|
||||
"v2": {
|
||||
"valid": {
|
||||
"get_conversation_key": [
|
||||
{
|
||||
"sec1": "315e59ff51cb9209768cf7da80791ddcaae56ac9775eb25b6dee1234bc5d2268",
|
||||
"pub2": "c2f9d9948dc8c7c38321e4b85c8558872eafa0641cd269db76848a6073e69133",
|
||||
"conversation_key": "3dfef0ce2a4d80a25e7a328accf73448ef67096f65f79588e358d9a0eb9013f1"
|
||||
},
|
||||
{
|
||||
"sec1": "a1e37752c9fdc1273be53f68c5f74be7c8905728e8de75800b94262f9497c86e",
|
||||
"pub2": "03bb7947065dde12ba991ea045132581d0954f042c84e06d8c00066e23c1a800",
|
||||
"conversation_key": "4d14f36e81b8452128da64fe6f1eae873baae2f444b02c950b90e43553f2178b"
|
||||
},
|
||||
{
|
||||
"sec1": "98a5902fd67518a0c900f0fb62158f278f94a21d6f9d33d30cd3091195500311",
|
||||
"pub2": "aae65c15f98e5e677b5050de82e3aba47a6fe49b3dab7863cf35d9478ba9f7d1",
|
||||
"conversation_key": "9c00b769d5f54d02bf175b7284a1cbd28b6911b06cda6666b2243561ac96bad7"
|
||||
},
|
||||
{
|
||||
"sec1": "86ae5ac8034eb2542ce23ec2f84375655dab7f836836bbd3c54cefe9fdc9c19f",
|
||||
"pub2": "59f90272378089d73f1339710c02e2be6db584e9cdbe86eed3578f0c67c23585",
|
||||
"conversation_key": "19f934aafd3324e8415299b64df42049afaa051c71c98d0aa10e1081f2e3e2ba"
|
||||
},
|
||||
{
|
||||
"sec1": "2528c287fe822421bc0dc4c3615878eb98e8a8c31657616d08b29c00ce209e34",
|
||||
"pub2": "f66ea16104c01a1c532e03f166c5370a22a5505753005a566366097150c6df60",
|
||||
"conversation_key": "c833bbb292956c43366145326d53b955ffb5da4e4998a2d853611841903f5442"
|
||||
},
|
||||
{
|
||||
"sec1": "49808637b2d21129478041813aceb6f2c9d4929cd1303cdaf4fbdbd690905ff2",
|
||||
"pub2": "74d2aab13e97827ea21baf253ad7e39b974bb2498cc747cdb168582a11847b65",
|
||||
"conversation_key": "4bf304d3c8c4608864c0fe03890b90279328cd24a018ffa9eb8f8ccec06b505d"
|
||||
},
|
||||
{
|
||||
"sec1": "af67c382106242c5baabf856efdc0629cc1c5b4061f85b8ceaba52aa7e4b4082",
|
||||
"pub2": "bdaf0001d63e7ec994fad736eab178ee3c2d7cfc925ae29f37d19224486db57b",
|
||||
"conversation_key": "a3a575dd66d45e9379904047ebfb9a7873c471687d0535db00ef2daa24b391db"
|
||||
},
|
||||
{
|
||||
"sec1": "0e44e2d1db3c1717b05ffa0f08d102a09c554a1cbbf678ab158b259a44e682f1",
|
||||
"pub2": "1ffa76c5cc7a836af6914b840483726207cb750889753d7499fb8b76aa8fe0de",
|
||||
"conversation_key": "a39970a667b7f861f100e3827f4adbf6f464e2697686fe1a81aeda817d6b8bdf"
|
||||
},
|
||||
{
|
||||
"sec1": "5fc0070dbd0666dbddc21d788db04050b86ed8b456b080794c2a0c8e33287bb6",
|
||||
"pub2": "31990752f296dd22e146c9e6f152a269d84b241cc95bb3ff8ec341628a54caf0",
|
||||
"conversation_key": "72c21075f4b2349ce01a3e604e02a9ab9f07e35dd07eff746de348b4f3c6365e"
|
||||
},
|
||||
{
|
||||
"sec1": "1b7de0d64d9b12ddbb52ef217a3a7c47c4362ce7ea837d760dad58ab313cba64",
|
||||
"pub2": "24383541dd8083b93d144b431679d70ef4eec10c98fceef1eff08b1d81d4b065",
|
||||
"conversation_key": "dd152a76b44e63d1afd4dfff0785fa07b3e494a9e8401aba31ff925caeb8f5b1"
|
||||
},
|
||||
{
|
||||
"sec1": "df2f560e213ca5fb33b9ecde771c7c0cbd30f1cf43c2c24de54480069d9ab0af",
|
||||
"pub2": "eeea26e552fc8b5e377acaa03e47daa2d7b0c787fac1e0774c9504d9094c430e",
|
||||
"conversation_key": "770519e803b80f411c34aef59c3ca018608842ebf53909c48d35250bd9323af6"
|
||||
},
|
||||
{
|
||||
"sec1": "cffff919fcc07b8003fdc63bc8a00c0f5dc81022c1c927c62c597352190d95b9",
|
||||
"pub2": "eb5c3cca1a968e26684e5b0eb733aecfc844f95a09ac4e126a9e58a4e4902f92",
|
||||
"conversation_key": "46a14ee7e80e439ec75c66f04ad824b53a632b8409a29bbb7c192e43c00bb795"
|
||||
},
|
||||
{
|
||||
"sec1": "64ba5a685e443e881e9094647ddd32db14444bb21aa7986beeba3d1c4673ba0a",
|
||||
"pub2": "50e6a4339fac1f3bf86f2401dd797af43ad45bbf58e0801a7877a3984c77c3c4",
|
||||
"conversation_key": "968b9dbbfcede1664a4ca35a5d3379c064736e87aafbf0b5d114dff710b8a946"
|
||||
},
|
||||
{
|
||||
"sec1": "dd0c31ccce4ec8083f9b75dbf23cc2878e6d1b6baa17713841a2428f69dee91a",
|
||||
"pub2": "b483e84c1339812bed25be55cff959778dfc6edde97ccd9e3649f442472c091b",
|
||||
"conversation_key": "09024503c7bde07eb7865505891c1ea672bf2d9e25e18dd7a7cea6c69bf44b5d"
|
||||
},
|
||||
{
|
||||
"sec1": "af71313b0d95c41e968a172b33ba5ebd19d06cdf8a7a98df80ecf7af4f6f0358",
|
||||
"pub2": "2a5c25266695b461ee2af927a6c44a3c598b8095b0557e9bd7f787067435bc7c",
|
||||
"conversation_key": "fe5155b27c1c4b4e92a933edae23726a04802a7cc354a77ac273c85aa3c97a92"
|
||||
},
|
||||
{
|
||||
"sec1": "6636e8a389f75fe068a03b3edb3ea4a785e2768e3f73f48ffb1fc5e7cb7289dc",
|
||||
"pub2": "514eb2064224b6a5829ea21b6e8f7d3ea15ff8e70e8555010f649eb6e09aec70",
|
||||
"conversation_key": "ff7afacd4d1a6856d37ca5b546890e46e922b508639214991cf8048ddbe9745c"
|
||||
},
|
||||
{
|
||||
"sec1": "94b212f02a3cfb8ad147d52941d3f1dbe1753804458e6645af92c7b2ea791caa",
|
||||
"pub2": "f0cac333231367a04b652a77ab4f8d658b94e86b5a8a0c472c5c7b0d4c6a40cc",
|
||||
"conversation_key": "e292eaf873addfed0a457c6bd16c8effde33d6664265697f69f420ab16f6669b"
|
||||
},
|
||||
{
|
||||
"sec1": "aa61f9734e69ae88e5d4ced5aae881c96f0d7f16cca603d3bed9eec391136da6",
|
||||
"pub2": "4303e5360a884c360221de8606b72dd316da49a37fe51e17ada4f35f671620a6",
|
||||
"conversation_key": "8e7d44fd4767456df1fb61f134092a52fcd6836ebab3b00766e16732683ed848"
|
||||
},
|
||||
{
|
||||
"sec1": "5e914bdac54f3f8e2cba94ee898b33240019297b69e96e70c8a495943a72fc98",
|
||||
"pub2": "5bd097924f606695c59f18ff8fd53c174adbafaaa71b3c0b4144a3e0a474b198",
|
||||
"conversation_key": "f5a0aecf2984bf923c8cd5e7bb8be262d1a8353cb93959434b943a07cf5644bc"
|
||||
},
|
||||
{
|
||||
"sec1": "8b275067add6312ddee064bcdbeb9d17e88aa1df36f430b2cea5cc0413d8278a",
|
||||
"pub2": "65bbbfca819c90c7579f7a82b750a18c858db1afbec8f35b3c1e0e7b5588e9b8",
|
||||
"conversation_key": "2c565e7027eb46038c2263563d7af681697107e975e9914b799d425effd248d6"
|
||||
},
|
||||
{
|
||||
"sec1": "1ac848de312285f85e0f7ec208aac20142a1f453402af9b34ec2ec7a1f9c96fc",
|
||||
"pub2": "45f7318fe96034d23ee3ddc25b77f275cc1dd329664dd51b89f89c4963868e41",
|
||||
"conversation_key": "b56e970e5057a8fd929f8aad9248176b9af87819a708d9ddd56e41d1aec74088"
|
||||
},
|
||||
{
|
||||
"sec1": "295a1cf621de401783d29d0e89036aa1c62d13d9ad307161b4ceb535ba1b40e6",
|
||||
"pub2": "840115ddc7f1034d3b21d8e2103f6cb5ab0b63cf613f4ea6e61ae3d016715cdd",
|
||||
"conversation_key": "b4ee9c0b9b9fef88975773394f0a6f981ca016076143a1bb575b9ff46e804753"
|
||||
},
|
||||
{
|
||||
"sec1": "a28eed0fe977893856ab9667e06ace39f03abbcdb845c329a1981be438ba565d",
|
||||
"pub2": "b0f38b950a5013eba5ab4237f9ed29204a59f3625c71b7e210fec565edfa288c",
|
||||
"conversation_key": "9d3a802b45bc5aeeb3b303e8e18a92ddd353375710a31600d7f5fff8f3a7285b"
|
||||
},
|
||||
{
|
||||
"sec1": "7ab65af72a478c05f5c651bdc4876c74b63d20d04cdbf71741e46978797cd5a4",
|
||||
"pub2": "f1112159161b568a9cb8c9dd6430b526c4204bcc8ce07464b0845b04c041beda",
|
||||
"conversation_key": "943884cddaca5a3fef355e9e7f08a3019b0b66aa63ec90278b0f9fdb64821e79"
|
||||
},
|
||||
{
|
||||
"sec1": "95c79a7b75ba40f2229e85756884c138916f9d103fc8f18acc0877a7cceac9fe",
|
||||
"pub2": "cad76bcbd31ca7bbda184d20cc42f725ed0bb105b13580c41330e03023f0ffb3",
|
||||
"conversation_key": "81c0832a669eea13b4247c40be51ccfd15bb63fcd1bba5b4530ce0e2632f301b"
|
||||
},
|
||||
{
|
||||
"sec1": "baf55cc2febd4d980b4b393972dfc1acf49541e336b56d33d429bce44fa12ec9",
|
||||
"pub2": "0c31cf87fe565766089b64b39460ebbfdedd4a2bc8379be73ad3c0718c912e18",
|
||||
"conversation_key": "37e2344da9ecdf60ae2205d81e89d34b280b0a3f111171af7e4391ded93b8ea6"
|
||||
},
|
||||
{
|
||||
"sec1": "6eeec45acd2ed31693c5256026abf9f072f01c4abb61f51cf64e6956b6dc8907",
|
||||
"pub2": "e501b34ed11f13d816748c0369b0c728e540df3755bab59ed3327339e16ff828",
|
||||
"conversation_key": "afaa141b522ddb27bb880d768903a7f618bb8b6357728cae7fb03af639b946e6"
|
||||
},
|
||||
{
|
||||
"sec1": "261a076a9702af1647fb343c55b3f9a4f1096273002287df0015ba81ce5294df",
|
||||
"pub2": "b2777c863878893ae100fb740c8fab4bebd2bf7be78c761a75593670380a6112",
|
||||
"conversation_key": "76f8d2853de0734e51189ced523c09427c3e46338b9522cd6f74ef5e5b475c74"
|
||||
},
|
||||
{
|
||||
"sec1": "ed3ec71ca406552ea41faec53e19f44b8f90575eda4b7e96380f9cc73c26d6f3",
|
||||
"pub2": "86425951e61f94b62e20cae24184b42e8e17afcf55bafa58645efd0172624fae",
|
||||
"conversation_key": "f7ffc520a3a0e9e9b3c0967325c9bf12707f8e7a03f28b6cd69ae92cf33f7036"
|
||||
},
|
||||
{
|
||||
"sec1": "5a788fc43378d1303ac78639c59a58cb88b08b3859df33193e63a5a3801c722e",
|
||||
"pub2": "a8cba2f87657d229db69bee07850fd6f7a2ed070171a06d006ec3a8ac562cf70",
|
||||
"conversation_key": "7d705a27feeedf78b5c07283362f8e361760d3e9f78adab83e3ae5ce7aeb6409"
|
||||
},
|
||||
{
|
||||
"sec1": "63bffa986e382b0ac8ccc1aa93d18a7aa445116478be6f2453bad1f2d3af2344",
|
||||
"pub2": "b895c70a83e782c1cf84af558d1038e6b211c6f84ede60408f519a293201031d",
|
||||
"conversation_key": "3a3b8f00d4987fc6711d9be64d9c59cf9a709c6c6481c2cde404bcc7a28f174e"
|
||||
},
|
||||
{
|
||||
"sec1": "e4a8bcacbf445fd3721792b939ff58e691cdcba6a8ba67ac3467b45567a03e5c",
|
||||
"pub2": "b54053189e8c9252c6950059c783edb10675d06d20c7b342f73ec9fa6ed39c9d",
|
||||
"conversation_key": "7b3933b4ef8189d347169c7955589fc1cfc01da5239591a08a183ff6694c44ad"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"get_message_keys": {
|
||||
"conversation_key": "a1a3d60f3470a8612633924e91febf96dc5366ce130f658b1f0fc652c20b3b54",
|
||||
"keys": [
|
||||
{
|
||||
"nonce": "e1e6f880560d6d149ed83dcc7e5861ee62a5ee051f7fde9975fe5d25d2a02d72",
|
||||
"chacha_key": "f145f3bed47cb70dbeaac07f3a3fe683e822b3715edb7c4fe310829014ce7d76",
|
||||
"chacha_nonce": "c4ad129bb01180c0933a160c",
|
||||
"hmac_key": "027c1db445f05e2eee864a0975b0ddef5b7110583c8c192de3732571ca5838c4"
|
||||
},
|
||||
{
|
||||
"nonce": "e1d6d28c46de60168b43d79dacc519698512ec35e8ccb12640fc8e9f26121101",
|
||||
"chacha_key": "e35b88f8d4a8f1606c5082f7a64b100e5d85fcdb2e62aeafbec03fb9e860ad92",
|
||||
"chacha_nonce": "22925e920cee4a50a478be90",
|
||||
"hmac_key": "46a7c55d4283cb0df1d5e29540be67abfe709e3b2e14b7bf9976e6df994ded30"
|
||||
},
|
||||
{
|
||||
"nonce": "cfc13bef512ac9c15951ab00030dfaf2626fdca638dedb35f2993a9eeb85d650",
|
||||
"chacha_key": "020783eb35fdf5b80ef8c75377f4e937efb26bcbad0e61b4190e39939860c4bf",
|
||||
"chacha_nonce": "d3594987af769a52904656ac",
|
||||
"hmac_key": "237ec0ccb6ebd53d179fa8fd319e092acff599ef174c1fdafd499ef2b8dee745"
|
||||
},
|
||||
{
|
||||
"nonce": "ea6eb84cac23c5c1607c334e8bdf66f7977a7e374052327ec28c6906cbe25967",
|
||||
"chacha_key": "ff68db24b34fa62c78ac5ffeeaf19533afaedf651fb6a08384e46787f6ce94be",
|
||||
"chacha_nonce": "50bb859aa2dde938cc49ec7a",
|
||||
"hmac_key": "06ff32e1f7b29753a727d7927b25c2dd175aca47751462d37a2039023ec6b5a6"
|
||||
},
|
||||
{
|
||||
"nonce": "8c2e1dd3792802f1f9f7842e0323e5d52ad7472daf360f26e15f97290173605d",
|
||||
"chacha_key": "2f9daeda8683fdeede81adac247c63cc7671fa817a1fd47352e95d9487989d8b",
|
||||
"chacha_nonce": "400224ba67fc2f1b76736916",
|
||||
"hmac_key": "465c05302aeeb514e41c13ed6405297e261048cfb75a6f851ffa5b445b746e4b"
|
||||
},
|
||||
{
|
||||
"nonce": "05c28bf3d834fa4af8143bf5201a856fa5fac1a3aee58f4c93a764fc2f722367",
|
||||
"chacha_key": "1e3d45777025a035be566d80fd580def73ed6f7c043faec2c8c1c690ad31c110",
|
||||
"chacha_nonce": "021905b1ea3afc17cb9bf96f",
|
||||
"hmac_key": "74a6e481a89dcd130aaeb21060d7ec97ad30f0007d2cae7b1b11256cc70dfb81"
|
||||
},
|
||||
{
|
||||
"nonce": "5e043fb153227866e75a06d60185851bc90273bfb93342f6632a728e18a07a17",
|
||||
"chacha_key": "1ea72c9293841e7737c71567d8120145a58991aaa1c436ef77bf7adb83f882f1",
|
||||
"chacha_nonce": "72f69a5a5f795465cee59da8",
|
||||
"hmac_key": "e9daa1a1e9a266ecaa14e970a84bce3fbbf329079bbccda626582b4e66a0d4c9"
|
||||
},
|
||||
{
|
||||
"nonce": "7be7338eaf06a87e274244847fe7a97f5c6a91f44adc18fcc3e411ad6f786dbf",
|
||||
"chacha_key": "881e7968a1f0c2c80742ee03cd49ea587e13f22699730f1075ade01931582bf6",
|
||||
"chacha_nonce": "6e69be92d61c04a276021565",
|
||||
"hmac_key": "901afe79e74b19967c8829af23617d7d0ffbf1b57190c096855c6a03523a971b"
|
||||
},
|
||||
{
|
||||
"nonce": "94571c8d590905bad7becd892832b472f2aa5212894b6ce96e5ba719c178d976",
|
||||
"chacha_key": "f80873dd48466cb12d46364a97b8705c01b9b4230cb3ec3415a6b9551dc42eef",
|
||||
"chacha_nonce": "3dda53569cfcb7fac1805c35",
|
||||
"hmac_key": "e9fc264345e2839a181affebc27d2f528756e66a5f87b04bf6c5f1997047051e"
|
||||
},
|
||||
{
|
||||
"nonce": "13a6ee974b1fd759135a2c2010e3cdda47081c78e771125e4f0c382f0284a8cb",
|
||||
"chacha_key": "bc5fb403b0bed0d84cf1db872b6522072aece00363178c98ad52178d805fca85",
|
||||
"chacha_nonce": "65064239186e50304cc0f156",
|
||||
"hmac_key": "e872d320dde4ed3487958a8e43b48aabd3ced92bc24bb8ff1ccb57b590d9701a"
|
||||
},
|
||||
{
|
||||
"nonce": "082fecdb85f358367b049b08be0e82627ae1d8edb0f27327ccb593aa2613b814",
|
||||
"chacha_key": "1fbdb1cf6f6ea816349baf697932b36107803de98fcd805ebe9849b8ad0e6a45",
|
||||
"chacha_nonce": "2e605e1d825a3eaeb613db9c",
|
||||
"hmac_key": "fae910f591cf3c7eb538c598583abad33bc0a03085a96ca4ea3a08baf17c0eec"
|
||||
},
|
||||
{
|
||||
"nonce": "4c19020c74932c30ec6b2d8cd0d5bb80bd0fc87da3d8b4859d2fb003810afd03",
|
||||
"chacha_key": "1ab9905a0189e01cda82f843d226a82a03c4f5b6dbea9b22eb9bc953ba1370d4",
|
||||
"chacha_nonce": "cbb2530ea653766e5a37a83a",
|
||||
"hmac_key": "267f68acac01ac7b34b675e36c2cef5e7b7a6b697214add62a491bedd6efc178"
|
||||
},
|
||||
{
|
||||
"nonce": "67723a3381497b149ce24814eddd10c4c41a1e37e75af161930e6b9601afd0ff",
|
||||
"chacha_key": "9ecbd25e7e2e6c97b8c27d376dcc8c5679da96578557e4e21dba3a7ef4e4ac07",
|
||||
"chacha_nonce": "ef649fcf335583e8d45e3c2e",
|
||||
"hmac_key": "04dbbd812fa8226fdb45924c521a62e3d40a9e2b5806c1501efdeba75b006bf1"
|
||||
},
|
||||
{
|
||||
"nonce": "42063fe80b093e8619b1610972b4c3ab9e76c14fd908e642cd4997cafb30f36c",
|
||||
"chacha_key": "211c66531bbcc0efcdd0130f9f1ebc12a769105eb39608994bcb188fa6a73a4a",
|
||||
"chacha_nonce": "67803605a7e5010d0f63f8c8",
|
||||
"hmac_key": "e840e4e8921b57647369d121c5a19310648105dbdd008200ebf0d3b668704ff8"
|
||||
},
|
||||
{
|
||||
"nonce": "b5ac382a4be7ac03b554fe5f3043577b47ea2cd7cfc7e9ca010b1ffbb5cf1a58",
|
||||
"chacha_key": "b3b5f14f10074244ee42a3837a54309f33981c7232a8b16921e815e1f7d1bb77",
|
||||
"chacha_nonce": "4e62a0073087ed808be62469",
|
||||
"hmac_key": "c8efa10230b5ea11633816c1230ca05fa602ace80a7598916d83bae3d3d2ccd7"
|
||||
},
|
||||
{
|
||||
"nonce": "e9d1eba47dd7e6c1532dc782ff63125db83042bb32841db7eeafd528f3ea7af9",
|
||||
"chacha_key": "54241f68dc2e50e1db79e892c7c7a471856beeb8d51b7f4d16f16ab0645d2f1a",
|
||||
"chacha_nonce": "a963ed7dc29b7b1046820a1d",
|
||||
"hmac_key": "aba215c8634530dc21c70ddb3b3ee4291e0fa5fa79be0f85863747bde281c8b2"
|
||||
},
|
||||
{
|
||||
"nonce": "a94ecf8efeee9d7068de730fad8daf96694acb70901d762de39fa8a5039c3c49",
|
||||
"chacha_key": "c0565e9e201d2381a2368d7ffe60f555223874610d3d91fbbdf3076f7b1374dd",
|
||||
"chacha_nonce": "329bb3024461e84b2e1c489b",
|
||||
"hmac_key": "ac42445491f092481ce4fa33b1f2274700032db64e3a15014fbe8c28550f2fec"
|
||||
},
|
||||
{
|
||||
"nonce": "533605ea214e70c25e9a22f792f4b78b9f83a18ab2103687c8a0075919eaaa53",
|
||||
"chacha_key": "ab35a5e1e54d693ff023db8500d8d4e79ad8878c744e0eaec691e96e141d2325",
|
||||
"chacha_nonce": "653d759042b85194d4d8c0a7",
|
||||
"hmac_key": "b43628e37ba3c31ce80576f0a1f26d3a7c9361d29bb227433b66f49d44f167ba"
|
||||
},
|
||||
{
|
||||
"nonce": "7f38df30ceea1577cb60b355b4f5567ff4130c49e84fed34d779b764a9cc184c",
|
||||
"chacha_key": "a37d7f211b84a551a127ff40908974eb78415395d4f6f40324428e850e8c42a3",
|
||||
"chacha_nonce": "b822e2c959df32b3cb772a7c",
|
||||
"hmac_key": "1ba31764f01f69b5c89ded2d7c95828e8052c55f5d36f1cd535510d61ba77420"
|
||||
},
|
||||
{
|
||||
"nonce": "11b37f9dbc4d0185d1c26d5f4ed98637d7c9701fffa65a65839fa4126573a4e5",
|
||||
"chacha_key": "964f38d3a31158a5bfd28481247b18dd6e44d69f30ba2a40f6120c6d21d8a6ba",
|
||||
"chacha_nonce": "5f72c5b87c590bcd0f93b305",
|
||||
"hmac_key": "2fc4553e7cedc47f29690439890f9f19c1077ef3e9eaeef473d0711e04448918"
|
||||
},
|
||||
{
|
||||
"nonce": "8be790aa483d4cdd843189f71f135b3ec7e31f381312c8fe9f177aab2a48eafa",
|
||||
"chacha_key": "95c8c74d633721a131316309cf6daf0804d59eaa90ea998fc35bac3d2fbb7a94",
|
||||
"chacha_nonce": "409a7654c0e4bf8c2c6489be",
|
||||
"hmac_key": "21bb0b06eb2b460f8ab075f497efa9a01c9cf9146f1e3986c3bf9da5689b6dc4"
|
||||
},
|
||||
{
|
||||
"nonce": "19fd2a718ea084827d6bd73f509229ddf856732108b59fc01819f611419fd140",
|
||||
"chacha_key": "cc6714b9f5616c66143424e1413d520dae03b1a4bd202b82b0a89b0727f5cdc8",
|
||||
"chacha_nonce": "1b7fd2534f015a8f795d8f32",
|
||||
"hmac_key": "2bef39c4ce5c3c59b817e86351373d1554c98bc131c7e461ed19d96cfd6399a0"
|
||||
},
|
||||
{
|
||||
"nonce": "3c2acd893952b2f6d07d8aea76f545ca45961a93fe5757f6a5a80811d5e0255d",
|
||||
"chacha_key": "c8de6c878cb469278d0af894bc181deb6194053f73da5014c2b5d2c8db6f2056",
|
||||
"chacha_nonce": "6ffe4f1971b904a1b1a81b99",
|
||||
"hmac_key": "df1cd69dd3646fca15594284744d4211d70e7d8472e545d276421fbb79559fd4"
|
||||
},
|
||||
{
|
||||
"nonce": "7dbea4cead9ac91d4137f1c0a6eebb6ba0d1fb2cc46d829fbc75f8d86aca6301",
|
||||
"chacha_key": "c8e030f6aa680c3d0b597da9c92bb77c21c4285dd620c5889f9beba7446446b0",
|
||||
"chacha_nonce": "a9b5a67d081d3b42e737d16f",
|
||||
"hmac_key": "355a85f551bc3cce9a14461aa60994742c9bbb1c81a59ca102dc64e61726ab8e"
|
||||
},
|
||||
{
|
||||
"nonce": "45422e676cdae5f1071d3647d7a5f1f5adafb832668a578228aa1155a491f2f3",
|
||||
"chacha_key": "758437245f03a88e2c6a32807edfabff51a91c81ca2f389b0b46f2c97119ea90",
|
||||
"chacha_nonce": "263830a065af33d9c6c5aa1f",
|
||||
"hmac_key": "7c581cf3489e2de203a95106bfc0de3d4032e9d5b92b2b61fb444acd99037e17"
|
||||
},
|
||||
{
|
||||
"nonce": "babc0c03fad24107ad60678751f5db2678041ff0d28671ede8d65bdf7aa407e9",
|
||||
"chacha_key": "bd68a28bd48d9ffa3602db72c75662ac2848a0047a313d2ae2d6bc1ac153d7e9",
|
||||
"chacha_nonce": "d0f9d2a1ace6c758f594ffdd",
|
||||
"hmac_key": "eb435e3a642adfc9d59813051606fc21f81641afd58ea6641e2f5a9f123bb50a"
|
||||
},
|
||||
{
|
||||
"nonce": "7a1b8aac37d0d20b160291fad124ab697cfca53f82e326d78fef89b4b0ea8f83",
|
||||
"chacha_key": "9e97875b651a1d30d17d086d1e846778b7faad6fcbc12e08b3365d700f62e4fe",
|
||||
"chacha_nonce": "ccdaad5b3b7645be430992eb",
|
||||
"hmac_key": "6f2f55cf35174d75752f63c06cc7cbc8441759b142999ed2d5a6d09d263e1fc4"
|
||||
},
|
||||
{
|
||||
"nonce": "8370e4e32d7e680a83862cab0da6136ef607014d043e64cdf5ecc0c4e20b3d9a",
|
||||
"chacha_key": "1472bed5d19db9c546106de946e0649cd83cc9d4a66b087a65906e348dcf92e2",
|
||||
"chacha_nonce": "ed02dece5fc3a186f123420b",
|
||||
"hmac_key": "7b3f7739f49d30c6205a46b174f984bb6a9fc38e5ccfacef2dac04fcbd3b184e"
|
||||
},
|
||||
{
|
||||
"nonce": "9f1c5e8a29cd5677513c2e3a816551d6833ee54991eb3f00d5b68096fc8f0183",
|
||||
"chacha_key": "5e1a7544e4d4dafe55941fcbdf326f19b0ca37fc49c4d47e9eec7fb68cde4975",
|
||||
"chacha_nonce": "7d9acb0fdc174e3c220f40de",
|
||||
"hmac_key": "e265ab116fbbb86b2aefc089a0986a0f5b77eda50c7410404ad3b4f3f385c7a7"
|
||||
},
|
||||
{
|
||||
"nonce": "c385aa1c37c2bfd5cc35fcdbdf601034d39195e1cabff664ceb2b787c15d0225",
|
||||
"chacha_key": "06bf4e60677a13e54c4a38ab824d2ef79da22b690da2b82d0aa3e39a14ca7bdd",
|
||||
"chacha_nonce": "26b450612ca5e905b937e147",
|
||||
"hmac_key": "22208152be2b1f5f75e6bfcc1f87763d48bb7a74da1be3d102096f257207f8b3"
|
||||
},
|
||||
{
|
||||
"nonce": "3ff73528f88a50f9d35c0ddba4560bacee5b0462d0f4cb6e91caf41847040ce4",
|
||||
"chacha_key": "850c8a17a23aa761d279d9901015b2bbdfdff00adbf6bc5cf22bd44d24ecabc9",
|
||||
"chacha_nonce": "4a296a1fb0048e5020d3b129",
|
||||
"hmac_key": "b1bf49a533c4da9b1d629b7ff30882e12d37d49c19abd7b01b7807d75ee13806"
|
||||
},
|
||||
{
|
||||
"nonce": "2dcf39b9d4c52f1cb9db2d516c43a7c6c3b8c401f6a4ac8f131a9e1059957036",
|
||||
"chacha_key": "17f8057e6156ba7cc5310d01eda8c40f9aa388f9fd1712deb9511f13ecc37d27",
|
||||
"chacha_nonce": "a8188daff807a1182200b39d",
|
||||
"hmac_key": "47b89da97f68d389867b5d8a2d7ba55715a30e3d88a3cc11f3646bc2af5580ef"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"payload": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb"
|
||||
},
|
||||
{
|
||||
"sec1": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||
"sec2": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"conversation_key": "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d",
|
||||
"nonce": "f00000000000000000000000000000f00000000000000000000000000000000f",
|
||||
"plaintext": "🍕🫃",
|
||||
"payload": "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj"
|
||||
},
|
||||
{
|
||||
"sec1": "5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a",
|
||||
"sec2": "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d",
|
||||
"conversation_key": "3e2b52a63be47d34fe0a80e34e73d436d6963bc8f39827f327057a9986c20a45",
|
||||
"nonce": "b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b",
|
||||
"plaintext": "表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀",
|
||||
"payload": "ArY1I2xC2yDwIbuNHN/1ynXdGgzHLqdCrXUPMwELJPc7s7JqlCMJBAIIjfkpHReBPXeoMCyuClwgbT419jUWU1PwaNl4FEQYKCDKVJz+97Mp3K+Q2YGa77B6gpxB/lr1QgoqpDf7wDVrDmOqGoiPjWDqy8KzLueKDcm9BVP8xeTJIxs="
|
||||
},
|
||||
{
|
||||
"sec1": "8f40e50a84a7462e2b8d24c28898ef1f23359fff50d8c509e6fb7ce06e142f9c",
|
||||
"sec2": "b9b0a1e9cc20100c5faa3bbe2777303d25950616c4c6a3fa2e3e046f936ec2ba",
|
||||
"conversation_key": "d5a2f879123145a4b291d767428870f5a8d9e5007193321795b40183d4ab8c2b",
|
||||
"nonce": "b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf",
|
||||
"plaintext": "ability🤝的 ȺȾ",
|
||||
"payload": "ArIJia3D3cQc0sQ1lSwNWakTFdjFIY1QQFc/w3SVQ6yvbG2S0x4Yu86QGwPTy7mP3961I1XqB6SFFTzqDZZavhxoWMj7mEVGMQIsh2RLWI5EYQaQDIePSnXPlzf7CIt+voTD"
|
||||
},
|
||||
{
|
||||
"sec1": "875adb475056aec0b4809bd2db9aa00cff53a649e7b59d8edcbf4e6330b0995c",
|
||||
"sec2": "9c05781112d5b0a2a7148a222e50e0bd891d6b60c5483f03456e982185944aae",
|
||||
"conversation_key": "3b15c977e20bfe4b8482991274635edd94f366595b1a3d2993515705ca3cedb8",
|
||||
"nonce": "8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1",
|
||||
"plaintext": "pepper👀їжак",
|
||||
"payload": "Ao1EQnE+udR5EXXLBA2Y1vxb6IZNbsL4nPCJWisrctGxY3AduCS+jTUgAAnfvKafkmpy15+i9YMwCdccisRa8SvzW671T2JO4LFSPX31K4kYUKelSAdSPwe9NwO6LhOsnoJ+"
|
||||
},
|
||||
{
|
||||
"sec1": "eba1687cab6a3101bfc68fd70f214aa4cc059e9ec1b79fdb9ad0a0a4e259829f",
|
||||
"sec2": "dff20d262bef9dfd94666548f556393085e6ea421c8af86e9d333fa8747e94b3",
|
||||
"conversation_key": "4f1538411098cf11c8af216836444787c462d47f97287f46cf7edb2c4915b8a5",
|
||||
"nonce": "2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407",
|
||||
"plaintext": "( ͡° ͜ʖ ͡°)",
|
||||
"payload": "AiGAtSrmRfz59QgNgbHwtdbyzXf/PJhogrtUkVhGLzQHv4qhKQwnFQ54OjVMgqCea/Vj0YqBSdhqNR777TJ4zIUk7R0fnizp6l1zwgzWv7+ee6u+0/89KIjY5q1wu6inyuiv"
|
||||
},
|
||||
{
|
||||
"sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e",
|
||||
"sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214",
|
||||
"conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd",
|
||||
"nonce": "e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68",
|
||||
"plaintext": "مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،",
|
||||
"payload": "AuTNX3zk7qAkvHGxetRWqYanSsQmwsYrChXrXFyPiItoIBsWu1CB+sStla2M4VeANASHxM78i1CfHQQH1YbBy24Tng7emYW44ol6QkFD6D8Zq7QPl+8L1c47lx8RoODEQMvNCbOk5ffUV3/AhONHBXnffrI+0025c+uRGzfqpYki4lBqm9iYU+k3Tvjczq9wU0mkVDEaM34WiQi30MfkJdRbeeYaq6kNvGPunLb3xdjjs5DL720d61Flc5ZfoZm+CBhADy9D9XiVZYLKAlkijALJur9dATYKci6OBOoc2SJS2Clai5hOVzR0yVeyHRgRfH9aLSlWW5dXcUxTo7qqRjNf8W5+J4jF4gNQp5f5d0YA4vPAzjBwSP/5bGzNDslKfcAH"
|
||||
},
|
||||
{
|
||||
"sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e",
|
||||
"sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214",
|
||||
"conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd",
|
||||
"nonce": "38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023",
|
||||
"plaintext": "الكل في المجمو عة (5)",
|
||||
"payload": "AjjRygq++eX1ZOiXYahs7gRXS2gl0+8gY7EK11iZ5LAjbOTrlfrxak5Lki42v2jMPpLSicy8eHjsWkkMtF0i925vOaKG/ZkMHh9ccQBdfTvgEGKzztedqDCAWb5TP1YwU1PsWaiiqG3+WgVvJiO4lUdMHXL7+zKKx8bgDtowzz4QAwI="
|
||||
},
|
||||
{
|
||||
"sec1": "d5633530f5bcfebceb5584cfbbf718a30df0751b729dd9a789b9f30c0587d74e",
|
||||
"sec2": "b74e6a341fb134127272b795a08b59250e5fa45a82a2eb4095e4ce9ed5f5e214",
|
||||
"conversation_key": "75fe686d21a035f0c7cd70da64ba307936e5ca0b20710496a6b6b5f573377bdd",
|
||||
"nonce": "4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0",
|
||||
"plaintext": "𝖑𝖆𝖟𝖞 社會科學院語學研究所",
|
||||
"payload": "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 ॣ ॣ冗",
|
||||
"payload": "AqPiGSQthUZecK3NZAtWSz/v9X0u+HRdXnoGY7LczOtUf05aMF89q1FLwJvaFJYICZoMYgRJHFLwPiOHce7fuAc40kX0wXJvipyBJ9HzCOj7CgtnC1/cmPCHR3s5AIORmroBWglm1LiFMohv1FSPEbaBD51VXxJa4JyWpYhreSOEjn1wd0lMKC9b+osV2N2tpbs+rbpQem2tRen3sWflmCqjkG5VOVwRErCuXuPb5+hYwd8BoZbfCrsiAVLd7YT44dRtKNBx6rkabWfddKSLtreHLDysOhQUVOp/XkE7OzSkWl6sky0Hva6qJJ/V726hMlomvcLHjE41iKmW2CpcZfOedg=="
|
||||
}
|
||||
],
|
||||
"encrypt_decrypt_long_msg": [
|
||||
{
|
||||
"conversation_key": "8fc262099ce0d0bb9b89bac05bb9e04f9bc0090acc181fef6840ccee470371ed",
|
||||
"nonce": "326bcb2c943cd6bb717588c9e5a7e738edf6ed14ec5f5344caa6ef56f0b9cff7",
|
||||
"pattern": "x",
|
||||
"repeat": 65535,
|
||||
"plaintext_sha256": "09ab7495d3e61a76f0deb12cb0306f0696cbb17ffc12131368c7a939f12f56d3",
|
||||
"payload_sha256": "90714492225faba06310bff2f249ebdc2a5e609d65a629f1c87f2d4ffc55330a"
|
||||
},
|
||||
{
|
||||
"conversation_key": "56adbe3720339363ab9c3b8526ffce9fd77600927488bfc4b59f7a68ffe5eae0",
|
||||
"nonce": "ad68da81833c2a8ff609c3d2c0335fd44fe5954f85bb580c6a8d467aa9fc5dd0",
|
||||
"pattern": "!",
|
||||
"repeat": 65535,
|
||||
"plaintext_sha256": "6af297793b72ae092c422e552c3bb3cbc310da274bd1cf9e31023a7fe4a2d75e",
|
||||
"payload_sha256": "8013e45a109fad3362133132b460a2d5bce235fe71c8b8f4014793fb52a49844"
|
||||
},
|
||||
{
|
||||
"conversation_key": "7fc540779979e472bb8d12480b443d1e5eb1098eae546ef2390bee499bbf46be",
|
||||
"nonce": "34905e82105c20de9a2f6cd385a0d541e6bcc10601d12481ff3a7575dc622033",
|
||||
"pattern": "🦄",
|
||||
"repeat": 16383,
|
||||
"plaintext_sha256": "a249558d161b77297bc0cb311dde7d77190f6571b25c7e4429cd19044634a61f",
|
||||
"payload_sha256": "b3348422471da1f3c59d79acfe2fe103f3cd24488109e5b18734cdb5953afd15"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invalid": {
|
||||
"encrypt_msg_lengths": [0, 65536, 100000, 10000000],
|
||||
"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",
|
||||
"payload": "#Atqupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJdU0MIDf06CUvEvdnr1cp1fiMtlM/GrE92xAc1K5odTpCzUB+mjXgbaqtntBUbTToSUoT0ovrlPwzGjyp",
|
||||
"note": "unknown encryption version"
|
||||
},
|
||||
{
|
||||
"conversation_key": "36f04e558af246352dcf73b692fbd3646a2207bd8abd4b1cd26b234db84d9481",
|
||||
"nonce": "ad408d4be8616dc84bb0bf046454a2a102edac937c35209c43cd7964c5feb781",
|
||||
"plaintext": "⚠️",
|
||||
"payload": "AK1AjUvoYW3IS7C/BGRUoqEC7ayTfDUgnEPNeWTF/reBZFaha6EAIRueE9D1B1RuoiuFScC0Q94yjIuxZD3JStQtE8JMNacWFs9rlYP+ZydtHhRucp+lxfdvFlaGV/sQlqZz",
|
||||
"note": "unknown encryption version 0"
|
||||
},
|
||||
{
|
||||
"conversation_key": "ca2527a037347b91bea0c8a30fc8d9600ffd81ec00038671e3a0f0cb0fc9f642",
|
||||
"nonce": "daaea5ca345b268e5b62060ca72c870c48f713bc1e00ff3fc0ddb78e826f10db",
|
||||
"plaintext": "n o s t r",
|
||||
"payload": "Atфupco0WyaOW2IGDKcshwxI9xO8HgD/P8Ddt46CbxDbrhdG8VmJZE0UICD06CUvEvdnr1cp1fiMtlM/GrE92xAc1EwsVCQEgWEu2gsHUVf4JAa3TpgkmFc3TWsax0v6n/Wq",
|
||||
"note": "invalid base64"
|
||||
},
|
||||
{
|
||||
"conversation_key": "cff7bd6a3e29a450fd27f6c125d5edeb0987c475fd1e8d97591e0d4d8a89763c",
|
||||
"nonce": "09ff97750b084012e15ecb84614ce88180d7b8ec0d468508a86b6d70c0361a25",
|
||||
"plaintext": "¯\\_(ツ)_/¯",
|
||||
"payload": "Agn/l3ULCEAS4V7LhGFM6IGA17jsDUaFCKhrbXDANholyySBfeh+EN8wNB9gaLlg4j6wdBYh+3oK+mnxWu3NKRbSvQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"note": "invalid MAC"
|
||||
},
|
||||
{
|
||||
"conversation_key": "cfcc9cf682dfb00b11357f65bdc45e29156b69db424d20b3596919074f5bf957",
|
||||
"nonce": "65b14b0b949aaa7d52c417eb753b390e8ad6d84b23af4bec6d9bfa3e03a08af4",
|
||||
"plaintext": "🥎",
|
||||
"payload": "AmWxSwuUmqp9UsQX63U7OQ6K1thLI69L7G2b+j4DoIr0oRWQ8avl4OLqWZiTJ10vIgKrNqjoaX+fNhE9RqmR5g0f6BtUg1ijFMz71MO1D4lQLQfW7+UHva8PGYgQ1QpHlKgR",
|
||||
"note": "invalid MAC"
|
||||
},
|
||||
{
|
||||
"conversation_key": "5254827d29177622d40a7b67cad014fe7137700c3c523903ebbe3e1b74d40214",
|
||||
"nonce": "7ab65dbb8bbc2b8e35cafb5745314e1f050325a864d11d0475ef75b3660d91c1",
|
||||
"plaintext": "elliptic-curve cryptography",
|
||||
"payload": "Anq2XbuLvCuONcr7V0UxTh8FAyWoZNEdBHXvdbNmDZHB573MI7R7rrTYftpqmvUpahmBC2sngmI14/L0HjOZ7lWGJlzdh6luiOnGPc46cGxf08MRC4CIuxx3i2Lm0KqgJ7vA",
|
||||
"note": "invalid padding"
|
||||
},
|
||||
{
|
||||
"conversation_key": "fea39aca9aa8340c3a78ae1f0902aa7e726946e4efcd7783379df8096029c496",
|
||||
"nonce": "7d4283e3b54c885d6afee881f48e62f0a3f5d7a9e1cb71ccab594a7882c39330",
|
||||
"plaintext": "noble",
|
||||
"payload": "An1Cg+O1TIhdav7ogfSOYvCj9dep4ctxzKtZSniCw5MwRrrPJFyAQYZh5VpjC2QYzny5LIQ9v9lhqmZR4WBYRNJ0ognHVNMwiFV1SHpvUFT8HHZN/m/QarflbvDHAtO6pY16",
|
||||
"note": "invalid padding"
|
||||
},
|
||||
{
|
||||
"conversation_key": "0c4cffb7a6f7e706ec94b2e879f1fc54ff8de38d8db87e11787694d5392d5b3f",
|
||||
"nonce": "6f9fd72667c273acd23ca6653711a708434474dd9eb15c3edb01ce9a95743e9b",
|
||||
"plaintext": "censorship-resistant and global social network",
|
||||
"payload": "Am+f1yZnwnOs0jymZTcRpwhDRHTdnrFcPtsBzpqVdD6b2NZDaNm/TPkZGr75kbB6tCSoq7YRcbPiNfJXNch3Tf+o9+zZTMxwjgX/nm3yDKR2kHQMBhVleCB9uPuljl40AJ8kXRD0gjw+aYRJFUMK9gCETZAjjmrsCM+nGRZ1FfNsHr6Z",
|
||||
"note": "invalid padding"
|
||||
},
|
||||
{
|
||||
"conversation_key": "5cd2d13b9e355aeb2452afbd3786870dbeecb9d355b12cb0a3b6e9da5744cd35",
|
||||
"nonce": "b60036976a1ada277b948fd4caa065304b96964742b89d26f26a25263a5060bd",
|
||||
"plaintext": "0",
|
||||
"payload": "",
|
||||
"note": "invalid payload length: 0"
|
||||
},
|
||||
{
|
||||
"conversation_key": "d61d3f09c7dfe1c0be91af7109b60a7d9d498920c90cbba1e137320fdd938853",
|
||||
"nonce": "1a29d02c8b4527745a2ccb38bfa45655deb37bc338ab9289d756354cea1fd07c",
|
||||
"plaintext": "1",
|
||||
"payload": "Ag==",
|
||||
"note": "invalid payload length: 4"
|
||||
},
|
||||
{
|
||||
"conversation_key": "873bb0fc665eb950a8e7d5971965539f6ebd645c83c08cd6a85aafbad0f0bc47",
|
||||
"nonce": "c826d3c38e765ab8cc42060116cd1464b2a6ce01d33deba5dedfb48615306d4a",
|
||||
"plaintext": "2",
|
||||
"payload": "AqxgToSh3H7iLYRJjoWAM+vSv/Y1mgNlm6OWWjOYUClrFF8=",
|
||||
"note": "invalid payload length: 48"
|
||||
},
|
||||
{
|
||||
"conversation_key": "9f2fef8f5401ac33f74641b568a7a30bb19409c76ffdc5eae2db6b39d2617fbe",
|
||||
"nonce": "9ff6484642545221624eaac7b9ea27133a4cc2356682a6033aceeef043549861",
|
||||
"plaintext": "3",
|
||||
"payload": "Ap/2SEZCVFIhYk6qx7nqJxM6TMI1ZoKmAzrO7vBDVJhhuZXWiM20i/tIsbjT0KxkJs2MZjh1oXNYMO9ggfk7i47WQA==",
|
||||
"note": "invalid payload length: 92"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LargeDBSignatureCheck {
|
||||
@Test
|
||||
fun insertDatabaseSample() =
|
||||
runBlocking {
|
||||
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_short.json")
|
||||
|
||||
val eventArray =
|
||||
JsonMapper.mapper.readValue<ArrayList<Event>>(
|
||||
InputStreamReader(fullDBInputStream),
|
||||
) as List<Event>
|
||||
|
||||
var counter = 0
|
||||
eventArray.forEach {
|
||||
assertTrue(it.verify())
|
||||
counter++
|
||||
}
|
||||
|
||||
assertEquals(eventArray.size, counter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertStartupDatabase() =
|
||||
runBlocking {
|
||||
// This file includes duplicates
|
||||
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json")
|
||||
|
||||
val eventArray =
|
||||
JsonMapper.mapper.readValue<ArrayList<Event>>(
|
||||
GZIPInputStream(fullDBInputStream),
|
||||
) as List<Event>
|
||||
|
||||
var counter = 0
|
||||
eventArray.forEach {
|
||||
if (it.sig != "") {
|
||||
assertTrue(it.verify())
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
assertEquals(eventArray.size, counter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.bloom
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import com.vitorpamplona.quartz.utils.sha256.Sha256Hasher
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.math.BigInteger
|
||||
import java.util.Base64
|
||||
import java.util.BitSet
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class BloomFilter(
|
||||
private val size: Int,
|
||||
private val rounds: Int,
|
||||
private val bits: BitSet = BitSet(size),
|
||||
private val salt: ByteArray = RandomInstance.bytes(8),
|
||||
) {
|
||||
private val hash = Sha256Hasher()
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
|
||||
fun add(value: HexKey) = add(value.hexToByteArray())
|
||||
|
||||
fun print() =
|
||||
buildString {
|
||||
for (seed in 0 until bits.size()) {
|
||||
append(if (bits.get(seed)) "1" else "0")
|
||||
}
|
||||
}
|
||||
|
||||
fun add(value: ByteArray) {
|
||||
lock.write {
|
||||
for (seed in 0 until rounds) {
|
||||
bits.set(hash(seed, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mightContains(value: HexKey): Boolean = mightContains(value.hexToByteArray())
|
||||
|
||||
fun mightContains(value: ByteArray): Boolean {
|
||||
lock.read {
|
||||
for (seed in 0 until rounds) {
|
||||
if (!bits.get(hash(seed, value))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun encode() = "$size:$rounds:${Base64.getEncoder().encodeToString(bits.toByteArray())}:${Base64.getEncoder().encodeToString(salt)}"
|
||||
|
||||
fun hash(
|
||||
seed: Int,
|
||||
value: ByteArray,
|
||||
) = BigInteger(1, hash.hash(value + salt + seed.toByte()))
|
||||
.remainder(BigInteger.valueOf(size.toLong()))
|
||||
.toInt()
|
||||
|
||||
companion object {
|
||||
fun decode(encodedStr: String): BloomFilter {
|
||||
val (sizeStr, roundsStr, filterB64, saltB64) = encodedStr.split(":")
|
||||
val bitSet = BitSet.valueOf(Base64.getDecoder().decode(filterB64))
|
||||
val salt = Base64.getDecoder().decode(saltB64)
|
||||
return BloomFilter(sizeStr.toInt(), roundsStr.toInt(), bitSet, salt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BloomFilterTest {
|
||||
val testEncoded = "100:10:AAAkAQANcYQFCQoB:hZkZYqqdxcE="
|
||||
val testInBinary = "00000000000000000010010010000000000000001011000010001110001000011010000010010000010100001000000000000000000000000000000000000000"
|
||||
|
||||
@Test
|
||||
fun testCreate() {
|
||||
val bloomFilter = BloomFilter(100, 10, salt = Base64.getDecoder().decode("hZkZYqqdxcE="))
|
||||
bloomFilter.add("ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b")
|
||||
bloomFilter.add("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
|
||||
assertEquals(testEncoded, bloomFilter.encode())
|
||||
assertEquals(testInBinary, bloomFilter.print())
|
||||
|
||||
assertTrue(bloomFilter.mightContains("ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"))
|
||||
assertTrue(bloomFilter.mightContains("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"))
|
||||
|
||||
assertFalse(bloomFilter.mightContains("560c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDecoding() {
|
||||
val bloomFilter = BloomFilter.decode(testEncoded)
|
||||
|
||||
assertEquals(testEncoded, bloomFilter.encode())
|
||||
assertEquals(testInBinary, bloomFilter.print())
|
||||
|
||||
assertTrue(bloomFilter.mightContains("ca29c211f1c72d5b6622268ff43d2288ea2b2cb5b9aa196ff9f1704fc914b71b"))
|
||||
assertTrue(bloomFilter.mightContains("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"))
|
||||
|
||||
assertFalse(bloomFilter.mightContains("560c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProb() {
|
||||
val bloomFilter = BloomFilter.decode(testEncoded)
|
||||
|
||||
var failureCounter = 0
|
||||
for (seed in 0..1000000) {
|
||||
if (bloomFilter.mightContains(Nip01.pubKeyCreate(Nip01.privKeyCreate()))) {
|
||||
failureCounter++
|
||||
}
|
||||
}
|
||||
assertEquals(0, failureCounter)
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.lightning
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LnInvoiceUtilTest {
|
||||
@Test
|
||||
fun test100KAmountCalculation() {
|
||||
val bolt11 =
|
||||
"lnbc1m1pjt9u0qsp553q90pj5mafzv20w45eqavned9tgwhl4q99n9s5ppcw24nzw3zeqpp5002kd3ktym67du86kj665fgaev7ka8ys7j5yz5fg686lr5e2gfkshp5dkk27nnuax05az3pk2r6ytxtvwn5j4xzsq9ajprhc7crjkmgvr3qxqyjw5qcqpjrzjqtzxvfsuxe4l92pf97tt4rcgpy2xalkmlwexh899wqxf83l8nwv4xzh0gvqq89qqqqqqqqlgqqqqq0gqvs9qxpqysgqx5mz04wd7kqu5zhhel9enr036hjrp4gga0nz084p2asjl36a0zmrk6mhqa249zsgqref2rlvhffm73u7rxgr47gden6rugup4ksvpzsqvds4pz"
|
||||
// Context of the app under test.
|
||||
Assert.assertEquals(100000, LnInvoiceUtil.getAmountInSats(bolt11).toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test100GAmountCalculation() {
|
||||
val bolt11 =
|
||||
"lnbc1000000000000000p1pjtxqf0pp5myqxhcufqy56elfsg9dd4dthnqptusnnpwnul7u86l95xzjgqd8shp5gueg34sgm3u3nxqjqyunvvqdu0pr6jz6mwh4ew4886f2lpf4cmrqxqztgsp5w0cdfd45dfnqwex5gn85x7fru3jcrxhlcx3enx835477m3gdfcuq9qyyssqelrcmm7p9qazgjuxtdg7sd8nq5cscl2tratjlclt5rk5mc7uq2lphq3r2a43j5ua4leakc4emq8yp2yxdnzvzszpw6u2afac0kgl7hspfj67ta"
|
||||
// Context of the app under test.
|
||||
Assert.assertEquals(100000000000, LnInvoiceUtil.getAmountInSats(bolt11).toLong())
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class EventSigCheck {
|
||||
val payload1 =
|
||||
"[\"EVENT\",\"40b9\",{\"id\":\"48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf\"," +
|
||||
"\"kind\":1,\"pubkey\":\"3d842afecd5e293f28b6627933704a3fb8ce153aa91d790ab11f6a752d44a42d\"," +
|
||||
"\"created_at\":1677940007,\"content\":" +
|
||||
"\"I got asked about follower count again today. Why does my follower count go down when " +
|
||||
"I delete public relays (in our list) and replace them with filter.nostr.wine? \\n\\nI’ll " +
|
||||
"give you one final explanation to rule them all. First, let’s go over how clients calculate " +
|
||||
"your follower count.\\n\\n1. Your client sends a request to all your connected relays asking " +
|
||||
"for accounts who follow you\\n2. Relays answer back with the events requested\\n3. The client " +
|
||||
"aggregates the event total and displays it\\n\\nEach relay has a set limit on how many stored " +
|
||||
"events it will return per request. For some relays it’s 500, others 1000, some as high as 5000. " +
|
||||
"Let’s say for simplicity that all your public relays use 500 as their limit. If you ask 10 " +
|
||||
"relays for your followers the max possible answer you can get is 5000. That won’t change if " +
|
||||
"you have 20,000 followers or 100,000. You may get back a “different” 5000 each time, but you’ll " +
|
||||
"still cap out at 5000 because that is the most events your client will receive.\u2028\u2028Our " +
|
||||
"limit on filter.nostr.wine is 2000 events. If you replace 10 public relays with only " +
|
||||
"filter.nostr.wine, the MOST followers you will ever get back from our filter relay is 2000. " +
|
||||
"That doesn’t mean you only have 2000 followers or that your reach is reduced in any way.\\n\\nAs " +
|
||||
"long as you are writing to and reading from the same public relays, neither your reach nor any " +
|
||||
"content was lost. That concludes my TED talk. I hope you all have a fantastic day and weekend.\"," +
|
||||
"\"tags\":[],\"sig\":\"dcaf8ab98bb9179017b35bd814092850d1062b26c263dff89fb1ae8c019a324139d1729012d" +
|
||||
"9d05ff0a517f76b1117d869b2cc7d36bea8aa5f4b94c5e2548aa8\"}]"
|
||||
|
||||
@Test
|
||||
fun testUnicode2028and2029ShouldNotBeEscaped() {
|
||||
val msg = JsonMapper.mapper.readTree(payload1)
|
||||
val event = JsonMapper.fromJson(msg[2])
|
||||
|
||||
// Should pass
|
||||
event.checkSignature()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.utils.Secp256k1Instance
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip01Test {
|
||||
private val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
|
||||
@Test
|
||||
fun testGetPublicFromPrivateKey() {
|
||||
assertEquals(
|
||||
"7d4b8806f1fd713c287235411bf95aa81b7242ead892733ec84b3f2719845be6",
|
||||
Nip01.pubKeyCreate(privateKey).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPublicCompressedWith2() {
|
||||
val key = "e6159851715b4aa6190c22b899b0c792847de0a4435ac5b678f35738351c43b0".hexToByteArray()
|
||||
assertEquals(
|
||||
"029fa4ce8c87ca546b196e6518db80a6780e1bd5552b61f9f17bafee5d4e34e09b",
|
||||
Secp256k1Instance.compressedPubKeyFor(key).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPublicCompressedWith3() {
|
||||
val key = "65f039136f8da8d3e87b4818746b53318d5481e24b2673f162815144223a0b5a".hexToByteArray()
|
||||
assertEquals(
|
||||
"033dcef7585efbdb68747d919152bd481e21f5e952aaaef5a19604fbd096a93dd5",
|
||||
Secp256k1Instance.compressedPubKeyFor(key).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeterministicSign() {
|
||||
assertEquals(
|
||||
"1484d0e0bd62165e822e31f1f4cc8e1ce8e20c30a060e24fb0ecd7baf7c624f661fb7a3e4f0ddb43018e5f0b4892c929af64d8b7a86021aa081ec8231e3dfa37",
|
||||
Nip01.sign(sha256("Test".toByteArray()), privateKey, null).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSha256() {
|
||||
assertEquals(
|
||||
"532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25",
|
||||
sha256("Test".toByteArray()).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeterministicVerify() {
|
||||
assertTrue(
|
||||
Nip01.verify(
|
||||
"1484d0e0bd62165e822e31f1f4cc8e1ce8e20c30a060e24fb0ecd7baf7c624f661fb7a3e4f0ddb43018e5f0b4892c929af64d8b7a86021aa081ec8231e3dfa37".hexToByteArray(),
|
||||
sha256("Test".toByteArray()),
|
||||
Nip01.pubKeyCreate(privateKey),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNonDeterministicSign() {
|
||||
assertNotEquals(
|
||||
"1484d0e0bd62165e822e31f1f4cc8e1ce8e20c30a060e24fb0ecd7baf7c624f661fb7a3e4f0ddb43018e5f0b4892c929af64d8b7a86021aa081ec8231e3dfa37",
|
||||
Nip01.sign(sha256("Test".toByteArray()), privateKey).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNonDeterministicSignVerify() {
|
||||
val signature = Nip01.sign(sha256("Test".toByteArray()), privateKey)
|
||||
assertTrue(
|
||||
Nip01.verify(
|
||||
signature,
|
||||
sha256("Test".toByteArray()),
|
||||
Nip01.pubKeyCreate(privateKey),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip01CryptoTest {
|
||||
@Test
|
||||
fun testGetPublicFromPrivateKey() {
|
||||
val privateKey =
|
||||
"f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
val publicKey = Nip01.pubKeyCreate(privateKey).toHexKey()
|
||||
assertEquals("7d4b8806f1fd713c287235411bf95aa81b7242ead892733ec84b3f2719845be6", publicKey)
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class AddressableTest {
|
||||
private lateinit var db: EventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, "test.db", relayUrl = "testUrl")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplacingAddressables() {
|
||||
val time = TimeUtils.now()
|
||||
val version1 = signer.sign(LongTextNoteEvent.build("my cool blog, version 1", "title", dTag = "my-cool-blog", createdAt = time))
|
||||
val version2 = signer.sign(LongTextNoteEvent.build("my cool blog, version 2", "title", dTag = "my-cool-blog", createdAt = time + 1))
|
||||
val version3 = signer.sign(LongTextNoteEvent.build("my cool blog, version 3", "title", dTag = "my-cool-blog", createdAt = time + 2))
|
||||
|
||||
db.insert(version1)
|
||||
|
||||
db.assertQuery(version1, Filter(ids = listOf(version1.id)))
|
||||
|
||||
db.insert(version2)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
db.assertQuery(version2, Filter(ids = listOf(version2.id)))
|
||||
|
||||
db.insert(version3)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version2.id)))
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBlockingOldAddressables() {
|
||||
val time = TimeUtils.now()
|
||||
val version1 = signer.sign(LongTextNoteEvent.build("my cool blog, version 1", "title", dTag = "my-cool-blog", createdAt = time))
|
||||
val version2 = signer.sign(LongTextNoteEvent.build("my cool blog, version 2", "title", dTag = "my-cool-blog", createdAt = time + 1))
|
||||
val version3 = signer.sign(LongTextNoteEvent.build("my cool blog, version 3", "title", dTag = "my-cool-blog", createdAt = time + 2))
|
||||
|
||||
db.insert(version3)
|
||||
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
|
||||
try {
|
||||
db.insert(version2)
|
||||
fail("It should not allow inserting an older version")
|
||||
} catch (e: Exception) {
|
||||
TestCase.assertTrue(e is SQLiteConstraintException)
|
||||
}
|
||||
|
||||
try {
|
||||
db.insert(version1)
|
||||
fail("It should not allow inserting an older version")
|
||||
} catch (e: Exception) {
|
||||
TestCase.assertTrue(e is SQLiteConstraintException)
|
||||
}
|
||||
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import junit.framework.TestCase
|
||||
|
||||
fun EventStore.assertQuery(
|
||||
expected: Event?,
|
||||
filter: Filter,
|
||||
) {
|
||||
val queryResult = query(filter)
|
||||
val countResult = count(filter)
|
||||
if (expected == null) {
|
||||
TestCase.assertEquals(0, queryResult.size)
|
||||
TestCase.assertEquals(0, countResult)
|
||||
} else {
|
||||
TestCase.assertEquals(1, queryResult.size)
|
||||
TestCase.assertEquals(1, countResult)
|
||||
TestCase.assertEquals(expected.toJson(), queryResult.first().toJson())
|
||||
}
|
||||
}
|
||||
|
||||
fun EventStore.assertQuery(
|
||||
expected: List<Event>,
|
||||
filter: Filter,
|
||||
) {
|
||||
val queryResult = query(filter)
|
||||
val countResult = count(filter)
|
||||
TestCase.assertEquals(expected.size, queryResult.size)
|
||||
TestCase.assertEquals(expected.size, countResult)
|
||||
expected.forEachIndexed { index, event ->
|
||||
TestCase.assertEquals(event.toJson(), queryResult[index].toJson())
|
||||
}
|
||||
}
|
||||
|
||||
fun SQLiteEventStore.assertQuery(
|
||||
expected: Event?,
|
||||
filter: Filter,
|
||||
) {
|
||||
val queryResult = query(filter)
|
||||
val countResult = count(filter)
|
||||
if (expected == null) {
|
||||
TestCase.assertEquals(0, queryResult.size)
|
||||
TestCase.assertEquals(0, countResult)
|
||||
} else {
|
||||
TestCase.assertEquals(1, queryResult.size)
|
||||
TestCase.assertEquals(1, countResult)
|
||||
TestCase.assertEquals(expected.toJson(), queryResult.first().toJson())
|
||||
}
|
||||
}
|
||||
|
||||
fun SQLiteEventStore.assertQuery(
|
||||
expected: List<Event>,
|
||||
filter: Filter,
|
||||
) {
|
||||
val queryResult = query(filter)
|
||||
val countResult = count(filter)
|
||||
TestCase.assertEquals(expected.size, queryResult.size)
|
||||
TestCase.assertEquals(expected.size, countResult)
|
||||
expected.forEachIndexed { index, event ->
|
||||
TestCase.assertEquals(event.toJson(), queryResult[index].toJson())
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class BasicTest {
|
||||
private lateinit var db: SQLiteEventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
companion object Companion {
|
||||
val profile =
|
||||
MetadataEvent(
|
||||
id = "490d7439e530423f2540d4f2bdb73a0a2935f3df9e1f2a6f699a140c7db311fe",
|
||||
pubKey = "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
createdAt = 1740669816,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", "User profile for Vitor"),
|
||||
arrayOf("name", "Vitor"),
|
||||
),
|
||||
content = "{\"name\":\"Vitor\"}",
|
||||
sig = "977a6152199f17d103d8d56736ed1b7767054464cf9423d017c01c8cdd2344698f0a5e13da8dff98d01bb1f798837e3b6271e1fd1cac861bb90686f622ae6ef4",
|
||||
)
|
||||
|
||||
val comment =
|
||||
CommentEvent(
|
||||
id = "fecb2ecf61a1433d417a784d10bd1e8ec19a916170a53ca8fb3a15fc666a6592",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1747753115,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", "Reply to geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3"),
|
||||
arrayOf("I", "geo:drt"),
|
||||
arrayOf("I", "geo:dr"),
|
||||
arrayOf("I", "geo:d"),
|
||||
arrayOf("K", "geo"),
|
||||
arrayOf("i", "geo:drt3n"),
|
||||
arrayOf("i", "geo:drt3"),
|
||||
arrayOf("i", "geo:drt"),
|
||||
arrayOf("i", "geo:dr"),
|
||||
arrayOf("i", "geo:d"),
|
||||
arrayOf("k", "geo"),
|
||||
),
|
||||
content = "testing",
|
||||
sig = "12070e663272f1227c639fb834eb2122fc7bb995f4c49e55ebb1dfe2135ef7347d44810bacd2e64fd26b8826fd47d2800ce6c3d3b579bb3afe39088ffd4faa60",
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = SQLiteEventStore(context, null)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertDeleteEvent() {
|
||||
val note = signer.sign(TextNoteEvent.build("test1"))
|
||||
|
||||
db.insertEvent(note)
|
||||
|
||||
db.assertQuery(note, Filter(ids = listOf(note.id)))
|
||||
|
||||
db.delete(note.id)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(note.id)))
|
||||
|
||||
db.insertEvent(note)
|
||||
|
||||
db.assertQuery(note, Filter(ids = listOf(note.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyFilter() {
|
||||
val note1 = signer.sign(TextNoteEvent.build("test1", createdAt = 1))
|
||||
val note2 = signer.sign(TextNoteEvent.build("test2", createdAt = 2))
|
||||
|
||||
db.insertEvent(note1)
|
||||
|
||||
db.assertQuery(note1, Filter())
|
||||
|
||||
db.insertEvent(note2)
|
||||
|
||||
db.assertQuery(listOf(note2, note1), Filter())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLimitFilter() {
|
||||
val note1 = signer.sign(TextNoteEvent.build("test1", createdAt = 1))
|
||||
val note2 = signer.sign(TextNoteEvent.build("test2", createdAt = 2))
|
||||
val note3 = signer.sign(TextNoteEvent.build("test3", createdAt = 3))
|
||||
val note4 = signer.sign(TextNoteEvent.build("test4", createdAt = 4))
|
||||
|
||||
db.insertEvent(note1)
|
||||
|
||||
db.assertQuery(note1, Filter(limit = 1))
|
||||
|
||||
db.insertEvent(note2)
|
||||
db.insertEvent(note3)
|
||||
db.insertEvent(note4)
|
||||
|
||||
db.assertQuery(listOf(note4), Filter(limit = 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPubkeyTag() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.assertQuery(
|
||||
comment,
|
||||
Filter(authors = listOf(comment.pubKey), tags = mapOf("I" to listOf("geo:drt3n"))),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagOnly() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.assertQuery(comment, Filter(tags = mapOf("I" to listOf("geo:drt3n"))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagWithSinceOnly() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.assertQuery(
|
||||
comment,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), since = comment.createdAt - 1),
|
||||
)
|
||||
db.assertQuery(
|
||||
comment,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), since = comment.createdAt),
|
||||
)
|
||||
db.assertQuery(
|
||||
null,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), since = comment.createdAt + 1),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagWithUntilOnly() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.assertQuery(
|
||||
null,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), until = comment.createdAt - 1),
|
||||
)
|
||||
db.assertQuery(
|
||||
comment,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), until = comment.createdAt),
|
||||
)
|
||||
db.assertQuery(
|
||||
comment,
|
||||
Filter(tags = mapOf("I" to listOf("geo:drt3n")), until = comment.createdAt + 1),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagWithUntilOnlyEmitting() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.query(Filter(tags = mapOf("I" to listOf("geo:drt3n")))) { event ->
|
||||
assertEquals(comment.toJson(), event.toJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class DeletionTest {
|
||||
private lateinit var db: EventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, null)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertDeleteEvent() {
|
||||
val note1 = signer.sign(TextNoteEvent.build("test1"))
|
||||
val note2 = signer.sign(TextNoteEvent.build("test2"))
|
||||
val note3 = signer.sign(TextNoteEvent.build("test3"))
|
||||
|
||||
db.insert(note1)
|
||||
db.insert(note2)
|
||||
db.insert(note3)
|
||||
|
||||
db.assertQuery(note1, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(note2, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
val deletion = signer.sign(DeletionEvent.build(listOf(note1)))
|
||||
|
||||
db.insert(deletion)
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(note2, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
// trying to insert again should fail.
|
||||
try {
|
||||
db.insert(note1)
|
||||
fail("Should not be able to insert a deleted event")
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
assertEquals("blocked: a deletion event for this event id exists (code 1811 SQLITE_CONSTRAINT_TRIGGER)", e.message)
|
||||
}
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(note2, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertDeleteEventOfAddressable() {
|
||||
val time = TimeUtils.now()
|
||||
val note1 = signer.sign(LongTextNoteEvent.build("my cool blog, version 1", "title", dTag = "my-cool-blog", createdAt = time))
|
||||
val note2 = signer.sign(LongTextNoteEvent.build("my cool blog, version 2", "title", dTag = "my-cool-blog", createdAt = time + 1))
|
||||
val note3 = signer.sign(LongTextNoteEvent.build("my cool blog, version 3", "title", dTag = "my-cool-blog", createdAt = time + 2))
|
||||
|
||||
db.insert(note1)
|
||||
|
||||
db.assertQuery(note1, Filter(ids = listOf(note1.id)))
|
||||
|
||||
db.insert(note2)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(note2, Filter(ids = listOf(note2.id)))
|
||||
|
||||
db.insert(note3)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
val deletion = signer.sign(DeletionEvent.build(listOf(note1)))
|
||||
|
||||
db.insert(deletion)
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note3.id)))
|
||||
|
||||
// trying to insert again should fail.
|
||||
try {
|
||||
db.insert(note1)
|
||||
fail("Should not be able to insert a deleted event")
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
assertEquals("blocked: a deletion event for this event id exists (code 1811 SQLITE_CONSTRAINT_TRIGGER)", e.message)
|
||||
}
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note3.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertDeleteEventOfAddressable2() {
|
||||
val time = TimeUtils.now()
|
||||
val note1 = signer.sign(LongTextNoteEvent.build("my cool blog, version 1", "title", dTag = "my-cool-blog", createdAt = time))
|
||||
val note2 = signer.sign(LongTextNoteEvent.build("my cool blog, version 2", "title", dTag = "my-cool-blog", createdAt = time + 1))
|
||||
val note3 = signer.sign(LongTextNoteEvent.build("my cool blog, version 3", "title", dTag = "my-cool-blog", createdAt = time + 2))
|
||||
|
||||
db.insert(note1)
|
||||
db.insert(note2)
|
||||
db.insert(note3)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
val deletion = signer.sign(DeletionEvent.buildAddressOnly(listOf(note1)))
|
||||
|
||||
db.insert(deletion)
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note3.id)))
|
||||
|
||||
// trying to insert again should fail.
|
||||
try {
|
||||
db.insert(note1)
|
||||
fail("Should not be able to insert a deleted event")
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
assertEquals("blocked: a deletion event for this address exists (code 1811 SQLITE_CONSTRAINT_TRIGGER)", e.message)
|
||||
}
|
||||
|
||||
db.assertQuery(deletion, Filter(ids = listOf(deletion.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note3.id)))
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ExpirationTest {
|
||||
private lateinit var db: EventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, null, relayUrl = "testUrl")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeletingExpiredEvents() {
|
||||
val time = TimeUtils.now()
|
||||
|
||||
val noteSafe =
|
||||
signer.sign(
|
||||
TextNoteEvent.build("test1", createdAt = time + 1) {
|
||||
expiration(time + 100)
|
||||
},
|
||||
)
|
||||
|
||||
db.insert(noteSafe)
|
||||
|
||||
val noteToExpire =
|
||||
signer.sign(
|
||||
TextNoteEvent.build("test1", createdAt = time + 1) {
|
||||
expiration(time + 1)
|
||||
},
|
||||
)
|
||||
|
||||
db.insert(noteToExpire)
|
||||
|
||||
db.assertQuery(noteToExpire, Filter(ids = listOf(noteToExpire.id)))
|
||||
|
||||
Thread.sleep(2000)
|
||||
|
||||
db.deleteExpiredEvents()
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(noteToExpire.id)))
|
||||
db.assertQuery(noteSafe, Filter(ids = listOf(noteSafe.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertingExpiredEvents() {
|
||||
val time = TimeUtils.now()
|
||||
|
||||
val note1 =
|
||||
signer.sign(
|
||||
TextNoteEvent.build("test1", createdAt = time - 12) {
|
||||
expiration(time - 10)
|
||||
},
|
||||
)
|
||||
|
||||
try {
|
||||
db.insert(note1)
|
||||
fail("Should not be able to insert expired events")
|
||||
} catch (e: Exception) {
|
||||
assertTrue(e is SQLiteConstraintException)
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.util.Log
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.zip.GZIPInputStream
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LargeDBTests {
|
||||
companion object {
|
||||
fun getEventDB(): List<Event> {
|
||||
// This file includes duplicates
|
||||
val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json")
|
||||
|
||||
return JsonMapper.mapper.readValue<ArrayList<Event>>(
|
||||
GZIPInputStream(fullDBInputStream),
|
||||
)
|
||||
}
|
||||
|
||||
val events = getEventDB().distinctBy { it.id }.filter { !it.isExpired() }.sortedBy { it.createdAt }
|
||||
}
|
||||
|
||||
private lateinit var db: EventStore
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, "largeDBTest.db")
|
||||
db.store.clearDB()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.store.clearDB()
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertHeavyEvent() {
|
||||
events.first { it.id == "3f34b8cb682307ec11753de4669ce8948e95fd6fb360d79136446c5547fd235e" }.let { event ->
|
||||
println(event.toJson())
|
||||
try {
|
||||
val measure =
|
||||
measureTimeMillis {
|
||||
db.insert(event)
|
||||
}
|
||||
if (measure > 1) {
|
||||
println("Inserted event ${event.id} of kind ${event.kind} in $measure ms")
|
||||
}
|
||||
} catch (e: SQLiteException) {
|
||||
Log.w("LargeDBTests", "Error inserting event: ${e.message} for event: ${event.toJson()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertDatabase() {
|
||||
events.forEach { event ->
|
||||
try {
|
||||
val measure =
|
||||
measureTimeMillis {
|
||||
db.insert(event)
|
||||
}
|
||||
if (measure > 1) {
|
||||
println("Inserted event ${event.id} of kind ${event.kind} in $measure ms")
|
||||
}
|
||||
} catch (e: SQLiteException) {
|
||||
Log.w("LargeDBTests", "Error inserting event: ${e.message} for event: ${event.toJson()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ReplaceableTest {
|
||||
private lateinit var db: EventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, null, relayUrl = "testUrl")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplacing() {
|
||||
val time = TimeUtils.now()
|
||||
val version1 = signer.sign(MetadataEvent.createNew("Vitor 1", createdAt = time))
|
||||
val version2 = signer.sign(MetadataEvent.createNew("Vitor 2", createdAt = time + 1))
|
||||
val version3 = signer.sign(MetadataEvent.createNew("Vitor 3", createdAt = time + 2))
|
||||
|
||||
db.insert(version1)
|
||||
|
||||
db.assertQuery(version1, Filter(ids = listOf(version1.id)))
|
||||
|
||||
db.insert(version2)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
db.assertQuery(version2, Filter(ids = listOf(version2.id)))
|
||||
|
||||
db.insert(version3)
|
||||
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version2.id)))
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBlockingOldVersions() {
|
||||
val time = TimeUtils.now()
|
||||
val version1 = signer.sign(MetadataEvent.createNew("Vitor 1", createdAt = time))
|
||||
val version2 = signer.sign(MetadataEvent.createNew("Vitor 2", createdAt = time + 1))
|
||||
val version3 = signer.sign(MetadataEvent.createNew("Vitor 3", createdAt = time + 2))
|
||||
|
||||
db.insert(version3)
|
||||
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
|
||||
try {
|
||||
db.insert(version2)
|
||||
fail("It should not allow inserting an older version")
|
||||
} catch (e: Exception) {
|
||||
TestCase.assertTrue(e is SQLiteConstraintException)
|
||||
}
|
||||
|
||||
try {
|
||||
db.insert(version1)
|
||||
fail("It should not allow inserting an older version")
|
||||
} catch (e: Exception) {
|
||||
TestCase.assertTrue(e is SQLiteConstraintException)
|
||||
}
|
||||
|
||||
db.assertQuery(version3, Filter(ids = listOf(version3.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version2.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(version1.id)))
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class RightToVanishTest {
|
||||
private lateinit var db: EventStore
|
||||
|
||||
val signer = NostrSignerSync()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = EventStore(context, null, relayUrl = "testUrl")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertDeleteEvent() {
|
||||
val time = TimeUtils.now()
|
||||
val note1 = signer.sign(TextNoteEvent.build("test1", createdAt = time))
|
||||
val note2 = signer.sign(TextNoteEvent.build("test2", createdAt = time + 1))
|
||||
val note3 = signer.sign(TextNoteEvent.build("test3", createdAt = time + 2))
|
||||
|
||||
db.insert(note1)
|
||||
db.insert(note2)
|
||||
db.insert(note3)
|
||||
|
||||
db.assertQuery(note1, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(note2, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
val vanish = signer.sign(RequestToVanishEvent.build("testUrl", createdAt = time + 2))
|
||||
|
||||
db.insert(vanish)
|
||||
|
||||
db.assertQuery(vanish, Filter(ids = listOf(vanish.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
|
||||
// trying to insert again should fail.
|
||||
try {
|
||||
db.insert(note1)
|
||||
fail("Should not be able to insert a deleted event")
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
assertEquals("blocked: a request to vanish event exists (code 1811 SQLITE_CONSTRAINT_TRIGGER)", e.message)
|
||||
}
|
||||
|
||||
db.assertQuery(vanish, Filter(ids = listOf(vanish.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note1.id)))
|
||||
db.assertQuery(null, Filter(ids = listOf(note2.id)))
|
||||
db.assertQuery(note3, Filter(ids = listOf(note3.id)))
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.sqlite
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class SearchTest {
|
||||
private lateinit var db: SQLiteEventStore
|
||||
|
||||
companion object Companion {
|
||||
val profile =
|
||||
MetadataEvent(
|
||||
id = "490d7439e530423f2540d4f2bdb73a0a2935f3df9e1f2a6f699a140c7db311fe",
|
||||
pubKey = "70a9b3c312a6b83e476739bd29d60ca700da1d5b982cbca87b5f3d27d4038d67",
|
||||
createdAt = 1740669816,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", "User profile for Vitor"),
|
||||
arrayOf("name", "Vitor"),
|
||||
),
|
||||
content = "{\"name\":\"Vitor\"}",
|
||||
sig = "977a6152199f17d103d8d56736ed1b7767054464cf9423d017c01c8cdd2344698f0a5e13da8dff98d01bb1f798837e3b6271e1fd1cac861bb90686f622ae6ef4",
|
||||
)
|
||||
|
||||
val comment =
|
||||
CommentEvent(
|
||||
id = "fecb2ecf61a1433d417a784d10bd1e8ec19a916170a53ca8fb3a15fc666a6592",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1747753115,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", "Reply to geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3"),
|
||||
arrayOf("I", "geo:drt"),
|
||||
arrayOf("I", "geo:dr"),
|
||||
arrayOf("I", "geo:d"),
|
||||
arrayOf("K", "geo"),
|
||||
arrayOf("i", "geo:drt3n"),
|
||||
arrayOf("i", "geo:drt3"),
|
||||
arrayOf("i", "geo:drt"),
|
||||
arrayOf("i", "geo:dr"),
|
||||
arrayOf("i", "geo:d"),
|
||||
arrayOf("k", "geo"),
|
||||
),
|
||||
content = "testing",
|
||||
sig = "12070e663272f1227c639fb834eb2122fc7bb995f4c49e55ebb1dfe2135ef7347d44810bacd2e64fd26b8826fd47d2800ce6c3d3b579bb3afe39088ffd4faa60",
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = SQLiteEventStore(context, null)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagWithSearch() {
|
||||
db.insertEvent(comment)
|
||||
db.insertEvent(profile)
|
||||
|
||||
db.assertQuery(null, Filter(search = "testing1"))
|
||||
db.assertQuery(comment, Filter(search = "testing"))
|
||||
db.assertQuery(comment, Filter(kinds = listOf(CommentEvent.KIND), search = "testing"))
|
||||
db.assertQuery(null, Filter(kinds = listOf(TextNoteEvent.KIND), search = "testing"))
|
||||
|
||||
db.delete(comment.id)
|
||||
|
||||
db.assertQuery(null, Filter(search = "testing"))
|
||||
db.assertQuery(null, Filter(kinds = listOf(CommentEvent.KIND), search = "testing"))
|
||||
|
||||
db.insertEvent(comment)
|
||||
|
||||
db.assertQuery(comment, Filter(search = "testing"))
|
||||
db.assertQuery(comment, Filter(kinds = listOf(CommentEvent.KIND), search = "testing"))
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.fail
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
|
||||
class OtsTest {
|
||||
val resolver = OtsResolver()
|
||||
|
||||
val otsEvent = "{\"content\":\"AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIqCiiW0FlsU9lqK5f1A+cL6CGJ1Ah4V/A1yNJY/stUE3wECJz6ng/QxU5Z6xwaMx97qkI//AQqJv8bEMrGTplGWRv5qm4DgjxIHkcQqzpL0Fjr9VBAAijDe0IsQYpOhw1SIjZIgQa6i16CPEEZck7CvAIxR0AloJzCZoAg9/jDS75DI4uLWh0dHBzOi8vYWxpY2UuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wEOwPtjIkKI1hmtv9t1kuxZcI8QRlyTsK8Ahl0wrCSggZzgCD3+MNLvkMjiwraHR0cHM6Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wEE1dVGa8JCuf2ek0c5ybDKII8SCBoVz8Sal45Kd1O8STWIGJTcl5JPtAZBZitqk3BE9MqAjxBGXJOwrwCHoGVgAZi9q9AIPf4w0u+QyOKShodHRwczovL2Zpbm5leS5jYWxlbmRhci5ldGVybml0eXdhbGwuY29t8BAFZFXFYg7DJJ0OzjmJ0FKWCPEEZck7CvAI0M49IcBR5bf/AIPf4w0u+QyOIyJodHRwczovL2J0Yy5jYWxlbmRhci5jYXRhbGxheHkuY29tCPEgoE3IfYTmxxo4W/x/QYp/NGX6Wu93gSQkwbpjpOhZORcI8SDWLLurVQaXHdUuwivCfTfuxYCaq+AzypSGqLDAVocrEgjwIBfgjta16y13Gp4etQOCa9YiKEcM+/9AieG/vZolr3IDCPAgMR2zFCb384CEi8tVuI2fHgLT3I9zpe7oqJTzCcEqxWEI8SAJSdgeeosr7IxdOt8r7f0ipWc8FI6GAhgep8zSRgWikAjxIGxYmtCsC79Tx4z4YsT1WuMo+ycMkwhGQsQltF597cchCPAgCLrBf9vR1Aex6yY+vSkXAvLjMKdMqM/a1g8zNPwLeJcI8SBDCbTk4CczTuiIyZeUyYRVh31BZdjaSd2nU/pBQxu+6QjwIOwqE9/WqGC6CHH0i+tr7edvYX5PstSDf08KmnMqsqCoCPEgQIdEBg358vfek3Qjfyrgl51iCU6WUWmThsGLPDTcB0QI8SAX4dp64iI8pBx+zBqAQwUN6XgZ1cEfT8+2vha/9I1vzwjxWQEAAAAB8YJxvxJJth8OnxIV6UOXveIZAcJPTHcAkWgnucpuYqYAAAAAAP3///8CRhoMAAAAAAAWABTUUZK82uAvbU3vVyaaPIOddZBicwAAAAAAAAAAImog8ARCqgwACAjwIB9TcMDLhzgeS1Uw647lNvCfWECkkUvfrrOe6nay0sGdCAjwICYfs90sbPggoMICyOHGYbmOzop2L9mlnh4xqiBLY7yPCAjxINDiVWOBHnRmGJleQdB9myvJAJbNJ9kciZlTOkgJy89mCAjwIHfxqDLdwycj1Vtyth2CaSDdLQwiey9oV6Xov4stLpWNCAjxIGurJYpKJnKp9+y7MAdC+gXgHOiAu5P3RRUFW9l5hGaCCAjxICXqs9hdY0QMP/MNeqlt6s7xaIYtEXZ1CLvou5gaZNEICAjxIHdYxVeI76NXbT2zHcv6lw+v819Ooib7KWxc1GAsiX2fCAjwICPWdi3uBXOlIdmYi+V9C7wAqLyGE4DMoHD+GtvLizqiCAjwIOF2vENtWN5okEMMS+JSf1SGTY9yYP9j0JjXLbC1s+N1CAjxINWsgCtsPxhRNbe372k8/20WDbiL9e8934hGF256DvRECAjwII3mi+Li06j10ORxg0dYMkcsyGb115Jiqq1YEV3K/u+aCAgABYiWDXPXGQEDw9Qy\",\"created_at\":1707690688,\"id\":\"759f9da5846e936fab06766a524b36ba71c03bbc69ad0944fb8ee4bb1f3dd705\",\"kind\":1040,\"pubkey\":\"82fbb08c8ef45c4d71c88368d0ae805bc62fb92f166ab04a0b7a0c83d8cbc29a\",\"sig\":\"07c7896c8cbb97b5d7483097590c9d31b73f35c1ad9e752002bb5c1776cbd852e1d32704333d6930c9bc3e40f8b899a1f2e9f91cc3bf797d86acdecba7792576\",\"tags\":[[\"e\",\"a828a25b4165b14f65a8ae5fd40f9c2fa086275021e15fc0d7234963fb2d504d\"],[\"p\",\"595ca8eaace5899cb6ab7e2542bfc972136376f2eabc09287f1857eb8f167e53\"],[\"alt\",\"NIP-03 time stamp\"]]}"
|
||||
val otsEvent2 = "{\"content\":\"AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIqGNPU2jhd4no+zg2ytDkuf5PIoivr8KHI8BL68aKGNbwENyCNtiEN98IzIZgEu3cl6YI//AQ6TkSRd3BTGhDHCK1KkJc+AjxIAHaizG++NNL3Vm13BJrIhT7Br6tEYpb0TVRGaadgiUMCPAgOSDREH9v1Y50UHu79LfC4Lcd9WklQJzRQpw+Unb/pyII8QRltDD58AgqrxfAVrLw7QCD3+MNLvkMji4taHR0cHM6Ly9hbGljZS5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFtcHMub3Jn//AQQMq/CLpGwY60nmddPS7OVgjxIDKxqd9nl+Mej41vP52Wd7gv7004r3n1rFGDObS8icRvCPAgH9TB/kwvXJEEw+h9Ce6fLaI3MORjtTEge0GbAefT6W4I8QRltDD58AhRcoU3gAo/swCD3+MNLvkMjiwraHR0cHM6Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wECWtWsKo0uvSr8BYonjs3DEI8CBlsh2ng1Spl0K4oStYElGuMJsjd2uo5nXB+apo5A7ipwjxIM8oxynBwNA+QS/X7Ebtl1kyhFgfoOQioASNfCBzZ4gaCPEEZbQw+fAId6Yd5cw5gioAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2FsbC5jb23wEJmPzXQbxv0AFTIyjTWjMskI8CAurbkrfrBtlinZXSDxj+m/oIkze57hGjTSxu1Xs87XYQjwIPk/LMD0zIgKoEE2dfeoYrrdHuO6dwmghTwUFajH2QzkCPEEZbQw+fAIE+Pq1/Wmdpj/AIPf4w0u+QyOIyJodHRwczovL2J0Yy5jYWxlbmRhci5jYXRhbGxheHkuY29tCPAgB2CbqkV7VpjRKIl3Ea6cBmB/EHcSN/YCgcc1E+mc07QI8CAfpkZ2Hh4Rukz3x4il3tZqQtlDlbna+I6so2t2YSEmMQjwIJOv32jbsMa2HJwpleRCKLEhgYOoHCSfpv1ZO0YNNNFsCPAgLMM7eFfCjokQfU4gdU5WpG/wBLkO9lDRF0GktL6ujt8I8SCRxJ0bC1PQ8qFmI/1jh8AS5d1/6VRJNMt1Hz41QmNr3QjwIBmgrKBF+OZ3y+XOMv2E7IZ4WwLr2u2H+ehsBfy7cPlICPEgm4ZMCSXzZVWu40d+zk2edaur6KOauo8X7V2KaFBR1VoI8SBKVVOiyq6IFqGn/15kLwk7L8upMAIZ0znjhYxYqSTQCQjwIMBD1twPZ33GxbwTiuOCeJPkoP++6R2wYpCii8UBTdgwCPAg84VkgMXwrt2xxRoeC1/6CtsFctki+w3m8Rs5/6g/IhEI8CCnzZQDhJyicX7bS7U8PMUObuC9Y4TXe+4THoXBMMXkxwjxIMZ0oAvshpcwowR3qPEDbwKZ6B4NPSU4Hz/+4PnD74gnCPAgIfLZEKqAvkMNXfakXoNq1UVqGSzL4Z86z5GzUfbvw8UI8SC8KoIeLvjd4vJ/xhNVphakPRd80YKeNkeYEuVH8k2EtAjwIPRtinLLxzt8iuw0XZtpTDzEstZOTNYVm+Bi3fEzdeIuCPFZAQAAAAE8vsasINN5DKon0KakX2HNdCB126ZLKXrw4PfvyEfqbwAAAAAA/f///wLPGw8AAAAAABYAFCvgxlh6msa4ZOtgvlc5KiZCx7IvAAAAAAAAAAAiaiDwBKygDAAICPEgB/2DJ3s6gMky/PceGZocTFRXjZUiCCAhHGYwQk/8yrUICPEgDuSd6+PJHUMuEHyLcKFxw7xfvRHRfInjkV3/Zy3BxqAICPEgZDgQ+4VXzlOIkGoO8EVxDgs2cWaeh4EEiaqa/y50gKAICPAgFI+zIuYcMF69GmPQVsXa9oy8eng7MeRZdIxArQyeX3oICPEgX5HYIuImpiSTEapgEssEW4l+W+4aRfNCG3pZf7z0hCoICPEgPkAbOSjFdtS4NT7MXgMYVQoQhI1JZtdFxUu4J3NTt7IICPEguW3qyuyGjctu5d9rM9P9ZCs/ZK4vAc+z21b9ygklgWAICPEgu4e2645xtvGhI1Zzuiv23vRhwE8uC9vj1TAgNg/C8UcICPAgwoQX8X0nY4HoQLRsJ0z8JCWQDzRh2iL2QXEb8z3gbjIICPAgzTwlPRtStsLJWhz3Q/0l8tMnrPSHVuh+zCiGk95dW2MICPAgGcBEuYZyzFNapHOfnJ9Q515QzO2VbIRhlVI0vIhd4jwICPAgidRMoM2pA+KmVJenVrLcbollsbUg9lL9bmv1C1dSxswICPAgZinGakwhbHdanTaRJeBkEUlbhfNokvj8b5KneyG+wzIICAAFiJYNc9cZAQOtwTI=\",\"created_at\":1706324334,\"id\":\"2ad074ddb7724eb13b4244b49cf2321b1057f37fdf8ce102e6329b839cf763a9\",\"kind\":1040,\"pubkey\":\"82fbb08c8ef45c4d71c88368d0ae805bc62fb92f166ab04a0b7a0c83d8cbc29a\",\"sig\":\"ad7274bb32ba9e9cfdbd52f4887e8a2fda1047c75a7185b2ab7ff254ebac14ed48a2b60737494d655e24c9400eeeec7e29293a77bfcaafaecd94b350c9a2c22b\",\"tags\":[[\"e\",\"a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6\"],[\"p\",\"c31e22c3715c1bde5608b7e0d04904f22f5fc453ba1806d21c9f2382e1e58c6c\"],[\"alt\",\"NIP-03 time stamp\"]]}"
|
||||
val otsPendingEvent = "{\"id\":\"12fa15ad4b4cf9dc5940389325b69b93c5c1f59c049c701ee669b275299fdaf1\",\"pubkey\":\"dcaa6c8a2f47b6fef4a34b20e8843c59dbe7c5f07a402338c09fd147dd01d22b\",\"created_at\":1708877521,\"kind\":1040,\"tags\":[[\"e\",\"a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6\"],[\"alt\",\"Opentimestamps Attestation\"]],\"content\":\"AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIqGNPU2jhd4no+zg2ytDkuf5PIoivr8KHI8BL68aKGNbwELidvzr0usf55CkpKf6OABQI//AQK3sWd2tq+7KO8YNJIARJugjxBGXbZtPwCL0H4/7GL5+SAIPf4w0u+QyOLCtodHRwczovL2JvYi5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFtcHMub3Jn//AQPDZsJgN1TnJXoUzlsgo93wjwIIfBc7LUqkCbC1BLZRZ+6LXztK50UdH5xe7fn40bupkrCPEEZdtm0/AI0CADXN5ZIncAg9/jDS75DI4uLWh0dHBzOi8vYWxpY2UuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ/AQcELcSrE04cuGKlZQf2LeVwjwILUDSf9vK2GaefKTpn/LV2oUsQaA5WbqaP3C+1ZxQfRNCPEEZdtm0/AIbCtb+yRXFqUAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2FsbC5jb20=\",\"sig\":\"f6854c0228c15c08aeb70bbabe9ed87bbb7289fab31b13cabac15138bb71179553e06080b83f4a813fbdaf614f63293beea3fc73fe865da6551193fa4d38de04\"}"
|
||||
|
||||
val otsEvent2Digest = "a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6"
|
||||
|
||||
@Test
|
||||
fun verifyNostrEvent() {
|
||||
val ots = Event.fromJson(otsEvent) as OtsEvent
|
||||
println(resolver.info(ots.otsByteArray()))
|
||||
assertEquals(1707688818L, ots.verify(resolver))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNostrEvent2() {
|
||||
val ots = Event.fromJson(otsEvent2) as OtsEvent
|
||||
println(resolver.info(ots.otsByteArray()))
|
||||
assertEquals(1706322179L, ots.verify(resolver))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNostrPendingEvent() {
|
||||
val ots = Event.fromJson(otsPendingEvent) as OtsEvent
|
||||
println(resolver.info(ots.otsByteArray()))
|
||||
assertEquals(null, ots.verify(resolver))
|
||||
|
||||
val eventId =
|
||||
ots.digestEventId() ?: run {
|
||||
fail("Should not be null")
|
||||
return
|
||||
}
|
||||
|
||||
val upgraded = OtsEvent.upgrade(ots.otsByteArray(), eventId, resolver)
|
||||
|
||||
assertNotNull(upgraded)
|
||||
|
||||
val signer = NostrSignerInternal(KeyPair())
|
||||
|
||||
val newOts = runBlocking { signer.sign(OtsEvent.build(eventId, upgraded!!)) }
|
||||
|
||||
println(newOts.toJson())
|
||||
println(resolver.info(newOts.otsByteArray()))
|
||||
|
||||
assertEquals(1708879025L, newOts.verify(resolver))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createOTSEventAndVerify() {
|
||||
val signer = NostrSignerInternal(KeyPair())
|
||||
|
||||
val ots =
|
||||
runBlocking {
|
||||
signer.sign(OtsEvent.build(otsEvent2Digest, OtsEvent.stamp(otsEvent2Digest, resolver)))
|
||||
}
|
||||
|
||||
println(ots.toJson())
|
||||
println(resolver.info(ots.otsByteArray()))
|
||||
|
||||
assertEquals(null, ots.verify(resolver))
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip04Dm
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip04Dm.crypto.EncryptedInfo
|
||||
import com.vitorpamplona.quartz.nip04Dm.crypto.Encryption
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class EncryptionTest {
|
||||
private val nip04 = Encryption()
|
||||
|
||||
val sk1 = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe".hexToByteArray()
|
||||
val sk2 = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220".hexToByteArray()
|
||||
val pk1 = Nip01.pubKeyCreate(sk1)
|
||||
val pk2 = Nip01.pubKeyCreate(sk2)
|
||||
|
||||
val expectedShared = "7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81"
|
||||
|
||||
@Test
|
||||
fun conversationKeyTest() {
|
||||
assertEquals(
|
||||
expectedShared,
|
||||
nip04.computeSharedSecret(sk2, pk1).toHexKey(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expectedShared,
|
||||
nip04.computeSharedSecret(sk1, pk2).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptTest() {
|
||||
val message = "testing"
|
||||
val cipher = nip04.encrypt(message, sk2, pk1)
|
||||
|
||||
assertEquals(message, nip04.decrypt(cipher, sk2, pk1))
|
||||
assertEquals(message, nip04.decrypt(cipher, sk1, pk2))
|
||||
|
||||
val cipher2 = nip04.encrypt(message, sk1, pk2)
|
||||
|
||||
assertEquals(message, nip04.decrypt(cipher2, sk2, pk1))
|
||||
assertEquals(message, nip04.decrypt(cipher2, sk1, pk2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptTest() {
|
||||
val cipher = "zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="
|
||||
|
||||
assertEquals("nanana", nip04.decrypt(cipher, nip04.computeSharedSecret(sk2, pk1)))
|
||||
assertEquals("nanana", nip04.decrypt(cipher, nip04.computeSharedSecret(sk1, pk2)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptLargePayloadTest() {
|
||||
val ciphertext =
|
||||
"6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="
|
||||
|
||||
val expected = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||
|
||||
assertEquals(expected, nip04.decrypt(ciphertext, nip04.computeSharedSecret(sk2, pk1)))
|
||||
assertEquals(expected, nip04.decrypt(ciphertext, nip04.computeSharedSecret(sk1, pk2)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isNIP04Encode() {
|
||||
assertTrue(EncryptedInfo.isNIP04("Xj/oZZolaItdyQ5v7xYFpA==?iv=+a6zagBp+mr5m1aFbHQ8lA=="))
|
||||
assertTrue(EncryptedInfo.isNIP04("zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="))
|
||||
assertTrue(
|
||||
EncryptedInfo.isNIP04("6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isNIP04EncodeWithBug() {
|
||||
assertTrue(
|
||||
EncryptedInfo.isNIP04(
|
||||
"QOAYBWa88ConWs2C4kSvNqAcowCtg0ZRtAl7FyLSv9VMaJH4oCiDx0h8VLBnV97HdE4lv" +
|
||||
"TW7AYC1eEw8/t1dbe0qRc3XrOt7MrPAO8yqpy1/3lFB1+10kip0+KdgT8Quvv02wTP8Dqi" +
|
||||
"xpr2fliAIG2ONvDn+O5V0q9aVUN9HitgL/myTyR0T42edmxWeZoMBEOKvJyO80FekSsgVL" +
|
||||
"ASafA/T5z4xs8oG88pSe9wSbSsw0xNjJeh3xLRCLuEuA9KI8hQ1Ys9nEax2UlaB/IL3o77" +
|
||||
"OwBL+rrdUbNHTxYifgygRhg3BaXMsXRFNJbqYeMaRaNbvHkLVAQV2jLY4P/cKHBjEcTC/f" +
|
||||
"lrCc2NCYF34rOQUY5EJVnFzM8qYVw6xNupBHTS7WFx1r60cPjG19P/+yoiTZ6bPdHTU0X2" +
|
||||
"t64ovF2YWUq6/iKAclMaZDhWfrKqf82e62oIff55WQw2bw8A/jtBQVCf66EtEJ2OSFxNaZ" +
|
||||
"rO+A4oLkHDCnAV+6fYzwo89gPOvORcVvSvg55yGiBFUZx9EHS6kdH1SU80/Mbxe2oI=" +
|
||||
"?iv=gxz9pUFJFZHuV+D+hgKEOw==-null",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip04Dm
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip04Dm.crypto.Nip04
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip04Test {
|
||||
@Test
|
||||
fun encryptDecryptNIP4Test() {
|
||||
val msg = "Hi"
|
||||
|
||||
val privateKey = Nip01.privKeyCreate()
|
||||
val publicKey = Nip01.pubKeyCreate(privateKey)
|
||||
|
||||
val encrypted = Nip04.encrypt(msg, privateKey, publicKey)
|
||||
val decrypted = Nip04.decrypt(encrypted, privateKey, publicKey)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptSharedSecretDecryptNIP4Test() {
|
||||
val msg = "Hi"
|
||||
|
||||
val privateKey = Nip01.privKeyCreate()
|
||||
val publicKey = Nip01.pubKeyCreate(privateKey)
|
||||
|
||||
val encrypted = Nip04.encrypt(msg, privateKey, publicKey)
|
||||
val decrypted = Nip04.decrypt(encrypted, privateKey, publicKey)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip06KeyDerivation
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Bip32SeedDerivationTest {
|
||||
val seedDerivation = Bip32SeedDerivation()
|
||||
|
||||
val masterBitcoin =
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("gun please vital unable phone catalog explain raise erosion zoo truly exist", ""),
|
||||
)
|
||||
|
||||
val nostrMnemonic0 =
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("leader monkey parrot ring guide accident before fence cannon height naive bean", ""),
|
||||
)
|
||||
|
||||
val nostrMnemonic1 =
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade", ""),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun restoreBIP44Wallet() {
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/44'/1'/0'"))
|
||||
assertEquals("50b3e7905c642309c8a8b73df5a49757a10f2bebb5804571b9db9004cce8a190", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoreBIP49Wallet() {
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/49'/1'/0'"))
|
||||
assertEquals("154c02c0b66899291a19012207642ba096a2d3ebf51baf153c9495976feb1b30", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoreBIP84Wallet() {
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/84'/1'/0'"))
|
||||
assertEquals("53e8c09a0e3ddcd8d68821c1e99e823966e99df91fb253e1f453a443ba543cb2", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGenerateMasterKeyForNostrMnemonics0() {
|
||||
assertEquals(
|
||||
"dbbcc0e112894d1430d5bc348d1bd72e8ac339952702be1fe572de80fe1b7fcb",
|
||||
nostrMnemonic0.secretkeybytes.toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGenerateMasterKeyForNostrMnemonics1() {
|
||||
assertEquals(
|
||||
"d58d40d5724435552fa442350b75e0ff95a19d990e908e3a516bcc88f780108f",
|
||||
nostrMnemonic1.secretkeybytes.toHexKey(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip06KeyDerivation
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Bip39KeyPathTest {
|
||||
// m/44'/1237'/<account>'/0/0
|
||||
private val nip6Base: KeyPath =
|
||||
KeyPath("")
|
||||
.derive(Hardener.hardened(44L))
|
||||
.derive(Hardener.hardened(1237L))
|
||||
|
||||
private fun nip6Path(account: Long): KeyPath =
|
||||
nip6Base
|
||||
.derive(Hardener.hardened(account))
|
||||
.derive(0L)
|
||||
.derive(0L)
|
||||
|
||||
@Test
|
||||
fun testKeyPath() {
|
||||
// m/44'/1237'/<account>'/0/0
|
||||
val path = nip6Path(0).path
|
||||
assertEquals(2147483692L, path[0])
|
||||
assertEquals(2147484885L, path[1])
|
||||
assertEquals(2147483648L, path[2])
|
||||
assertEquals(0L, path[3])
|
||||
assertEquals(0L, path[4])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyDecompiledPath() {
|
||||
// m/44'/1237'/<account>'/0/0
|
||||
val path = KeyPath.computePath("m/44'/1237'/0'/0/0")
|
||||
assertEquals(2147483692L, path[0])
|
||||
assertEquals(2147484885L, path[1])
|
||||
assertEquals(2147483648L, path[2])
|
||||
assertEquals(0L, path[3])
|
||||
assertEquals(0L, path[4])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHardening() {
|
||||
assertEquals(2147483692, Hardener.hardened(44))
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip06KeyDerivation
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Bip39MnemonicsTest {
|
||||
private val tests =
|
||||
jacksonObjectMapper()
|
||||
.readTree(
|
||||
InstrumentationRegistry
|
||||
.getInstrumentation()
|
||||
.context.assets
|
||||
.open("bip39.vectors.json"),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun toSeed() {
|
||||
val mnemonics = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
val passphrase = ""
|
||||
val seed = Bip39Mnemonics.toSeed(mnemonics, passphrase)
|
||||
assertEquals(
|
||||
"5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
|
||||
Hex.encode(seed),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun referenceTests() {
|
||||
tests.get("english").map {
|
||||
val raw = it.get(0).asText()
|
||||
val mnemonics = it.get(1).asText()
|
||||
val seed = it.get(2).asText()
|
||||
assertEquals(Bip39Mnemonics.toMnemonics(Hex.decode(raw)).joinToString(" "), mnemonics)
|
||||
assertEquals(Hex.encode(Bip39Mnemonics.toSeed(Bip39Mnemonics.toMnemonics(Hex.decode(raw)), "TREZOR")), seed)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMnemonicsValid() {
|
||||
for (i in 0..99) {
|
||||
for (length in listOf(16, 20, 24, 28, 32, 36, 40)) {
|
||||
val mnemonics = Bip39Mnemonics.toMnemonics(RandomInstance.bytes(length))
|
||||
Bip39Mnemonics.validate(mnemonics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNostrMnemonics1() {
|
||||
assertEquals(
|
||||
"173b9c5f0d165502d08a4d122b2c9bf1e33e27806eac119713600a263c1241101dc55fb7cffb8f48a59b19a5ba65b037904f907bb8d08eb5bff8a17e85c2ee93",
|
||||
Bip39Mnemonics.toSeed("leader monkey parrot ring guide accident before fence cannon height naive bean", "").toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNostrMnemonics2() {
|
||||
assertEquals(
|
||||
"5e2bd11b4d371f25098ed95ded029e2b9268cf188e6b764023bafbbd8fe843244fb72ca8f66c9378085d69fcb4d4224e709ffe071acafa7b7d5eb54b2905d553",
|
||||
Bip39Mnemonics.toSeed("what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade", "").toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnortMnemonics() {
|
||||
assertEquals(
|
||||
"b8fb0abca0a032d74b6a58def8ba51402810528d0a7bb9acec8fa1880e1003e13c4421b916cb4c8a861f2694b255010fdc91c2d1ac24c8ce51287ac65d1eb399",
|
||||
Bip39Mnemonics.toSeed("clog remember sample endorse mountain key rib hurry question supreme palm future stage style swing faith erase thumb then warm truth mule vivid endless", "").toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMnemonicsInvalid() {
|
||||
val invalidMnemonics =
|
||||
listOf(
|
||||
"",
|
||||
// one word missing
|
||||
"gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow",
|
||||
// one extra word
|
||||
"gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog fog",
|
||||
// wrong word
|
||||
"gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fig",
|
||||
)
|
||||
invalidMnemonics.map {
|
||||
try {
|
||||
Bip39Mnemonics.validate(it)
|
||||
fail()
|
||||
} catch (e: Exception) {
|
||||
assertTrue(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip06KeyDerivation
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip06Test {
|
||||
val nip06 = Nip06()
|
||||
|
||||
// private key (hex): 7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a
|
||||
// nsec: nsec10allq0gjx7fddtzef0ax00mdps9t2kmtrldkyjfs8l5xruwvh2dq0lhhkp
|
||||
private val menemonic0 = "leader monkey parrot ring guide accident before fence cannon height naive bean"
|
||||
|
||||
// private key (hex): c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add
|
||||
// nsec: nsec1c9wh8xy5eqdzln7n5t0ctgxjcrdug73gp5yj0x03gntn67h83twssdfhel
|
||||
private val menemonic1 = "what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade"
|
||||
|
||||
// private key (hex): c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add
|
||||
// nsec: nsec1c9wh8xy5eqdzln7n5t0ctgxjcrdug73gp5yj0x03gntn67h83twssdfhel
|
||||
private val snortTest = "clog remember sample endorse mountain key rib hurry question supreme palm future stage style swing faith erase thumb then warm truth mule vivid endless"
|
||||
|
||||
@Test
|
||||
fun fromSeedNip06TestVector0() {
|
||||
val privateKeyHex = nip06.privateKeyFromMnemonic(menemonic0).toHexKey()
|
||||
assertEquals("7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a", privateKeyHex)
|
||||
|
||||
val privateKeyHex21 = nip06.privateKeyFromMnemonic(menemonic0, 21).toHexKey()
|
||||
assertEquals("576390ec69951fcfbf159f2aac0965bb2e6d7a07da2334992af3225c57eaefca", privateKeyHex21)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromSeedNip06TestVector1() {
|
||||
val privateKeyHex = nip06.privateKeyFromMnemonic(menemonic1).toHexKey()
|
||||
assertEquals("c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add", privateKeyHex)
|
||||
|
||||
val privateKeyHex21 = nip06.privateKeyFromMnemonic(menemonic1, 42).toHexKey()
|
||||
assertEquals("ad993054383da74e955f8b86346365b5ffd6575992e1de3738dda9f94407052b", privateKeyHex21)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Snort is not correctly implemented")
|
||||
fun fromSeedNip06FromSnort() {
|
||||
val privateKeyNsec = nip06.privateKeyFromMnemonic(snortTest).toHexKey()
|
||||
assertEquals("nsec1ppw9ltr2x9qwg9a2qnmgv98tfruy2ejnja7me76mwmsreu3s8u2sscj5nt", privateKeyNsec)
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip10Notes
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEventIds
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CitationTests {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"98b574c3527f0ffb30b7271084e3f07480733c7289f8de424d29eae82e36c758",
|
||||
"46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d",
|
||||
1683596206,
|
||||
arrayOf(
|
||||
arrayOf("e", "27ac621d7dc4a932e1a79f984308e7d20656dd6fddb2ce9cdfcb6a67b9a7bcc3", "", "root"),
|
||||
arrayOf("e", "be7245af96210a0dd048cab4ad38e52dbd6c09a53ea21a7edb6be8898e5727cc", "", "reply"),
|
||||
arrayOf("p", "22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
arrayOf("p", "22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
arrayOf("p", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"),
|
||||
arrayOf("p", "ec4d241c334311b3a304433ee3442be29d0e88e7ec19b85edf2bba29b93565e2"),
|
||||
arrayOf("p", "0fe0b18b4dbf0e0aa40fcd47209b2a49b3431fc453b460efcf45ca0bd16bd6ac"),
|
||||
arrayOf("p", "8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168"),
|
||||
arrayOf("p", "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"),
|
||||
arrayOf("p", "4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0"),
|
||||
arrayOf("p", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
),
|
||||
"Astral:\n\nhttps://void.cat/d/A5Fba5B1bcxwEmeyoD9nBs.webp\n\nIris:\n\nhttps://void.cat/d/44hTcVvhRps6xYYs99QsqA.webp\n\nSnort:\n\nhttps://void.cat/d/4nJD5TRePuQChM5tzteYbU.webp\n\nAmethyst agrees with Astral which I suspect are both wrong. nostr:npub13sx6fp3pxq5rl70x0kyfmunyzaa9pzt5utltjm0p8xqyafndv95q3saapa nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49 nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z ",
|
||||
"4aa5264965018fa12a326686ad3d3bd8beae3218dcc83689b19ca1e6baeb791531943c15363aa6707c7c0c8b2d601deca1f20c32078b2872d356cdca03b04cce",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun verifyEvent() {
|
||||
assertTrue(note.verify())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCitedUsers() {
|
||||
val expectedCitations =
|
||||
setOf(
|
||||
"8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168",
|
||||
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
|
||||
"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0",
|
||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
)
|
||||
|
||||
assertEquals(expectedCitations, note.citedUsers())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagsWithoutCitations() {
|
||||
val expectedTagsWithoutCitations =
|
||||
listOf(
|
||||
"27ac621d7dc4a932e1a79f984308e7d20656dd6fddb2ce9cdfcb6a67b9a7bcc3",
|
||||
"be7245af96210a0dd048cab4ad38e52dbd6c09a53ea21a7edb6be8898e5727cc",
|
||||
)
|
||||
|
||||
assertEquals(expectedTagsWithoutCitations, note.tagsWithoutCitations())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaggedEventIds() {
|
||||
val eventIds =
|
||||
listOf(
|
||||
"27ac621d7dc4a932e1a79f984308e7d20656dd6fddb2ce9cdfcb6a67b9a7bcc3",
|
||||
"be7245af96210a0dd048cab4ad38e52dbd6c09a53ea21a7edb6be8898e5727cc",
|
||||
)
|
||||
|
||||
assertEquals(eventIds, note.taggedEventIds())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaggedEvents() {
|
||||
val eventIds =
|
||||
listOf(
|
||||
ETag("27ac621d7dc4a932e1a79f984308e7d20656dd6fddb2ce9cdfcb6a67b9a7bcc3"),
|
||||
ETag("be7245af96210a0dd048cab4ad38e52dbd6c09a53ea21a7edb6be8898e5727cc"),
|
||||
)
|
||||
|
||||
assertEquals(eventIds, note.taggedEvents())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaggedUsers() {
|
||||
val eventIds =
|
||||
listOf(
|
||||
PTag("22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
PTag("22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
PTag("3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"),
|
||||
PTag("ec4d241c334311b3a304433ee3442be29d0e88e7ec19b85edf2bba29b93565e2"),
|
||||
PTag("0fe0b18b4dbf0e0aa40fcd47209b2a49b3431fc453b460efcf45ca0bd16bd6ac"),
|
||||
PTag("8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168"),
|
||||
PTag("63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"),
|
||||
PTag("4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0"),
|
||||
PTag("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
)
|
||||
|
||||
assertEquals(eventIds, note.taggedUsers())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaggedUserIds() {
|
||||
val eventIds =
|
||||
listOf(
|
||||
"22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954",
|
||||
"22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954",
|
||||
"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24",
|
||||
"ec4d241c334311b3a304433ee3442be29d0e88e7ec19b85edf2bba29b93565e2",
|
||||
"0fe0b18b4dbf0e0aa40fcd47209b2a49b3431fc453b460efcf45ca0bd16bd6ac",
|
||||
"8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168",
|
||||
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
|
||||
"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0",
|
||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
)
|
||||
|
||||
assertEquals(eventIds, note.taggedUserIds())
|
||||
}
|
||||
}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip10Notes
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ThreadingTests {
|
||||
@Test
|
||||
fun testLegacyEvent() {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
arrayOf(
|
||||
arrayOf("p", "4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0"),
|
||||
arrayOf("p", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
arrayOf("p", "77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7"),
|
||||
arrayOf("e", "89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d"),
|
||||
arrayOf("e", "090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051"),
|
||||
arrayOf("e", "567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976"),
|
||||
arrayOf("e", "49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4"),
|
||||
arrayOf("e", "5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64"),
|
||||
arrayOf("e", "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631"),
|
||||
arrayOf("e", "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c"),
|
||||
),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
val taggedUsers =
|
||||
listOf(
|
||||
PTag("4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0"),
|
||||
PTag("534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
PTag("77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7"),
|
||||
)
|
||||
|
||||
assertEquals(taggedUsers, note.taggedUsers())
|
||||
|
||||
val expectedReply = MarkedETag("b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c", null, MarkedETag.MARKER.REPLY)
|
||||
|
||||
// check replies
|
||||
assertEquals(null, note.markedReply())
|
||||
assertEquals(expectedReply, note.unmarkedReply())
|
||||
assertEquals(expectedReply, note.reply())
|
||||
|
||||
val replyTos =
|
||||
listOf(
|
||||
"89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d",
|
||||
"090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051",
|
||||
"567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976",
|
||||
"49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4",
|
||||
"5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64",
|
||||
"bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
|
||||
"b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
|
||||
)
|
||||
|
||||
assertEquals(emptyList<String>(), note.markedReplyTos())
|
||||
assertEquals(replyTos, note.unmarkedReplyTos())
|
||||
|
||||
assertEquals(expectedReply.eventId, note.replyingTo())
|
||||
assertEquals(expectedReply.eventId, note.replyingToAddressOrEvent())
|
||||
|
||||
// check root
|
||||
val expectedRoot = MarkedETag("89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d", null, MarkedETag.MARKER.ROOT)
|
||||
|
||||
assertEquals(null, note.markedRoot())
|
||||
assertEquals(expectedRoot, note.unmarkedRoot())
|
||||
assertEquals(expectedRoot, note.root())
|
||||
|
||||
// check quotes
|
||||
assertEquals(emptySet<String>(), note.citedUsers())
|
||||
assertEquals(emptySet<String>(), note.findCitations())
|
||||
assertEquals(replyTos, note.tagsWithoutCitations())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMarkedEvent() {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
arrayOf(
|
||||
arrayOf("p", "4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0"),
|
||||
arrayOf("p", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
arrayOf("e", "77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7"),
|
||||
arrayOf("e", "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631", "", "root"),
|
||||
arrayOf("e", "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c", "", "reply"),
|
||||
),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
val taggedUsers =
|
||||
listOf(
|
||||
PTag("4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0"),
|
||||
PTag("534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
)
|
||||
|
||||
assertEquals(taggedUsers, note.taggedUsers())
|
||||
|
||||
val expectedReply = MarkedETag("b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c", null, MarkedETag.MARKER.REPLY)
|
||||
val expectedReplyWrong = MarkedETag("77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7", null, MarkedETag.MARKER.REPLY)
|
||||
|
||||
// check replies
|
||||
assertEquals(expectedReply, note.markedReply())
|
||||
assertEquals(expectedReplyWrong, note.unmarkedReply())
|
||||
assertEquals(expectedReply, note.reply())
|
||||
|
||||
val markedReplies =
|
||||
listOf(
|
||||
"bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
|
||||
"b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
|
||||
)
|
||||
|
||||
val unmarkedReplies =
|
||||
listOf(
|
||||
"77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7",
|
||||
)
|
||||
|
||||
assertEquals(markedReplies, note.markedReplyTos())
|
||||
assertEquals(unmarkedReplies, note.unmarkedReplyTos())
|
||||
|
||||
assertEquals(expectedReply.eventId, note.replyingTo())
|
||||
assertEquals(expectedReply.eventId, note.replyingToAddressOrEvent())
|
||||
|
||||
// check root
|
||||
val expectedRoot = MarkedETag("bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631", null, MarkedETag.MARKER.ROOT)
|
||||
|
||||
val expectedRootWrong = MarkedETag("77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7", null, MarkedETag.MARKER.ROOT)
|
||||
|
||||
assertEquals(expectedRoot, note.markedRoot())
|
||||
assertEquals(expectedRootWrong, note.unmarkedRoot())
|
||||
assertEquals(expectedRoot, note.root())
|
||||
|
||||
// check quotes
|
||||
assertEquals(emptySet<String>(), note.citedUsers())
|
||||
assertEquals(emptySet<String>(), note.findCitations())
|
||||
assertEquals(markedReplies, note.tagsWithoutCitations())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMarkedInverted() {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
arrayOf(
|
||||
arrayOf("p", "4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0", "wss://goiaba.com"),
|
||||
arrayOf("p", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
arrayOf("p", "77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7"),
|
||||
arrayOf("e", "5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64", "", "reply"),
|
||||
arrayOf("e", "bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631", "wss://banana.com", "root", "4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0"),
|
||||
arrayOf("e", "b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c"),
|
||||
),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
val taggedUsers =
|
||||
listOf(
|
||||
PTag("4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0", RelayUrlNormalizer.normalize("wss://goiaba.com")),
|
||||
PTag("534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
PTag("77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7"),
|
||||
)
|
||||
|
||||
assertEquals(taggedUsers, note.taggedUsers())
|
||||
|
||||
val expectedReply = MarkedETag("5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64", null, MarkedETag.MARKER.REPLY)
|
||||
val expectedReplyWrong = MarkedETag("b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c", null, MarkedETag.MARKER.REPLY)
|
||||
|
||||
// check replies
|
||||
assertEquals(expectedReply, note.markedReply())
|
||||
assertEquals(expectedReplyWrong, note.unmarkedReply())
|
||||
assertEquals(expectedReply, note.reply())
|
||||
|
||||
val markedReplies =
|
||||
listOf(
|
||||
"bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631",
|
||||
"5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64",
|
||||
)
|
||||
|
||||
val unmarkedReplies =
|
||||
listOf(
|
||||
"b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c",
|
||||
)
|
||||
|
||||
assertEquals(markedReplies, note.markedReplyTos())
|
||||
assertEquals(unmarkedReplies, note.unmarkedReplyTos())
|
||||
|
||||
assertEquals(expectedReply.eventId, note.replyingTo())
|
||||
assertEquals(expectedReply.eventId, note.replyingToAddressOrEvent())
|
||||
|
||||
// check root
|
||||
val expectedRoot = MarkedETag("bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631", null, MarkedETag.MARKER.ROOT)
|
||||
val expectedRootWrong = MarkedETag("b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c", null, MarkedETag.MARKER.ROOT)
|
||||
|
||||
assertEquals(expectedRoot, note.markedRoot())
|
||||
assertEquals(expectedRootWrong, note.unmarkedRoot())
|
||||
assertEquals(expectedRoot, note.root())
|
||||
|
||||
// check quotes
|
||||
assertEquals(emptySet<String>(), note.citedUsers())
|
||||
assertEquals(emptySet<String>(), note.findCitations())
|
||||
assertEquals(markedReplies, note.tagsWithoutCitations())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyRoot() {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
arrayOf(
|
||||
arrayOf("p", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec", "wss://banana.com"),
|
||||
arrayOf("e", "9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", "", "root", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
val taggedUsers =
|
||||
listOf(
|
||||
PTag("534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec", RelayUrlNormalizer.normalize("wss://banana.com")),
|
||||
)
|
||||
|
||||
assertEquals(taggedUsers, note.taggedUsers())
|
||||
|
||||
val expectedReply = MarkedETag("9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", null, MarkedETag.MARKER.REPLY)
|
||||
|
||||
// check replies
|
||||
assertEquals(null, note.markedReply())
|
||||
assertEquals(null, note.unmarkedReply())
|
||||
assertEquals(null, note.reply())
|
||||
|
||||
val replyTos =
|
||||
listOf(
|
||||
"9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590",
|
||||
)
|
||||
|
||||
assertEquals(replyTos, note.markedReplyTos())
|
||||
assertEquals(emptyList<String>(), note.unmarkedReplyTos())
|
||||
|
||||
assertEquals(expectedReply.eventId, note.replyingTo())
|
||||
assertEquals(expectedReply.eventId, note.replyingToAddressOrEvent())
|
||||
|
||||
// check root
|
||||
val expectedRoot = MarkedETag("9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", null, MarkedETag.MARKER.ROOT, "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec")
|
||||
|
||||
assertEquals(expectedRoot, note.markedRoot())
|
||||
assertEquals(null, note.unmarkedRoot())
|
||||
assertEquals(expectedRoot, note.root())
|
||||
|
||||
// check quotes
|
||||
assertEquals(emptySet<String>(), note.citedUsers())
|
||||
assertEquals(emptySet<String>(), note.findCitations())
|
||||
assertEquals(replyTos, note.tagsWithoutCitations())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyReply() {
|
||||
val note =
|
||||
TextNoteEvent(
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
arrayOf(
|
||||
arrayOf("p", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec", "wss://banana.com"),
|
||||
arrayOf("e", "9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", "", "reply", "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec"),
|
||||
),
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
val taggedUsers =
|
||||
listOf(
|
||||
PTag("534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec", RelayUrlNormalizer.normalize("wss://banana.com")),
|
||||
)
|
||||
|
||||
assertEquals(taggedUsers, note.taggedUsers())
|
||||
|
||||
val expectedReply = MarkedETag("9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", null, MarkedETag.MARKER.REPLY)
|
||||
|
||||
// check replies
|
||||
assertEquals(expectedReply, note.markedReply())
|
||||
assertEquals(null, note.unmarkedReply())
|
||||
assertEquals(expectedReply, note.reply())
|
||||
|
||||
val replyTos =
|
||||
listOf(
|
||||
"9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590",
|
||||
)
|
||||
|
||||
assertEquals(replyTos, note.markedReplyTos())
|
||||
assertEquals(emptyList<String>(), note.unmarkedReplyTos())
|
||||
|
||||
assertEquals(expectedReply.eventId, note.replyingTo())
|
||||
assertEquals(expectedReply.eventId, note.replyingToAddressOrEvent())
|
||||
|
||||
// check root
|
||||
val expectedRoot = MarkedETag("9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590", null, MarkedETag.MARKER.ROOT, "534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec")
|
||||
|
||||
assertEquals(null, note.markedRoot())
|
||||
assertEquals(null, note.unmarkedRoot())
|
||||
assertEquals(null, note.root())
|
||||
|
||||
// check quotes
|
||||
assertEquals(emptySet<String>(), note.citedUsers())
|
||||
assertEquals(emptySet<String>(), note.findCitations())
|
||||
assertEquals(replyTos, note.tagsWithoutCitations())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip13Pow
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip13Pow.miner.PoWMiner
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PoWMinerTest {
|
||||
val baseTemplate =
|
||||
EventTemplate<TextNoteEvent>(
|
||||
1683596206,
|
||||
TextNoteEvent.KIND,
|
||||
arrayOf(
|
||||
arrayOf("e", "27ac621d7dc4a932e1a79f984308e7d20656dd6fddb2ce9cdfcb6a67b9a7bcc3", "", "root"),
|
||||
arrayOf("e", "be7245af96210a0dd048cab4ad38e52dbd6c09a53ea21a7edb6be8898e5727cc", "", "reply"),
|
||||
arrayOf("p", "22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
arrayOf("p", "22aa81510ee63fe2b16cae16e0921f78e9ba9882e2868e7e63ad6d08ae9b5954"),
|
||||
arrayOf("p", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"),
|
||||
arrayOf("p", "ec4d241c334311b3a304433ee3442be29d0e88e7ec19b85edf2bba29b93565e2"),
|
||||
arrayOf("p", "0fe0b18b4dbf0e0aa40fcd47209b2a49b3431fc453b460efcf45ca0bd16bd6ac"),
|
||||
arrayOf("p", "8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168"),
|
||||
arrayOf("p", "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"),
|
||||
arrayOf("p", "4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0"),
|
||||
arrayOf("p", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
),
|
||||
"Astral:\n\nhttps://void.cat/d/A5Fba5B1bcxwEmeyoD9nBs.webp\n\nIris:\n\nhttps://void.cat/d/44hTcVvhRps6xYYs99QsqA.webp\n\nSnort:\n\nhttps://void.cat/d/4nJD5TRePuQChM5tzteYbU.webp\n\nAmethyst agrees with Astral which I suspect are both wrong. nostr:npub13sx6fp3pxq5rl70x0kyfmunyzaa9pzt5utltjm0p8xqyafndv95q3saapa nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49 nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk nostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z ",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun mine() {
|
||||
val signer = NostrSignerSync(KeyPair())
|
||||
println("Starting")
|
||||
val template = PoWMiner.run(baseTemplate, signer.pubKey, 25)
|
||||
println("Finished")
|
||||
|
||||
val event = signer.sign(template)
|
||||
|
||||
println(event?.toJson())
|
||||
|
||||
assertEquals(25, event!!.pow())
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip13Pow
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip13Pow.miner.PoWRankEvaluator
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PoWRankEvaluatorTest {
|
||||
val tests =
|
||||
mapOf(
|
||||
"000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358" to 21,
|
||||
"6bf5b4f434813c64b523d2b0e6efe18f3bd0cbbd0a5effd8ece9e00fd2531996" to 1,
|
||||
"00003479309ecdb46b1c04ce129d2709378518588bed6776e60474ebde3159ae" to 18,
|
||||
"01a76167d41add96be4959d9e618b7a35f26551d62c43c11e5e64094c6b53c83" to 7,
|
||||
"ac4f44bae06a45ebe88cfbd3c66358750159650a26c0d79e8ccaa92457fca4f6" to 0,
|
||||
"0000000000000000006cfbd3c66358750159650a26c0d79e8ccaa92457fca4f6" to 73,
|
||||
"00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e" to 26,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testHex() {
|
||||
tests.forEach {
|
||||
assertEquals(it.value, PoWRankEvaluator.calculatePowRankOf(it.key))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testByte() {
|
||||
tests.forEach {
|
||||
assertEquals(it.value, PoWRankEvaluator.calculatePowRankOf(it.key.hexToByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
val commitmentTest = "00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e"
|
||||
|
||||
@Test
|
||||
fun setPoWIfCommited25() {
|
||||
assertEquals(25, PoWRankEvaluator.compute(commitmentTest, 25))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setPoWIfCommited26() {
|
||||
assertEquals(26, PoWRankEvaluator.compute(commitmentTest, 26))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setPoWIfCommited27() {
|
||||
assertEquals(26, PoWRankEvaluator.compute(commitmentTest, 27))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip17Dm
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AESGCMTest {
|
||||
val decryptionNonce = "01e77c94bd5aba3e3cbb69594e7ba07c"
|
||||
val decryptionKey = "c128ecffab90ee7810e3df08e7fb2cc39a8d40f24201f48b2b36e23b34ac50ee"
|
||||
|
||||
val cipher = AESGCM(decryptionKey.hexToByteArray(), decryptionNonce.toByteArray(Charsets.UTF_8))
|
||||
|
||||
@Test
|
||||
fun encryptDecrypt() {
|
||||
val encrypted = cipher.encrypt("Testing".toByteArray(Charsets.UTF_8))
|
||||
val decrypted = cipher.decrypt(encrypted)
|
||||
|
||||
assertEquals("Testing", String(decrypted))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imageTest() {
|
||||
val image =
|
||||
getInstrumentation().context.assets.open("ovxxk2vz.jpg").use {
|
||||
it.readAllBytes()
|
||||
}
|
||||
|
||||
val decrypted = cipher.decrypt(image)
|
||||
|
||||
assertEquals(44201, decrypted.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun videoTest2() {
|
||||
val myCipher =
|
||||
AESGCM(
|
||||
"373d19850ebc8ed5b0fefcca5cd6f27fde9cb6ac54fd32f6b4fad9d68ebe8ee0".hexToByteArray(),
|
||||
"95e67b6874784a54299b58b8990499bd".hexToByteArray(),
|
||||
)
|
||||
|
||||
val encrypted =
|
||||
getInstrumentation().context.assets.open("trouble_video").use {
|
||||
it.readAllBytes()
|
||||
}
|
||||
|
||||
val decrypted = myCipher.decrypt(encrypted)
|
||||
|
||||
assertEquals(1277122, decrypted.size)
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip17Dm
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ChatroomKeyTest {
|
||||
@Test
|
||||
fun testEquals() {
|
||||
val k1 = ChatroomKey(persistentSetOf("Key1", "Key2"))
|
||||
val k2 = ChatroomKey(persistentSetOf("Key1", "Key2"))
|
||||
|
||||
assertEquals(k1, k2)
|
||||
assertEquals(k1.hashCode(), k2.hashCode())
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip19Bech32
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NIP19EmbedTests {
|
||||
@Test
|
||||
fun testEmbedKind1Event() {
|
||||
val signer =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
|
||||
)
|
||||
|
||||
val textNote: TextNoteEvent =
|
||||
runBlocking {
|
||||
signer.sign(
|
||||
TextNoteEvent.build("I like this. It could solve the ninvite problem in #1062, and it seems like it could be applied very broadly to limit the spread of events that shouldn't stand on their own or need to be private. The one question I have is how long are these embeds? If it's 50 lines of text, that breaks the human readable (or at least parseable) requirement of kind 1s. Also, encoding json in a tlv is silly, we should at least use the tlv to reduce the payload size."),
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull(textNote)
|
||||
|
||||
val bech32 = NEmbed.create(textNote!!)
|
||||
|
||||
println(bech32)
|
||||
|
||||
val decodedNote = (Nip19Parser.uriToRoute(bech32)?.entity as NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.verify())
|
||||
|
||||
assertEquals(textNote!!.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVisionPrescriptionEmbedEvent() {
|
||||
val signer =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
|
||||
)
|
||||
|
||||
val eyeglassesPrescriptionEvent =
|
||||
runBlocking {
|
||||
signer.sign(FhirResourceEvent.build(visionPrescriptionFhir))
|
||||
}
|
||||
|
||||
assertNotNull(eyeglassesPrescriptionEvent)
|
||||
|
||||
val bech32 = NEmbed.create(eyeglassesPrescriptionEvent)
|
||||
|
||||
println(eyeglassesPrescriptionEvent.toJson())
|
||||
println(bech32)
|
||||
|
||||
val decodedNote = (Nip19Parser.uriToRoute(bech32)?.entity as NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.verify())
|
||||
|
||||
assertEquals(eyeglassesPrescriptionEvent.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVisionPrescriptionBundleEmbedEvent() {
|
||||
val signer =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
|
||||
)
|
||||
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
|
||||
val eyeglassesPrescriptionEvent =
|
||||
runBlocking {
|
||||
signer.sign(FhirResourceEvent.build(visionPrescriptionBundle))
|
||||
}
|
||||
|
||||
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
|
||||
|
||||
assertNotNull(eyeglassesPrescriptionEvent)
|
||||
|
||||
val bech32 = NEmbed.create(eyeglassesPrescriptionEvent)
|
||||
|
||||
println(eyeglassesPrescriptionEvent.toJson())
|
||||
println(bech32)
|
||||
|
||||
val decodedNote = (Nip19Parser.uriToRoute(bech32)?.entity as NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.verify())
|
||||
|
||||
assertEquals(eyeglassesPrescriptionEvent.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVisionPrescriptionBundle2EmbedEvent() {
|
||||
val signer =
|
||||
NostrSignerInternal(
|
||||
KeyPair(decodePrivateKeyAsHexOrNull("nsec1arn3jlxv20y76n8ek8ydecy9ga06rl7aw8evznjylc3ap00hwkvqx4vvy6")!!.hexToByteArray()),
|
||||
)
|
||||
|
||||
val eyeglassesPrescriptionEvent =
|
||||
runBlocking {
|
||||
signer.sign(FhirResourceEvent.build(visionPrescriptionBundle2))
|
||||
}
|
||||
|
||||
assertNotNull(eyeglassesPrescriptionEvent)
|
||||
|
||||
val bech32 = NEmbed.create(eyeglassesPrescriptionEvent)
|
||||
|
||||
println(eyeglassesPrescriptionEvent.toJson())
|
||||
println(bech32)
|
||||
|
||||
val decodedNote = (Nip19Parser.uriToRoute(bech32)?.entity as NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.verify())
|
||||
|
||||
assertEquals(eyeglassesPrescriptionEvent.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTimsNembed() {
|
||||
val uri = "nembed1r79ssq9446hkwqhl642ukmku8qg0c92pu7w3j0jyfte8tc7tvg85vmrys8x3sqgle5vjy7jpjswqhphl0kd6yf4sz0n3peyjq5rp3zkat4w6c6j3f7um0724jmfu5456xxgg2yxkn8dp23j64xsn9npcggzafyh2effyntqrqxzja8dp52kpcvc9zqxlj86e8mx05vevzxkeprjkfs4wmppxm3p96vj6yvu2mqgf5l4v99492r2qsggquxuv93uzx244652h2kkj8xseg9xkq0afpygknjtty9j4ju5v0nm9mezux9wyl6s5wr7lzce7cj397mnu0u04ha7aq3w7exelrhe3zs3l3urwa9sp36u80npllrs0hmsxqdn0fsuyav3nv0azjs5suzuurg2uymncjxez8p9xksc2j6gw992enjflgrdd7n5uq2xrpvfrd3rckw624ey0elvm6grr27tyzlf4vaswgm5vc3hdyczsl983g2j8e67r6z5zt30lat84ma4wclkwwxxrcflvdsuwd7346h7zqav4vdwe3gkt9lr87sfk4aqd2aey03tt4eyspldrqcmkx9pqe2pn63rv7grwwalr86akuldnvjm6m87wrw9sdwns8wq0rnsmj57vqwtc3g7hkwum3vl2dda78dwkycgfzw6qna3ufhpatcvq5a4hm4ehl45an8umwt0clf7rn77ctke475qglwu86hhfwhn7dkca4pkfpyc4y75rll6nvr5qc8nlhf8mk22celn5mecvyuzxd830drhdck9tcdpcafymk8wajwu2w8ha8gatggjfvq0a4jlf2sdamzj0ysqks9dk8me3q7a0qpmf6vykurkrcls4pug3u4pn4u26ezx3h8e482n07x2nsmu80dpufxqc0ttcyzhnppguxma4d8aumdawnlsyy7yzcuxl7lw5y9p4nv5h8fn6u8anpm2tsze3p6mgxy9j9uuqfxg2jvlmtjpakna5m4hln0msmw804hnun96h66fh62270yhhljnmmdl7jln07ll5vft7e870hemcld34a09n943ed6629fgtctsftma9q6tf4jfm2p0ukd2j2n2dpz53fqrkk4ctdcy2j5jar095g5jntf6u807ggkzauzt6uqkwk4tg5w7w55kskspc9663zx5dzzzfwpg3q546g2ve4kukr70n0a46eyce2crsqqq247ql5"
|
||||
|
||||
val decodedNote = (Nip19Parser.uriToRoute(uri)?.entity as NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.verify())
|
||||
|
||||
assertEquals(timsPrescription, decodedNote.toJson())
|
||||
}
|
||||
|
||||
val visionPrescriptionFhir = "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/Donald Duck\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/Adam Careful\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}"
|
||||
val visionPrescriptionBundle = "{\"resourceType\":\"Bundle\",\"id\":\"bundle-vision-test\",\"type\":\"document\",\"entry\":[{\"resourceType\":\"Practitioner\",\"id\":\"2\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Careful\",\"given\":[\"Adam\"]}],\"gender\":\"male\"},{\"resourceType\":\"Patient\",\"id\":\"1\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Duck\",\"given\":[\"Donald\"]}],\"gender\":\"male\"},{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"#1\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"#2\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}]}"
|
||||
|
||||
val visionPrescriptionBundle2 = "{\"resourceType\":\"Bundle\",\"id\":\"bundle-vision-test\",\"type\":\"document\",\"entry\":[{\"resourceType\":\"Practitioner\",\"id\":\"2\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Smith\",\"given\":[\"Dr. Joe\"]}],\"gender\":\"male\"},{\"resourceType\":\"Patient\",\"id\":\"1\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Doe\",\"given\":[\"Jane\"]}],\"gender\":\"male\"},{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"#1\"},\"dateWritten\":\"2014-06-15\",\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}]}"
|
||||
val timsPrescription = "{\"id\":\"18d8b22e6455dfc9f4c6d6be8c2cf015e961b8d160dfe5e4b7fc1578f2c4e0be\",\"pubkey\":\"46f1826abf5b03de972192e619e25fa94d775a1c555efe53a775412dbf49889b\",\"created_at\":1739566773,\"kind\":82,\"tags\":[[\"p\",\"46f1826abf5b03de972192e619e25fa94d775a1c555efe53a775412dbf49889b\"]],\"content\":\"{\\\"resourceType\\\": \\\"VisionPrescription\\\", \\\"id\\\": \\\"eyeglass-prescription-001\\\", \\\"status\\\": \\\"active\\\", \\\"created\\\": \\\"2025-02-14T10:00:00Z\\\", \\\"patient\\\": {\\\"reference\\\": \\\"Patient/12345\\\", \\\"display\\\": \\\"John Doe\\\"}, \\\"encounter\\\": {\\\"reference\\\": \\\"Encounter/67890\\\"}, \\\"dateWritten\\\": \\\"2025-02-10T15:00:00Z\\\", \\\"prescriber\\\": {\\\"reference\\\": \\\"Practitioner/56789\\\", \\\"display\\\": \\\"Dr. Emily Smith\\\"}, \\\"lensSpecification\\\": [{\\\"product\\\": {\\\"coding\\\": [{\\\"system\\\": \\\"http://terminology.hl7.org/CodeSystem/ex-visionprescriptionproduct\\\", \\\"code\\\": \\\"lens\\\", \\\"display\\\": \\\"Eyeglasses\\\"}]}, \\\"eye\\\": \\\"right\\\", \\\"sphere\\\": -2.5, \\\"cylinder\\\": -1.0, \\\"axis\\\": 180, \\\"prism\\\": [{\\\"amount\\\": 0.5, \\\"base\\\": \\\"up\\\"}], \\\"add\\\": 2.0, \\\"duration\\\": {\\\"value\\\": 24, \\\"unit\\\": \\\"months\\\", \\\"system\\\": \\\"http://unitsofmeasure.org\\\", \\\"code\\\": \\\"mo\\\"}, \\\"note\\\": [{\\\"text\\\": \\\"Right eye prescription for near-sightedness with astigmatism.\\\"}]}, {\\\"product\\\": {\\\"coding\\\": [{\\\"system\\\": \\\"http://terminology.hl7.org/CodeSystem/ex-visionprescriptionproduct\\\", \\\"code\\\": \\\"lens\\\", \\\"display\\\": \\\"Eyeglasses\\\"}]}, \\\"eye\\\": \\\"left\\\", \\\"sphere\\\": -3.0, \\\"cylinder\\\": -0.75, \\\"axis\\\": 160, \\\"prism\\\": [{\\\"amount\\\": 0.5, \\\"base\\\": \\\"down\\\"}], \\\"add\\\": 2.0, \\\"duration\\\": {\\\"value\\\": 24, \\\"unit\\\": \\\"months\\\", \\\"system\\\": \\\"http://unitsofmeasure.org\\\", \\\"code\\\": \\\"mo\\\"}, \\\"note\\\": [{\\\"text\\\": \\\"Left eye prescription for near-sightedness with astigmatism.\\\"}]}]}\",\"sig\":\"d22d3b86aea397094de8b6cdf69decdfd886c90008aeebf95fd43a2770b37d486b313bff5bdb44b78e33bbaa3f336d74ee8b36bc5b16050374054246c72d93c2\"}"
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip22Comments
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.geohashedScope
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GeolocatedComments {
|
||||
private val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
private val signer = NostrSignerSync(KeyPair(privateKey))
|
||||
|
||||
val event =
|
||||
CommentEvent(
|
||||
"fecb2ecf61a1433d417a784d10bd1e8ec19a916170a53ca8fb3a15fc666a6592",
|
||||
"f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
1747753115,
|
||||
arrayOf(
|
||||
arrayOf("alt", "Reply to geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3"),
|
||||
arrayOf("I", "geo:drt"),
|
||||
arrayOf("I", "geo:dr"),
|
||||
arrayOf("I", "geo:d"),
|
||||
arrayOf("K", "geo"),
|
||||
arrayOf("i", "geo:drt3n"),
|
||||
arrayOf("i", "geo:drt3"),
|
||||
arrayOf("i", "geo:drt"),
|
||||
arrayOf("i", "geo:dr"),
|
||||
arrayOf("i", "geo:d"),
|
||||
arrayOf("k", "geo"),
|
||||
),
|
||||
"testing",
|
||||
"12070e663272f1227c639fb834eb2122fc7bb995f4c49e55ebb1dfe2135ef7347d44810bacd2e64fd26b8826fd47d2800ce6c3d3b579bb3afe39088ffd4faa60",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun verifyEvent() {
|
||||
assertTrue(event.verify())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScopes() {
|
||||
assertEquals("drt3n", event.geohashedScope())
|
||||
assertTrue(event.isTaggedScope("drt3n", GeohashId::match))
|
||||
assertTrue(event.isTaggedScope(GeohashId.toScope("drt3n")))
|
||||
assertFalse(event.isTaggedScope("drt3n"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreation() {
|
||||
val event =
|
||||
signer.sign(
|
||||
CommentEvent.replyExternalIdentity(
|
||||
"Message",
|
||||
GeohashId("drt3n"),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals("drt3n", event!!.geohashedScope())
|
||||
assertTrue(event.isTaggedScope(GeohashId.toScope("drt3n")))
|
||||
assertFalse(event.isTaggedScope("drt3n"))
|
||||
|
||||
assertTrue(event.hasScopeKind(GeohashId.KIND))
|
||||
assertTrue(event.hasRootScopeKind(GeohashId.KIND))
|
||||
assertTrue(event.hasReplyScopeKind(GeohashId.KIND))
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip44Encryption
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip44v1Test {
|
||||
private val nip44v1 = Nip44v1()
|
||||
|
||||
@Test
|
||||
fun testSharedSecretCompatibilityWithCoracle() {
|
||||
val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561"
|
||||
val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f"
|
||||
|
||||
val key =
|
||||
nip44v1.getSharedSecret(
|
||||
privateKey = privateKey.hexToByteArray(),
|
||||
pubKey = publicKey.hexToByteArray(),
|
||||
)
|
||||
|
||||
assertEquals("577c966f499dddd8e8dcc34e8f352e283cc177e53ae372794947e0b8ede7cfd8", key.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSharedSecret() {
|
||||
val sender = KeyPair()
|
||||
val receiver = KeyPair()
|
||||
|
||||
val sharedSecret1 = nip44v1.getSharedSecret(sender.privKey!!, receiver.pubKey)
|
||||
val sharedSecret2 = nip44v1.getSharedSecret(receiver.privKey!!, sender.pubKey)
|
||||
|
||||
assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey())
|
||||
|
||||
val secretKey1 = KeyPair(privKey = sharedSecret1)
|
||||
val secretKey2 = KeyPair(privKey = sharedSecret2)
|
||||
|
||||
assertEquals(secretKey1.pubKey.toHexKey(), secretKey2.pubKey.toHexKey())
|
||||
assertEquals(secretKey1.privKey?.toHexKey(), secretKey2.privKey?.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecrypt() {
|
||||
val msg = "Hi"
|
||||
|
||||
val privateKey = Nip01.privKeyCreate()
|
||||
val publicKey = Nip01.pubKeyCreate(privateKey)
|
||||
|
||||
val encrypted = nip44v1.encrypt(msg, privateKey, publicKey)
|
||||
val decrypted = nip44v1.decrypt(encrypted, privateKey, publicKey)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptSharedSecret() {
|
||||
val msg = "Hi"
|
||||
|
||||
val privateKey = Nip01.privKeyCreate()
|
||||
val publicKey = Nip01.pubKeyCreate(privateKey)
|
||||
|
||||
val sharedSecret = nip44v1.getSharedSecret(privateKey, publicKey)
|
||||
|
||||
val encrypted = nip44v1.encrypt(msg, sharedSecret)
|
||||
val decrypted = nip44v1.decrypt(encrypted, sharedSecret)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip44Encryption
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertNull
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip44v2Test {
|
||||
private val vectors: VectorFile =
|
||||
jacksonObjectMapper()
|
||||
.readValue(
|
||||
getInstrumentation().context.assets.open("nip44.vectors.json"),
|
||||
VectorFile::class.java,
|
||||
)
|
||||
|
||||
private val nip44v2 = Nip44v2()
|
||||
|
||||
@Test
|
||||
fun conversationKeyTest() {
|
||||
for (v in vectors.v2?.valid?.getConversationKey!!) {
|
||||
val conversationKey =
|
||||
nip44v2.getConversationKey(v.sec1!!.hexToByteArray(), v.pub2!!.hexToByteArray())
|
||||
|
||||
assertEquals(v.conversationKey, conversationKey.toHexKey())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun paddingTest() {
|
||||
for (v in vectors.v2?.valid?.calcPaddedLen!!) {
|
||||
val actual = nip44v2.calcPaddedLen(v[0])
|
||||
assertEquals(v[1], actual)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompressedWith02Keys() {
|
||||
val privateKeyA = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
val privateKeyB = "65f039136f8da8d3e87b4818746b53318d5481e24b2673f162815144223a0b5a".hexToByteArray()
|
||||
|
||||
val publicKeyA = Nip01.pubKeyCreate(privateKeyA)
|
||||
val publicKeyB = Nip01.pubKeyCreate(privateKeyB)
|
||||
|
||||
assertEquals(
|
||||
nip44v2.getConversationKey(privateKeyA, publicKeyB).toHexKey(),
|
||||
nip44v2.getConversationKey(privateKeyB, publicKeyA).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompressedWith03Keys() {
|
||||
val privateKeyA = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
val privateKeyB = "e6159851715b4aa6190c22b899b0c792847de0a4435ac5b678f35738351c43b0".hexToByteArray()
|
||||
|
||||
val publicKeyA = Nip01.pubKeyCreate(privateKeyA)
|
||||
val publicKeyB = Nip01.pubKeyCreate(privateKeyB)
|
||||
|
||||
assertEquals(
|
||||
nip44v2.getConversationKey(privateKeyA, publicKeyB).toHexKey(),
|
||||
nip44v2.getConversationKey(privateKeyB, publicKeyA).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptTest() {
|
||||
for (v in vectors.v2?.valid?.encryptDecrypt!!) {
|
||||
val pub2 = KeyPair(v.sec2!!.hexToByteArray())
|
||||
val conversationKey1 = nip44v2.getConversationKey(v.sec1!!.hexToByteArray(), pub2.pubKey)
|
||||
assertEquals(v.conversationKey, conversationKey1.toHexKey())
|
||||
|
||||
val ciphertext =
|
||||
nip44v2
|
||||
.encryptWithNonce(
|
||||
v.plaintext!!,
|
||||
conversationKey1,
|
||||
v.nonce!!.hexToByteArray(),
|
||||
).encodePayload()
|
||||
|
||||
assertEquals(v.payload, ciphertext)
|
||||
|
||||
val pub1 = KeyPair(v.sec1.hexToByteArray())
|
||||
val conversationKey2 = nip44v2.getConversationKey(v.sec2.hexToByteArray(), pub1.pubKey)
|
||||
assertEquals(v.conversationKey, conversationKey2.toHexKey())
|
||||
|
||||
val decrypted = nip44v2.decrypt(v.payload!!, conversationKey2)
|
||||
assertEquals(v.plaintext, decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptLongTest() {
|
||||
for (v in vectors.v2?.valid?.encryptDecryptLongMsg!!) {
|
||||
val conversationKey = v.conversationKey!!.hexToByteArray()
|
||||
val plaintext = v.pattern!!.repeat(v.repeat!!)
|
||||
|
||||
assertEquals(v.plaintextSha256, sha256Hex(plaintext.toByteArray(Charsets.UTF_8)))
|
||||
|
||||
val ciphertext =
|
||||
nip44v2
|
||||
.encryptWithNonce(
|
||||
plaintext,
|
||||
conversationKey,
|
||||
v.nonce!!.hexToByteArray(),
|
||||
).encodePayload()
|
||||
|
||||
assertEquals(v.payloadSha256, sha256Hex(ciphertext.toByteArray(Charsets.UTF_8)))
|
||||
|
||||
val decrypted = nip44v2.decrypt(ciphertext, conversationKey)
|
||||
|
||||
assertEquals(plaintext, decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invalidMessageLengths() {
|
||||
for (v in vectors.v2?.invalid?.encryptMsgLengths!!) {
|
||||
val key = RandomInstance.bytes(32)
|
||||
try {
|
||||
nip44v2.encrypt("a".repeat(v), key)
|
||||
fail("Should Throw for $v")
|
||||
} catch (e: Exception) {
|
||||
assertNotNull(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invalidDecrypt() {
|
||||
for (v in vectors.v2?.invalid?.decrypt!!) {
|
||||
try {
|
||||
val result = nip44v2.decrypt(v.payload!!, v.conversationKey!!.hexToByteArray())
|
||||
assertNull(result)
|
||||
// fail("Should Throw for ${v.note}")
|
||||
} catch (e: Exception) {
|
||||
assertNotNull(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invalidConversationKey() {
|
||||
for (v in vectors.v2?.invalid?.getConversationKey!!) {
|
||||
try {
|
||||
nip44v2.getConversationKey(v.sec1!!.hexToByteArray(), v.pub2!!.hexToByteArray())
|
||||
fail("Should Throw for ${v.note}")
|
||||
} catch (e: Exception) {
|
||||
assertNotNull(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sha256Hex(data: ByteArray) = sha256(data).toHexKey()
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip44Encryption
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
data class VectorFile(
|
||||
val v2: V2? = V2(),
|
||||
)
|
||||
|
||||
data class V2(
|
||||
val valid: Valid? = Valid(),
|
||||
val invalid: Invalid? = Invalid(),
|
||||
)
|
||||
|
||||
data class Valid(
|
||||
@field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
|
||||
@field:JsonProperty("get_message_keys") val getMessageKeys: GetMessageKeys? = GetMessageKeys(),
|
||||
@field:JsonProperty("calc_padded_len") val calcPaddedLen: ArrayList<ArrayList<Int>> = arrayListOf(),
|
||||
@field:JsonProperty("encrypt_decrypt") val encryptDecrypt: ArrayList<EncryptDecrypt> = arrayListOf(),
|
||||
@field:JsonProperty("encrypt_decrypt_long_msg")
|
||||
val encryptDecryptLongMsg: ArrayList<EncryptDecryptLongMsg> = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Invalid(
|
||||
@field:JsonProperty("encrypt_msg_lengths") val encryptMsgLengths: ArrayList<Int> = arrayListOf(),
|
||||
@field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
|
||||
@field:JsonProperty("decrypt") val decrypt: ArrayList<Decrypt> = arrayListOf(),
|
||||
)
|
||||
|
||||
data class GetConversationKey(
|
||||
val sec1: String? = null,
|
||||
val pub2: String? = null,
|
||||
val note: String? = null,
|
||||
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
|
||||
)
|
||||
|
||||
data class GetMessageKeys(
|
||||
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
|
||||
val keys: ArrayList<Keys> = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Keys(
|
||||
@field:JsonProperty("nonce") val nonce: String? = null,
|
||||
@field:JsonProperty("chacha_key") val chachaKey: String? = null,
|
||||
@field:JsonProperty("chacha_nonce") val chachaNonce: String? = null,
|
||||
@field:JsonProperty("hmac_key") val hmacKey: String? = null,
|
||||
)
|
||||
|
||||
data class EncryptDecrypt(
|
||||
val sec1: String? = null,
|
||||
val sec2: String? = null,
|
||||
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
|
||||
val nonce: String? = null,
|
||||
val plaintext: String? = null,
|
||||
val payload: String? = null,
|
||||
)
|
||||
|
||||
data class EncryptDecryptLongMsg(
|
||||
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
|
||||
val nonce: String? = null,
|
||||
val pattern: String? = null,
|
||||
val repeat: Int? = null,
|
||||
@field:JsonProperty("plaintext_sha256") val plaintextSha256: String? = null,
|
||||
@field:JsonProperty("payload_sha256") val payloadSha256: String? = null,
|
||||
)
|
||||
|
||||
data class Decrypt(
|
||||
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
|
||||
val nonce: String? = null,
|
||||
val plaintext: String? = null,
|
||||
val payload: String? = null,
|
||||
val note: String? = null,
|
||||
)
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip46RemoteSigner
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ReadWrite
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class Nip46Test {
|
||||
val remoteKey = NostrSignerInternal(KeyPair())
|
||||
val signer = NostrSignerInternal(KeyPair())
|
||||
val peer = NostrSignerInternal(KeyPair())
|
||||
|
||||
val dummyEvent =
|
||||
EventTemplate<Event>(
|
||||
createdAt = TimeUtils.now(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "test",
|
||||
)
|
||||
|
||||
val dummyEventSigned =
|
||||
TextNoteEvent(
|
||||
id = "",
|
||||
pubKey = "",
|
||||
createdAt = TimeUtils.now(),
|
||||
tags = emptyArray(),
|
||||
content = "test",
|
||||
sig = "",
|
||||
)
|
||||
|
||||
suspend fun <T : BunkerMessage> encodeDecodeEvent(req: T): T {
|
||||
val eventStr = NostrConnectEvent.create(req, remoteKey.pubKey, signer).toJson()
|
||||
|
||||
return (Event.fromJson(eventStr) as NostrConnectEvent).decryptMessage(signer) as T
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signEncoder() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestSign(event = dummyEvent)
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(BunkerRequestSign.METHOD_NAME, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(dummyEvent.createdAt, actual.event.createdAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connectEncoder() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestConnect(remoteKey = remoteKey.pubKey)
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pingEncoder() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestPing()
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getPubkeyEncoder() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestGetPublicKey()
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getRelaysEncoder() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestGetRelays()
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNip04Encrypt() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestNip04Encrypt(pubKey = peer.pubKey, message = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.pubKey, actual.pubKey)
|
||||
assertEquals(expected.message, actual.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNip44Encrypt() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestNip44Encrypt(pubKey = peer.pubKey, message = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.pubKey, actual.pubKey)
|
||||
assertEquals(expected.message, actual.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNip04Decrypt() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestNip04Decrypt(pubKey = peer.pubKey, ciphertext = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.pubKey, actual.pubKey)
|
||||
assertEquals(expected.ciphertext, actual.ciphertext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNip44Decrypt() =
|
||||
runBlocking {
|
||||
val expected = BunkerRequestNip44Decrypt(pubKey = peer.pubKey, ciphertext = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.method, actual.method)
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.pubKey, actual.pubKey)
|
||||
assertEquals(expected.ciphertext, actual.ciphertext)
|
||||
}
|
||||
|
||||
// Responses
|
||||
|
||||
@Test
|
||||
fun testAckResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseAck()
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPongResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponsePong()
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseError(error = "Error")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEventResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseEvent(event = dummyEventSigned)
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
assertEquals(dummyEventSigned.id, actual.event.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPubkeyResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponsePublicKey(pubkey = peer.pubKey)
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
assertEquals(expected.pubkey, actual.pubkey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelaysResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseGetRelays(relays = mapOf("url" to ReadWrite(true, false)))
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
assertEquals(expected.relays["url"], actual.relays["url"])
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Impossible to recreate the class since there are no hints on the json")
|
||||
fun testDecryptResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseDecrypt(plaintext = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
assertEquals(expected.plaintext, actual.plaintext)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Impossible to recreate the class since there are no hints on the json")
|
||||
fun testEncryptResponse() =
|
||||
runBlocking {
|
||||
val expected = BunkerResponseEncrypt(ciphertext = "Test")
|
||||
val actual = encodeDecodeEvent(expected)
|
||||
|
||||
assertEquals(expected.id, actual.id)
|
||||
assertEquals(expected.result, actual.result)
|
||||
assertEquals(expected.error, actual.error)
|
||||
assertEquals(expected.ciphertext, actual.ciphertext)
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip49PrivKeyEnc
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
public class NIP49Test {
|
||||
companion object {
|
||||
val TEST_CASE = "ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p"
|
||||
|
||||
val TEST_CASE_EXPECTED = "3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683"
|
||||
val TEST_CASE_PASSWORD = "nostr"
|
||||
|
||||
val MAIN_TEST_CASES =
|
||||
listOf<Nip49TestCase>(
|
||||
Nip49TestCase(".ksjabdk.aselqwe", "14c226dbdd865d5e1645e72c7470fd0a17feb42cc87b750bab6538171b3a3f8a", 1, 0x00),
|
||||
Nip49TestCase("skjdaklrnçurbç l", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 2, 0x01),
|
||||
Nip49TestCase("777z7z7z7z7z7z7z", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 3, 0x02),
|
||||
Nip49TestCase(".ksjabdk.aselqwe", "14c226dbdd865d5e1645e72c7470fd0a17feb42cc87b750bab6538171b3a3f8a", 7, 0x00),
|
||||
Nip49TestCase("skjdaklrnçurbç l", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 8, 0x01),
|
||||
Nip49TestCase("777z7z7z7z7z7z7z", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 9, 0x02),
|
||||
Nip49TestCase("", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 4, 0x00),
|
||||
Nip49TestCase("", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 5, 0x01),
|
||||
Nip49TestCase("", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 1, 0x00),
|
||||
Nip49TestCase("", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 9, 0x01),
|
||||
)
|
||||
}
|
||||
|
||||
val nip49 = Nip49()
|
||||
|
||||
@Test
|
||||
fun decodeBech32() {
|
||||
val data =
|
||||
Nip49.EncryptedInfo.decodePayload(
|
||||
TEST_CASE,
|
||||
)!!
|
||||
|
||||
assertEquals(2, data.version)
|
||||
assertEquals(16, data.logn)
|
||||
assertEquals("52d7c3f8580e7b41953381e5bc49646b", data.salt.toHexKey())
|
||||
assertEquals("c33f02a7dcaac8bdd8da23cd449783240b6ebc12edeea7bf", data.nonce.toHexKey())
|
||||
assertEquals(0, data.keySecurity)
|
||||
assertEquals("b8e8803440de7b3e9519c3e734cb2ac9a211ea2dc52312e5117a11a3022d813ab438719ca0b504a1193be510c3aee776", data.encryptedKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decrypt() {
|
||||
val decrypted = nip49.decrypt(TEST_CASE, TEST_CASE_PASSWORD)
|
||||
assertEquals(TEST_CASE_EXPECTED, decrypted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptTestCase() {
|
||||
val encrypted = nip49.encrypt(TEST_CASE_EXPECTED, TEST_CASE_PASSWORD, 16, 0)
|
||||
val decrypted = nip49.decrypt(encrypted!!, TEST_CASE_PASSWORD)
|
||||
|
||||
assertEquals(TEST_CASE_EXPECTED, decrypted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecrypt() {
|
||||
MAIN_TEST_CASES.forEach {
|
||||
val encrypted = nip49.encrypt(it.secretKey, it.password, it.logn, it.ksb)
|
||||
|
||||
assertNotNull(encrypted)
|
||||
|
||||
val decrypted = nip49.decrypt(encrypted!!, it.password)
|
||||
|
||||
assertEquals(it.secretKey, decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalization() {
|
||||
val samePassword1 = String(byteArrayOf(0xE2.toByte(), 0x84.toByte(), 0xAB.toByte(), 0xE2.toByte(), 0x84.toByte(), 0xA6.toByte(), 0xE1.toByte(), 0xBA.toByte(), 0x9B.toByte(), 0xCC.toByte(), 0xA3.toByte()))
|
||||
val samePassword2 = String(byteArrayOf(0xC3.toByte(), 0x85.toByte(), 0xCE.toByte(), 0xA9.toByte(), 0xE1.toByte(), 0xB9.toByte(), 0xA9.toByte()))
|
||||
|
||||
if (samePassword1.toByteArray().contentEquals(samePassword2.toByteArray())) {
|
||||
fail("Passwords should have a different byte representation")
|
||||
}
|
||||
|
||||
val encrypted = nip49.encrypt(TEST_CASE_EXPECTED, samePassword1, 8, 0)
|
||||
|
||||
assertNotNull(encrypted)
|
||||
|
||||
val decrypted = nip49.decrypt(encrypted!!, samePassword2)
|
||||
|
||||
assertEquals(TEST_CASE_EXPECTED, decrypted)
|
||||
}
|
||||
|
||||
class Nip49TestCase(
|
||||
val password: String,
|
||||
val secretKey: String,
|
||||
val logn: Int,
|
||||
val ksb: Byte,
|
||||
)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip55AndroidSigner
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SignStringTest {
|
||||
@Test
|
||||
fun signString() {
|
||||
val random = "319cc5596fdd6cd767e5a59d976e8e059c61306af90dff1e6ee1067b3a1fdbc0".hexToByteArray()
|
||||
val message = "8e58c8251bb406b6ded69e9eb14f55282a9a53bdab16fc49a3218c2ad3abc887".hexToByteArray()
|
||||
val keyPair = KeyPair("a5ab474552c8f9c46c2eda5a0b68f27430ad81f96cb405e0cb4e34bf0c6494a2".hexToByteArray())
|
||||
|
||||
val signedMessage = Nip01.sign(message, keyPair.privKey!!, random).toHexKey()
|
||||
val expectedValue = "0f9be7e01ba53d5ee6874b9180c7956269fda7a5be424634c3d17b5cfcea6da001be89183876415ba08b7dafa6cff4555e393dc228fb8769b384344e9a27b77c"
|
||||
assertEquals(expectedValue, signedMessage)
|
||||
|
||||
val message2 = "Hello"
|
||||
val signedMessage2 = signString(message2, keyPair.privKey!!, random).toHexKey()
|
||||
val expectedValue2 = "7ec8194a585bfb513564113b6b7bfeaafa0254c99d24eaf92280657c2291bab908b1b7bc553c83276a0254aef5041bbe6a50e93381edc4de3d859efa1c3a5a1e"
|
||||
assertEquals(expectedValue2, signedMessage2)
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip57Zaps
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEncryption.Companion.createEncryptionPrivateKey
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.fail
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PrivateZapTests {
|
||||
@Test
|
||||
fun testPollZap() {
|
||||
val poll =
|
||||
JsonMapper.fromJson(
|
||||
"""{
|
||||
"content": "New poll \n\n #zappoll",
|
||||
"created_at": 1682440713,
|
||||
"id": "16291ba452bb0786a4bf5c278d38de73c96b58c056ed75c5ea466b0795197288",
|
||||
"kind": 6969,
|
||||
"pubkey": "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
"sig": "ac05fa4004c3f7c42913c87b11bf9714bb61a3f0940863a6b9ff0f8105b399add72dbc09bf944c79b9a72ef009ec6905adedbd2c4c8fb3d2f57007bad8fcb279",
|
||||
"tags": [
|
||||
[
|
||||
"poll_option",
|
||||
"0",
|
||||
"Test 1"
|
||||
],
|
||||
[
|
||||
"poll_option",
|
||||
"1",
|
||||
"Test 2"
|
||||
],
|
||||
[
|
||||
"value_maximum",
|
||||
"null"
|
||||
],
|
||||
[
|
||||
"value_minimum",
|
||||
"null"
|
||||
],
|
||||
[
|
||||
"consensus_threshold",
|
||||
"null"
|
||||
],
|
||||
[
|
||||
"closed_at",
|
||||
"null"
|
||||
]
|
||||
],
|
||||
"seenOn": [
|
||||
"wss://relay.damus.io/"
|
||||
]
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
val loggedIn =
|
||||
NostrSignerInternal(
|
||||
KeyPair(
|
||||
Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598"),
|
||||
),
|
||||
)
|
||||
|
||||
val privateZapRequest =
|
||||
runBlocking {
|
||||
LnZapRequestEvent.create(
|
||||
zappedEvent = poll,
|
||||
relays = setOf(RelayUrlNormalizer.normalize("wss://relay.damus.io/")),
|
||||
signer = loggedIn,
|
||||
pollOption = 0,
|
||||
message = "",
|
||||
zapType = LnZapEvent.ZapType.PRIVATE,
|
||||
toUserPubHex = null,
|
||||
)
|
||||
}
|
||||
|
||||
val recepientPK = privateZapRequest.zappedAuthor().firstOrNull()
|
||||
val recepientPost = privateZapRequest.zappedPost().firstOrNull()
|
||||
|
||||
if (recepientPK != null && recepientPost != null) {
|
||||
val privateKey =
|
||||
createEncryptionPrivateKey(
|
||||
loggedIn.keyPair.privKey!!.toHexKey(),
|
||||
recepientPost,
|
||||
privateZapRequest.createdAt,
|
||||
)
|
||||
val decodedPrivateZap = PrivateZapRequestBuilder().decryptAnonTag(privateZapRequest.getAnonTag(), privateKey, recepientPK)
|
||||
|
||||
assertNotNull(decodedPrivateZap)
|
||||
} else {
|
||||
fail("Should not be null")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKind1PrivateZap() {
|
||||
val textNote =
|
||||
Event.fromJson(
|
||||
"""{
|
||||
"content": "Testing copied author. \n\nnostr:npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z",
|
||||
"created_at": 1682369982,
|
||||
"id": "c757e1371d715c711ec9ef9740a3df6475d64b3d0af45ffcbfca08d273baf1c1",
|
||||
"kind": 1,
|
||||
"pubkey": "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
"sig": "1fb5b6fd980f4c2ef058d5f4f7b166c0e5fb21eff26fe9cacd87a9aa4feb344485841ebcc26a233bf8d6ea0a66acf0db2bfdb11ad1cb04bcea4cfa3e78c3eaf1",
|
||||
"tags": [
|
||||
[
|
||||
"p",
|
||||
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
|
||||
]
|
||||
],
|
||||
"seenOn": [
|
||||
"wss://relay.damus.io/"
|
||||
]
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
val loggedIn =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
|
||||
)
|
||||
|
||||
val privateZapRequest =
|
||||
runBlocking {
|
||||
LnZapRequestEvent.create(
|
||||
zappedEvent = textNote,
|
||||
relays =
|
||||
setOf(
|
||||
RelayUrlNormalizer.normalize("wss://relay.damus.io/"),
|
||||
RelayUrlNormalizer.normalize("wss://relay.damus2.io/"),
|
||||
RelayUrlNormalizer.normalize("wss://relay.damus3.io/"),
|
||||
),
|
||||
signer = loggedIn,
|
||||
pollOption = null,
|
||||
message = "test",
|
||||
zapType = LnZapEvent.ZapType.PRIVATE,
|
||||
toUserPubHex = null,
|
||||
)
|
||||
}
|
||||
|
||||
val recepientPK = privateZapRequest.zappedAuthor().firstOrNull()
|
||||
val recepientPost = privateZapRequest.zappedPost().firstOrNull()
|
||||
|
||||
if (recepientPK != null && recepientPost != null) {
|
||||
val privateKey =
|
||||
createEncryptionPrivateKey(
|
||||
loggedIn.keyPair.privKey!!.toHexKey(),
|
||||
recepientPost,
|
||||
privateZapRequest.createdAt,
|
||||
)
|
||||
val decodedPrivateZap = PrivateZapRequestBuilder().decryptAnonTag(privateZapRequest.getAnonTag(), privateKey, recepientPK)
|
||||
|
||||
assertNotNull(decodedPrivateZap)
|
||||
} else {
|
||||
fail("Should not be null")
|
||||
}
|
||||
}
|
||||
}
|
||||
+668
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip59GiftWraps
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.checkSignature
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GiftWrapEventTest {
|
||||
@Test()
|
||||
fun testNip17Utils() =
|
||||
runBlocking {
|
||||
val sender = NostrSignerInternal(KeyPair())
|
||||
val receiver = NostrSignerInternal(KeyPair())
|
||||
val message = "Hola, que tal?"
|
||||
|
||||
val events =
|
||||
NIP17Factory().createMessageNIP17(
|
||||
ChatMessageEvent.build(
|
||||
message,
|
||||
listOf(PTag(receiver.pubKey, null)),
|
||||
),
|
||||
sender,
|
||||
)
|
||||
|
||||
// Simulate Receiver
|
||||
val eventsReceiverGets = events.wraps.filter { it.isTaggedUser(receiver.pubKey) }
|
||||
eventsReceiverGets.forEach {
|
||||
val event = it.unwrapThrowing(receiver)
|
||||
if (event is SealedRumorEvent) {
|
||||
val innerData = event.unsealThrowing(receiver)
|
||||
assertEquals(message, innerData.content)
|
||||
} else {
|
||||
fail("Wrong Event")
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate Sender
|
||||
val eventsSenderGets = events.wraps.filter { it.isTaggedUser(sender.pubKey) }
|
||||
eventsSenderGets.forEach {
|
||||
val event = it.unwrapThrowing(sender)
|
||||
if (event is SealedRumorEvent) {
|
||||
val innerData = event.unsealThrowing(sender)
|
||||
assertEquals(message, innerData.content)
|
||||
} else {
|
||||
fail("Wrong Event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun testNip17UtilsForGroups() =
|
||||
runBlocking {
|
||||
val sender = NostrSignerInternal(KeyPair())
|
||||
val receiver1 = NostrSignerInternal(KeyPair())
|
||||
val receiver2 = NostrSignerInternal(KeyPair())
|
||||
val receiver3 = NostrSignerInternal(KeyPair())
|
||||
val receiver4 = NostrSignerInternal(KeyPair())
|
||||
val message = "Hola, que tal?"
|
||||
|
||||
val receivers =
|
||||
listOf(
|
||||
receiver1,
|
||||
receiver2,
|
||||
receiver3,
|
||||
receiver4,
|
||||
)
|
||||
|
||||
val events =
|
||||
NIP17Factory().createMessageNIP17(
|
||||
ChatMessageEvent.build(
|
||||
message,
|
||||
receivers.map { PTag(it.pubKey, null) },
|
||||
),
|
||||
sender,
|
||||
)
|
||||
|
||||
// Simulate Receiver
|
||||
receivers.forEach { receiver ->
|
||||
val eventsReceiverGets = events.wraps.filter { it.isTaggedUser(receiver.pubKey) }
|
||||
eventsReceiverGets.forEach {
|
||||
val event = it.unwrapThrowing(receiver)
|
||||
if (event is SealedRumorEvent) {
|
||||
val innerData = event.unsealThrowing(receiver)
|
||||
assertEquals(message, innerData.content)
|
||||
} else {
|
||||
fail("Wrong Event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate Sender
|
||||
val eventsSenderGets = events.wraps.filter { it.isTaggedUser(sender.pubKey) }
|
||||
eventsSenderGets.forEach {
|
||||
val event = it.unwrapThrowing(sender)
|
||||
if (event is SealedRumorEvent) {
|
||||
val innerData = event.unsealThrowing(sender)
|
||||
assertEquals(message, innerData.content)
|
||||
} else {
|
||||
fail("Wrong Event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun testInternalsSimpleMessage() =
|
||||
runBlocking {
|
||||
val sender = NostrSignerInternal(KeyPair())
|
||||
val receiver = NostrSignerInternal(KeyPair())
|
||||
|
||||
val senderMessage =
|
||||
sender.sign(
|
||||
ChatMessageEvent.build(
|
||||
msg = "Hi There!",
|
||||
to = listOf(PTag(receiver.pubKey, null)),
|
||||
),
|
||||
)
|
||||
// MsgFor the Receiver
|
||||
val encMsgFromSenderToReceiver =
|
||||
SealedRumorEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = receiver.pubKey,
|
||||
signer = sender,
|
||||
)
|
||||
|
||||
// Should expose sender
|
||||
assertEquals(encMsgFromSenderToReceiver.pubKey, sender.pubKey)
|
||||
// Should not expose receiver
|
||||
assertTrue(encMsgFromSenderToReceiver.tags.isEmpty())
|
||||
|
||||
val giftWrapToReceiver =
|
||||
GiftWrapEvent.create(
|
||||
event = encMsgFromSenderToReceiver,
|
||||
recipientPubKey = receiver.pubKey,
|
||||
)
|
||||
|
||||
// Should not be signed by neither sender nor receiver
|
||||
assertNotEquals(giftWrapToReceiver.pubKey, sender.pubKey)
|
||||
assertNotEquals(giftWrapToReceiver.pubKey, receiver.pubKey)
|
||||
|
||||
// Should not include sender as recipient
|
||||
assertNotEquals(giftWrapToReceiver.recipientPubKey(), sender.pubKey)
|
||||
|
||||
// Should be addressed to the receiver
|
||||
assertEquals(giftWrapToReceiver.recipientPubKey(), receiver.pubKey)
|
||||
|
||||
// MsgFor the Sender
|
||||
val encMsgFromSenderToSender =
|
||||
SealedRumorEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = sender.pubKey,
|
||||
signer = sender,
|
||||
)
|
||||
|
||||
// Should expose sender
|
||||
assertEquals(encMsgFromSenderToSender.pubKey, sender.pubKey)
|
||||
// Should not expose receiver
|
||||
assertTrue(encMsgFromSenderToSender.tags.isEmpty())
|
||||
|
||||
val giftWrapToSender =
|
||||
GiftWrapEvent.create(
|
||||
event = encMsgFromSenderToSender,
|
||||
recipientPubKey = sender.pubKey,
|
||||
)
|
||||
|
||||
// Should not be signed by neither the sender, not the receiver
|
||||
assertNotEquals(giftWrapToSender.pubKey, sender.pubKey)
|
||||
assertNotEquals(giftWrapToSender.pubKey, receiver.pubKey)
|
||||
|
||||
// Should not be addressed to the receiver
|
||||
assertNotEquals(giftWrapToSender.recipientPubKey(), receiver.pubKey)
|
||||
// Should be addressed to the sender
|
||||
assertEquals(giftWrapToSender.recipientPubKey(), sender.pubKey)
|
||||
|
||||
// Done
|
||||
// -----
|
||||
// Start receiving
|
||||
|
||||
// Receiver's side
|
||||
// Makes sure it can only be decrypted by the target user
|
||||
|
||||
assertNotNull(giftWrapToSender)
|
||||
assertNotNull(giftWrapToReceiver)
|
||||
|
||||
val unwrappedMsgForSenderBySender = giftWrapToSender.unwrapThrowing(sender)
|
||||
assertEquals(SealedRumorEvent.KIND, unwrappedMsgForSenderBySender.kind)
|
||||
assertTrue(unwrappedMsgForSenderBySender is SealedRumorEvent)
|
||||
|
||||
if (unwrappedMsgForSenderBySender is SealedRumorEvent) {
|
||||
val unwrappedRumorToSenderBySender = unwrappedMsgForSenderBySender.unsealThrowing(sender)
|
||||
assertEquals("Hi There!", unwrappedRumorToSenderBySender.content)
|
||||
|
||||
unwrappedMsgForSenderBySender.unsealOrNull(receiver)?.let { _ ->
|
||||
fail("Should not be able to decrypt msg for the sender by the sender but decrypted with receiver")
|
||||
}
|
||||
}
|
||||
|
||||
giftWrapToReceiver.unwrapOrNull(sender)?.let { _ ->
|
||||
fail("Should not be able to decrypt msg for the receiver decrypted by the sender")
|
||||
}
|
||||
|
||||
giftWrapToReceiver.unwrapOrNull(receiver)?.let { _ ->
|
||||
fail("Should not be able to decrypt msg for the sender decrypted by the receiver")
|
||||
}
|
||||
|
||||
val unwrappedMsgForReceiverByReceiver = giftWrapToReceiver.unwrapThrowing(receiver)
|
||||
assertEquals(SealedRumorEvent.KIND, unwrappedMsgForReceiverByReceiver.kind)
|
||||
assertTrue(unwrappedMsgForReceiverByReceiver is SealedRumorEvent)
|
||||
|
||||
if (unwrappedMsgForReceiverByReceiver is SealedRumorEvent) {
|
||||
val unwrappedRumorToReceiverByReceiver = unwrappedMsgForReceiverByReceiver.unsealThrowing(receiver)
|
||||
assertEquals("Hi There!", unwrappedRumorToReceiverByReceiver.content)
|
||||
|
||||
unwrappedMsgForReceiverByReceiver.unsealOrNull(sender)?.let { _ ->
|
||||
fail(
|
||||
"Should not be able to decrypt msg for the receiver by the receiver but decrypted with the sender",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun testInternalsGroupMessage() =
|
||||
runBlocking {
|
||||
val sender = NostrSignerInternal(KeyPair())
|
||||
val receiverA = NostrSignerInternal(KeyPair())
|
||||
val receiverB = NostrSignerInternal(KeyPair())
|
||||
|
||||
val senderMessage =
|
||||
sender.sign(
|
||||
ChatMessageEvent.build(
|
||||
msg = "Who is going to the party tonight?",
|
||||
to = listOf(PTag(receiverA.pubKey), PTag(receiverB.pubKey)),
|
||||
),
|
||||
)
|
||||
|
||||
val msgFromSenderToReceiverA =
|
||||
SealedRumorEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = receiverA.pubKey,
|
||||
signer = sender,
|
||||
)
|
||||
|
||||
// Should expose sender
|
||||
assertEquals(msgFromSenderToReceiverA.pubKey, sender.pubKey)
|
||||
// Should not expose receiver
|
||||
assertTrue(msgFromSenderToReceiverA.tags.isEmpty())
|
||||
|
||||
val giftWrapForReceiverA =
|
||||
GiftWrapEvent.create(
|
||||
event = msgFromSenderToReceiverA,
|
||||
recipientPubKey = receiverA.pubKey,
|
||||
)
|
||||
|
||||
// Should not be signed by neither sender nor receiver
|
||||
assertNotEquals(giftWrapForReceiverA.pubKey, sender.pubKey)
|
||||
assertNotEquals(giftWrapForReceiverA.pubKey, receiverA.pubKey)
|
||||
assertNotEquals(giftWrapForReceiverA.pubKey, receiverB.pubKey)
|
||||
|
||||
// Should not include sender as recipient
|
||||
assertNotEquals(giftWrapForReceiverA.recipientPubKey(), sender.pubKey)
|
||||
|
||||
// Should be addressed to the receiver
|
||||
assertEquals(giftWrapForReceiverA.recipientPubKey(), receiverA.pubKey)
|
||||
|
||||
val msgFromSenderToReceiverB =
|
||||
SealedRumorEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = receiverB.pubKey,
|
||||
signer = sender,
|
||||
)
|
||||
|
||||
// Should expose sender
|
||||
assertEquals(msgFromSenderToReceiverB.pubKey, sender.pubKey)
|
||||
// Should not expose receiver
|
||||
assertTrue(msgFromSenderToReceiverB.tags.isEmpty())
|
||||
|
||||
val giftWrapForReceiverB =
|
||||
GiftWrapEvent.create(
|
||||
event = msgFromSenderToReceiverB,
|
||||
recipientPubKey = receiverB.pubKey,
|
||||
)
|
||||
|
||||
// Should not be signed by neither sender nor receiver
|
||||
assertNotEquals(giftWrapForReceiverB.pubKey, sender.pubKey)
|
||||
assertNotEquals(giftWrapForReceiverB.pubKey, receiverA.pubKey)
|
||||
assertNotEquals(giftWrapForReceiverB.pubKey, receiverB.pubKey)
|
||||
|
||||
// Should not include sender as recipient
|
||||
assertNotEquals(giftWrapForReceiverB.recipientPubKey(), sender.pubKey)
|
||||
|
||||
// Should be addressed to the receiver
|
||||
assertEquals(giftWrapForReceiverB.recipientPubKey(), receiverB.pubKey)
|
||||
|
||||
val msgFromSenderToSender =
|
||||
SealedRumorEvent.create(
|
||||
event = senderMessage,
|
||||
encryptTo = sender.pubKey,
|
||||
signer = sender,
|
||||
)
|
||||
|
||||
// Should expose sender
|
||||
assertEquals(msgFromSenderToSender.pubKey, sender.pubKey)
|
||||
// Should not expose receiver
|
||||
assertTrue(msgFromSenderToSender.tags.isEmpty())
|
||||
|
||||
val giftWrapToSender =
|
||||
GiftWrapEvent.create(
|
||||
event = msgFromSenderToSender,
|
||||
recipientPubKey = sender.pubKey,
|
||||
)
|
||||
|
||||
// Should not be signed by neither the sender, not the receiver
|
||||
assertNotEquals(giftWrapToSender.pubKey, sender.pubKey)
|
||||
assertNotEquals(giftWrapToSender.pubKey, receiverA.pubKey)
|
||||
assertNotEquals(giftWrapToSender.pubKey, receiverB.pubKey)
|
||||
|
||||
// Should not be addressed to the receiver
|
||||
assertNotEquals(giftWrapToSender.recipientPubKey(), receiverA.pubKey)
|
||||
assertNotEquals(giftWrapToSender.recipientPubKey(), receiverB.pubKey)
|
||||
// Should be addressed to the sender
|
||||
assertEquals(giftWrapToSender.recipientPubKey(), sender.pubKey)
|
||||
|
||||
// Done
|
||||
// -----
|
||||
// Decrypting
|
||||
|
||||
// Receiver's side
|
||||
// Makes sure it can only be decrypted by the target user
|
||||
|
||||
assertNotNull(giftWrapToSender)
|
||||
assertNotNull(giftWrapForReceiverA)
|
||||
assertNotNull(giftWrapForReceiverB)
|
||||
|
||||
val unwrappedMsgForSenderBySender = giftWrapToSender.unwrapThrowing(sender)
|
||||
assertEquals(SealedRumorEvent.KIND, unwrappedMsgForSenderBySender.kind)
|
||||
|
||||
if (unwrappedMsgForSenderBySender is SealedRumorEvent) {
|
||||
unwrappedMsgForSenderBySender.unsealOrNull(receiverA)?.let { _ ->
|
||||
fail()
|
||||
}
|
||||
|
||||
unwrappedMsgForSenderBySender.unsealOrNull(receiverB)?.let { _ ->
|
||||
fail()
|
||||
}
|
||||
|
||||
val unwrappedRumorToSenderBySender = unwrappedMsgForSenderBySender.unsealThrowing(sender)
|
||||
assertEquals("Who is going to the party tonight?", unwrappedRumorToSenderBySender.content)
|
||||
}
|
||||
|
||||
giftWrapForReceiverA.unwrapOrNull(sender)?.let { unwrappedMsgForReceiverBySenderA ->
|
||||
fail("Should not be able to decode msg to the receiver A with the sender's key")
|
||||
}
|
||||
|
||||
giftWrapForReceiverB.unwrapOrNull(sender)?.let { unwrappedMsgForReceiverBySenderB ->
|
||||
fail("Should not be able to decode msg to the receiver B with the sender's key")
|
||||
}
|
||||
|
||||
giftWrapToSender.unwrapOrNull(receiverA)?.let {
|
||||
fail("Should not be able to decode msg to sender with the receiver A's key")
|
||||
}
|
||||
|
||||
val unwrappedMsgForReceiverAByReceiverA = giftWrapForReceiverA.unwrapThrowing(receiverA)
|
||||
assertEquals(SealedRumorEvent.KIND, unwrappedMsgForReceiverAByReceiverA.kind)
|
||||
|
||||
if (unwrappedMsgForReceiverAByReceiverA is SealedRumorEvent) {
|
||||
val unwrappedRumorToReceiverAByReceiverA = unwrappedMsgForReceiverAByReceiverA.unsealThrowing(receiverA)
|
||||
assertEquals("Who is going to the party tonight?", unwrappedRumorToReceiverAByReceiverA.content)
|
||||
|
||||
unwrappedMsgForReceiverAByReceiverA.unsealOrNull(sender)?.let { unwrappedRumorToReceiverABySender ->
|
||||
fail()
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverAByReceiverA.unsealOrNull(receiverB)?.let { unwrappedRumorToReceiverAByReceiverB ->
|
||||
fail()
|
||||
}
|
||||
}
|
||||
|
||||
giftWrapForReceiverB.unwrapOrNull(receiverA)?.let {
|
||||
fail("Should not be able to decode msg to sender with the receiver A's key")
|
||||
}
|
||||
|
||||
giftWrapToSender.unwrapOrNull(receiverB)?.let { unwrappedMsgForSenderByReceiverB ->
|
||||
fail("Should not be able to decode msg to sender with the receiver B's key")
|
||||
}
|
||||
giftWrapForReceiverA.unwrapOrNull(receiverB)?.let { unwrappedMsgForReceiverAByReceiverB ->
|
||||
fail("Should not be able to decode msg to receiver A with the receiver B's key")
|
||||
}
|
||||
|
||||
val unwrappedMsgForReceiverBByReceiverB = giftWrapForReceiverB.unwrapThrowing(receiverB)
|
||||
assertEquals(SealedRumorEvent.KIND, unwrappedMsgForReceiverBByReceiverB.kind)
|
||||
|
||||
if (unwrappedMsgForReceiverBByReceiverB is SealedRumorEvent) {
|
||||
unwrappedMsgForReceiverBByReceiverB.unsealOrNull(receiverA)?.let { unwrappedRumorToReceiverBByReceiverA ->
|
||||
fail()
|
||||
}
|
||||
|
||||
val unwrappedRumorToReceiverBByReceiverB = unwrappedMsgForReceiverBByReceiverB.unsealThrowing(receiverB)
|
||||
assertEquals(
|
||||
"Who is going to the party tonight?",
|
||||
unwrappedRumorToReceiverBByReceiverB.content,
|
||||
)
|
||||
|
||||
unwrappedMsgForReceiverBByReceiverB.unsealOrNull(sender)?.let { unwrappedRumorToReceiverBBySender ->
|
||||
fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCaseFromAmethyst1() {
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"content":"{\"ciphertext\":\"AaTN5Mt7AOeMosjHeLfai89kmvW/qJ7W2VMttAwuh6hwRGV+ylJhpDbdVRhVmkCotbDjBgS6xioLrSDcdSngFOiVMHS5dTAP0MkQv09aZlBh/NgdmyfHHd24YlHPkDuF5Yb4Vmz7kq/vmjsNZvDrTen3TG2DcEoTV9GKexdMEqyBA4LsB2DLnWfpvOi0olDkGjPGSteTaU1nCdOtN8knoEKumrxwevvbygKphorvKX/j3ojMMb0AceJM6Cr6TRIvSsQnKGEv5V8qbC/uIrQoH3N108Fd/2SY2MWuyLKRnuak9F/w82MV13elq8ngyjcktLYM5yrPg5nrxZlyJsV8D7V/g/bvhoL+UmWe0XoCR5LXzy77SfIkgA1ePKEfGp5sD2CVIzXt9zHdFwGxAKZuyB4qwrRaAFrS2xx+Bw4nnEmF6V9NhfheSCmGzTILuTePx4ubvnYw/j8Hmqd6UvM3DBNnlJ3D6po0blirfWvMe/ea+Em4CMXfq8Iq+7r4gRx8azADygKeJ+C89GTBEvS9EvgrXCVfTMVTcFc44YAZhekOqYY1BOZgfxIV4gUiJfpMMd4B9MQv/tmnewrpTsq1reSQQcEW/mXT2cnMeCZbAIJSPg8usZ30QlrH+np+YSzFKWYDP1kThcV0ElEE2Ne8KaUUFIRE5KmhBQc/qtORefCpne5s7V7J5vLjT5rinsDzzENB1XVlmY1Icx42raP5tGAL1gOK5gRHLvtcgFQR3WcDRYaNqELiYxx41j9w9lz5e00Ttla255rZkb760KSLaBFBss6wYGiYCabVgtBNpkExpCFPPEd5eAZa5rNK2QrnojYsdxEnlicF6A+zSChLy/TbzxYwyQywDfoF9F8kBakPZkAhsciQViCii2KlieRq4OgJFZGndmnS82hyPqsoJIm22vWr1iqMvSBHo/9cLj/r+lfmGVOdgM62JHckPZjOLS0QWIb9gQiT+zXZG22+eZElMYbGXVpR1dyMaQtde8ivEVVLas6kMCVKaDTHEFglaCBXjJ3RNJv73HsG1kb0rMmOj8ltbBakjHpv7M59amavuu6SReYt\",\"nonce\":\"6anNjUdNwW6MNfoKzRZcz1R09N1h8G4L\",\"v\":1}",
|
||||
"created_at":1690660515,
|
||||
"id":"d90739741c2f5a8c1a03aab5dc219c0b708ed6b0566044495731cd0307cf19a5",
|
||||
"kind":1059,
|
||||
"pubkey":"a79b7162f8ebb9c9f7aa65a48977ab7f32aa097520bc543e4d625812154ff6af",
|
||||
"sig":"9b012504e779632a2a1f55562fa9a85f8ae6245cbc149b83d25b2971249053abc77f65cc068e5d025b871d743678265fede70de4eaf5af642e675a5b6210077d",
|
||||
"tags":[
|
||||
[
|
||||
"p",
|
||||
"c55f0b0cb4dd180dd4395867b28dd4c208b709144a69fb7c31a46c369a5ad1c6"
|
||||
]
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val rumor: Event =
|
||||
runBlocking {
|
||||
val privateKey = "de6152a85a0dea3b09a08a6f8139a314d498a7b52f7e5c28858b64270abd4c70"
|
||||
unwrapUnsealRumor(json, privateKey)
|
||||
}
|
||||
|
||||
assertNotNull(rumor)
|
||||
assertEquals("Hola, que tal?", rumor.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCaseFromAmethyst2() {
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"content":"{\"ciphertext\":\"Zb0ZNYAcDG5y7BiCWgbxY/i7rN7TxPwr3Oaste6em4VcetuenaMu2SyH6OuCCxxmIa7kFennJD8ZCrev0086azsPNutl9I6OCoOfDQb2GoFaLoJAkE/FuW0uEoEJuN72KsKj05HEjOM6nqL2KiW0pxTCNmlGpweMwpXQdm2ItWkybNpq8+b4NJUDee2czBUd9Kr2ELbPISTYzA17z1IzPXGQw8c73NL+QX9I/QZjM/agqX2x5q11SU52xiRyVd9zHf7TMctZI4QEsqDB6xi54D1bAeZlMhVdcpQRpGDfqRz3KXFlhB3Bwdc8GLgY0aLTn6tJs4qrHP3mQkxFYk0mju0afoc0rloMEUHcBVtM18S9OrTPqfmSqFTQsjaT8g+PkmeiLBo1sXsMCS62w0abSZD9OzQtciMz70ZpcWoLjx5f8panjFClvg4tJ8czMURIHM/IFS1uKAUHBArGN8QpCw8MXQBblpyLDiEkFcSX334Zdps0OIw4z328JSdeejyRh4ks+NHDt9FcjC4iicEqfEh8OTkXuKqEAVkRyfAioNQxWQPnXDzMX0Q+BXvKzBA7NaEBDpbV36H/KnrpBBQwokV9/Byb6Seh3g6GSqRAWD3U6Nk2aBMXkD0xY8vnIqMckBeYHxn8BW7k1FdXFC9lE5xCxWZHkmksJ4f0NVaF37O6d8qOe6RK7bfUeF8/SouJEu+eEX1f4KCMboslwkdk8QA8bThGcRGn8GQBMrPKrpZwHYNyyH8jwt9pywigXJejRLDDnDp3FH/3dbZy5CfuNH6KGydf/O5xx1r316so1UPO1mL5LHJUFZVIaMaMMUsgq12gpI0lLEh5NJPpsi9e3ibkzEZGf7FlAJjJQURbQ8xacN7R+w3GWKbJNHiQbUZ2lXo6fwz33t0DrSqEW970yWPHlqxcpd27EI+qqb5IqfklQZ3RObZZBhzDvImaCPG+U7SmgLhPxnilpGjd5lw/ttiqJhPG9mYFMf1eJXSG+Q9VVkGzN7jxXYtx0q0WGjVq98ZGv5RSnF1d9+QVGCd1fiPS3rsaWdYWly8l0y2quYObJ6Mv3Wh3\",\"nonce\":\"/Q2UTTjVZthm/atcCuDjU1e4reF+ZSgZ\",\"v\":1}",
|
||||
"created_at":1690660515,
|
||||
"id":"087d9627d63135a5050758a69222e566c86702e930c9905f0b93ccd6bebeca3f",
|
||||
"kind":1059,
|
||||
"pubkey":"e59c00796ae2aa9077fc8bcd57fe8d32c0fc363f7c8b93d055c70804ffff3772",
|
||||
"sig":"807cb641c314ca6910aaeefadcf87d859137520be1039eb40e39832ed59d456fdd800c5f88bba09e1b395ee90c66d5330847bdd010b63be9919bf091adbc2c2a",
|
||||
"tags":[
|
||||
[
|
||||
"p",
|
||||
"f85f315c06aaf19c2b30a96ca80d9644720655ee8d3ec43b84657a7c98f36a23"
|
||||
]
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "409ff7654141eaa16cd2161fe5bd127aeaef71f270c67587474b78998a8e3533"
|
||||
|
||||
val rumor: Event =
|
||||
runBlocking {
|
||||
unwrapUnsealRumor(json, privateKey)
|
||||
}
|
||||
|
||||
assertNotNull(rumor)
|
||||
assertEquals("Hola, que tal?", rumor.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDecryptFromCoracle() {
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"content": "{\"ciphertext\":\"fo0/Ywyfu86cXKoOOeFFlMRv+LYM6GJUL+F/J4ARv6EcAufOZP46vimlurPBLPjNgzuGemGjlTFfC3vtq84AqIsqFo3dqKunq8Vp+mmubvxIQUDzOGYvM0WE/XOiW5LEe3U3Vq399Dq07xRpXELcp4EZxGyu4Fowv2Ppb4SKpH8g+9N3z2+bwYcSxBvI6SrL+hgmVMrRlgvsUHN1d53ni9ehRseBqrLj/DqyyEiygsKm6vnEZAPKnl1MrBaVOBZmGsyjAa/G4SBVVmk78sW7xWWvo4cV+C22FluDWUOdj/bYabH4aR4scnBX3GLYqfLuzGnuQlRNsb5unXVX41+39uXzROrmNP6iYVyYxy5tfoyN7PPZ4osoKpLDUGldmXHD6RjMcAFuou4hXt2JlTPmXpj/x8qInXId5mkmU4nTGiasvsCIpJljbCujwCjbjLTcD4QrjuhMdtSsAzjT0CDv5Lmc632eKRYtDu/9B+lkqBBkp7amhzbqp8suNTnybkvbGFQQGEQnsLfNJw/GGopAuthfi8zkTgUZR/LxFR7ZKAX73G+5PQSDSjPuGH/dQEnsFo45zsh1Xro8SfUQBsPphbX2GS31Lwu5vA30O922T4UiWuU+EdNgZR0JankQ5NPgvr1uS56C3v84VwdrNWQUCwC4eYJl4Mb/OdpEy9qwsisisppq6uuzxmxd1qx3JfocnGsvB7h2g2sG+0lyZADDSobOEZEKHaBP3w+dRcJW9D95EmzPym9GO0n+33OfqFQbda7G0rzUWfPDV0gXIuZcKs/HmDqepgIZN8FG7JhRBeAv0bCbKQACre0c8tzVEn5yCYemltScdKop3pC/r6gH50jRhAlFAiIKx8R+XwuMmJRqOcH4WfkpZlfVU85/I0XJOCHWKk6BnJi/NPP9zYiZiJe+5LecqMUVjtO0YAlv138+U/3FIT/anQ4H5bjVWBZmajwf\",\"nonce\":\"Mv70S6jgrs4D1rlqV9b5DddiymGAcVVe\",\"v\":1}",
|
||||
"created_at": 1690528373,
|
||||
"id": "6b108e4236c3c338236ee589388ce0f91f921e1532ae52e75d1d2add6f8e691a",
|
||||
"kind": 1059,
|
||||
"pubkey": "627dc0248335e2bf9adac14be9494139ebbeb12c422d7df5b0e3cd72d04c209c",
|
||||
"sig": "be11da487196db298e4ffb7a03e74176c37441c88b39c95b518fadce6fd02f23c58b2c435ca38c24d512713935ab719dae80bf952267630809e1f84be8e95174",
|
||||
"tags": [
|
||||
[
|
||||
"p",
|
||||
"e7764a227c12ac1ef2db79ae180392c90903b2cec1e37f5c1a4afed38117185e"
|
||||
]
|
||||
],
|
||||
"seenOn": [
|
||||
"wss://relay.damus.io/"
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771"
|
||||
|
||||
val rumor: Event =
|
||||
runBlocking {
|
||||
unwrapUnsealRumor(json, privateKey)
|
||||
}
|
||||
|
||||
assertNotNull(rumor)
|
||||
assertEquals("test", rumor.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromCoracle2() {
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"content": "{\"ciphertext\":\"Hn/dHo/I8Qk6QWWAiKyo/SfKJqQfHdV0O5tMmgqMyfHrsFoDY6IhGQP2EgCJ/6HsNQyO/8EMAmLW8w0PbDKlBKYGKGpaMwCA6B1r0rLjvu+149RJZuggRNm9rd7tNVNkNs38iqt1KYD++bohePm52q+VhAQikbX2gTONV82ROwZylAg9vjvMnYkDt45g6N97s9FRB6V7YMiUEtJnneMixa6klucpUuenQ4569tyt5vnUMD2VNhKYCc2jit2hf7k0DIhvZrVC3OdopUvxIuYYWr3r7XpuEB3HJ6Ji3ajHPzgGeFcItBR7uKZ9s6XU34F3keyZbxrv3yWHFM5NrOctAdZexSGpqWRW93M0KZUAp9HgQh3YzMLl8xt0mcrVywCgjU6Kx8IwkI0bjPU+Am8acY3cItted6hZQ4Vy1xFITdKVfPWDl3Ab59iBg9+IkY5C31wqsKPgPVVycwQE6UpaGW74gy3qZshwyoo01owvEIbVvrSJWXH7EUVvndDPvUbo+f+EVa84IEwVjPmY2oR7VsxVfqRBdmPg23OSw/9rzVybmruqaQHd3xrTTEcnG0qBc/ugCXsiuILTeScOovEnqIlKKK3KB36jMtdScdJB+b4YrzJInY1AvqU7IAgqe0vmo1LdbMtj7kjuxkXJhhQsunAbTvPigTrsOfJ08P9l7r/95kpxudgagEaW7XAjYVfLphseJT3Iy1IuQEyG5sshQ+pl/CYvkGide7ykHwm9pjSBVkD9Mdcn5X6lSnLNJEcwY43pz43r6Kq3L09qneILY3DSKyQ16Zcu1MiAMAM5r6JGvpAHqcMmixi9ORuiryjteTmY4L0vI7b3W/0RSUblXxUrb8IpeysBrFmiKJBgCoU0r/D/8tgR+Eewyp1qxKI4SfKG5GFH40zZ2oVvKyoHAR4x1oVDp/MttcnxkzAsCFL6QuJC9A/vImjsumpmYB/EChcZCOAsfqkuzH4VSjZx\",\"nonce\":\"K537d+7m5tUcXZfkr3Qk2J2G86vdBMmY\",\"v\":1}",
|
||||
"created_at": 1690655012,
|
||||
"id": "c4f97c6332b0a63912c44c9e1f8c7b23581dc67a8489ec1522ec205fea7133db",
|
||||
"kind": 1059,
|
||||
"pubkey": "8def03a22b1039256a3883d46c7ccd5562f61743100db401344284547de7ec61",
|
||||
"sig": "25dcf24bdda99c04abc72274d9f7a30538a4a00a70ac4b39db4082b73823979858df93cd649c25edfb759857eac46ed70bb9ad0598f2e011d733a5a382bc4def",
|
||||
"tags": [
|
||||
[
|
||||
"p",
|
||||
"e7764a227c12ac1ef2db79ae180392c90903b2cec1e37f5c1a4afed38117185e"
|
||||
]
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771"
|
||||
|
||||
val rumor =
|
||||
runBlocking {
|
||||
unwrapUnsealRumor(json, privateKey)
|
||||
}
|
||||
|
||||
assertEquals("asdfasdfasdf", rumor.content)
|
||||
assertEquals(1690659269L, rumor.createdAt)
|
||||
assertEquals("827ba09d32ab81d62c60f657b350198c8aaba84372dab9ad3f4f6b8b7274b707", rumor.id)
|
||||
assertEquals(14, rumor.kind)
|
||||
assertEquals("subject", rumor.tags.firstOrNull()?.get(0))
|
||||
assertEquals("test", rumor.tags.firstOrNull()?.get(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromCoracle3() {
|
||||
val json =
|
||||
"""
|
||||
{
|
||||
"content": "{\"ciphertext\":\"PGCodiacmCB/sClw0C6DQRpP/XIfNAKCVZdKLQpOqytgbFryhs9Z+cPldq2FajXzxs3jQsF7/EVQyWpuV8pXetvFT9tvzjg4Xcm7ZcooLUnAeAo2xZNcJontN4cGubuDqKuXy5n59yXP1fIxfnJxRTRRdCZ2edhsKeNR5NSByUi+StjV10rnfHt8AhZCpiXiZ/giTOsC4wdaeONPgMzMeljaJWLvl6n11VjmXhkx1mXIQt43CNB1hIqO3p89Mbd9p+nlLrOsR+Xs0TB4DCh4XTPbvgf7B7Z+PgOfl3GZfJy9x6TciLcF4E3Ba1zrPe4f79czCIEiJ1yrIKrzzYvv+it35DZQ8fgveFXpyHnNL29hml8PNjyOsFbCHVYLMGw88evI5PijOcpe1TtdoioX8kX5kVEQSKJXuoSjTorvbRPCgGzaa1m0J0uTpzri5VD22a/Jh2CcAnubg6w4JDdUWCogdSV3NqiJllo7ZF7WnZ3apPdRD23MEfphVBJrcLBUNlmwajnY5IvVTKTkZOP50r9dBapvMWXIo6M6zhy/5vVWJz57863pelYCRG4upaXZuNK9sMBtbiphxmFR83i8RML8KN8Q391Cd/xBN7TxJNo5p2YU25VeGZUAmHY8DYlMQDm8Br0nStAXp3T+DzTRL8FTECa8DJV+KTAPoCxqhv3B28Ehr0XAP75CsHoLU00G48cR7h3vQ0CnfKh6KXU6nnDA5OWfpMYpirACCpsnpSD0OaCQ3gkQp3zZNMS3HcOpnPK/IY7R0esbzgAkvNhkyxaIfPDdf+eRUSOA9+2Ji28MwjjY8Dw3SLdUqCOzIDjQeR/T5oNmaQJm3lZ8G0FxxC6ejD4VJX/NI/x+STeB9jWHWmHZvqKzV6JHNh6qmZb6TKSIPOHpafWFoeJFOmiiigf46sju9vRXmVEAx59HXWnvnvCBNJg877yCMulB6xyQuSdVDuotQU4tQZwCKedTHJ6GqjesM98UlJrDtdWQURwwW1qc7N8tS6PukmUVEf0jmbIWVIBmUlkcVuiSs1g1h1kjt8c4MnGTz3CSgpOd1MqxLrl9WwrTqM+YnE+yeZYUjFoewyKZIQ==\",\"nonce\":\"OdCZczJiUGR4bOGIElQ4UUH4dQmG5U/3\",\"v\":1}",
|
||||
"kind": 1059,
|
||||
"created_at": 1690772945,
|
||||
"pubkey": "e01475e87896800b7285eb0daf263c59f811c8fc5bc8daa105d2c98b6d7c4952",
|
||||
"tags": [
|
||||
[
|
||||
"p",
|
||||
"b08d8857a92b4d6aa580ff55cc3c18c4edf313c83388c34abc118621f74f1a78"
|
||||
]
|
||||
],
|
||||
"id": "d9fc85ece892ce45ffa737b3ddc0f8b752623181d75363b966191f8c03d2debe",
|
||||
"sig": "1b20416b83f4b5b8eead11e29c185f46b5e76d1960e4505210ddd00f7a6973cc11268f52a8989e3799b774d5f3a55db95bed4d66a1b6e88ab54becec5c771c17"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "7dd22cafc512c0bc363a259f6dcda515b13ae3351066d7976fd0bb79cbd0d700"
|
||||
|
||||
val rumor =
|
||||
runBlocking {
|
||||
unwrapUnsealRumor(json, privateKey)
|
||||
}
|
||||
|
||||
assertEquals("8d1a56008d4e31dae2fb8bef36b3efea519eff75f57033107e2aa16702466ef2", rumor.id)
|
||||
assertEquals("Howdy", rumor.content)
|
||||
assertEquals(1690833960L, rumor.createdAt)
|
||||
assertEquals(14, rumor.kind)
|
||||
assertEquals("p", rumor.tags.firstOrNull()?.get(0))
|
||||
assertEquals(
|
||||
"b08d8857a92b4d6aa580ff55cc3c18c4edf313c83388c34abc118621f74f1a78",
|
||||
rumor.tags.firstOrNull()?.get(1),
|
||||
)
|
||||
assertEquals("subject", rumor.tags.getOrNull(1)?.get(0))
|
||||
assertEquals("Stuff", rumor.tags.getOrNull(1)?.get(1))
|
||||
}
|
||||
|
||||
suspend fun unwrapUnsealRumor(
|
||||
json: String,
|
||||
privateKey: HexKey,
|
||||
): Event {
|
||||
val pkBytes = NostrSignerInternal(KeyPair(privateKey.hexToByteArray()))
|
||||
|
||||
val wrap = Event.fromJson(json) as GiftWrapEvent
|
||||
wrap.checkSignature()
|
||||
|
||||
assertEquals(pkBytes.pubKey, wrap.recipientPubKey())
|
||||
|
||||
val event = wrap.unwrapThrowing(pkBytes)
|
||||
return if (event is SealedRumorEvent) {
|
||||
event.unsealThrowing(pkBytes)
|
||||
} else {
|
||||
println(event.toJson())
|
||||
fail("Event is not a Sealed Rumor")
|
||||
throw Exception("Event is not a Sealed Rumor")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptMsgFromNostrTools() {
|
||||
val receiversPrivateKey =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("df51ec558372612918a83446d279d683039bece79b7a721274b1d3cb612dc6af")),
|
||||
)
|
||||
val msg =
|
||||
"""
|
||||
{
|
||||
"tags": [],
|
||||
"content": "AUC1i3lHsEOYQZaqav8jAw/Dv25r6BpUX4r7ARaj/7JEqvtHkbtaWXEx3LvMlDJstNX1C90RIelgYTzxb4Xnql7zFmXtxGGd/gXOZzW/OCNWECTrhFTruZUcsyn2ssJMgEMBZKY3PgbAKykHlGCuWR3KI9bo+IA5sTqHlrwDGAysxBypRuAxTdtEApw1LSu2A+1UQsdHK/4HcW/fQLPguWGyPv09dftJIJkFWM8VYBQT7b5FeAEMhjlUM+lEmLMnx6qb07Ji/YMESkhzFlgGjHNVl1Q/BT4i6X+Skogl6Si3lWQzlS9oebUim1BQW+RO0IOyQLalZwjzGP+eE7Ry62ukQg7cPiqk62p7NNula17SF2Q8aVFLxr8WjbLXoWhZOWY25uFbTl7OPGGQb5TewRsjHoFeU4h05Ien3Ymf1VPqJVJCMIxU+yFZ1IMZh/vQW4BSx8VotRdNA05fz03ST88GzGxUvqEm4VW/Yp5q4UUkCDQTKmUImaSFmTser39WmvS5+dHY6ne4RwnrZR0ZYrG1bthRHycnPmaJiYsHn9Ox37EzgLR07pmNxr2+86NR3S3TLAVfTDN3XaXRee/7UfW/MXULVyuyweksIHOYBvANC0PxmGSs4UiFoCbwNi45DT2y0SwP6CxzDuM=",
|
||||
"kind": 1059,
|
||||
"created_at": 1694192155914,
|
||||
"pubkey": "8253eb518413b57f0df329d3d4287bdef4031fd71c32ad1952d854e703dae6a7",
|
||||
"id": "ae625fd43612127d63bfd1967ba32ae915100842a205fc2c3b3fc02ab3827f08",
|
||||
"sig": "2807a7ab5728984144676fd34686267cbe6fe38bc2f65a3640ba9243c13e8a1ae5a9a051e8852aa0c997a3623d7fa066cf2073a233c6d7db46fb1a0d4c01e5a3"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val wrap = Event.fromJson(msg) as GiftWrapEvent
|
||||
wrap.checkSignature()
|
||||
|
||||
val event =
|
||||
runBlocking {
|
||||
wrap.unwrapThrowing(receiversPrivateKey)
|
||||
}
|
||||
|
||||
assertNotNull(event)
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HexEncodingTest {
|
||||
val testHex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf"
|
||||
|
||||
@Test
|
||||
fun testHexEncodeDecodeOurs() {
|
||||
assertEquals(
|
||||
testHex,
|
||||
Hex.encode(
|
||||
Hex.decode(testHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHexEncodeDecodeSecp256k1() {
|
||||
assertEquals(
|
||||
testHex,
|
||||
fr.acinq.secp256k1.Hex.encode(
|
||||
fr.acinq.secp256k1.Hex
|
||||
.decode(testHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRandoms() {
|
||||
for (i in 0..1000) {
|
||||
val bytes = RandomInstance.bytes(32)
|
||||
val hex =
|
||||
fr.acinq.secp256k1.Hex
|
||||
.encode(bytes)
|
||||
assertEquals(
|
||||
fr.acinq.secp256k1.Hex
|
||||
.encode(bytes),
|
||||
Hex.encode(bytes),
|
||||
)
|
||||
assertEquals(
|
||||
bytes.toList(),
|
||||
Hex.decode(hex).toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsHex() {
|
||||
assertFalse("/0", Hex.isHex("/0"))
|
||||
assertFalse("/.", Hex.isHex("/."))
|
||||
assertFalse("::", Hex.isHex("::"))
|
||||
assertFalse("!!", Hex.isHex("!!"))
|
||||
assertFalse("@@", Hex.isHex("@@"))
|
||||
assertFalse("GG", Hex.isHex("GG"))
|
||||
assertFalse("FG", Hex.isHex("FG"))
|
||||
assertFalse("`a", Hex.isHex("`a"))
|
||||
assertFalse("gg", Hex.isHex("gg"))
|
||||
assertFalse("fg", Hex.isHex("fg"))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Test
|
||||
fun testRandomsIsHex() {
|
||||
for (i in 0..10000) {
|
||||
val bytes = RandomInstance.bytes(32)
|
||||
val hex = bytes.toHexString(HexFormat.Default)
|
||||
assertTrue(hex, Hex.isHex(hex))
|
||||
val hexUpper = bytes.toHexString(HexFormat.UpperCase)
|
||||
assertTrue(hexUpper, Hex.isHex(hexUpper))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Test
|
||||
fun testRandomsUppercase() {
|
||||
for (i in 0..1000) {
|
||||
val bytes = RandomInstance.bytes(32)
|
||||
val hex = bytes.toHexString(HexFormat.UpperCase)
|
||||
assertEquals(
|
||||
bytes.toList(),
|
||||
Hex.decode(hex).toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user