Skip to content

Conversation

@gupnik
Copy link
Collaborator

@gupnik gupnik commented Apr 1, 2025

Description

This PR migrates the implementation in PrivateKey.cpp and PublicKey.cpp to use Rust instead of TrezorCrypto.

How to test

Existing tests continue to work

Types of changes

Refactor

Checklist

  • Create pull request as draft initially, unless its complete.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • If there is a related Issue, mention it in the description.

If you're adding a new blockchain

  • I have read the guidelines for adding a new blockchain.

@gupnik gupnik marked this pull request as ready for review April 2, 2025 10:36
@gupnik gupnik requested a review from satoshiotomakan as a code owner April 2, 2025 10:36
@github-actions
Copy link

github-actions bot commented Apr 2, 2025

Binary size comparison

➡️ aarch64-apple-ios:

- 13.93 MB
+ 13.96 MB 	 +40 KB

➡️ aarch64-apple-ios-sim:

- 13.93 MB
+ 13.97 MB 	 +40 KB

➡️ aarch64-linux-android:

- 18.35 MB
+ 18.41 MB 	 +70 KB

➡️ armv7-linux-androideabi:

- 15.36 MB
+ 15.41 MB 	 +47 KB

➡️ wasm32-unknown-emscripten:

- 13.06 MB
+ 13.11 MB 	 +53 KB

Copy link
Collaborator

@satoshiotomakan satoshiotomakan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add extensive tests to tw_keypair/src/zilliqa_schnorr/mod.rs. Take a look at other curve implementations, it's highly important to cover it well as it's critical section for us

PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const {
auto* pubkey = Rust::tw_private_key_get_public_key_by_type(_impl.get(), static_cast<uint32_t>(type));
if (pubkey == nullptr) {
return PublicKey(Data(), type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not good to return a PublicKey initialized with an empty data in case of an error, even if PublicKey(Data(), type) constructor throws an exception.
Please throw an exception directly from this function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not good to return a PublicKey initialized with an empty data in case of an error, even if PublicKey(Data(), type) constructor throws an exception. Please throw an exception directly from this function

For backward compatibility:

PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's legacy, and we should improve it. If either PrivateKey or PublicKey instances created, it means that they are valid, and can be used later in the code.
I really hope this line is never reached in prod

return PublicKey(result, type);

Comment on lines 128 to 129
// graphene adds 31 to the recovery id
result[0] += 31;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this logic to Rust

PublicKey::PublicKey(std::shared_ptr<TW::Rust::TWPublicKey> _impl)
: _impl(_impl) {
auto pubkeyData = Rust::tw_public_key_data(_impl.get());
bytes = Data(pubkeyData.data, pubkeyData.data + pubkeyData.size);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think bytes filed should be replaced with bytes() method to avoid confusion as the pubkey bytes also stored in Rust's PublicKey

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think bytes filed should be replaced with bytes() method to avoid confusion as the pubkey bytes also stored in Rust's PublicKey

This is for consistency to avoid breaking the API further.

Comment on lines 125 to 127
if (messageDigest.size() < PrivateKey::_size) {
throw std::invalid_argument("digest too short");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move all these conditions above to Rust


// TODO uncomment when nist256p1 Rust implementation is enabled.
// EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex);
EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected 00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac instead. Can you please check if all correct?
I could be wrong with the comment by a mistake, but still better to validate

@trustwallet trustwallet deleted a comment from Ruyeduardo Apr 3, 2025
Copy link
Collaborator

@satoshiotomakan satoshiotomakan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of changes please

}

PublicKey::PublicKey(std::shared_ptr<TW::Rust::TWPublicKey> _impl)
: _impl(_impl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::move(_impl)

}
}

pub fn recover_from_signature(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secp256k1SignatureSize is the same as nist256p1 signature length.
For the reference, we have nist256p1::PublicKey::recover function, so I think we can expose it to FFI too

type SigningMessage = Vec<u8>;
type Signature = Signature;

fn sign(&self, message: Self::SigningMessage) -> KeyPairResult<Self::Signature> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I mean a link to zilliqa_rs crate

bytes = data;
_curve = curve;
auto* privkey = Rust::tw_private_key_create_with_data(data.data(), data.size(), static_cast<uint32_t>(curve));
_impl = privkey ? Rust::wrapTWPrivateKey(privkey) : nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed yet. Let's throw an exception if privkey is null

append(bytes, chainCode2);
_curve = curve;
auto* privkey = Rust::tw_private_key_create_with_data(bytes.data(), bytes.size(), static_cast<uint32_t>(curve));
_impl = privkey ? Rust::wrapTWPrivateKey(privkey) : nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, let's throw an exception

PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const {
auto* pubkey = Rust::tw_private_key_get_public_key_by_type(_impl.get(), static_cast<uint32_t>(type));
if (pubkey == nullptr) {
return PublicKey(Data(), type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's legacy, and we should improve it. If either PrivateKey or PublicKey instances created, it means that they are valid, and can be used later in the code.
I really hope this line is never reached in prod

return PublicKey(result, type);

Comment on lines 55 to 62
auto* pubkey = Rust::tw_public_key_create_with_data(data.data(), data.size(), static_cast<uint32_t>(type));
if (pubkey != nullptr) {
_impl = Rust::wrapTWPublicKey(pubkey);
Rust::CByteArrayWrapper pubkeyData = Rust::tw_public_key_data(_impl.get());
bytes = pubkeyData.data;
} else {
_impl = nullptr;
bytes = data;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please throw an exception if couldn't create the PublicKey

Copy link
Collaborator

@satoshiotomakan satoshiotomakan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more important change

Comment on lines 92 to 101
let bytes = if bytes.len() == Self::SIZE {
// For a regular private key, replicate it 6 times to match the Cardano extended format
let mut extended_bytes = Vec::with_capacity(Self::CARDANO_SIZE);
for _ in 0..6 {
extended_bytes.extend_from_slice(&bytes[Self::KEY_RANGE]);
}
extended_bytes
} else {
bytes.to_vec()
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix as discussed DM

Copy link
Collaborator

@satoshiotomakan satoshiotomakan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

dummyKey, dummyKey, dummyKey, TWCurveED25519);
auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
EXPECT_ANY_THROW(new AddressV3(publicKey));
dummyKey, dummyKey, dummyKey, TWCurveED25519));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this test pass if we replace TWCurveED25519ExtendedCardano?

dummyKey, dummyKey, dummyKey, TWCurveED25519);
auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
EXPECT_ANY_THROW(new AddressV2(publicKey));
dummyKey, dummyKey, dummyKey, TWCurveED25519));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question

@satoshiotomakan satoshiotomakan merged commit 6b46c47 into dev Apr 4, 2025
14 checks passed
@satoshiotomakan satoshiotomakan deleted the gupnik/ffi-rust-migration branch April 4, 2025 12:04
@gupnik gupnik mentioned this pull request Apr 4, 2025
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants