From a05c01a44251254749801ebeb0fc4f9623c44f10 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 7 Aug 2024 23:51:10 +0700 Subject: [PATCH] [TON]: Add `CryptoBoxSecretKey` FFIs to create from Bytes, get Bytes (#3977) * feat(ton): Add Rust FFI to manipulate the secret's data * feat(ton): Add C++ FFI to manipulate the secret's data * feat(ton): Add Android, iOS tests * [CI] Trigger CI --- .../core/app/utils/TestCryptoBox.kt | 18 ++++++++ .../TrustWalletCore/TWCryptoBoxSecretKey.h | 22 +++++++++ .../src/ffi/crypto_box/secret_key.rs | 46 ++++++++++++++++++- .../src/nacl_crypto_box/secret_key.rs | 14 ++++++ rust/tw_keypair/tests/crypto_box_ffi_tests.rs | 19 +++++++- src/CryptoBox.cpp | 19 ++++++++ src/CryptoBox.h | 12 +++++ src/interface/TWCryptoBoxSecretKey.cpp | 19 ++++++++ swift/Tests/CryptoBoxTests.swift | 14 ++++++ tests/interface/TWCryptoBoxTests.cpp | 10 ++++ 10 files changed, 191 insertions(+), 2 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt index 60433329350..4b5bc226ce9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt @@ -26,4 +26,22 @@ class TestCryptoBox { val decrypted = CryptoBox.decryptEasy(otherSecret, myPubkey, encrypted) assertEquals(decrypted.toString(Charsets.UTF_8), message) } + + @Test + fun testSecretKeyFromToBytes() { + val secretBytesHex = "0xdd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4" + val secretBytes = secretBytesHex.toHexByteArray() + assert(CryptoBoxSecretKey.isValid(secretBytes)) + val secret = CryptoBoxSecretKey(secretBytes) + assertEquals(secret.data().toHex(), secretBytesHex) + } + + @Test + fun testPublicKeyFromToBytes() { + val publicBytesHex = "0xafccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747" + val publicBytes = publicBytesHex.toHexByteArray() + assert(CryptoBoxPublicKey.isValid(publicBytes)) + val pubkey = CryptoBoxPublicKey(publicBytes) + assertEquals(pubkey.data().toHex(), publicBytesHex) + } } \ No newline at end of file diff --git a/include/TrustWalletCore/TWCryptoBoxSecretKey.h b/include/TrustWalletCore/TWCryptoBoxSecretKey.h index f2d5d10d5b1..f93ad92eb56 100644 --- a/include/TrustWalletCore/TWCryptoBoxSecretKey.h +++ b/include/TrustWalletCore/TWCryptoBoxSecretKey.h @@ -15,6 +15,13 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWCryptoBoxSecretKey; +/// Determines if the given secret key is valid or not. +/// +/// \param data *non-null* byte array. +/// \return true if the secret key is valid, false otherwise. +TW_EXPORT_STATIC_METHOD +bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data); + /// Create a random secret key. /// /// \note Should be deleted with \tw_crypto_box_secret_key_delete. @@ -22,6 +29,14 @@ struct TWCryptoBoxSecretKey; TW_EXPORT_STATIC_METHOD struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate(); +/// Create a `crypto_box` secret key with the given block of data. +/// +/// \param data *non-null* byte array. Expected to have 32 bytes. +/// \note Should be deleted with \tw_crypto_box_secret_key_delete. +/// \return Nullable pointer to Secret Key. +TW_EXPORT_STATIC_METHOD +struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data); + /// Delete the given secret `key`. /// /// \param key *non-null* pointer to secret key. @@ -35,4 +50,11 @@ void TWCryptoBoxSecretKeyDelete(struct TWCryptoBoxSecretKey* _Nonnull key); TW_EXPORT_METHOD struct TWCryptoBoxPublicKey* _Nonnull TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBoxSecretKey* _Nonnull key); +/// Returns the raw data of the given secret-key. +/// +/// \param secretKey *non-null* pointer to a secret key. +/// \return C-compatible result with a C-compatible byte array. +TW_EXPORT_PROPERTY +TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey); + TW_EXTERN_C_END diff --git a/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs b/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs index b0582a016f1..c55503b9f88 100644 --- a/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs +++ b/rust/tw_keypair/src/ffi/crypto_box/secret_key.rs @@ -6,14 +6,26 @@ use crate::ffi::crypto_box::public_key::TWCryptoBoxPublicKey; use crate::nacl_crypto_box::secret_key::SecretKey; +use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; -use tw_misc::try_or_else; +use tw_misc::traits::ToBytesZeroizing; +use tw_misc::{try_or_else, try_or_false}; /// Secret key used in `crypto_box` cryptography. pub struct TWCryptoBoxSecretKey(pub(crate) SecretKey); impl RawPtrTrait for TWCryptoBoxSecretKey {} +/// Determines if the given secret key is valid or not. +/// +/// \param data *non-null* byte array. +/// \return true if the secret key is valid, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn tw_crypto_box_secret_key_is_valid(data: *const TWData) -> bool { + let bytes_ref = try_or_false!(TWData::from_ptr_as_ref(data)); + SecretKey::try_from(bytes_ref.as_slice()).is_ok() +} + /// Create a random secret key. /// /// \note Should be deleted with \tw_crypto_box_secret_key_delete. @@ -23,6 +35,23 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_create() -> *mut TWCryptoBoxSe TWCryptoBoxSecretKey(SecretKey::random()).into_ptr() } +/// Create a `crypto_box` secret key with the given block of data. +/// +/// \param data *non-null* byte array. Expected to have 32 bytes. +/// \note Should be deleted with \tw_crypto_box_secret_key_delete. +/// \return Nullable pointer to Secret Key. +#[no_mangle] +pub unsafe extern "C" fn tw_crypto_box_secret_key_create_with_data( + data: *const TWData, +) -> *mut TWCryptoBoxSecretKey { + let bytes_ref = try_or_else!(TWData::from_ptr_as_ref(data), std::ptr::null_mut); + let secret = try_or_else!( + SecretKey::try_from(bytes_ref.as_slice()), + std::ptr::null_mut + ); + TWCryptoBoxSecretKey(secret).into_ptr() +} + /// Delete the given secret `key`. /// /// \param key *non-null* pointer to secret key. @@ -46,3 +75,18 @@ pub unsafe extern "C" fn tw_crypto_box_secret_key_get_public_key( ); TWCryptoBoxPublicKey(secret.0.public_key()).into_ptr() } + +/// Returns the raw data of a given secret-key. +/// +/// \param secret_key *non-null* pointer to a secret key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_crypto_box_secret_key_data( + secret_key: *const TWCryptoBoxSecretKey, +) -> *mut TWData { + let secret_ref = try_or_else!( + TWCryptoBoxSecretKey::from_ptr_as_ref(secret_key), + std::ptr::null_mut + ); + TWData::from(secret_ref.0.to_zeroizing_vec().to_vec()).into_ptr() +} diff --git a/rust/tw_keypair/src/nacl_crypto_box/secret_key.rs b/rust/tw_keypair/src/nacl_crypto_box/secret_key.rs index 7a8bb0054f0..1eca9f6f6bb 100644 --- a/rust/tw_keypair/src/nacl_crypto_box/secret_key.rs +++ b/rust/tw_keypair/src/nacl_crypto_box/secret_key.rs @@ -5,6 +5,9 @@ use crate::nacl_crypto_box::public_key::PublicKey; use crate::rand::{CryptoRngCore, OsRng}; use crate::KeyPairError; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroizing; pub struct SecretKey { secret: crypto_box::SecretKey, @@ -27,6 +30,17 @@ impl SecretKey { pub(crate) fn inner(&self) -> &crypto_box::SecretKey { &self.secret } + + pub fn to_vec(&self) -> Zeroizing { + Zeroizing::new(H256::from(self.secret.to_bytes())) + } +} + +impl ToBytesZeroizing for SecretKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let bytes = Zeroizing::new(self.secret.to_bytes()); + Zeroizing::new(bytes.to_vec()) + } } impl<'a> TryFrom<&'a [u8]> for SecretKey { diff --git a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs index fd2a3308ae7..bf98b67b07e 100644 --- a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs +++ b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs @@ -8,7 +8,9 @@ use tw_keypair::ffi::crypto_box::public_key::{ tw_crypto_box_public_key_is_valid, }; use tw_keypair::ffi::crypto_box::secret_key::{ - tw_crypto_box_secret_key_create, tw_crypto_box_secret_key_get_public_key, + tw_crypto_box_secret_key_create, tw_crypto_box_secret_key_create_with_data, + tw_crypto_box_secret_key_data, tw_crypto_box_secret_key_get_public_key, + tw_crypto_box_secret_key_is_valid, }; use tw_keypair::ffi::crypto_box::{tw_crypto_box_decrypt_easy, tw_crypto_box_encrypt_easy}; use tw_keypair::test_utils::tw_crypto_box_helpers::{ @@ -86,3 +88,18 @@ fn test_public_key() { let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_public_key_data(pubkey.ptr()) }); assert_eq!(actual_data.to_vec().unwrap(), pubkey_bytes); } + +#[test] +fn test_secret_key() { + let secret_bytes = "dd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4" + .decode_hex() + .unwrap(); + + let secret_data = TWDataHelper::create(secret_bytes.clone()); + assert!(unsafe { tw_crypto_box_secret_key_is_valid(secret_data.ptr()) }); + + let pubkey = + TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_create_with_data(secret_data.ptr()) }); + let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_secret_key_data(pubkey.ptr()) }); + assert_eq!(actual_data.to_vec().unwrap(), secret_bytes); +} diff --git a/src/CryptoBox.cpp b/src/CryptoBox.cpp index 89f77f72e68..f0d5218559f 100644 --- a/src/CryptoBox.cpp +++ b/src/CryptoBox.cpp @@ -30,11 +30,30 @@ SecretKey::SecretKey() { impl = SecretKeyPtr(secretKey, Rust::tw_crypto_box_secret_key_delete); } +bool SecretKey::isValid(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + return Rust::tw_crypto_box_secret_key_is_valid(data.get()); +} + +std::optional SecretKey::fromBytes(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + if (!Rust::tw_crypto_box_secret_key_is_valid(data.get())) { + return std::nullopt; + } + auto* secretKey = Rust::tw_crypto_box_secret_key_create_with_data(data.get()); + return SecretKey(SecretKeyPtr(secretKey, Rust::tw_crypto_box_secret_key_delete)); +} + PublicKey SecretKey::getPublicKey() const noexcept { auto* publicKey = Rust::tw_crypto_box_secret_key_get_public_key(impl.get()); return PublicKey(PublicKeyPtr(publicKey, Rust::tw_crypto_box_public_key_delete)); } +Data SecretKey::getData() const { + Rust::TWDataWrapper data = Rust::tw_crypto_box_secret_key_data(impl.get()); + return data.toDataOrDefault(); +} + Data encryptEasy(const SecretKey& mySecret, const PublicKey& otherPubkey, const Data& message) { Rust::TWDataWrapper messageData = message; Rust::TWDataWrapper encrypted = Rust::tw_crypto_box_encrypt_easy(mySecret.impl.get(), otherPubkey.impl.get(), messageData.get()); diff --git a/src/CryptoBox.h b/src/CryptoBox.h index 474179965bd..ce00f9c2404 100644 --- a/src/CryptoBox.h +++ b/src/CryptoBox.h @@ -34,9 +34,21 @@ class SecretKey { /// Create a random secret key. SecretKey(); + explicit SecretKey(SecretKeyPtr ptr): impl(std::move(ptr)) { + } + + /// Determines if the given secret key is valid or not. + static bool isValid(const Data& bytes); + + /// Create a `crypto_box` secret key with the given block of data. + static std::optional fromBytes(const Data& bytes); + /// Returns the public key associated with the given `key`. PublicKey getPublicKey() const noexcept; + /// Returns the raw data of the given secret-key. + Data getData() const; + SecretKeyPtr impl; }; diff --git a/src/interface/TWCryptoBoxSecretKey.cpp b/src/interface/TWCryptoBoxSecretKey.cpp index 358445bd4a3..0a16fa6a06f 100644 --- a/src/interface/TWCryptoBoxSecretKey.cpp +++ b/src/interface/TWCryptoBoxSecretKey.cpp @@ -7,6 +7,20 @@ using namespace TW; +bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + return CryptoBox::SecretKey::isValid(bytes); +} + +struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + auto secretKey = CryptoBox::SecretKey::fromBytes(bytes); + if (!secretKey) { + return nullptr; + } + return new TWCryptoBoxSecretKey { secretKey.value() }; +} + struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate() { CryptoBox::SecretKey secretKey; return new TWCryptoBoxSecretKey { secretKey }; @@ -20,3 +34,8 @@ struct TWCryptoBoxPublicKey* TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBox auto publicKey = key->impl.getPublicKey(); return new TWCryptoBoxPublicKey { publicKey }; } + +TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey) { + auto bytes = secretKey->impl.getData(); + return TWDataCreateWithBytes(bytes.data(), bytes.size()); +} diff --git a/swift/Tests/CryptoBoxTests.swift b/swift/Tests/CryptoBoxTests.swift index 3e929c78109..03632513e81 100644 --- a/swift/Tests/CryptoBoxTests.swift +++ b/swift/Tests/CryptoBoxTests.swift @@ -21,4 +21,18 @@ class CryptoBoxTests: XCTestCase { let decryptedStr = String(bytes: decrypted, encoding: .utf8) XCTAssertEqual(decryptedStr, message) } + + func testSecretKeyFromToBytes() { + let secretBytes = Data(hexString: "dd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4")! + XCTAssert(CryptoBoxSecretKey.isValid(data: secretBytes)) + let secret = CryptoBoxSecretKey(data: secretBytes) + XCTAssertEqual(secret?.data, secretBytes) + } + + func testPublicKeyFromToBytes() { + let publicBytes = Data(hexString: "afccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747")! + XCTAssert(CryptoBoxPublicKey.isValid(data: publicBytes)) + let pubkey = CryptoBoxPublicKey(data: publicBytes) + XCTAssertEqual(pubkey?.data, publicBytes) + } } diff --git a/tests/interface/TWCryptoBoxTests.cpp b/tests/interface/TWCryptoBoxTests.cpp index 0ae626b13e6..cb9a3335c54 100644 --- a/tests/interface/TWCryptoBoxTests.cpp +++ b/tests/interface/TWCryptoBoxTests.cpp @@ -39,6 +39,16 @@ TEST(TWCryptoBox, PublicKeyWithData) { assertHexEqual(actualBytes, pubkeyBytesHex); } +TEST(TWCryptoBox, SecretKeyWithData) { + auto secretBytesHex = "dd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4"; + auto secretBytes = DATA(secretBytesHex); + + ASSERT_TRUE(TWCryptoBoxSecretKeyIsValid(secretBytes.get())); + const auto publicKey = WRAP(TWCryptoBoxSecretKey, TWCryptoBoxSecretKeyCreateWithData(secretBytes.get())); + const auto actualBytes = WRAPD(TWCryptoBoxSecretKeyData(publicKey.get())); + assertHexEqual(actualBytes, secretBytesHex); +} + TEST(TWCryptoBox, DecryptEasyError) { auto otherPubkeyBytes = DATA("afccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747");