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() { fun resetEOSEStatuses() {
afterEOSEPerSubscription.keys.toList().forEach { afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
afterEOSEPerSubscription[it] = false
}
} }
fun sendFilter(requestId: String) { fun sendFilter(requestId: String) {

View File

@@ -5,12 +5,6 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair 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 junit.framework.TestCase.assertNotNull
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -38,7 +32,7 @@ class CryptoBenchmark {
val keyPair2 = KeyPair() val keyPair2 = KeyPair()
benchmarkRule.measureRepeated { 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() val keyPair2 = KeyPair()
benchmarkRule.measureRepeated { 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) 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 // Simulate Receiver
events!!.wraps.forEach { events!!.wraps.forEach {

View File

@@ -4,15 +4,12 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.crypto.decodeNIP44
import com.vitorpamplona.quartz.encoders.hexToByteArray import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.events.ChatMessageEvent import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.Gossip import com.vitorpamplona.quartz.events.Gossip
import com.vitorpamplona.quartz.events.NIP24Factory
import com.vitorpamplona.quartz.events.SealedGossipEvent import com.vitorpamplona.quartz.events.SealedGossipEvent
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerInternal 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 @Test
fun decryptWrapEvent() { fun decryptWrapEvent() {
val sender = NostrSignerInternal(KeyPair()) val sender = NostrSignerInternal(KeyPair())
@@ -158,10 +143,8 @@ class GiftWrapReceivingBenchmark {
val wrap = createWrap(sender, receiver) val wrap = createWrap(sender, receiver)
val toDecrypt = decodeNIP44(wrap.content) ?: return
benchmarkRule.measureRepeated { 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 wrap = createWrap(sender, receiver)
val toDecrypt = decodeNIP44(wrap.content) ?: return val innerJson = CryptoUtils.decryptNIP44v1(wrap.content, receiver.keyPair.privKey!!, wrap.pubKey.hexToByteArray())
val innerJson = CryptoUtils.decryptNIP44(toDecrypt, receiver.keyPair.privKey!!, wrap.pubKey.hexToByteArray())
benchmarkRule.measureRepeated { benchmarkRule.measureRepeated {
assertNotNull(innerJson?.let { Event.fromJson(it) }) 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 @Test
fun decryptSealedEvent() { fun decryptSealedEvent() {
val sender = NostrSignerInternal(KeyPair()) val sender = NostrSignerInternal(KeyPair())
@@ -199,10 +169,8 @@ class GiftWrapReceivingBenchmark {
val seal = createSeal(sender, receiver) val seal = createSeal(sender, receiver)
val toDecrypt = decodeNIP44(seal.content) ?: return
benchmarkRule.measureRepeated { 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 seal = createSeal(sender, receiver)
val toDecrypt = decodeNIP44(seal.content) ?: return val innerJson = CryptoUtils.decryptNIP44v1(seal.content, receiver.keyPair.privKey!!, seal.pubKey.hexToByteArray())
val innerJson = CryptoUtils.decryptNIP44(toDecrypt, receiver.keyPair.privKey!!, seal.pubKey.hexToByteArray())
benchmarkRule.measureRepeated { benchmarkRule.measureRepeated {
assertNotNull(innerJson?.let { Gossip.fromJson(it) }) 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 privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561"
val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f" 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()) assertEquals("577c966f499dddd8e8dcc34e8f352e283cc177e53ae372794947e0b8ede7cfd8", key.toHexKey())
} }
@@ -36,8 +36,8 @@ class CryptoUtilsTest {
val sender = KeyPair() val sender = KeyPair()
val receiver = KeyPair() val receiver = KeyPair()
val sharedSecret1 = CryptoUtils.getSharedSecretNIP44(sender.privKey!!, receiver.pubKey) val sharedSecret1 = CryptoUtils.getSharedSecretNIP44v1(sender.privKey!!, receiver.pubKey)
val sharedSecret2 = CryptoUtils.getSharedSecretNIP44(receiver.privKey!!, sender.pubKey) val sharedSecret2 = CryptoUtils.getSharedSecretNIP44v1(receiver.privKey!!, sender.pubKey)
assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey()) assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey())
@@ -61,28 +61,16 @@ class CryptoUtilsTest {
assertEquals(msg, decrypted) assertEquals(msg, decrypted)
} }
@Test @Test
fun encryptDecryptNIP4WithJsonSchemaTest() { fun encryptDecryptNIP44v1Test() {
val msg = "Hi" val msg = "Hi"
val privateKey = CryptoUtils.privkeyCreate() val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey) val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val encrypted = CryptoUtils.encryptNIP04Json(msg, privateKey, publicKey) val encrypted = CryptoUtils.encryptNIP44v1(msg, privateKey, publicKey)
val decrypted = CryptoUtils.decryptNIP04(encrypted, privateKey, publicKey) val decrypted = CryptoUtils.decryptNIP44v1(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)
assertEquals(msg, decrypted) assertEquals(msg, decrypted)
} }
@@ -101,15 +89,15 @@ class CryptoUtilsTest {
} }
@Test @Test
fun encryptSharedSecretDecryptNIP44Test() { fun encryptSharedSecretDecryptNIP44v1Test() {
val msg = "Hi" val msg = "Hi"
val privateKey = CryptoUtils.privkeyCreate() val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey) val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val sharedSecret = CryptoUtils.getSharedSecretNIP44(privateKey, publicKey) val sharedSecret = CryptoUtils.getSharedSecretNIP44v1(privateKey, publicKey)
val encrypted = CryptoUtils.encryptNIP44(msg, sharedSecret) val encrypted = CryptoUtils.encryptNIP44v1(msg, sharedSecret)
val decrypted = CryptoUtils.decryptNIP44(encrypted, sharedSecret) val decrypted = CryptoUtils.decryptNIP44v1(encrypted, sharedSecret)
assertEquals(msg, decrypted) 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 package com.vitorpamplona.quartz.crypto
import android.util.Log import android.util.Log
import android.util.LruCache import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.utils.Key
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import fr.acinq.secp256k1.Secp256k1 import fr.acinq.secp256k1.Secp256k1
import java.security.MessageDigest import java.security.MessageDigest
import java.security.SecureRandom import java.security.SecureRandom
import java.util.Base64 import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object CryptoUtils { object CryptoUtils {
private val sharedKeyCache04 = LruCache<Int, ByteArray>(200)
private val sharedKeyCache44 = LruCache<Int, ByteArray>(200)
private val secp256k1 = Secp256k1.get() private val secp256k1 = Secp256k1.get()
private val libSodium = SodiumAndroid()
private val random = SecureRandom() 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() { fun clearCache() {
sharedKeyCache04.evictAll() nip04.clearCache()
sharedKeyCache44.evictAll() nip44v1.clearCache()
nip44v2.clearCache()
} }
fun randomInt(bound: Int): Int { fun randomInt(bound: Int): Int {
@@ -63,242 +57,174 @@ object CryptoUtils {
return MessageDigest.getInstance("SHA-256").digest(data) return MessageDigest.getInstance("SHA-256").digest(data)
} }
/**
* NIP 04 Utils
*/
fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
val info = encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey)) return nip04.encrypt(msg, 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}"
} }
fun encryptNIP04Json(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo { fun encryptNIP04(msg: String, sharedSecret: ByteArray): Nip04.EncryptedInfo {
return encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey)) return nip04.encrypt(msg, sharedSecret)
}
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 decryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { fun decryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey) return nip04.decrypt(msg, privateKey, pubKey)
return decryptNIP04(msg, sharedSecret)
} }
fun decryptNIP04(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String { fun decryptNIP04(encryptedInfo: Nip04.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey) return nip04.decrypt(encryptedInfo, privateKey, pubKey)
return decryptNIP04(encryptedInfo.ciphertext, encryptedInfo.nonce, sharedSecret)
} }
fun decryptNIP04(msg: String, sharedSecret: ByteArray): String { fun decryptNIP04(msg: String, sharedSecret: ByteArray): String {
val parts = msg.split("?iv=") return nip04.decrypt(msg, sharedSecret)
return decryptNIP04(parts[0], parts[1], sharedSecret)
} }
private fun decryptNIP04(cipher: String, nonce: String, sharedSecret: ByteArray): String { private fun decryptNIP04(cipher: String, nonce: String, sharedSecret: ByteArray): String {
val iv = Base64.getDecoder().decode(nonce) return nip04.decrypt(cipher, nonce, sharedSecret)
val encryptedMsg = Base64.getDecoder().decode(cipher)
return decryptNIP04(encryptedMsg, iv, sharedSecret)
} }
private fun decryptNIP04(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String { private fun decryptNIP04(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") return nip04.decrypt(encryptedMsg, iv, sharedSecret)
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
return String(cipher.doFinal(encryptedMsg))
} }
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 { fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
val hash = combinedHashCode(privateKey, pubKey) return nip04.getSharedSecret(privateKey, pubKey)
val preComputed = sharedKeyCache04[hash] }
if (preComputed != null) return preComputed
val computed = computeSharedSecretNIP04(privateKey, pubKey) fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
sharedKeyCache04.put(hash, computed) return nip04.computeSharedSecret(privateKey, pubKey)
return computed }
/**
* 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)
/** fun encryptNIP44v2(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v2.EncryptedInfo {
* @return 32B shared secret return nip44v2.encrypt(msg, privateKey, pubKey)
*/
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, sharedSecret: ByteArray): Nip44v2.EncryptedInfo {
* @return 32B shared secret return nip44v2.encrypt(msg, sharedSecret)
*/ }
fun computeSharedSecretNIP44(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
sha256(secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33))
}
data class EncryptedInfo(val ciphertext: ByteArray, val nonce: ByteArray, val v: Int) fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int) return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
}
enum class Nip44Version(val versionCode: Int) { fun decryptNIP44v2(encryptedInfo: String, privateKey: ByteArray, pubKey: ByteArray): String? {
NIP04(0), return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
NIP44(1) }
}
fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, sharedSecret: ByteArray): String? {
return nip44v2.decrypt(encryptedInfo, sharedSecret)
}
fun encodeNIP44(info: EncryptedInfo): String { fun getSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
return encodeByteArray(info) return nip44v2.getConversationKey(privateKey, pubKey)
} }
fun decodeNIP44(str: String): EncryptedInfo? { fun computeSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
if (str.isEmpty()) return null return nip44v2.computeConversationKey(privateKey, pubKey)
return if (str[0] == '{') { }
decodeJackson(str)
fun decryptNIP44(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
if (payload.isEmpty()) return null
return if (payload[0] == '{') {
decryptNIP44FromJackson(payload, privateKey, pubKey)
} else { } else {
decodeByteArray(str) decryptNIP44FromBase64(payload, privateKey, pubKey)
}
} }
}
fun encodeByteArray(info: EncryptedInfo): String { data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int, val mac: String?)
return Base64.getEncoder().encodeToString(byteArrayOf(info.v.toByte()) + info.nonce + info.ciphertext)
}
fun decodeByteArray(base64: String): EncryptedInfo? { fun decryptNIP44FromJackson(json: String, privateKey: ByteArray, pubKey: ByteArray): String? {
return try { return try {
val byteArray = Base64.getDecoder().decode(base64) val info = Event.mapper.readValue(json, EncryptedInfoString::class.java)
return EncryptedInfo(
v = byteArray[0].toInt(), when (info.v) {
nonce = byteArray.copyOfRange(1, 25), Nip04.EncryptedInfo.v -> {
ciphertext = byteArray.copyOfRange(25, byteArray.size) 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) { } 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 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 { subject?.let {
tags.add(arrayOf("subject", it)) tags.add(arrayOf("subject", it))
} }
tags.add(arrayOf("alt", alt)) //tags.add(arrayOf("alt", alt))
signer.sign(createdAt, kind, tags.toTypedArray(), msg, onReady) signer.sign(createdAt, kind, tags.toTypedArray(), msg, onReady)
} }

View File

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

View File

@@ -4,15 +4,9 @@ import android.util.Log
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey 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.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
import java.util.UUID
@Immutable @Immutable
class SealedGossipEvent( class SealedGossipEvent(

View File

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