mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-30 00:40:49 +00:00
rearranges the crypto package into separate nips and reduces the amount of circular dependencies.
This commit is contained in:
parent
79ace7f18c
commit
a8a2bda9af
@ -33,7 +33,6 @@ import com.vitorpamplona.ammolite.relays.Client
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.crypto.nip06.Nip06
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.encoders.bechToBytes
|
||||
@ -129,8 +128,8 @@ class AccountStateViewModel : ViewModel() {
|
||||
proxyPort = proxyPort,
|
||||
signer = NostrSignerInternal(keyPair),
|
||||
)
|
||||
} else if (key.contains(" ") && Nip06().isValidMnemonic(key)) {
|
||||
val keyPair = KeyPair(privKey = Nip06().privateKeyFromMnemonic(key))
|
||||
} else if (key.contains(" ") && CryptoUtils.isValidMnemonic(key)) {
|
||||
val keyPair = KeyPair(privKey = CryptoUtils.privateKeyFromMnemonic(key))
|
||||
Account(
|
||||
keyPair,
|
||||
proxy = proxy,
|
||||
|
@ -50,7 +50,7 @@ class CryptoBenchmark {
|
||||
val keyPair2 = KeyPair()
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertNotNull(CryptoUtils.getSharedSecretNIP44v1(keyPair1.privKey!!, keyPair2.pubKey))
|
||||
assertNotNull(CryptoUtils.nip44.v1.getSharedSecret(keyPair1.privKey!!, keyPair2.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ class CryptoBenchmark {
|
||||
val keyPair2 = KeyPair()
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertNotNull(CryptoUtils.computeSharedSecretNIP44v1(keyPair1.privKey!!, keyPair2.pubKey))
|
||||
assertNotNull(CryptoUtils.nip44.v1.computeSharedSecret(keyPair1.privKey!!, keyPair2.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ class GiftWrapReceivingBenchmark {
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertNotNull(
|
||||
CryptoUtils.decryptNIP44v2(
|
||||
CryptoUtils.decryptNIP44(
|
||||
wrap.content,
|
||||
receiver.keyPair.privKey!!,
|
||||
wrap.pubKey.hexToByteArray(),
|
||||
@ -182,7 +182,7 @@ class GiftWrapReceivingBenchmark {
|
||||
val wrap = createWrap(sender, receiver)
|
||||
|
||||
val innerJson =
|
||||
CryptoUtils.decryptNIP44v2(
|
||||
CryptoUtils.decryptNIP44(
|
||||
wrap.content,
|
||||
receiver.keyPair.privKey!!,
|
||||
wrap.pubKey.hexToByteArray(),
|
||||
@ -200,7 +200,7 @@ class GiftWrapReceivingBenchmark {
|
||||
|
||||
benchmarkRule.measureRepeated {
|
||||
assertNotNull(
|
||||
CryptoUtils.decryptNIP44v2(
|
||||
CryptoUtils.decryptNIP44(
|
||||
seal.content,
|
||||
receiver.keyPair.privKey!!,
|
||||
seal.pubKey.hexToByteArray(),
|
||||
@ -217,7 +217,7 @@ class GiftWrapReceivingBenchmark {
|
||||
val seal = createSeal(sender, receiver)
|
||||
|
||||
val innerJson =
|
||||
CryptoUtils.decryptNIP44v2(
|
||||
CryptoUtils.decryptNIP44(
|
||||
seal.content,
|
||||
receiver.keyPair.privKey!!,
|
||||
seal.pubKey.hexToByteArray(),
|
||||
|
@ -43,7 +43,7 @@ class CryptoUtilsTest {
|
||||
val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f"
|
||||
|
||||
val key =
|
||||
CryptoUtils.getSharedSecretNIP44v1(
|
||||
CryptoUtils.nip44.v1.getSharedSecret(
|
||||
privateKey = privateKey.hexToByteArray(),
|
||||
pubKey = publicKey.hexToByteArray(),
|
||||
)
|
||||
@ -56,8 +56,8 @@ class CryptoUtilsTest {
|
||||
val sender = KeyPair()
|
||||
val receiver = KeyPair()
|
||||
|
||||
val sharedSecret1 = CryptoUtils.getSharedSecretNIP44v1(sender.privKey!!, receiver.pubKey)
|
||||
val sharedSecret2 = CryptoUtils.getSharedSecretNIP44v1(receiver.privKey!!, sender.pubKey)
|
||||
val sharedSecret1 = CryptoUtils.nip44.v1.getSharedSecret(sender.privKey!!, receiver.pubKey)
|
||||
val sharedSecret2 = CryptoUtils.nip44.v1.getSharedSecret(receiver.privKey!!, sender.pubKey)
|
||||
|
||||
assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey())
|
||||
|
||||
@ -88,8 +88,8 @@ class CryptoUtilsTest {
|
||||
val privateKey = CryptoUtils.privkeyCreate()
|
||||
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
|
||||
|
||||
val encrypted = CryptoUtils.encryptNIP44v1(msg, privateKey, publicKey)
|
||||
val decrypted = CryptoUtils.decryptNIP44v1(encrypted, privateKey, publicKey)
|
||||
val encrypted = CryptoUtils.nip44.v1.encrypt(msg, privateKey, publicKey)
|
||||
val decrypted = CryptoUtils.nip44.v1.decrypt(encrypted, privateKey, publicKey)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
@ -113,10 +113,10 @@ class CryptoUtilsTest {
|
||||
|
||||
val privateKey = CryptoUtils.privkeyCreate()
|
||||
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
|
||||
val sharedSecret = CryptoUtils.getSharedSecretNIP44v1(privateKey, publicKey)
|
||||
val sharedSecret = CryptoUtils.nip44.v1.getSharedSecret(privateKey, publicKey)
|
||||
|
||||
val encrypted = CryptoUtils.encryptNIP44v1(msg, sharedSecret)
|
||||
val decrypted = CryptoUtils.decryptNIP44v1(encrypted, sharedSecret)
|
||||
val encrypted = CryptoUtils.nip44.v1.encrypt(msg, sharedSecret)
|
||||
val decrypted = CryptoUtils.nip44.v1.decrypt(encrypted, sharedSecret)
|
||||
|
||||
assertEquals(msg, decrypted)
|
||||
}
|
||||
|
@ -22,42 +22,45 @@ package com.vitorpamplona.quartz.crypto.nip06
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Bip32SeedDerivationTest {
|
||||
val seedDerivation = Bip32SeedDerivation(Secp256k1.get())
|
||||
|
||||
val masterBitcoin =
|
||||
Bip32SeedDerivation.generate(
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("gun please vital unable phone catalog explain raise erosion zoo truly exist", ""),
|
||||
)
|
||||
|
||||
val nostrMnemonic0 =
|
||||
Bip32SeedDerivation.generate(
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("leader monkey parrot ring guide accident before fence cannon height naive bean", ""),
|
||||
)
|
||||
|
||||
val nostrMnemonic1 =
|
||||
Bip32SeedDerivation.generate(
|
||||
seedDerivation.generate(
|
||||
Bip39Mnemonics.toSeed("what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade", ""),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun restoreBIP44Wallet() {
|
||||
val privateKey = Bip32SeedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/44'/1'/0'"))
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/44'/1'/0'"))
|
||||
assertEquals("50b3e7905c642309c8a8b73df5a49757a10f2bebb5804571b9db9004cce8a190", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoreBIP49Wallet() {
|
||||
val privateKey = Bip32SeedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/49'/1'/0'"))
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/49'/1'/0'"))
|
||||
assertEquals("154c02c0b66899291a19012207642ba096a2d3ebf51baf153c9495976feb1b30", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoreBIP84Wallet() {
|
||||
val privateKey = Bip32SeedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/84'/1'/0'"))
|
||||
val privateKey = seedDerivation.derivePrivateKey(masterBitcoin, KeyPath("m/84'/1'/0'"))
|
||||
assertEquals("53e8c09a0e3ddcd8d68821c1e99e823966e99df91fb253e1f453a443ba543cb2", privateKey.toHexKey())
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ package com.vitorpamplona.quartz.crypto.nip06
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
@ -29,6 +30,8 @@ import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Nip06Test {
|
||||
val nip06 = Nip06(Secp256k1.get())
|
||||
|
||||
// private key (hex): 7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a
|
||||
// nsec: nsec10allq0gjx7fddtzef0ax00mdps9t2kmtrldkyjfs8l5xruwvh2dq0lhhkp
|
||||
private val menemonic0 = "leader monkey parrot ring guide accident before fence cannon height naive bean"
|
||||
@ -43,26 +46,26 @@ class Nip06Test {
|
||||
|
||||
@Test
|
||||
fun fromSeedNip06TestVector0() {
|
||||
val privateKeyHex = Nip06().privateKeyFromMnemonic(menemonic0).toHexKey()
|
||||
val privateKeyHex = nip06.privateKeyFromMnemonic(menemonic0).toHexKey()
|
||||
assertEquals("7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a", privateKeyHex)
|
||||
|
||||
val privateKeyHex21 = Nip06().privateKeyFromMnemonic(menemonic0, 21).toHexKey()
|
||||
val privateKeyHex21 = nip06.privateKeyFromMnemonic(menemonic0, 21).toHexKey()
|
||||
assertEquals("576390ec69951fcfbf159f2aac0965bb2e6d7a07da2334992af3225c57eaefca", privateKeyHex21)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromSeedNip06TestVector1() {
|
||||
val privateKeyHex = Nip06().privateKeyFromMnemonic(menemonic1).toHexKey()
|
||||
val privateKeyHex = nip06.privateKeyFromMnemonic(menemonic1).toHexKey()
|
||||
assertEquals("c15d739894c81a2fcfd3a2df85a0d2c0dbc47a280d092799f144d73d7ae78add", privateKeyHex)
|
||||
|
||||
val privateKeyHex21 = Nip06().privateKeyFromMnemonic(menemonic1, 42).toHexKey()
|
||||
val privateKeyHex21 = nip06.privateKeyFromMnemonic(menemonic1, 42).toHexKey()
|
||||
assertEquals("ad993054383da74e955f8b86346365b5ffd6575992e1de3738dda9f94407052b", privateKeyHex21)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Snort is not correctly implemented")
|
||||
fun fromSeedNip06FromSnort() {
|
||||
val privateKeyNsec = Nip06().privateKeyFromMnemonic(snortTest).toHexKey()
|
||||
val privateKeyNsec = nip06.privateKeyFromMnemonic(snortTest).toHexKey()
|
||||
assertEquals("nsec1ppw9ltr2x9qwg9a2qnmgv98tfruy2ejnja7me76mwmsreu3s8u2sscj5nt", privateKeyNsec)
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,13 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
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.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
@ -79,8 +80,7 @@ class NIP44v2Test {
|
||||
v.plaintext!!,
|
||||
conversationKey1,
|
||||
v.nonce!!.hexToByteArray(),
|
||||
)
|
||||
.encodePayload()
|
||||
).encodePayload()
|
||||
|
||||
assertEquals(v.payload, ciphertext)
|
||||
|
||||
@ -107,8 +107,7 @@ class NIP44v2Test {
|
||||
plaintext,
|
||||
conversationKey,
|
||||
v.nonce!!.hexToByteArray(),
|
||||
)
|
||||
.encodePayload()
|
||||
).encodePayload()
|
||||
|
||||
assertEquals(v.payloadSha256, sha256Hex(ciphertext.toByteArray(Charsets.UTF_8)))
|
||||
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip49
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import org.junit.Assert.assertEquals
|
||||
@ -31,8 +31,8 @@ class HexEncodingTest {
|
||||
fun testHexEncodeDecodeOurs() {
|
||||
assertEquals(
|
||||
testHex,
|
||||
com.vitorpamplona.quartz.encoders.Hex.encode(
|
||||
com.vitorpamplona.quartz.encoders.Hex.decode(testHex),
|
||||
Hex.encode(
|
||||
Hex.decode(testHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -42,7 +42,8 @@ class HexEncodingTest {
|
||||
assertEquals(
|
||||
testHex,
|
||||
fr.acinq.secp256k1.Hex.encode(
|
||||
fr.acinq.secp256k1.Hex.decode(testHex),
|
||||
fr.acinq.secp256k1.Hex
|
||||
.decode(testHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -51,14 +52,17 @@ class HexEncodingTest {
|
||||
fun testRandoms() {
|
||||
for (i in 0..1000) {
|
||||
val bytes = CryptoUtils.privkeyCreate()
|
||||
val hex = fr.acinq.secp256k1.Hex.encode(bytes)
|
||||
val hex =
|
||||
fr.acinq.secp256k1.Hex
|
||||
.encode(bytes)
|
||||
assertEquals(
|
||||
fr.acinq.secp256k1.Hex.encode(bytes),
|
||||
com.vitorpamplona.quartz.encoders.Hex.encode(bytes),
|
||||
fr.acinq.secp256k1.Hex
|
||||
.encode(bytes),
|
||||
Hex.encode(bytes),
|
||||
)
|
||||
assertEquals(
|
||||
bytes.toList(),
|
||||
com.vitorpamplona.quartz.encoders.Hex.decode(hex).toList(),
|
||||
Hex.decode(hex).toList(),
|
||||
)
|
||||
}
|
||||
}
|
@ -18,10 +18,9 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
@ -18,14 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.encoders.decodePrivateKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
@ -18,10 +18,9 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
@ -18,11 +18,9 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
@ -18,10 +18,9 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -18,18 +18,13 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
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.NIP17Factory
|
||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerInternal
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
@ -276,14 +271,12 @@ class GiftWrapEventTest {
|
||||
assertTrue(unwrappedMsgForReceiverByReceiver is SealedGossipEvent)
|
||||
|
||||
if (unwrappedMsgForReceiverByReceiver is SealedGossipEvent) {
|
||||
unwrappedMsgForReceiverByReceiver.cachedGossip(receiver) {
|
||||
unwrappedGossipToReceiverByReceiver ->
|
||||
unwrappedMsgForReceiverByReceiver.cachedGossip(receiver) { unwrappedGossipToReceiverByReceiver ->
|
||||
assertEquals("Hi There!", unwrappedGossipToReceiverByReceiver?.content)
|
||||
countDownDecryptLatch.countDown()
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverByReceiver.cachedGossip(sender) { unwrappedGossipToReceiverBySender,
|
||||
->
|
||||
unwrappedMsgForReceiverByReceiver.cachedGossip(sender) { unwrappedGossipToReceiverBySender ->
|
||||
fail(
|
||||
"Should not be able to decrypt msg for the receiver by the receiver but decrypted with the sender",
|
||||
)
|
||||
@ -422,13 +415,11 @@ class GiftWrapEventTest {
|
||||
assertEquals(SealedGossipEvent.KIND, unwrappedMsgForSenderBySender.kind)
|
||||
|
||||
if (unwrappedMsgForSenderBySender is SealedGossipEvent) {
|
||||
unwrappedMsgForSenderBySender.cachedGossip(receiverA) { unwrappedGossipToSenderByReceiverA,
|
||||
->
|
||||
unwrappedMsgForSenderBySender.cachedGossip(receiverA) { unwrappedGossipToSenderByReceiverA ->
|
||||
fail()
|
||||
}
|
||||
|
||||
unwrappedMsgForSenderBySender.cachedGossip(receiverB) { unwrappedGossipToSenderByReceiverB,
|
||||
->
|
||||
unwrappedMsgForSenderBySender.cachedGossip(receiverB) { unwrappedGossipToSenderByReceiverB ->
|
||||
fail()
|
||||
}
|
||||
|
||||
@ -459,21 +450,18 @@ class GiftWrapEventTest {
|
||||
assertEquals(SealedGossipEvent.KIND, unwrappedMsgForReceiverAByReceiverA.kind)
|
||||
|
||||
if (unwrappedMsgForReceiverAByReceiverA is SealedGossipEvent) {
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(receiverA) {
|
||||
unwrappedGossipToReceiverAByReceiverA ->
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(receiverA) { unwrappedGossipToReceiverAByReceiverA ->
|
||||
assertEquals(
|
||||
"Who is going to the party tonight?",
|
||||
unwrappedGossipToReceiverAByReceiverA.content,
|
||||
)
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(sender) {
|
||||
unwrappedGossipToReceiverABySender ->
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(sender) { unwrappedGossipToReceiverABySender ->
|
||||
fail()
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(receiverB) {
|
||||
unwrappedGossipToReceiverAByReceiverB ->
|
||||
unwrappedMsgForReceiverAByReceiverA.cachedGossip(receiverB) { unwrappedGossipToReceiverAByReceiverB ->
|
||||
fail()
|
||||
}
|
||||
}
|
||||
@ -495,13 +483,11 @@ class GiftWrapEventTest {
|
||||
assertEquals(SealedGossipEvent.KIND, unwrappedMsgForReceiverBByReceiverB.kind)
|
||||
|
||||
if (unwrappedMsgForReceiverBByReceiverB is SealedGossipEvent) {
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(receiverA) {
|
||||
unwrappedGossipToReceiverBByReceiverA ->
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(receiverA) { unwrappedGossipToReceiverBByReceiverA ->
|
||||
fail()
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(receiverB) {
|
||||
unwrappedGossipToReceiverBByReceiverB ->
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(receiverB) { unwrappedGossipToReceiverBByReceiverB ->
|
||||
assertEquals(
|
||||
"Who is going to the party tonight?",
|
||||
unwrappedGossipToReceiverBByReceiverB.content,
|
||||
@ -510,8 +496,7 @@ class GiftWrapEventTest {
|
||||
countDownDecryptLatch.countDown()
|
||||
}
|
||||
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(sender) {
|
||||
unwrappedGossipToReceiverBBySender ->
|
||||
unwrappedMsgForReceiverBByReceiverB.cachedGossip(sender) { unwrappedGossipToReceiverBBySender ->
|
||||
fail()
|
||||
}
|
||||
}
|
||||
@ -538,8 +523,7 @@ class GiftWrapEventTest {
|
||||
]
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
var gossip: Event? = null
|
||||
|
||||
@ -573,8 +557,7 @@ class GiftWrapEventTest {
|
||||
]
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "409ff7654141eaa16cd2161fe5bd127aeaef71f270c67587474b78998a8e3533"
|
||||
|
||||
@ -612,8 +595,7 @@ class GiftWrapEventTest {
|
||||
"wss://relay.damus.io/"
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771"
|
||||
var gossip: Event? = null
|
||||
@ -647,8 +629,7 @@ class GiftWrapEventTest {
|
||||
]
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771"
|
||||
|
||||
@ -687,8 +668,7 @@ class GiftWrapEventTest {
|
||||
"id": "d9fc85ece892ce45ffa737b3ddc0f8b752623181d75363b966191f8c03d2debe",
|
||||
"sig": "1b20416b83f4b5b8eead11e29c185f46b5e76d1960e4505210ddd00f7a6973cc11268f52a8989e3799b774d5f3a55db95bed4d66a1b6e88ab54becec5c771c17"
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
val privateKey = "7dd22cafc512c0bc363a259f6dcda515b13ae3351066d7976fd0bb79cbd0d700"
|
||||
|
||||
@ -753,8 +733,7 @@ class GiftWrapEventTest {
|
||||
"id": "ae625fd43612127d63bfd1967ba32ae915100842a205fc2c3b3fc02ab3827f08",
|
||||
"sig": "2807a7ab5728984144676fd34686267cbe6fe38bc2f65a3640ba9243c13e8a1ae5a9a051e8852aa0c997a3623d7fa066cf2073a233c6d7db46fb1a0d4c01e5a3"
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
""".trimIndent()
|
||||
|
||||
val wrap = Event.fromJson(msg) as GiftWrapEvent
|
||||
wrap.checkSignature()
|
@ -18,15 +18,12 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent.Companion.createEncryptionPrivateKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerInternal
|
||||
import junit.framework.TestCase.assertNotNull
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.ots
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
@ -20,35 +20,32 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.nip01.Nip01
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.crypto.nip06.Nip06
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44v2
|
||||
import com.vitorpamplona.quartz.crypto.nip49.Nip49
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
|
||||
object CryptoUtils {
|
||||
private val secp256k1 = Secp256k1.get()
|
||||
private val random = SecureRandom()
|
||||
|
||||
private val nip04 = Nip04(secp256k1, random)
|
||||
private val nip44v1 = Nip44v1(secp256k1, random)
|
||||
private val nip44v2 = Nip44v2(secp256k1, random)
|
||||
private val nip49 = Nip49(secp256k1, random)
|
||||
public val nip01 = Nip01(secp256k1, random)
|
||||
public val nip06 = Nip06(secp256k1)
|
||||
public val nip04 = Nip04(secp256k1, random)
|
||||
public val nip44 = Nip44(secp256k1, random, nip04)
|
||||
public val nip49 = Nip49(secp256k1, random)
|
||||
|
||||
fun clearCache() {
|
||||
nip04.clearCache()
|
||||
nip44v1.clearCache()
|
||||
nip44v2.clearCache()
|
||||
nip44.clearCache()
|
||||
}
|
||||
|
||||
fun randomInt(bound: Int): Int {
|
||||
return random.nextInt(bound)
|
||||
}
|
||||
|
||||
/** Provides a 32B "private key" aka random number */
|
||||
fun privkeyCreate() = random(32)
|
||||
fun randomInt(bound: Int): Int = random.nextInt(bound)
|
||||
|
||||
fun random(size: Int): ByteArray {
|
||||
val bytes = ByteArray(size)
|
||||
@ -56,39 +53,32 @@ object CryptoUtils {
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun pubkeyCreateBitcoin(privKey: ByteArray) = secp256k1.pubKeyCompress(secp256k1.pubkeyCreate(privKey))
|
||||
/** Provides a 32B "private key" aka random number */
|
||||
fun privkeyCreate() = nip01.privkeyCreate()
|
||||
|
||||
fun pubkeyCreate(privKey: ByteArray) = secp256k1.pubKeyCompress(secp256k1.pubkeyCreate(privKey)).copyOfRange(1, 33)
|
||||
|
||||
fun isPrivKeyValid(il: ByteArray): Boolean {
|
||||
return secp256k1.secKeyVerify(il)
|
||||
}
|
||||
fun pubkeyCreate(privKey: ByteArray) = nip01.pubkeyCreate(privKey)
|
||||
|
||||
fun signString(
|
||||
message: String,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray = random(32),
|
||||
): ByteArray {
|
||||
return sign(sha256(message.toByteArray()), privKey, auxrand32)
|
||||
}
|
||||
): ByteArray = nip01.signString(message, privKey, auxrand32)
|
||||
|
||||
fun sign(
|
||||
data: ByteArray,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray? = null,
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, auxrand32)
|
||||
): ByteArray = nip01.sign(data, privKey, auxrand32)
|
||||
|
||||
fun verifySignature(
|
||||
signature: ByteArray,
|
||||
hash: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): Boolean {
|
||||
return secp256k1.verifySchnorr(signature, hash, pubKey)
|
||||
}
|
||||
): Boolean = nip01.verify(signature, hash, pubKey)
|
||||
|
||||
fun sha256(data: ByteArray): ByteArray {
|
||||
// Creates a new buffer every time
|
||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||
return nip01.sha256(data)
|
||||
}
|
||||
|
||||
/** NIP 04 Utils */
|
||||
@ -96,189 +86,84 @@ object CryptoUtils {
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String {
|
||||
return nip04.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
): String = nip04.encrypt(msg, privateKey, pubKey)
|
||||
|
||||
fun encryptNIP04(
|
||||
msg: String,
|
||||
sharedSecret: ByteArray,
|
||||
): Nip04.EncryptedInfo {
|
||||
return nip04.encrypt(msg, sharedSecret)
|
||||
}
|
||||
): Nip04.EncryptedInfo = nip04.encrypt(msg, sharedSecret)
|
||||
|
||||
fun decryptNIP04(
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String {
|
||||
return nip04.decrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
): String = nip04.decrypt(msg, privateKey, pubKey)
|
||||
|
||||
fun decryptNIP04(
|
||||
encryptedInfo: Nip04.EncryptedInfo,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String {
|
||||
return nip04.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
): String = nip04.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
|
||||
fun decryptNIP04(
|
||||
msg: String,
|
||||
sharedSecret: ByteArray,
|
||||
): String {
|
||||
return nip04.decrypt(msg, sharedSecret)
|
||||
}
|
||||
): String = nip04.decrypt(msg, sharedSecret)
|
||||
|
||||
private fun decryptNIP04(
|
||||
cipher: String,
|
||||
nonce: String,
|
||||
sharedSecret: ByteArray,
|
||||
): String {
|
||||
return nip04.decrypt(cipher, nonce, sharedSecret)
|
||||
}
|
||||
): String = nip04.decrypt(cipher, nonce, sharedSecret)
|
||||
|
||||
private fun decryptNIP04(
|
||||
encryptedMsg: ByteArray,
|
||||
iv: ByteArray,
|
||||
sharedSecret: ByteArray,
|
||||
): String {
|
||||
return nip04.decrypt(encryptedMsg, iv, sharedSecret)
|
||||
}
|
||||
): String = nip04.decrypt(encryptedMsg, iv, sharedSecret)
|
||||
|
||||
fun getSharedSecretNIP04(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray {
|
||||
return nip04.getSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
): ByteArray = nip04.getSharedSecret(privateKey, pubKey)
|
||||
|
||||
fun computeSharedSecretNIP04(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray {
|
||||
return nip04.computeSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
): ByteArray = nip04.computeSharedSecret(privateKey, pubKey)
|
||||
|
||||
/** NIP 44v1 Utils */
|
||||
fun encryptNIP44v1(
|
||||
/** NIP 06 Utils */
|
||||
fun isValidMnemonic(mnemonic: String): Boolean = nip06.isValidMnemonic(mnemonic)
|
||||
|
||||
fun privateKeyFromMnemonic(
|
||||
mnemonic: String,
|
||||
account: Long = 0,
|
||||
) = nip06.privateKeyFromMnemonic(mnemonic, account)
|
||||
|
||||
/** NIP 44 Utils */
|
||||
fun getSharedSecretNIP44(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray = nip44.getSharedSecret(privateKey, pubKey)
|
||||
|
||||
fun computeSharedSecretNIP44(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray = nip44.computeSharedSecret(privateKey, pubKey)
|
||||
|
||||
fun encryptNIP44(
|
||||
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)
|
||||
}
|
||||
|
||||
/** NIP 44v2 Utils */
|
||||
fun encryptNIP44v2(
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): Nip44v2.EncryptedInfo {
|
||||
return nip44v2.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun encryptNIP44v2(
|
||||
msg: String,
|
||||
sharedSecret: ByteArray,
|
||||
): Nip44v2.EncryptedInfo {
|
||||
return nip44v2.encrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun decryptNIP44v2(
|
||||
encryptedInfo: Nip44v2.EncryptedInfo,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? {
|
||||
return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
|
||||
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 getSharedSecretNIP44v2(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray {
|
||||
return nip44v2.getConversationKey(privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun computeSharedSecretNIP44v2(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray {
|
||||
return nip44v2.computeConversationKey(privateKey, pubKey)
|
||||
}
|
||||
): Nip44v2.EncryptedInfo = nip44.encrypt(msg, 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 {
|
||||
decryptNIP44FromBase64(payload, privateKey, pubKey)
|
||||
}
|
||||
}
|
||||
): String? = nip44.decrypt(payload, privateKey, pubKey)
|
||||
|
||||
/** NIP 49 Utils */
|
||||
fun decryptNIP49(
|
||||
payload: String,
|
||||
password: String,
|
||||
@ -290,87 +175,5 @@ object CryptoUtils {
|
||||
fun encryptNIP49(
|
||||
key: HexKey,
|
||||
password: String,
|
||||
): String? {
|
||||
return nip49.encrypt(key, password, 16, Nip49.EncryptedInfo.CLIENT_DOES_NOT_TRACK)
|
||||
}
|
||||
|
||||
class EncryptedInfoString(
|
||||
val ciphertext: String,
|
||||
val nonce: String,
|
||||
val v: Int,
|
||||
val mac: String?,
|
||||
)
|
||||
|
||||
fun decryptNIP44FromJackson(
|
||||
json: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? {
|
||||
return try {
|
||||
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.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 sum(
|
||||
first: ByteArray,
|
||||
second: ByteArray,
|
||||
): ByteArray {
|
||||
return secp256k1.privKeyTweakAdd(first, second)
|
||||
}
|
||||
): String = nip49.encrypt(key, password)
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto.nip01
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils.random
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
|
||||
class Nip01(
|
||||
val secp256k1: Secp256k1,
|
||||
val random: SecureRandom,
|
||||
) {
|
||||
/** Provides a 32B "private key" aka random number */
|
||||
fun privkeyCreate() = random(32)
|
||||
|
||||
fun pubkeyCreate(privKey: ByteArray) = secp256k1.pubKeyCompress(secp256k1.pubkeyCreate(privKey)).copyOfRange(1, 33)
|
||||
|
||||
fun sign(
|
||||
data: ByteArray,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray? = null,
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, auxrand32)
|
||||
|
||||
fun verify(
|
||||
signature: ByteArray,
|
||||
hash: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): Boolean = secp256k1.verifySchnorr(signature, hash, pubKey)
|
||||
|
||||
fun sha256(data: ByteArray): ByteArray {
|
||||
// Creates a new buffer every time
|
||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||
}
|
||||
|
||||
fun signString(
|
||||
message: String,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray = random(32),
|
||||
): ByteArray = sign(CryptoUtils.sha256(message.toByteArray()), privKey, auxrand32)
|
||||
}
|
@ -18,9 +18,11 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip04
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.SharedKeyCache
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44v1
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.SecureRandom
|
||||
@ -29,7 +31,10 @@ import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
class Nip04(
|
||||
val secp256k1: Secp256k1,
|
||||
val random: SecureRandom,
|
||||
) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
private val h02 = Hex.decode("02")
|
||||
|
||||
@ -41,9 +46,7 @@ class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String {
|
||||
return encrypt(msg, getSharedSecret(privateKey, pubKey)).encodeToNIP04()
|
||||
}
|
||||
): String = encrypt(msg, getSharedSecret(privateKey, pubKey)).encodeToNIP04()
|
||||
|
||||
fun encrypt(
|
||||
msg: String,
|
||||
@ -146,8 +149,8 @@ class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeFromNIP04(payload: String): EncryptedInfo? {
|
||||
return try {
|
||||
fun decodeFromNIP04(payload: String): EncryptedInfo? =
|
||||
try {
|
||||
val parts = payload.split("?iv=")
|
||||
EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(parts[0]),
|
||||
@ -158,14 +161,13 @@ class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder()
|
||||
fun encodePayload(): String =
|
||||
Base64
|
||||
.getEncoder()
|
||||
.encodeToString(
|
||||
byteArrayOf(V.toByte()) + nonce + ciphertext,
|
||||
)
|
||||
}
|
||||
|
||||
fun encodeToNIP04(): String {
|
||||
val nonce = Base64.getEncoder().encodeToString(nonce)
|
@ -20,14 +20,16 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto.nip06
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/*
|
||||
Simplified from: https://github.com/ACINQ/bitcoin-kmp/
|
||||
*/
|
||||
object Bip32SeedDerivation {
|
||||
class Bip32SeedDerivation(
|
||||
val secp256k1: Secp256k1,
|
||||
) {
|
||||
class ExtendedPrivateKey(
|
||||
val secretkeybytes: ByteArray,
|
||||
val chaincode: ByteArray,
|
||||
@ -53,6 +55,15 @@ object Bip32SeedDerivation {
|
||||
return mac.doFinal(data)
|
||||
}
|
||||
|
||||
fun isPrivKeyValid(il: ByteArray): Boolean = secp256k1.secKeyVerify(il)
|
||||
|
||||
fun pubkeyCreateBitcoin(privKey: ByteArray) = secp256k1.pubKeyCompress(secp256k1.pubkeyCreate(privKey))
|
||||
|
||||
fun sum(
|
||||
first: ByteArray,
|
||||
second: ByteArray,
|
||||
): ByteArray = secp256k1.privKeyTweakAdd(first, second)
|
||||
|
||||
fun derivePrivateKey(
|
||||
parent: ExtendedPrivateKey,
|
||||
index: Long,
|
||||
@ -62,17 +73,17 @@ object Bip32SeedDerivation {
|
||||
val data = arrayOf(0.toByte()).toByteArray() + parent.secretkeybytes + writeInt32BE(index.toInt())
|
||||
hmac512(parent.chaincode, data)
|
||||
} else {
|
||||
val data = CryptoUtils.pubkeyCreateBitcoin(parent.secretkeybytes) + writeInt32BE(index.toInt())
|
||||
val data = pubkeyCreateBitcoin(parent.secretkeybytes) + writeInt32BE(index.toInt())
|
||||
hmac512(parent.chaincode, data)
|
||||
}
|
||||
val il = i.take(32).toByteArray()
|
||||
val ir = i.takeLast(32).toByteArray()
|
||||
|
||||
require(CryptoUtils.isPrivKeyValid(il)) { "cannot generate child private key: IL is invalid" }
|
||||
require(isPrivKeyValid(il)) { "cannot generate child private key: IL is invalid" }
|
||||
|
||||
val key = CryptoUtils.sum(il, parent.secretkeybytes)
|
||||
val key = sum(il, parent.secretkeybytes)
|
||||
|
||||
require(CryptoUtils.isPrivKeyValid(key)) { "cannot generate child private key: resulting private key is invalid" }
|
||||
require(isPrivKeyValid(key)) { "cannot generate child private key: resulting private key is invalid" }
|
||||
|
||||
return ExtendedPrivateKey(key, ir)
|
||||
}
|
||||
@ -94,7 +105,7 @@ object Bip32SeedDerivation {
|
||||
fun derivePrivateKey(
|
||||
parent: ExtendedPrivateKey,
|
||||
chain: List<Long>,
|
||||
): ExtendedPrivateKey = chain.fold(parent, Bip32SeedDerivation::derivePrivateKey)
|
||||
): ExtendedPrivateKey = chain.fold(parent, this::derivePrivateKey)
|
||||
|
||||
fun derivePrivateKey(
|
||||
parent: ExtendedPrivateKey,
|
||||
|
@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto.nip06
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.PBKDF
|
||||
import com.vitorpamplona.quartz.crypto.nip49.PBKDF
|
||||
import java.security.MessageDigest
|
||||
|
||||
// CODE FROM: https://github.com/ACINQ/bitcoin-kmp/
|
||||
|
||||
@ -37,6 +37,11 @@ object Bip39Mnemonics {
|
||||
return zeroes + digits
|
||||
}
|
||||
|
||||
fun sha256(data: ByteArray): ByteArray {
|
||||
// Creates a new buffer every time
|
||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||
}
|
||||
|
||||
private fun toBinary(x: ByteArray): List<Boolean> = x.map(Bip39Mnemonics::toBinary).flatten()
|
||||
|
||||
private fun fromBinary(bin: List<Boolean>): Int = bin.fold(0) { acc, flag -> if (flag) 2 * acc + 1 else 2 * acc }
|
||||
@ -45,13 +50,12 @@ object Bip39Mnemonics {
|
||||
items: List<Boolean>,
|
||||
size: Int,
|
||||
acc: List<List<Boolean>> = emptyList(),
|
||||
): List<List<Boolean>> {
|
||||
return when {
|
||||
): List<List<Boolean>> =
|
||||
when {
|
||||
items.isEmpty() -> acc
|
||||
items.size < size -> acc + listOf(items)
|
||||
else -> group(items.drop(size), size, acc + listOf(items.take(size)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mnemonics list of mnemonic words
|
||||
@ -80,20 +84,19 @@ object Bip39Mnemonics {
|
||||
val databits = bits.subList(0, bitlength)
|
||||
val checksumbits = bits.subList(bitlength, bits.size)
|
||||
val data = group(databits, 8).map { fromBinary(it) }.map { it.toByte() }.toByteArray()
|
||||
val check = toBinary(CryptoUtils.sha256(data)).take(data.size / 4)
|
||||
val check = toBinary(sha256(data)).take(data.size / 4)
|
||||
require(check == checksumbits) { "invalid checksum" }
|
||||
}
|
||||
|
||||
fun validate(mnemonics: String): Unit = validate(mnemonics.split(" "))
|
||||
|
||||
fun isValid(mnemonics: String): Boolean {
|
||||
return try {
|
||||
fun isValid(mnemonics: String): Boolean =
|
||||
try {
|
||||
validate(mnemonics)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BIP39 entropy encoding
|
||||
@ -108,7 +111,7 @@ object Bip39Mnemonics {
|
||||
wordlist: Array<String>,
|
||||
): List<String> {
|
||||
require(wordlist.size == 2048) { "invalid word list (size should be 2048)" }
|
||||
val digits = toBinary(entropy) + toBinary(CryptoUtils.sha256(entropy)).take(entropy.size / 4)
|
||||
val digits = toBinary(entropy) + toBinary(sha256(entropy)).take(entropy.size / 4)
|
||||
|
||||
return group(digits, 11).map(Bip39Mnemonics::fromBinary).map { wordlist[it] }
|
||||
}
|
||||
|
@ -20,29 +20,31 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto.nip06
|
||||
|
||||
class Nip06 {
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
|
||||
class Nip06(
|
||||
val secp256k1: Secp256k1,
|
||||
) {
|
||||
val derivation = Bip32SeedDerivation(secp256k1)
|
||||
|
||||
// m/44'/1237'/<account>'/0/0
|
||||
private val nip6Base: KeyPath =
|
||||
KeyPath("")
|
||||
.derive(Hardener.hardened(44L))
|
||||
.derive(Hardener.hardened(1237L))
|
||||
|
||||
private fun nip6Path(account: Long): KeyPath {
|
||||
return nip6Base.derive(Hardener.hardened(account))
|
||||
private fun nip6Path(account: Long): KeyPath =
|
||||
nip6Base
|
||||
.derive(Hardener.hardened(account))
|
||||
.derive(0L)
|
||||
.derive(0L)
|
||||
}
|
||||
|
||||
fun isValidMnemonic(mnemonic: String): Boolean {
|
||||
return Bip39Mnemonics.isValid(mnemonic)
|
||||
}
|
||||
fun isValidMnemonic(mnemonic: String): Boolean = Bip39Mnemonics.isValid(mnemonic)
|
||||
|
||||
fun privateKeyFromSeed(
|
||||
seed: ByteArray,
|
||||
account: Long = 0,
|
||||
): ByteArray {
|
||||
return Bip32SeedDerivation.derivePrivateKey(Bip32SeedDerivation.generate(seed), nip6Path(account))
|
||||
}
|
||||
): ByteArray = derivation.derivePrivateKey(derivation.generate(seed), nip6Path(account))
|
||||
|
||||
fun privateKeyFromMnemonic(
|
||||
mnemonic: String,
|
||||
|
@ -18,13 +18,16 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Hkdf(val algorithm: String = "HmacSHA256", val hashLen: Int = 32) {
|
||||
class Hkdf(
|
||||
val algorithm: String = "HmacSHA256",
|
||||
val hashLen: Int = 32,
|
||||
) {
|
||||
fun extract(
|
||||
key: ByteArray,
|
||||
salt: ByteArray,
|
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
|
||||
class Nip44(
|
||||
secp256k1: Secp256k1,
|
||||
random: SecureRandom,
|
||||
val nip04: Nip04,
|
||||
) {
|
||||
public val v1 = Nip44v1(secp256k1, random)
|
||||
public val v2 = Nip44v2(secp256k1, random)
|
||||
|
||||
fun clearCache() {
|
||||
v1.clearCache()
|
||||
v2.clearCache()
|
||||
}
|
||||
|
||||
/** NIP 44v2 Utils */
|
||||
fun getSharedSecret(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray = v2.getConversationKey(privateKey, pubKey)
|
||||
|
||||
fun computeSharedSecret(
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): ByteArray = v2.computeConversationKey(privateKey, pubKey)
|
||||
|
||||
fun encrypt(
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): Nip44v2.EncryptedInfo {
|
||||
// current version should be used.
|
||||
return v2.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
// Always decrypt from any version/any encoding
|
||||
fun decrypt(
|
||||
payload: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? {
|
||||
if (payload.isEmpty()) return null
|
||||
return if (payload[0] == '{') {
|
||||
decryptNIP44FromJackson(payload, privateKey, pubKey)
|
||||
} else {
|
||||
decryptNIP44FromBase64(payload, privateKey, pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
class EncryptedInfoString(
|
||||
val ciphertext: String,
|
||||
val nonce: String,
|
||||
val v: Int,
|
||||
val mac: String?,
|
||||
)
|
||||
|
||||
fun decryptNIP44FromJackson(
|
||||
json: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? =
|
||||
try {
|
||||
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),
|
||||
)
|
||||
nip04.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
Nip44v1.EncryptedInfo.V -> {
|
||||
val encryptedInfo =
|
||||
Nip44v1.EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext),
|
||||
nonce = Base64.getDecoder().decode(info.nonce),
|
||||
)
|
||||
v1.decrypt(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),
|
||||
)
|
||||
v2.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
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 -> nip04.decrypt(payload, privateKey, pubKey)
|
||||
Nip44v1.EncryptedInfo.V -> v1.decrypt(payload, privateKey, pubKey)
|
||||
Nip44v2.EncryptedInfo.V -> v2.decrypt(payload, privateKey, pubKey)
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("CryptoUtils", "Could not identify the version for NIP44 payload $payload")
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
@ -18,18 +18,22 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
import android.util.Log
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
import com.vitorpamplona.quartz.crypto.SharedKeyCache
|
||||
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) {
|
||||
class Nip44v1(
|
||||
val secp256k1: Secp256k1,
|
||||
val random: SecureRandom,
|
||||
) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
private val h02 = Hex.decode("02")
|
||||
private val libSodium = SodiumAndroid()
|
||||
@ -97,15 +101,13 @@ class Nip44v1(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
fun decrypt(
|
||||
encryptedInfo: EncryptedInfo,
|
||||
sharedSecret: ByteArray,
|
||||
): String? {
|
||||
return cryptoStreamXChaCha20Xor(
|
||||
): String? =
|
||||
cryptoStreamXChaCha20Xor(
|
||||
libSodium = libSodium,
|
||||
messageBytes = encryptedInfo.ciphertext,
|
||||
nonce = encryptedInfo.nonce,
|
||||
key = Key.fromBytes(sharedSecret),
|
||||
)
|
||||
?.decodeToString()
|
||||
}
|
||||
)?.decodeToString()
|
||||
|
||||
fun getSharedSecret(
|
||||
privateKey: ByteArray,
|
||||
@ -155,11 +157,11 @@ class Nip44v1(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
}
|
||||
}
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder()
|
||||
fun encodePayload(): String =
|
||||
Base64
|
||||
.getEncoder()
|
||||
.encodeToString(
|
||||
byteArrayOf(V.toByte()) + nonce + ciphertext,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -18,11 +18,12 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
import android.util.Log
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.vitorpamplona.quartz.crypto.SharedKeyCache
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
@ -33,7 +34,10 @@ import java.util.Base64
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
class Nip44v2(
|
||||
val secp256k1: Secp256k1,
|
||||
val random: SecureRandom,
|
||||
) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
|
||||
private val libSodium = SodiumAndroid()
|
||||
@ -55,9 +59,7 @@ class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): EncryptedInfo {
|
||||
return encrypt(msg, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
): EncryptedInfo = encrypt(msg, getConversationKey(privateKey, pubKey))
|
||||
|
||||
fun encrypt(
|
||||
plaintext: String,
|
||||
@ -99,17 +101,13 @@ class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
payload: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? {
|
||||
return decrypt(payload, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
): String? = decrypt(payload, getConversationKey(privateKey, pubKey))
|
||||
|
||||
fun decrypt(
|
||||
decoded: EncryptedInfo,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? {
|
||||
return decrypt(decoded, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
): String? = decrypt(decoded, getConversationKey(privateKey, pubKey))
|
||||
|
||||
fun decrypt(
|
||||
payload: String,
|
||||
@ -173,7 +171,11 @@ class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
check(unpaddedLen <= maxPlaintextSize) { "Message is too long ($unpaddedLen): $plaintext" }
|
||||
|
||||
val prefix =
|
||||
ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort(unpaddedLen.toShort()).array()
|
||||
ByteBuffer
|
||||
.allocate(2)
|
||||
.order(ByteOrder.BIG_ENDIAN)
|
||||
.putShort(unpaddedLen.toShort())
|
||||
.array()
|
||||
val suffix = ByteArray(calcPaddedLen(unpaddedLen) - unpaddedLen)
|
||||
return ByteBuffer.wrap(prefix + unpadded + suffix).array()
|
||||
}
|
||||
@ -182,13 +184,12 @@ class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
byte1: Byte,
|
||||
byte2: Byte,
|
||||
bigEndian: Boolean,
|
||||
): Int {
|
||||
return if (bigEndian) {
|
||||
): Int =
|
||||
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)
|
||||
@ -273,11 +274,11 @@ class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
}
|
||||
}
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder()
|
||||
fun encodePayload(): String =
|
||||
Base64
|
||||
.getEncoder()
|
||||
.encodeToString(
|
||||
byteArrayOf(V.toByte()) + nonce + ciphertext + mac,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip44
|
||||
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
@ -66,9 +66,7 @@ fun cryptoStreamXchacha20Xor(
|
||||
messageLen: Long,
|
||||
nonce: ByteArray,
|
||||
key: ByteArray,
|
||||
): Int {
|
||||
return cryptoStreamXchacha20XorIc(libSodium, cipher, message, messageLen, nonce, 0, key)
|
||||
}
|
||||
): Int = cryptoStreamXchacha20XorIc(libSodium, cipher, message, messageLen, nonce, 0, key)
|
||||
|
||||
fun cryptoStreamXChaCha20Xor(
|
||||
libSodium: SodiumAndroid,
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
package com.vitorpamplona.quartz.crypto.nip49
|
||||
|
||||
import android.util.Log
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
||||
@ -32,16 +32,17 @@ import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.SecureRandom
|
||||
import java.text.Normalizer
|
||||
|
||||
class Nip49(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
class Nip49(
|
||||
val secp256k1: Secp256k1,
|
||||
val random: SecureRandom,
|
||||
) {
|
||||
private val libSodium = SodiumAndroid()
|
||||
private val lazySodium = LazySodiumAndroid(libSodium)
|
||||
|
||||
fun decrypt(
|
||||
nCryptSec: String,
|
||||
password: String,
|
||||
): HexKey {
|
||||
return decrypt(EncryptedInfo.decodePayload(nCryptSec), password)
|
||||
}
|
||||
): HexKey = decrypt(EncryptedInfo.decodePayload(nCryptSec), password)
|
||||
|
||||
fun decrypt(
|
||||
encryptedInfo: EncryptedInfo?,
|
||||
@ -75,11 +76,9 @@ class Nip49(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
fun encrypt(
|
||||
secretKeyHex: String,
|
||||
password: String,
|
||||
logn: Int,
|
||||
ksb: Byte,
|
||||
): String? {
|
||||
return encrypt(secretKeyHex.hexToByteArray(), password, logn, ksb)
|
||||
}
|
||||
logn: Int = 16,
|
||||
ksb: Byte = EncryptedInfo.CLIENT_DOES_NOT_TRACK,
|
||||
): String = encrypt(secretKeyHex.hexToByteArray(), password, logn, ksb)
|
||||
|
||||
fun encrypt(
|
||||
secretKey: ByteArray,
|
||||
@ -104,9 +103,12 @@ class Nip49(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
// byte[] ad, long adLen,
|
||||
// byte[] nSec, byte[] nPub, byte[] k
|
||||
lazySodium.cryptoAeadXChaCha20Poly1305IetfEncrypt(
|
||||
ciphertext, longArrayOf(48),
|
||||
secretKey, secretKey.size.toLong(),
|
||||
byteArrayOf(ksb), 1,
|
||||
ciphertext,
|
||||
longArrayOf(48),
|
||||
secretKey,
|
||||
secretKey.size.toLong(),
|
||||
byteArrayOf(ksb),
|
||||
1,
|
||||
key,
|
||||
nonce,
|
||||
key,
|
||||
@ -163,8 +165,8 @@ class Nip49(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
}
|
||||
|
||||
// ln(n.toDouble()).toInt().toByte(),
|
||||
fun encodePayload(): String {
|
||||
return Bech32.encodeBytes(
|
||||
fun encodePayload(): String =
|
||||
Bech32.encodeBytes(
|
||||
hrp = "ncryptsec",
|
||||
byteArrayOf(
|
||||
version,
|
||||
@ -173,5 +175,4 @@ class Nip49(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
Bech32.Encoding.Bech32,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// Copyright (C) 2011 - Will Glozer. All rights reserved.
|
||||
|
||||
package com.vitorpamplona.quartz.crypto;
|
||||
package com.vitorpamplona.quartz.crypto.nip49;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
@ -1,6 +1,6 @@
|
||||
// Copyright (C) 2011 - Will Glozer. All rights reserved.
|
||||
|
||||
package com.vitorpamplona.quartz.crypto;
|
||||
package com.vitorpamplona.quartz.crypto.nip49;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import java.security.GeneralSecurityException;
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.vitorpamplona.quartz.crypto;
|
||||
package com.vitorpamplona.quartz.crypto.nip49;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.spec.KeySpec;
|
@ -31,7 +31,9 @@ import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
|
||||
class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toHexKey()) {
|
||||
class NostrSignerInternal(
|
||||
val keyPair: KeyPair,
|
||||
) : NostrSigner(keyPair.pubKey.toHexKey()) {
|
||||
override fun <T : Event> sign(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
@ -52,10 +54,9 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
|
||||
fun isUnsignedPrivateEvent(
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
): Boolean {
|
||||
return kind == LnZapRequestEvent.KIND &&
|
||||
): Boolean =
|
||||
kind == LnZapRequestEvent.KIND &&
|
||||
tags.any { t -> t.size > 1 && t[0] == "anon" && t[1].isBlank() }
|
||||
}
|
||||
|
||||
fun <T : Event> signNormal(
|
||||
createdAt: Long,
|
||||
@ -123,12 +124,12 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
onReady(
|
||||
CryptoUtils.encryptNIP44v2(
|
||||
CryptoUtils
|
||||
.encryptNIP44(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray(),
|
||||
)
|
||||
.encodePayload(),
|
||||
).encodePayload(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -139,12 +140,12 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
CryptoUtils.decryptNIP44(
|
||||
CryptoUtils
|
||||
.decryptNIP44(
|
||||
payload = encryptedContent,
|
||||
privateKey = keyPair.privKey,
|
||||
pubKey = fromPublicKey.hexToByteArray(),
|
||||
)
|
||||
?.let { onReady(it) }
|
||||
)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
private fun <T> signPrivateZap(
|
||||
|
@ -18,10 +18,8 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.containsAny
|
||||
import junit.framework.TestCase
|
||||
import org.junit.Test
|
||||
|
||||
@ -30,8 +28,7 @@ class TimeUtilsTest {
|
||||
"""Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
|
||||
|
||||
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
|
||||
"""
|
||||
.intern()
|
||||
""".intern()
|
||||
|
||||
val atTheMiddle = DualCase("Lorem Ipsum".lowercase(), "Lorem Ipsum".uppercase())
|
||||
val atTheBeginning = DualCase("contrAry".lowercase(), "contrAry".uppercase())
|
Loading…
Reference in New Issue
Block a user