Moves to NIP-44v2

This commit is contained in:
Vitor Pamplona
2023-12-20 18:07:37 -05:00
parent a22b6db130
commit e794ff44a1
17 changed files with 1550 additions and 319 deletions

View File

@@ -275,9 +275,7 @@ class Relay(
}
fun resetEOSEStatuses() {
afterEOSEPerSubscription.keys.toList().forEach {
afterEOSEPerSubscription[it] = false
}
afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
}
fun sendFilter(requestId: String) {

View File

@@ -5,12 +5,6 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.encoders.Bech32
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.bechToBytes
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.Event
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import org.junit.Rule
import org.junit.Test
@@ -38,7 +32,7 @@ class CryptoBenchmark {
val keyPair2 = KeyPair()
benchmarkRule.measureRepeated {
assertNotNull(CryptoUtils.getSharedSecretNIP44(keyPair1.privKey!!, keyPair2.pubKey))
assertNotNull(CryptoUtils.getSharedSecretNIP44v1(keyPair1.privKey!!, keyPair2.pubKey))
}
}
@@ -58,7 +52,7 @@ class CryptoBenchmark {
val keyPair2 = KeyPair()
benchmarkRule.measureRepeated {
assertNotNull(CryptoUtils.computeSharedSecretNIP44(keyPair1.privKey!!, keyPair2.pubKey))
assertNotNull(CryptoUtils.computeSharedSecretNIP44v1(keyPair1.privKey!!, keyPair2.pubKey))
}
}

View File

@@ -54,7 +54,7 @@ class GiftWrapBenchmark {
val countDownLatch2 = CountDownLatch(1)
Assert.assertEquals(expectedLength, events!!.wraps.map { it.toJson() }.joinToString("").length)
Assert.assertEquals(expectedLength, events!!.wraps.map { println("TEST ${it.toJson()}"); it.toJson() }.joinToString("").length)
// Simulate Receiver
events!!.wraps.forEach {

View File

@@ -4,15 +4,12 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.crypto.decodeNIP44
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.Gossip
import com.vitorpamplona.quartz.events.NIP24Factory
import com.vitorpamplona.quartz.events.SealedGossipEvent
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerInternal
@@ -139,18 +136,6 @@ class GiftWrapReceivingBenchmark {
}
}
@Test
fun decodeWrapEvent() {
val sender = NostrSignerInternal(KeyPair())
val receiver = NostrSignerInternal(KeyPair())
val wrap = createWrap(sender, receiver)
benchmarkRule.measureRepeated {
assertNotNull(decodeNIP44(wrap.content))
}
}
@Test
fun decryptWrapEvent() {
val sender = NostrSignerInternal(KeyPair())
@@ -158,10 +143,8 @@ class GiftWrapReceivingBenchmark {
val wrap = createWrap(sender, receiver)
val toDecrypt = decodeNIP44(wrap.content) ?: return
benchmarkRule.measureRepeated {
assertNotNull(CryptoUtils.decryptNIP44(toDecrypt, sender.keyPair.privKey!!, wrap.pubKey.hexToByteArray()))
assertNotNull(CryptoUtils.decryptNIP44v1(wrap.content, sender.keyPair.privKey!!, wrap.pubKey.hexToByteArray()))
}
}
@@ -172,26 +155,13 @@ class GiftWrapReceivingBenchmark {
val wrap = createWrap(sender, receiver)
val toDecrypt = decodeNIP44(wrap.content) ?: return
val innerJson = CryptoUtils.decryptNIP44(toDecrypt, receiver.keyPair.privKey!!, wrap.pubKey.hexToByteArray())
val innerJson = CryptoUtils.decryptNIP44v1(wrap.content, receiver.keyPair.privKey!!, wrap.pubKey.hexToByteArray())
benchmarkRule.measureRepeated {
assertNotNull(innerJson?.let { Event.fromJson(it) })
}
}
@Test
fun decodeSealEvent() {
val sender = NostrSignerInternal(KeyPair())
val receiver = NostrSignerInternal(KeyPair())
val seal = createSeal(sender, receiver)
benchmarkRule.measureRepeated {
assertNotNull(decodeNIP44(seal.content))
}
}
@Test
fun decryptSealedEvent() {
val sender = NostrSignerInternal(KeyPair())
@@ -199,10 +169,8 @@ class GiftWrapReceivingBenchmark {
val seal = createSeal(sender, receiver)
val toDecrypt = decodeNIP44(seal.content) ?: return
benchmarkRule.measureRepeated {
assertNotNull(CryptoUtils.decryptNIP44(toDecrypt, sender.keyPair.privKey!!, seal.pubKey.hexToByteArray()))
assertNotNull(CryptoUtils.decryptNIP44v1(seal.content, sender.keyPair.privKey!!, seal.pubKey.hexToByteArray()))
}
}
@@ -213,8 +181,7 @@ class GiftWrapReceivingBenchmark {
val seal = createSeal(sender, receiver)
val toDecrypt = decodeNIP44(seal.content) ?: return
val innerJson = CryptoUtils.decryptNIP44(toDecrypt, receiver.keyPair.privKey!!, seal.pubKey.hexToByteArray())
val innerJson = CryptoUtils.decryptNIP44v1(seal.content, receiver.keyPair.privKey!!, seal.pubKey.hexToByteArray())
benchmarkRule.measureRepeated {
assertNotNull(innerJson?.let { Gossip.fromJson(it) })

View File

@@ -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鷗Œé逍Üߪąñ丂㐀𠀀",
"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"
}
]
}
}
}

View File

@@ -26,7 +26,7 @@ class CryptoUtilsTest {
val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561"
val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f"
val key = CryptoUtils.getSharedSecretNIP44(privateKey = privateKey.hexToByteArray(), pubKey = publicKey.hexToByteArray())
val key = CryptoUtils.getSharedSecretNIP44v1(privateKey = privateKey.hexToByteArray(), pubKey = publicKey.hexToByteArray())
assertEquals("577c966f499dddd8e8dcc34e8f352e283cc177e53ae372794947e0b8ede7cfd8", key.toHexKey())
}
@@ -36,8 +36,8 @@ class CryptoUtilsTest {
val sender = KeyPair()
val receiver = KeyPair()
val sharedSecret1 = CryptoUtils.getSharedSecretNIP44(sender.privKey!!, receiver.pubKey)
val sharedSecret2 = CryptoUtils.getSharedSecretNIP44(receiver.privKey!!, sender.pubKey)
val sharedSecret1 = CryptoUtils.getSharedSecretNIP44v1(sender.privKey!!, receiver.pubKey)
val sharedSecret2 = CryptoUtils.getSharedSecretNIP44v1(receiver.privKey!!, sender.pubKey)
assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey())
@@ -61,28 +61,16 @@ class CryptoUtilsTest {
assertEquals(msg, decrypted)
}
@Test
fun encryptDecryptNIP4WithJsonSchemaTest() {
fun encryptDecryptNIP44v1Test() {
val msg = "Hi"
val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val encrypted = CryptoUtils.encryptNIP04Json(msg, privateKey, publicKey)
val decrypted = CryptoUtils.decryptNIP04(encrypted, privateKey, publicKey)
assertEquals(msg, decrypted)
}
@Test
fun encryptDecryptNIP44Test() {
val msg = "Hi"
val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val encrypted = CryptoUtils.encryptNIP44(msg, privateKey, publicKey)
val decrypted = CryptoUtils.decryptNIP44(encrypted, privateKey, publicKey)
val encrypted = CryptoUtils.encryptNIP44v1(msg, privateKey, publicKey)
val decrypted = CryptoUtils.decryptNIP44v1(encrypted, privateKey, publicKey)
assertEquals(msg, decrypted)
}
@@ -101,15 +89,15 @@ class CryptoUtilsTest {
}
@Test
fun encryptSharedSecretDecryptNIP44Test() {
fun encryptSharedSecretDecryptNIP44v1Test() {
val msg = "Hi"
val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val sharedSecret = CryptoUtils.getSharedSecretNIP44(privateKey, publicKey)
val sharedSecret = CryptoUtils.getSharedSecretNIP44v1(privateKey, publicKey)
val encrypted = CryptoUtils.encryptNIP44(msg, sharedSecret)
val decrypted = CryptoUtils.decryptNIP44(encrypted, sharedSecret)
val encrypted = CryptoUtils.encryptNIP44v1(msg, sharedSecret)
val decrypted = CryptoUtils.decryptNIP44v1(encrypted, sharedSecret)
assertEquals(msg, decrypted)
}

View File

@@ -0,0 +1,202 @@
package com.vitorpamplona.quartz;
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.quartz.crypto.Nip44v2
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey
import fr.acinq.secp256k1.Secp256k1
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
import java.security.MessageDigest
import java.security.SecureRandom
@RunWith(AndroidJUnit4::class)
public class NIP44v2Test {
val vectors: VectorFile = jacksonObjectMapper().readValue(
getInstrumentation().context.assets.open("nip44.vectors.json"),
VectorFile::class.java
)
val random = SecureRandom()
val nip44v2 = Nip44v2(Secp256k1.get(), random)
@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 encryptDecryptTest() {
for (v in vectors.v2?.valid?.encryptDecrypt!!) {
val pub2 = com.vitorpamplona.quartz.crypto.KeyPair(v.sec2!!.hexToByteArray())
val conversationKey = nip44v2.getConversationKey(v.sec1!!.hexToByteArray(), pub2.pubKey)
assertEquals(v.conversationKey, conversationKey.toHexKey())
val ciphertext = nip44v2.encryptWithNonce(
v.plaintext!!,
conversationKey,
v.nonce!!.hexToByteArray()
).encodePayload()
assertEquals(v.payload, ciphertext)
val decrypted = nip44v2.decrypt(v.payload!!, conversationKey)
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 invalidMessageLenghts() {
for (v in vectors.v2?.invalid?.encryptMsgLengths!!) {
val key = ByteArray(32)
random.nextBytes(key)
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)
}
}
}
fun sha256Hex(data: ByteArray): String {
// Creates a new buffer every time
return MessageDigest.getInstance("SHA-256").digest(data).toHexKey()
}
}
data class VectorFile (
val v2 : V2? = V2()
)
data class V2 (
val valid : Valid? = Valid(),
val invalid : Invalid? = Invalid()
)
data class Valid (
@JsonProperty("get_conversation_key" ) val getConversationKey : ArrayList<GetConversationKey> = arrayListOf(),
@JsonProperty("get_message_keys" ) val getMessageKeys : GetMessageKeys? = GetMessageKeys(),
@JsonProperty("calc_padded_len" ) val calcPaddedLen : ArrayList<ArrayList<Int>> = arrayListOf(),
@JsonProperty("encrypt_decrypt" ) val encryptDecrypt : ArrayList<EncryptDecrypt> = arrayListOf(),
@JsonProperty("encrypt_decrypt_long_msg" ) val encryptDecryptLongMsg : ArrayList<EncryptDecryptLongMsg> = arrayListOf()
)
data class Invalid (
@JsonProperty("encrypt_msg_lengths" ) val encryptMsgLengths : ArrayList<Int> = arrayListOf(),
@JsonProperty("get_conversation_key" ) val getConversationKey : ArrayList<GetConversationKey> = arrayListOf(),
@JsonProperty("decrypt" ) val decrypt : ArrayList<Decrypt> = arrayListOf()
)
data class GetConversationKey (
val sec1 : String? = null,
val pub2 : String? = null,
val note : String? = null,
@JsonProperty("conversation_key" ) val conversationKey : String? = null
)
data class GetMessageKeys (
@JsonProperty("conversation_key" ) val conversationKey : String? = null,
val keys : ArrayList<Keys> = arrayListOf()
)
data class Keys (
@JsonProperty("nonce" ) val nonce : String? = null,
@JsonProperty("chacha_key" ) val chachaKey : String? = null,
@JsonProperty("chacha_nonce" ) val chachaNonce : String? = null,
@JsonProperty("hmac_key" ) val hmacKey : String? = null
)
data class EncryptDecrypt (
val sec1 : String? = null,
val sec2 : String? = null,
@JsonProperty("conversation_key" ) val conversationKey : String? = null,
val nonce : String? = null,
val plaintext : String? = null,
val payload : String? = null
)
data class EncryptDecryptLongMsg (
@JsonProperty("conversation_key" ) val conversationKey : String? = null,
val nonce : String? = null,
val pattern : String? = null,
val repeat : Int? = null,
@JsonProperty("plaintext_sha256" ) val plaintextSha256 : String? = null,
@JsonProperty("payload_sha256" ) val payloadSha256 : String? = null
)
data class Decrypt (
@JsonProperty("conversation_key" ) val conversationKey : String? = null,
val nonce : String? = null,
val plaintext : String? = null,
val payload : String? = null,
val note : String? = null
)

View File

@@ -1,32 +1,26 @@
package com.vitorpamplona.quartz.crypto
import android.util.Log
import android.util.LruCache
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.utils.Key
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.events.Event
import fr.acinq.secp256k1.Secp256k1
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object CryptoUtils {
private val sharedKeyCache04 = LruCache<Int, ByteArray>(200)
private val sharedKeyCache44 = LruCache<Int, ByteArray>(200)
private val secp256k1 = Secp256k1.get()
private val libSodium = SodiumAndroid()
private val random = SecureRandom()
private val h02 = Hex.decode("02")
private val nip04 = Nip04(secp256k1, random)
private val nip44v1 = Nip44v1(secp256k1, random)
private val nip44v2 = Nip44v2(secp256k1, random)
fun clearCache() {
sharedKeyCache04.evictAll()
sharedKeyCache44.evictAll()
nip04.clearCache()
nip44v1.clearCache()
nip44v2.clearCache()
}
fun randomInt(bound: Int): Int {
@@ -63,242 +57,174 @@ object CryptoUtils {
return MessageDigest.getInstance("SHA-256").digest(data)
}
/**
* NIP 04 Utils
*/
fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
val info = encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey))
val encryptionInfo = EncryptedInfoString(
v = info.v,
nonce = Base64.getEncoder().encodeToString(info.nonce),
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
)
return "${encryptionInfo.ciphertext}?iv=${encryptionInfo.nonce}"
return nip04.encrypt(msg, privateKey, pubKey)
}
fun encryptNIP04Json(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
return encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey))
}
fun encryptNIP04(msg: String, sharedSecret: ByteArray): EncryptedInfo {
val iv = ByteArray(16)
random.nextBytes(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
//val ivBase64 = Base64.getEncoder().encodeToString(iv)
val encryptedMsg = cipher.doFinal(msg.toByteArray())
//val encryptedMsgBase64 = Base64.getEncoder().encodeToString(encryptedMsg)
return EncryptedInfo(encryptedMsg, iv, Nip44Version.NIP04.versionCode)
fun encryptNIP04(msg: String, sharedSecret: ByteArray): Nip04.EncryptedInfo {
return nip04.encrypt(msg, sharedSecret)
}
fun decryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey)
return decryptNIP04(msg, sharedSecret)
return nip04.decrypt(msg, privateKey, pubKey)
}
fun decryptNIP04(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey)
return decryptNIP04(encryptedInfo.ciphertext, encryptedInfo.nonce, sharedSecret)
fun decryptNIP04(encryptedInfo: Nip04.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
return nip04.decrypt(encryptedInfo, privateKey, pubKey)
}
fun decryptNIP04(msg: String, sharedSecret: ByteArray): String {
val parts = msg.split("?iv=")
return decryptNIP04(parts[0], parts[1], sharedSecret)
return nip04.decrypt(msg, sharedSecret)
}
private fun decryptNIP04(cipher: String, nonce: String, sharedSecret: ByteArray): String {
val iv = Base64.getDecoder().decode(nonce)
val encryptedMsg = Base64.getDecoder().decode(cipher)
return decryptNIP04(encryptedMsg, iv, sharedSecret)
return nip04.decrypt(cipher, nonce, sharedSecret)
}
private fun decryptNIP04(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
return String(cipher.doFinal(encryptedMsg))
return nip04.decrypt(encryptedMsg, iv, sharedSecret)
}
fun encryptNIP44(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
val sharedSecret = getSharedSecretNIP44(privateKey, pubKey)
return encryptNIP44(msg, sharedSecret)
}
fun encryptNIP44(msg: String, sharedSecret: ByteArray): EncryptedInfo {
val nonce = ByteArray(24)
random.nextBytes(nonce)
val cipher = cryptoStreamXChaCha20Xor(
libSodium = libSodium,
messageBytes = msg.toByteArray(),//compress(msg),
nonce = nonce,
key = Key.fromBytes(sharedSecret)
)
return EncryptedInfo(
ciphertext = cipher ?: ByteArray(0),
nonce = nonce,
v = Nip44Version.NIP44.versionCode
)
}
fun decryptNIP44(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
val sharedSecret = getSharedSecretNIP44(privateKey, pubKey)
return decryptNIP44(encryptedInfo, sharedSecret)
}
fun decryptNIP44(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? {
return cryptoStreamXChaCha20Xor(
libSodium = libSodium,
messageBytes = encryptedInfo.ciphertext,
nonce = encryptedInfo.nonce,
key = Key.fromBytes(sharedSecret)
)?.decodeToString() //?.let { decompress(it) }
}
/**
* @return 32B shared secret
*/
fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val hash = combinedHashCode(privateKey, pubKey)
val preComputed = sharedKeyCache04[hash]
if (preComputed != null) return preComputed
return nip04.getSharedSecret(privateKey, pubKey)
}
val computed = computeSharedSecretNIP04(privateKey, pubKey)
sharedKeyCache04.put(hash, computed)
return computed
fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return nip04.computeSharedSecret(privateKey, pubKey)
}
/**
* NIP 44v1 Utils
*/
fun encryptNIP44v1(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v1.EncryptedInfo {
return nip44v1.encrypt(msg, privateKey, pubKey)
}
fun encryptNIP44v1(msg: String, sharedSecret: ByteArray): Nip44v1.EncryptedInfo {
return nip44v1.encrypt(msg, sharedSecret)
}
fun decryptNIP44v1(encryptedInfo: Nip44v1.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
return nip44v1.decrypt(encryptedInfo, privateKey, pubKey)
}
fun decryptNIP44v1(encryptedInfo: String, privateKey: ByteArray, pubKey: ByteArray): String? {
return nip44v1.decrypt(encryptedInfo, privateKey, pubKey)
}
fun decryptNIP44v1(encryptedInfo: Nip44v1.EncryptedInfo, sharedSecret: ByteArray): String? {
return nip44v1.decrypt(encryptedInfo, sharedSecret)
}
fun getSharedSecretNIP44v1(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return nip44v1.getSharedSecret(privateKey, pubKey)
}
fun computeSharedSecretNIP44v1(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return nip44v1.computeSharedSecret(privateKey, pubKey)
}
/**
* @return 32B shared secret
* NIP 44v2 Utils
*/
fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
/**
* @return 32B shared secret
*/
fun getSharedSecretNIP44(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val hash = combinedHashCode(privateKey, pubKey)
val preComputed = sharedKeyCache44[hash]
if (preComputed != null) return preComputed
val computed = computeSharedSecretNIP44(privateKey, pubKey)
sharedKeyCache44.put(hash, computed)
return computed
fun encryptNIP44v2(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v2.EncryptedInfo {
return nip44v2.encrypt(msg, privateKey, pubKey)
}
/**
* @return 32B shared secret
*/
fun computeSharedSecretNIP44(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
sha256(secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33))
}
fun encryptNIP44v2(msg: String, sharedSecret: ByteArray): Nip44v2.EncryptedInfo {
return nip44v2.encrypt(msg, sharedSecret)
}
data class EncryptedInfo(val ciphertext: ByteArray, val nonce: ByteArray, val v: Int)
data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int)
fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
}
enum class Nip44Version(val versionCode: Int) {
NIP04(0),
NIP44(1)
}
fun decryptNIP44v2(encryptedInfo: String, privateKey: ByteArray, pubKey: ByteArray): String? {
return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
}
fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, sharedSecret: ByteArray): String? {
return nip44v2.decrypt(encryptedInfo, sharedSecret)
}
fun encodeNIP44(info: EncryptedInfo): String {
return encodeByteArray(info)
}
fun getSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return nip44v2.getConversationKey(privateKey, pubKey)
}
fun decodeNIP44(str: String): EncryptedInfo? {
if (str.isEmpty()) return null
return if (str[0] == '{') {
decodeJackson(str)
fun computeSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return nip44v2.computeConversationKey(privateKey, pubKey)
}
fun decryptNIP44(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
if (payload.isEmpty()) return null
return if (payload[0] == '{') {
decryptNIP44FromJackson(payload, privateKey, pubKey)
} else {
decodeByteArray(str)
decryptNIP44FromBase64(payload, privateKey, pubKey)
}
}
}
fun encodeByteArray(info: EncryptedInfo): String {
return Base64.getEncoder().encodeToString(byteArrayOf(info.v.toByte()) + info.nonce + info.ciphertext)
}
data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int, val mac: String?)
fun decodeByteArray(base64: String): EncryptedInfo? {
fun decryptNIP44FromJackson(json: String, privateKey: ByteArray, pubKey: ByteArray): String? {
return try {
val byteArray = Base64.getDecoder().decode(base64)
return EncryptedInfo(
v = byteArray[0].toInt(),
nonce = byteArray.copyOfRange(1, 25),
ciphertext = byteArray.copyOfRange(25, byteArray.size)
val info = Event.mapper.readValue(json, EncryptedInfoString::class.java)
when (info.v) {
Nip04.EncryptedInfo.v -> {
val encryptedInfo = Nip04.EncryptedInfo(
ciphertext = Base64.getDecoder().decode(info.ciphertext),
nonce = Base64.getDecoder().decode(info.nonce)
)
decryptNIP04(encryptedInfo, privateKey, pubKey)
}
Nip44v1.EncryptedInfo.v -> {
val encryptedInfo = Nip44v1.EncryptedInfo(
ciphertext = Base64.getDecoder().decode(info.ciphertext),
nonce = Base64.getDecoder().decode(info.nonce)
)
decryptNIP44v1(encryptedInfo, privateKey, pubKey)
}
Nip44v2.EncryptedInfo.v -> {
val encryptedInfo = Nip44v2.EncryptedInfo(
ciphertext = Base64.getDecoder().decode(info.ciphertext),
nonce = Base64.getDecoder().decode(info.nonce),
mac = Base64.getDecoder().decode(info.mac)
)
decryptNIP44v2(encryptedInfo, privateKey, pubKey)
}
else -> null
}
} catch (e: Exception) {
Log.w("CryptoUtils", "Unable to Parse encrypted payload: ${base64}")
Log.e("CryptoUtils", "Could not identify the version for NIP44 payload ${json}")
e.printStackTrace()
null
}
}
fun decryptNIP44FromBase64(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
if (payload.isEmpty()) return null
return try {
val byteArray = Base64.getDecoder().decode(payload)
when (byteArray[0].toInt()) {
Nip04.EncryptedInfo.v -> decryptNIP04(payload, privateKey, pubKey)
Nip44v1.EncryptedInfo.v -> decryptNIP44v1(payload, privateKey, pubKey)
Nip44v2.EncryptedInfo.v -> decryptNIP44v2(payload, privateKey, pubKey)
else -> null
}
} catch (e: Exception) {
Log.e("CryptoUtils", "Could not identify the version for NIP44 payload ${payload}")
e.printStackTrace()
null
}
}
}
fun encodeJackson(info: EncryptedInfo): String {
return Event.mapper.writeValueAsString(
EncryptedInfoString(
v = info.v,
nonce = Base64.getEncoder().encodeToString(info.nonce),
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
)
)
}
fun decodeJackson(json: String): EncryptedInfo {
val info = Event.mapper.readValue(json, EncryptedInfoString::class.java)
return EncryptedInfo(
v = info.v,
nonce = Base64.getDecoder().decode(info.nonce),
ciphertext = Base64.getDecoder().decode(info.ciphertext)
)
}
fun combinedHashCode(a: ByteArray, b: ByteArray): Int {
var result = 1
for (element in a) result = 31 * result + element
for (element in b) result = 31 * result + element
return result
}
/*
OLD Versions used for the Benchmark
fun encodeKotlin(info: EncryptedInfo): String {
return Json.encodeToString(
EncryptedInfoString(
v = info.v,
nonce = Base64.getEncoder().encodeToString(info.nonce),
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
)
)
}
fun decodeKotlin(json: String): EncryptedInfo {
val info = Json.decodeFromString<EncryptedInfoString>(json)
return EncryptedInfo(
v = info.v,
nonce = Base64.getDecoder().decode(info.nonce),
ciphertext = Base64.getDecoder().decode(info.ciphertext)
)
}
fun encodeCSV(info: EncryptedInfo): String {
return "${info.v},${Base64.getEncoder().encodeToString(info.nonce)},${Base64.getEncoder().encodeToString(info.ciphertext)}"
}
fun decodeCSV(base64: String): EncryptedInfo {
val parts = base64.split(",")
return EncryptedInfo(
v = parts[0].toInt(),
nonce = Base64.getDecoder().decode(parts[1]),
ciphertext = Base64.getDecoder().decode(parts[2])
)
}
fun compress(input: String): ByteArray {
return DeflaterInputStream(input.toByteArray().inputStream()).readBytes()
}
fun decompress(inputBytes: ByteArray): String {
return InflaterInputStream(inputBytes.inputStream()).bufferedReader().use { it.readText() }
}
*/

View File

@@ -0,0 +1,37 @@
package com.vitorpamplona.quartz.crypto
import java.nio.ByteBuffer
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class Hkdf(val algorithm: String = "HmacSHA256", val hashLen: Int = 32) {
fun extract(key: ByteArray, salt: ByteArray): ByteArray {
val mac = Mac.getInstance(algorithm)
mac.init(SecretKeySpec(salt, algorithm))
return mac.doFinal(key)
}
fun expand(key: ByteArray, nonce: ByteArray, outputLength: Int): ByteArray {
check(key.size == hashLen)
check(nonce.size == hashLen)
val n = if (outputLength % hashLen == 0) outputLength / hashLen else outputLength / hashLen + 1
var hashRound = ByteArray(0)
val generatedBytes = ByteBuffer.allocate(Math.multiplyExact(n, hashLen))
val mac = Mac.getInstance(algorithm)
mac.init(SecretKeySpec(key, algorithm))
for (roundNum in 1..n) {
mac.reset()
val t = ByteBuffer.allocate(hashRound.size + nonce.size + 1)
t.put(hashRound)
t.put(nonce)
t.put(roundNum.toByte())
hashRound = mac.doFinal(t.array())
generatedBytes.put(hashRound)
}
val result = ByteArray(outputLength)
generatedBytes.rewind()
generatedBytes[result, 0, outputLength]
return result
}
}

View File

@@ -0,0 +1,130 @@
package com.vitorpamplona.quartz.crypto
import android.util.Log
import android.util.LruCache
import com.vitorpamplona.quartz.encoders.Hex
import fr.acinq.secp256k1.Secp256k1
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
private val sharedKeyCache = SharedKeyCache()
private val h02 = Hex.decode("02")
fun clearCache() {
sharedKeyCache.clearCache()
}
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
return encrypt(msg, getSharedSecret(privateKey, pubKey)).encodeToNIP04()
}
fun encrypt(msg: String, sharedSecret: ByteArray): EncryptedInfo {
val iv = ByteArray(16)
random.nextBytes(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
//val ivBase64 = Base64.getEncoder().encodeToString(iv)
val encryptedMsg = cipher.doFinal(msg.toByteArray())
//val encryptedMsgBase64 = Base64.getEncoder().encodeToString(encryptedMsg)
return EncryptedInfo(encryptedMsg, iv)
}
fun decrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecret(privateKey, pubKey)
return decrypt(msg, sharedSecret)
}
fun decrypt(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecret(privateKey, pubKey)
return decrypt(encryptedInfo.ciphertext, encryptedInfo.nonce, sharedSecret)
}
fun decrypt(msg: String, sharedSecret: ByteArray): String {
val decoded = EncryptedInfo.decodeFromNIP04(msg)
check(decoded != null) {
"Unable to decode msg $msg as NIP04"
}
return decrypt(decoded.ciphertext, decoded.nonce, sharedSecret)
}
fun decrypt(cipher: String, nonce: String, sharedSecret: ByteArray): String {
val iv = Base64.getDecoder().decode(nonce)
val encryptedMsg = Base64.getDecoder().decode(cipher)
return decrypt(encryptedMsg, iv, sharedSecret)
}
fun decrypt(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
return String(cipher.doFinal(encryptedMsg))
}
fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val preComputed = sharedKeyCache.get(privateKey, pubKey)
if (preComputed != null) return preComputed
val computed = computeSharedSecret(privateKey, pubKey)
sharedKeyCache.add(privateKey, pubKey, computed)
return computed
}
/**
* @return 32B shared secret
*/
fun computeSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
class EncryptedInfo(
val ciphertext: ByteArray,
val nonce: ByteArray
) {
companion object {
const val v: Int = 0
fun decodePayload(payload: String): EncryptedInfo? {
return try {
val byteArray = Base64.getDecoder().decode(payload)
check(byteArray[0].toInt() == Nip44v1.EncryptedInfo.v)
return EncryptedInfo(
nonce = byteArray.copyOfRange(1, 25),
ciphertext = byteArray.copyOfRange(25, byteArray.size)
)
} catch (e: Exception) {
Log.w("NIP04", "Unable to Parse encrypted payload: ${payload}")
null
}
}
fun decodeFromNIP04(payload: String): EncryptedInfo? {
return try {
val parts = payload.split("?iv=")
EncryptedInfo(
ciphertext = Base64.getDecoder().decode(parts[0]),
nonce = Base64.getDecoder().decode(parts[1])
)
} catch (e: Exception) {
Log.w("NIP04", "Unable to Parse encrypted payload: ${payload}")
null
}
}
}
fun encodePayload(): String {
return Base64.getEncoder().encodeToString(
byteArrayOf(v.toByte()) + nonce + ciphertext
)
}
fun encodeToNIP04(): String {
val nonce = Base64.getEncoder().encodeToString(nonce)
val ciphertext = Base64.getEncoder().encodeToString(ciphertext)
return "${ciphertext}?iv=${nonce}"
}
}
}

View File

@@ -0,0 +1,117 @@
package com.vitorpamplona.quartz.crypto
import android.util.Log
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.utils.Key
import com.vitorpamplona.quartz.encoders.Hex
import fr.acinq.secp256k1.Secp256k1
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.Base64
class Nip44v1(val secp256k1: Secp256k1, val random: SecureRandom) {
private val sharedKeyCache = SharedKeyCache()
private val h02 = Hex.decode("02")
private val libSodium = SodiumAndroid()
fun clearCache() {
sharedKeyCache.clearCache()
}
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
val sharedSecret = getSharedSecret(privateKey, pubKey)
return encrypt(msg, sharedSecret)
}
fun encrypt(msg: String, sharedSecret: ByteArray): EncryptedInfo {
val nonce = ByteArray(24)
random.nextBytes(nonce)
val cipher = cryptoStreamXChaCha20Xor(
libSodium = libSodium,
messageBytes = msg.toByteArray(),
nonce = nonce,
key = Key.fromBytes(sharedSecret)
)
return EncryptedInfo(
ciphertext = cipher ?: ByteArray(0),
nonce = nonce,
)
}
fun decrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
val sharedSecret = getSharedSecret(privateKey, pubKey)
return decrypt(payload, sharedSecret)
}
fun decrypt(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
val sharedSecret = getSharedSecret(privateKey, pubKey)
return decrypt(encryptedInfo, sharedSecret)
}
fun decrypt(payload: String, sharedSecret: ByteArray): String? {
val encryptedInfo = EncryptedInfo.decodePayload(payload) ?: return null
return decrypt(encryptedInfo, sharedSecret)
}
fun decrypt(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? {
return cryptoStreamXChaCha20Xor(
libSodium = libSodium,
messageBytes = encryptedInfo.ciphertext,
nonce = encryptedInfo.nonce,
key = Key.fromBytes(sharedSecret)
)?.decodeToString()
}
fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val preComputed = sharedKeyCache.get(privateKey, pubKey)
if (preComputed != null) return preComputed
val computed = computeSharedSecret(privateKey, pubKey)
sharedKeyCache.add(privateKey, pubKey, computed)
return computed
}
/**
* @return 32B shared secret
*/
fun computeSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
sha256(
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
)
fun sha256(data: ByteArray): ByteArray {
// Creates a new buffer every time
return MessageDigest.getInstance("SHA-256").digest(data)
}
class EncryptedInfo(
val ciphertext: ByteArray,
val nonce: ByteArray
) {
companion object {
const val v: Int = 1
fun decodePayload(payload: String): EncryptedInfo? {
return try {
val byteArray = Base64.getDecoder().decode(payload)
check(byteArray[0].toInt() == v)
return EncryptedInfo(
nonce = byteArray.copyOfRange(1, 25),
ciphertext = byteArray.copyOfRange(25, byteArray.size)
)
} catch (e: Exception) {
Log.w("NIP44v1", "Unable to Parse encrypted payload: ${payload}")
null
}
}
}
fun encodePayload(): String {
return Base64.getEncoder().encodeToString(
byteArrayOf(v.toByte()) + nonce + ciphertext
)
}
}
}

View File

@@ -0,0 +1,223 @@
package com.vitorpamplona.quartz.crypto
import android.util.Log
import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.toHexKey
import fr.acinq.secp256k1.Secp256k1
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.SecureRandom
import java.util.Base64
import kotlin.experimental.and
import kotlin.math.floor
import kotlin.math.log2
class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
private val sharedKeyCache = SharedKeyCache()
private val libSodium = SodiumAndroid()
private val lazySodium = LazySodiumAndroid(libSodium)
private val hkdf = Hkdf()
private val h02 = Hex.decode("02")
private val hashLength = 32
private val minPlaintextSize: Int = 0x0001 // 1b msg => padded to 32b
private val maxPlaintextSize: Int = 0xffff // 65535 (64kb-1) => padded to 64kb
fun clearCache() {
sharedKeyCache.clearCache()
}
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
return encrypt(msg, getConversationKey(privateKey, pubKey))
}
fun encrypt(plaintext: String, conversationKey: ByteArray): EncryptedInfo {
val nonce = ByteArray(hashLength)
random.nextBytes(nonce)
return encryptWithNonce(plaintext, conversationKey, nonce)
}
fun encryptWithNonce(plaintext: String, conversationKey: ByteArray, nonce: ByteArray): EncryptedInfo {
val messageKeys = getMessageKeys(conversationKey, nonce)
val padded = pad(plaintext)
val ciphertext = ByteArray(padded.size)
lazySodium.cryptoStreamChaCha20IetfXor(
ciphertext, padded, padded.size.toLong(), messageKeys.chachaNonce, messageKeys.chachaKey
)
val mac = hmacAad(messageKeys.hmacKey, ciphertext, nonce)
return EncryptedInfo(
nonce = nonce,
ciphertext = ciphertext,
mac = mac
)
}
fun decrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
return decrypt(payload, getConversationKey(privateKey, pubKey))
}
fun decrypt(decoded: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
return decrypt(decoded, getConversationKey(privateKey, pubKey))
}
fun decrypt(payload: String, conversationKey: ByteArray): String? {
val decoded = EncryptedInfo.decodePayload(payload) ?: return null
return decrypt(decoded, conversationKey)
}
fun decrypt(decoded: EncryptedInfo, conversationKey: ByteArray): String? {
val messageKey = getMessageKeys(conversationKey, decoded.nonce)
val calculatedMac = hmacAad(messageKey.hmacKey, decoded.ciphertext, decoded.nonce)
check(calculatedMac.contentEquals(decoded.mac)) {
"Invalid Mac: Calculated ${calculatedMac.toHexKey()}, decoded: ${decoded.mac.toHexKey()}"
}
val mLen = decoded.ciphertext.size.toLong()
val padded = ByteArray(decoded.ciphertext.size)
lazySodium.cryptoStreamChaCha20IetfXor(
padded, decoded.ciphertext, mLen, messageKey.chachaNonce, messageKey.chachaKey
)
return unpad(padded)
}
fun getConversationKey(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val preComputed = sharedKeyCache.get(privateKey, pubKey)
if (preComputed != null) return preComputed
val computed = computeConversationKey(privateKey, pubKey)
sharedKeyCache.add(privateKey, pubKey, computed)
return computed
}
fun calcPaddedLen(len: Int): Int {
check(len > 0) {
"expected positive integer"
}
if (len <= 32) return 32
val nextPower = 1 shl (floor(log2(len - 1f)) + 1).toInt()
val chunk = if (nextPower <= 256) 32 else nextPower / 8
return chunk * (floor((len - 1f) / chunk).toInt() + 1)
}
fun pad(plaintext: String): ByteArray {
val unpadded = plaintext.toByteArray(Charsets.UTF_8)
val unpaddedLen = unpadded.size
check(unpaddedLen > 0) {
"Message is empty ($unpaddedLen): $plaintext"
}
check(unpaddedLen <= maxPlaintextSize) {
"Message is too long ($unpaddedLen): $plaintext"
}
val prefix = ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort(unpaddedLen.toShort()).array()
val suffix = ByteArray(calcPaddedLen(unpaddedLen) - unpaddedLen)
return ByteBuffer.wrap(prefix + unpadded + suffix).array()
}
private fun bytesToInt(byte1: Byte, byte2: Byte, bigEndian: Boolean): Int {
return if (bigEndian)
(byte1.toInt() and 0xFF shl 8 or (byte2.toInt() and 0xFF))
else
(byte2.toInt() and 0xFF shl 8 or (byte1.toInt() and 0xFF))
}
fun unpad(padded: ByteArray): String {
val unpaddedLen: Int = bytesToInt(padded[0], padded[1], true)
val unpadded = padded.sliceArray(2 until 2 + unpaddedLen)
check(
unpaddedLen in minPlaintextSize..maxPlaintextSize
&& unpadded.size == unpaddedLen
&& padded.size == 2 + calcPaddedLen(unpaddedLen)) {
"invalid padding ${unpadded.size} != $unpaddedLen"
}
return unpadded.decodeToString()
}
fun hmacAad(key: ByteArray, message: ByteArray, aad: ByteArray): ByteArray {
check (aad.size == hashLength) {
"AAD associated data must be 32 bytes, but it was ${aad.size} bytes"
}
return hkdf.extract(aad + message, key)
}
fun getMessageKeys(conversationKey: ByteArray, nonce: ByteArray): MessageKey {
val keys = hkdf.expand(conversationKey, nonce, 76)
return MessageKey(
chachaKey = keys.copyOfRange(0, 32),
chachaNonce = keys.copyOfRange(32, 44),
hmacKey = keys.copyOfRange(44, 76),
)
}
class MessageKey(
val chachaKey: ByteArray,
val chachaNonce: ByteArray,
val hmacKey: ByteArray
)
/**
* @return 32B shared secret
*/
fun computeConversationKey(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val sharedX = secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
return hkdf.extract(sharedX, "nip44-v2".toByteArray(Charsets.UTF_8))
}
class EncryptedInfo(
val nonce: ByteArray,
val ciphertext: ByteArray,
val mac: ByteArray
) {
companion object {
const val v: Int = 2
fun decodePayload(payload: String): EncryptedInfo? {
check(payload.length >= 132 || payload.length <= 87472) {
"Invalid payload length ${payload.length} for ${payload}"
}
check(payload[0] != '#') {
"Unknown encryption version ${payload.get(0)}"
}
return try {
val byteArray = Base64.getDecoder().decode(payload)
check(byteArray[0].toInt() == v)
return EncryptedInfo(
nonce = byteArray.copyOfRange(1, 33),
ciphertext = byteArray.copyOfRange(33, byteArray.size-32),
mac = byteArray.copyOfRange(byteArray.size-32, byteArray.size)
)
} catch (e: Exception) {
Log.w("NIP44v2", "Unable to Parse encrypted payload: $payload")
null
}
}
}
fun encodePayload(): String {
return Base64.getEncoder().encodeToString(
byteArrayOf(v.toByte()) + nonce + ciphertext + mac
)
}
}
}

View File

@@ -0,0 +1,26 @@
package com.vitorpamplona.quartz.crypto
import android.util.LruCache
class SharedKeyCache {
private val sharedKeyCache = LruCache<Int, ByteArray>(200)
fun clearCache() {
sharedKeyCache.evictAll()
}
fun combinedHashCode(a: ByteArray, b: ByteArray): Int {
var result = 1
for (element in a) result = 31 * result + element
for (element in b) result = 31 * result + element
return result
}
fun get(privateKey: ByteArray, pubKey: ByteArray): ByteArray? {
return sharedKeyCache[combinedHashCode(privateKey, pubKey)]
}
fun add(privateKey: ByteArray, pubKey: ByteArray, secret: ByteArray) {
sharedKeyCache.put(combinedHashCode(privateKey, pubKey), secret)
}
}

View File

@@ -90,7 +90,7 @@ class ChatMessageEvent(
subject?.let {
tags.add(arrayOf("subject", it))
}
tags.add(arrayOf("alt", alt))
//tags.add(arrayOf("alt", alt))
signer.sign(createdAt, kind, tags.toTypedArray(), msg, onReady)
}

View File

@@ -1,15 +1,8 @@
package com.vitorpamplona.quartz.events
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.crypto.Nip44Version
import com.vitorpamplona.quartz.crypto.decodeNIP44
import com.vitorpamplona.quartz.crypto.encodeNIP44
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerInternal
@@ -31,7 +24,6 @@ class GiftWrapEvent(
onReady(it)
return
}
unwrap(signer) { gift ->
if (gift is WrappedEvent) {
gift.host = this
@@ -72,7 +64,7 @@ class GiftWrapEvent(
) {
val signer = NostrSignerInternal(KeyPair()) // GiftWrap is always a random key
val serializedContent = toJson(event)
val tags = arrayOf(arrayOf("p", recipientPubKey), arrayOf("alt", alt))
val tags = arrayOf(arrayOf("p", recipientPubKey))
signer.nip44Encrypt(serializedContent, recipientPubKey) {
signer.sign(createdAt, kind, tags, it, onReady)

View File

@@ -4,15 +4,9 @@ import android.util.Log
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.annotation.JsonProperty
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.Nip44Version
import com.vitorpamplona.quartz.crypto.decodeNIP44
import com.vitorpamplona.quartz.crypto.encodeNIP44
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import java.util.UUID
@Immutable
class SealedGossipEvent(

View File

@@ -3,19 +3,13 @@ package com.vitorpamplona.quartz.signers
import android.util.Log
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.crypto.Nip44Version
import com.vitorpamplona.quartz.crypto.decodeNIP44
import com.vitorpamplona.quartz.crypto.encodeNIP44
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventFactory
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class NostrSignerInternal(val keyPair: KeyPair): NostrSigner(keyPair.pubKey.toHexKey()) {
override fun <T: Event> sign(
@@ -94,28 +88,23 @@ class NostrSignerInternal(val keyPair: KeyPair): NostrSigner(keyPair.pubKey.toHe
override fun nip44Encrypt(decryptedContent: String, toPublicKey: HexKey, onReady: (String)-> Unit) {
if (keyPair.privKey == null) return
val sharedSecret = CryptoUtils.getSharedSecretNIP44(keyPair.privKey, toPublicKey.hexToByteArray())
onReady(
encodeNIP44(
CryptoUtils.encryptNIP44(
CryptoUtils.encryptNIP44v2(
decryptedContent,
sharedSecret
)
)
keyPair.privKey,
toPublicKey.hexToByteArray()
).encodePayload()
)
}
override fun nip44Decrypt(encryptedContent: String, fromPublicKey: HexKey, onReady: (String)-> Unit) {
if (keyPair.privKey == null) return
val toDecrypt = decodeNIP44(encryptedContent) ?: return
when (toDecrypt.v) {
Nip44Version.NIP04.versionCode -> CryptoUtils.decryptNIP04(toDecrypt, keyPair.privKey, fromPublicKey.hexToByteArray())
Nip44Version.NIP44.versionCode -> CryptoUtils.decryptNIP44(toDecrypt, keyPair.privKey, fromPublicKey.hexToByteArray())
else -> null
}?.let {
CryptoUtils.decryptNIP44(
payload = encryptedContent,
privateKey = keyPair.privKey,
pubKey = fromPublicKey.hexToByteArray()
)?.let {
onReady(it)
}
}