From 0c46ed7b20a3567540d34d615f072d23588b0d78 Mon Sep 17 00:00:00 2001 From: sergiupuhalschi-rdx <164897324+sergiupuhalschi-rdx@users.noreply.github.com> Date: Thu, 9 May 2024 15:09:12 +0300 Subject: [PATCH] Update notary private key implementation (#128) * Update notary private key implementation * Address PR comments * Add exception annotation to sign method * Version bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- .../sargon/extensions/Curve25519SecretKey.kt | 60 +++++++++++++++++++ .../sargon/extensions/NotaryPrivateKey.kt | 50 ---------------- .../sargon/samples/Exactly32BytesSample.kt | 14 +++++ ...eKeyTest.kt => Curve25519SecretKeyTest.kt} | 32 ++++++---- src/core/types/keys/ed25519/public_key.rs | 24 +++----- .../low_level/notary_signature_uniffi_fn.rs | 35 ++++++++--- 8 files changed, 132 insertions(+), 87 deletions(-) create mode 100644 jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Curve25519SecretKey.kt delete mode 100644 jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NotaryPrivateKey.kt create mode 100644 jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/Exactly32BytesSample.kt rename jvm/sargon-android/src/test/java/com/radixdlt/sargon/{NotaryPrivateKeyTest.kt => Curve25519SecretKeyTest.kt} (50%) diff --git a/Cargo.lock b/Cargo.lock index f75528a60..eb12de8ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2529,7 +2529,7 @@ dependencies = [ [[package]] name = "sargon" -version = "0.7.3" +version = "0.7.8" dependencies = [ "actix-rt", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index cdefbe304..7aeb93445 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon" -version = "0.7.3" +version = "0.7.8" edition = "2021" build = "build.rs" diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Curve25519SecretKey.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Curve25519SecretKey.kt new file mode 100644 index 000000000..4158310bf --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/Curve25519SecretKey.kt @@ -0,0 +1,60 @@ +package com.radixdlt.sargon.extensions + +import com.radixdlt.sargon.Ed25519Signature +import com.radixdlt.sargon.Exactly32Bytes +import com.radixdlt.sargon.Hash +import com.radixdlt.sargon.NotarySignature +import com.radixdlt.sargon.PublicKey +import com.radixdlt.sargon.SignedIntentHash +import com.radixdlt.sargon.androidNotarizeHashWithPrivateKeyBytes +import com.radixdlt.sargon.androidSecretKeyGetPublicKeyFromPrivateKeyBytes +import com.radixdlt.sargon.androidSignHashWithPrivateKeyBytes +import com.radixdlt.sargon.annotation.KoverIgnore + +class Curve25519SecretKey( + private val exactly32Bytes: Exactly32Bytes +) { + + companion object { + fun secureRandom(): Curve25519SecretKey = + Curve25519SecretKey(exactly32Bytes = Exactly32Bytes.init(randomBagOfBytes(32))) + } + + @Throws(SargonException::class) + fun notarize(signedIntentHash: SignedIntentHash): NotarySignature = + androidNotarizeHashWithPrivateKeyBytes( + privateKeyBytes = exactly32Bytes, + signedIntentHash = signedIntentHash + ) + + @Throws(SargonException::class) + fun sign(hash: Hash): Ed25519Signature = + androidSignHashWithPrivateKeyBytes( + privateKeyBytes = exactly32Bytes, + hash = hash + ) + + @Throws(SargonException::class) + fun toPublicKey(): PublicKey.Ed25519 = + androidSecretKeyGetPublicKeyFromPrivateKeyBytes(privateKeyBytes = exactly32Bytes).asGeneral() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Curve25519SecretKey + + return exactly32Bytes == other.exactly32Bytes + } + + override fun hashCode(): Int { + return exactly32Bytes.hashCode() + } + + @KoverIgnore + override fun toString(): String { + return "Curve25519SecretKey(exactly32Bytes=$exactly32Bytes)" + } + + +} \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NotaryPrivateKey.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NotaryPrivateKey.kt deleted file mode 100644 index a5e9853ab..000000000 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/NotaryPrivateKey.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.radixdlt.sargon.extensions - -import com.radixdlt.sargon.Entropy32Bytes -import com.radixdlt.sargon.NotarySignature -import com.radixdlt.sargon.PublicKey -import com.radixdlt.sargon.SignedIntentHash -import com.radixdlt.sargon.androidNotarizeHashWithPrivateKeyBytes -import com.radixdlt.sargon.androidNotaryKeyGetPublicKeyFromPrivateKeyBytes -import com.radixdlt.sargon.annotation.KoverIgnore - -class NotaryPrivateKey internal constructor( - private val entropy32Bytes: Entropy32Bytes -) { - - companion object { - fun secureRandom(): NotaryPrivateKey = - NotaryPrivateKey(entropy32Bytes = Entropy32Bytes.random()) - } - - @Throws(SargonException::class) - fun notarize(signedIntentHash: SignedIntentHash): NotarySignature = - androidNotarizeHashWithPrivateKeyBytes( - privateKeyBytes = entropy32Bytes, - signedIntentHash = signedIntentHash - ) - - @Throws(SargonException::class) - fun toPublicKey(): PublicKey.Ed25519 = - androidNotaryKeyGetPublicKeyFromPrivateKeyBytes(privateKeyBytes = entropy32Bytes).asGeneral() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as NotaryPrivateKey - - return entropy32Bytes == other.entropy32Bytes - } - - override fun hashCode(): Int { - return entropy32Bytes.hashCode() - } - - @KoverIgnore - override fun toString(): String { - return "NotaryPrivateKey(entropy32Bytes=$entropy32Bytes)" - } - - -} \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/Exactly32BytesSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/Exactly32BytesSample.kt new file mode 100644 index 000000000..f082387bd --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/Exactly32BytesSample.kt @@ -0,0 +1,14 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.Exactly32Bytes +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.newExactly32BytesSample +import com.radixdlt.sargon.newExactly32BytesSampleOther + +@UsesSampleValues +val Exactly32Bytes.Companion.sample: Sample + get() = object : Sample { + override fun invoke(): Exactly32Bytes = newExactly32BytesSample() + + override fun other(): Exactly32Bytes = newExactly32BytesSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/NotaryPrivateKeyTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/Curve25519SecretKeyTest.kt similarity index 50% rename from jvm/sargon-android/src/test/java/com/radixdlt/sargon/NotaryPrivateKeyTest.kt rename to jvm/sargon-android/src/test/java/com/radixdlt/sargon/Curve25519SecretKeyTest.kt index d22e31b5c..c30b765a0 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/NotaryPrivateKeyTest.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/Curve25519SecretKeyTest.kt @@ -1,23 +1,22 @@ package com.radixdlt.sargon -import com.radixdlt.sargon.extensions.NotaryPrivateKey +import com.radixdlt.sargon.extensions.Curve25519SecretKey import com.radixdlt.sargon.extensions.hex import com.radixdlt.sargon.extensions.signature import com.radixdlt.sargon.extensions.string import com.radixdlt.sargon.samples.sample -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test -class NotaryPrivateKeyTest { +class Curve25519SecretKeyTest { @Test fun testRandomness() { val privateKeysCount = 100 val privateKeys = List(privateKeysCount) { - NotaryPrivateKey.secureRandom() + Curve25519SecretKey.secureRandom() } assertEquals( @@ -28,8 +27,8 @@ class NotaryPrivateKeyTest { @Test fun testEquality() { - val thisNotary = NotaryPrivateKey(Entropy32Bytes.sample()) - val otherNotary = NotaryPrivateKey(Entropy32Bytes.sample()) + val thisNotary = Curve25519SecretKey(Exactly32Bytes.sample()) + val otherNotary = Curve25519SecretKey(Exactly32Bytes.sample()) assertEquals(thisNotary, otherNotary) assertEquals(thisNotary, thisNotary) @@ -38,21 +37,32 @@ class NotaryPrivateKeyTest { @Test fun testNotarize() { - val sut = NotaryPrivateKey(Entropy32Bytes.sample()) + val sut = Curve25519SecretKey(Exactly32Bytes.sample()) val result = sut.notarize(SignedIntentHash.sample()) assertEquals( - "08c6129fa6938a31e38dfe94effdce8f1a4021e22cf62344830d83dc45f32de0e3d112794c369450e62d245a17b18835f40c639033fbb4b1f975ad0ad71dbf0a", + "1a30347a04bc5d746b35a568330ba69c9b6ac60ef72d0a28cb63e25680e64908557d85a0e864c423ce782b5f43da3002c301045c6385b40cb013374045392404", result.signature.string ) } + @Test + fun testSign() { + val sut = Curve25519SecretKey(Exactly32Bytes.sample()) + val result = sut.sign(Hash.sample()) + + assertEquals( + "1a30347a04bc5d746b35a568330ba69c9b6ac60ef72d0a28cb63e25680e64908557d85a0e864c423ce782b5f43da3002c301045c6385b40cb013374045392404", + result.bytes.hex + ) + } + @Test fun testGetPublicKey() { - val sut = NotaryPrivateKey(Entropy32Bytes.sample()) + val sut = Curve25519SecretKey(Exactly32Bytes.sample()) - Assertions.assertEquals( - "248acbdbaf9e050196de704bea2d68770e519150d103b587dae2d9cad53dd930", + assertEquals( + "3b321b74bdcb169f7260c60592bbb63d9b4d629424a0c58aff4640a75f0a2b06", sut.toPublicKey().hex ) } diff --git a/src/core/types/keys/ed25519/public_key.rs b/src/core/types/keys/ed25519/public_key.rs index 08595437c..753ed68a6 100644 --- a/src/core/types/keys/ed25519/public_key.rs +++ b/src/core/types/keys/ed25519/public_key.rs @@ -70,19 +70,11 @@ pub fn new_ed25519_public_key_sample_other() -> Ed25519PublicKey { } #[uniffi::export] -pub fn android_notary_key_get_public_key_from_private_key_bytes( - private_key_bytes: Entropy32Bytes, +pub fn android_secret_key_get_public_key_from_private_key_bytes( + private_key_bytes: Exactly32Bytes, ) -> Result { - let mut private_key_bytes = private_key_bytes; - - let private_key = - Ed25519PrivateKey::from_bytes(private_key_bytes.to_bytes())?; - let public_key = private_key.public_key(); - - private_key_bytes.zeroize(); - // private_key.zeroize() // FIXME: Zeroize once RET has added Zeroize to PrivateKeys - - Ok(public_key) + Ed25519PrivateKey::try_from(private_key_bytes.as_ref()) + .map(|k| k.public_key()) } /// Encodes the `Ed25519PublicKey` to a hexadecimal string, lowercased, without any `0x` prefix, e.g. @@ -410,15 +402,15 @@ mod uniffi_tests { } #[test] - fn test_android_notary_key_get_public_key_from_private_key_bytes() { - let entropy = Entropy32Bytes::sample(); + fn test_android_secret_key_get_public_key_from_private_key_bytes() { + let entropy = Exactly32Bytes::sample(); let sut = - android_notary_key_get_public_key_from_private_key_bytes(entropy) + android_secret_key_get_public_key_from_private_key_bytes(entropy) .unwrap(); assert_eq!( - "248acbdbaf9e050196de704bea2d68770e519150d103b587dae2d9cad53dd930", + "3b321b74bdcb169f7260c60592bbb63d9b4d629424a0c58aff4640a75f0a2b06", sut.to_hex() ) } diff --git a/src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs b/src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs index 2344b4f42..46f424431 100644 --- a/src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs +++ b/src/wrapped_radix_engine_toolkit/low_level/notary_signature_uniffi_fn.rs @@ -24,22 +24,27 @@ pub fn notary_signature_get_signature( #[uniffi::export] pub fn android_notarize_hash_with_private_key_bytes( - private_key_bytes: Entropy32Bytes, + private_key_bytes: Exactly32Bytes, signed_intent_hash: &SignedIntentHash, ) -> Result { - let mut private_key_bytes = private_key_bytes; - let ed25519_private_key = - Ed25519PrivateKey::from_bytes(private_key_bytes.to_bytes())?; + Ed25519PrivateKey::try_from(private_key_bytes.as_ref())?; let private_key = PrivateKey::from(ed25519_private_key); let signature = private_key.notarize_hash(signed_intent_hash); - private_key_bytes.zeroize(); - // private_key.zeroize() // FIXME: Zeroize once RET has added Zeroize to PrivateKeys Ok(signature) } +#[uniffi::export] +pub fn android_sign_hash_with_private_key_bytes( + private_key_bytes: Exactly32Bytes, + hash: &Hash, +) -> Result { + Ed25519PrivateKey::try_from(private_key_bytes.as_ref()) + .map(|pk| pk.sign(hash)) +} + #[cfg(test)] mod tests { use super::*; @@ -74,13 +79,27 @@ mod tests { #[test] fn test_android_notarize_hash_with_private_key_bytes() { let sut = android_notarize_hash_with_private_key_bytes( - Entropy32Bytes::sample(), + Exactly32Bytes::sample(), &SignedIntentHash::sample(), ) .unwrap(); assert_eq!( - "08c6129fa6938a31e38dfe94effdce8f1a4021e22cf62344830d83dc45f32de0e3d112794c369450e62d245a17b18835f40c639033fbb4b1f975ad0ad71dbf0a", + "1a30347a04bc5d746b35a568330ba69c9b6ac60ef72d0a28cb63e25680e64908557d85a0e864c423ce782b5f43da3002c301045c6385b40cb013374045392404", + sut.to_string() + ) + } + + #[test] + fn test_android_sign_hash_with_private_key_bytes() { + let sut = android_sign_hash_with_private_key_bytes( + Exactly32Bytes::sample(), + &Hash::sample(), + ) + .unwrap(); + + assert_eq!( + "1a30347a04bc5d746b35a568330ba69c9b6ac60ef72d0a28cb63e25680e64908557d85a0e864c423ce782b5f43da3002c301045c6385b40cb013374045392404", sut.to_string() ) }