Skip to content

Commit 3ac6990

Browse files
authored
Message load performance improvement (#133)
* add rust backed libraries * update to use the rust library * remove the performance test * move the signature test into the rust testable classes
1 parent 5454bbb commit 3ac6990

File tree

7 files changed

+108
-49
lines changed

7 files changed

+108
-49
lines changed

library/src/test/java/org/xmtp/android/library/SignatureTest.kt library/src/androidTest/java/org/xmtp/android/library/SignatureTest.kt

+3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.xmtp.android.library
22

3+
import androidx.test.ext.junit.runners.AndroidJUnit4
34
import com.google.protobuf.kotlin.toByteStringUtf8
45
import kotlinx.coroutines.runBlocking
56
import org.junit.Test
7+
import org.junit.runner.RunWith
68
import org.web3j.crypto.Hash
79
import org.xmtp.android.library.messages.PrivateKeyBuilder
810
import org.xmtp.android.library.messages.verify
911

12+
@RunWith(AndroidJUnit4::class)
1013
class SignatureTest {
1114
@Test
1215
fun testVerify() {

library/src/main/java/org/xmtp/android/library/messages/Signature.kt

+11-47
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
11
package org.xmtp.android.library.messages
22

33
import com.google.protobuf.kotlin.toByteString
4-
import org.bouncycastle.jce.ECNamedCurveTable
5-
import org.bouncycastle.jce.ECPointUtil
6-
import org.bouncycastle.jce.provider.BouncyCastleProvider
7-
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec
8-
import org.bouncycastle.jce.spec.ECNamedCurveSpec
9-
import org.bouncycastle.util.Arrays
104
import org.xmtp.android.library.Util
115
import org.xmtp.android.library.toHex
126
import org.xmtp.proto.message.contents.SignatureOuterClass
13-
import java.math.BigInteger
14-
import java.security.KeyFactory
15-
import java.security.interfaces.ECPublicKey
16-
import java.security.spec.ECPublicKeySpec
177

188
typealias Signature = org.xmtp.proto.message.contents.SignatureOuterClass.Signature
199

@@ -61,44 +51,18 @@ val Signature.rawDataWithNormalizedRecovery: ByteArray
6151
return data
6252
}
6353

54+
@OptIn(ExperimentalUnsignedTypes::class)
6455
fun Signature.verify(signedBy: PublicKey, digest: ByteArray): Boolean {
65-
val ecdsaVerify = java.security.Signature.getInstance("SHA256withECDSA", BouncyCastleProvider())
66-
ecdsaVerify.initVerify(getPublicKeyFromBytes(signedBy.secp256K1Uncompressed.bytes.toByteArray()))
67-
ecdsaVerify.update(digest)
68-
return ecdsaVerify.verify(normalizeSignatureForVerification(this.rawDataWithNormalizedRecovery))
69-
}
70-
71-
private fun normalizeSignatureForVerification(signature: ByteArray): ByteArray {
72-
val r: ByteArray = BigInteger(1, Arrays.copyOfRange(signature, 0, 32)).toByteArray()
73-
val s: ByteArray = BigInteger(1, Arrays.copyOfRange(signature, 32, 64)).toByteArray()
74-
val der = ByteArray(6 + r.size + s.size)
75-
der[0] = 0x30 // Tag of signature object
76-
77-
der[1] = (der.size - 2).toByte() // Length of signature object
78-
79-
var o = 2
80-
der[o++] = 0x02 // Tag of ASN1 Integer
81-
82-
der[o++] = r.size.toByte() // Length of first signature part
83-
84-
System.arraycopy(r, 0, der, o, r.size)
85-
o += r.size
86-
der[o++] = 0x02 // Tag of ASN1 Integer
87-
88-
der[o++] = s.size.toByte() // Length of second signature part
89-
90-
System.arraycopy(s, 0, der, o, s.size)
91-
92-
return der
93-
}
94-
95-
private fun getPublicKeyFromBytes(pubKey: ByteArray): java.security.PublicKey {
96-
val spec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
97-
val kf: KeyFactory = KeyFactory.getInstance("ECDSA", BouncyCastleProvider())
98-
val params = ECNamedCurveSpec("secp256k1", spec.curve, spec.g, spec.n)
99-
val point = ECPointUtil.decodePoint(params.curve, pubKey)
100-
val pubKeySpec = ECPublicKeySpec(point, params)
101-
return kf.generatePublic(pubKeySpec) as ECPublicKey
56+
return try {
57+
uniffi.xmtp_dh.verifyK256Sha256(
58+
signedBy.secp256K1Uncompressed.bytes.toByteArray().toUByteArray().toList(),
59+
digest.toUByteArray().toList(),
60+
ecdsaCompact.bytes.toByteArray().toUByteArray().toList(),
61+
ecdsaCompact.recovery.toUByte()
62+
)
63+
} catch (e: Exception) {
64+
false
65+
}
10266
}
10367

10468
fun Signature.ensureWalletSignature(): Signature {

library/src/main/java/xmtp_dh.kt

+94-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ import java.util.concurrent.ConcurrentHashMap
3535
open class RustBuffer : Structure() {
3636
@JvmField
3737
var capacity: Int = 0
38-
3938
@JvmField
4039
var len: Int = 0
41-
4240
@JvmField
4341
var data: Pointer? = null
4442

@@ -407,6 +405,14 @@ internal interface _UniFFILib : Library {
407405
`privateKeyBytes`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus,
408406
): RustBuffer.ByValue
409407

408+
fun uniffi_xmtp_dh_fn_func_verify_k256_sha256(
409+
`signedBy`: RustBuffer.ByValue,
410+
`message`: RustBuffer.ByValue,
411+
`signature`: RustBuffer.ByValue,
412+
`recoveryId`: Byte,
413+
_uniffi_out_err: RustCallStatus,
414+
): Byte
415+
410416
fun ffi_xmtp_dh_rustbuffer_alloc(
411417
`size`: Int, _uniffi_out_err: RustCallStatus,
412418
): RustBuffer.ByValue
@@ -435,6 +441,9 @@ internal interface _UniFFILib : Library {
435441
fun uniffi_xmtp_dh_checksum_func_generate_private_preferences_topic_identifier(
436442
): Short
437443

444+
fun uniffi_xmtp_dh_checksum_func_verify_k256_sha256(
445+
): Short
446+
438447
fun ffi_xmtp_dh_uniffi_contract_version(
439448
): Int
440449

@@ -464,6 +473,9 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
464473
if (lib.uniffi_xmtp_dh_checksum_func_generate_private_preferences_topic_identifier() != 65141.toShort()) {
465474
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
466475
}
476+
if (lib.uniffi_xmtp_dh_checksum_func_verify_k256_sha256() != 45969.toShort()) {
477+
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
478+
}
467479
}
468480

469481
// Public interface members begin here.
@@ -489,6 +501,26 @@ public object FfiConverterUByte : FfiConverter<UByte, Byte> {
489501
}
490502
}
491503

504+
public object FfiConverterBoolean : FfiConverter<Boolean, Byte> {
505+
override fun lift(value: Byte): Boolean {
506+
return value.toInt() != 0
507+
}
508+
509+
override fun read(buf: ByteBuffer): Boolean {
510+
return lift(buf.get())
511+
}
512+
513+
override fun lower(value: Boolean): Byte {
514+
return if (value) 1.toByte() else 0.toByte()
515+
}
516+
517+
override fun allocationSize(value: Boolean) = 1
518+
519+
override fun write(value: Boolean, buf: ByteBuffer) {
520+
buf.put(lower(value))
521+
}
522+
}
523+
492524
public object FfiConverterString : FfiConverter<String, RustBuffer.ByValue> {
493525
// Note: we don't inherit from FfiConverterRustBuffer, because we use a
494526
// special encoding when lowering/lifting. We can use `RustBuffer.len` to
@@ -612,6 +644,44 @@ public object FfiConverterTypeEciesError : FfiConverterRustBuffer<EciesException
612644
}
613645

614646

647+
sealed class VerifyException(message: String) : Exception(message) {
648+
// Each variant is a nested class
649+
// Flat enums carries a string error message, so no special implementation is necessary.
650+
class GenericException(message: String) : VerifyException(message)
651+
652+
653+
companion object ErrorHandler : CallStatusErrorHandler<VerifyException> {
654+
override fun lift(error_buf: RustBuffer.ByValue): VerifyException =
655+
FfiConverterTypeVerifyError.lift(error_buf)
656+
}
657+
}
658+
659+
public object FfiConverterTypeVerifyError : FfiConverterRustBuffer<VerifyException> {
660+
override fun read(buf: ByteBuffer): VerifyException {
661+
662+
return when (buf.getInt()) {
663+
1 -> VerifyException.GenericException(FfiConverterString.read(buf))
664+
else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
665+
}
666+
667+
}
668+
669+
override fun allocationSize(value: VerifyException): Int {
670+
return 4
671+
}
672+
673+
override fun write(value: VerifyException, buf: ByteBuffer) {
674+
when (value) {
675+
is VerifyException.GenericException -> {
676+
buf.putInt(1)
677+
Unit
678+
}
679+
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
680+
}
681+
682+
}
683+
684+
615685
public object FfiConverterSequenceUByte : FfiConverterRustBuffer<List<UByte>> {
616686
override fun read(buf: ByteBuffer): List<UByte> {
617687
val len = buf.getInt()
@@ -698,4 +768,26 @@ fun `generatePrivatePreferencesTopicIdentifier`(`privateKeyBytes`: List<UByte>):
698768
})
699769
}
700770

771+
@Throws(VerifyException::class)
772+
773+
fun `verifyK256Sha256`(
774+
`signedBy`: List<UByte>,
775+
`message`: List<UByte>,
776+
`signature`: List<UByte>,
777+
`recoveryId`: UByte,
778+
): Boolean {
779+
return FfiConverterBoolean.lift(
780+
rustCallWithError(VerifyException) { _status ->
781+
_UniFFILib.INSTANCE.uniffi_xmtp_dh_fn_func_verify_k256_sha256(
782+
FfiConverterSequenceUByte.lower(
783+
`signedBy`
784+
),
785+
FfiConverterSequenceUByte.lower(`message`),
786+
FfiConverterSequenceUByte.lower(`signature`),
787+
FfiConverterUByte.lower(`recoveryId`),
788+
_status
789+
)
790+
})
791+
}
792+
701793

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)