-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Solana]: Add Solana custom message signing (#4134)
* [Solana]: Add Solana UTF8 message signer * [Solana]: Add `TWMessageSigner` - a generic custom message signer for any chain * [Solana]: Add Android, iOS tests * [Solana]: Minor improvement
- Loading branch information
1 parent
9dae7e1
commit 60ef068
Showing
16 changed files
with
382 additions
and
18 deletions.
There are no files selected for viewing
44 changes: 44 additions & 0 deletions
44
...c/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaMessageSigner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.trustwallet.core.app.blockchains.solana | ||
|
||
import com.google.protobuf.ByteString | ||
import com.trustwallet.core.app.utils.toHex | ||
import com.trustwallet.core.app.utils.toHexByteArray | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
import wallet.core.jni.Base58 | ||
import wallet.core.java.AnySigner | ||
import wallet.core.jni.CoinType.SOLANA | ||
import wallet.core.jni.MessageSigner | ||
import wallet.core.jni.proto.Common.SigningError | ||
import wallet.core.jni.proto.Solana | ||
|
||
class TestSolanaMessageSigner { | ||
init { | ||
System.loadLibrary("TrustWalletCore") | ||
} | ||
|
||
@Test | ||
fun testMessageSign() { | ||
val signingInput = Solana.MessageSigningInput.newBuilder().apply { | ||
privateKey = ByteString.copyFrom("44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d".toHexByteArray()) | ||
message = "Hello world" | ||
}.build() | ||
|
||
val outputData = MessageSigner.sign(SOLANA, signingInput.toByteArray()) | ||
val output = Solana.MessageSigningOutput.parseFrom(outputData) | ||
|
||
assertEquals(output.error, SigningError.OK) | ||
assertEquals(output.signature, "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG") | ||
} | ||
|
||
@Test | ||
fun testMessageVerify() { | ||
val verifyingInput = Solana.MessageVerifyingInput.newBuilder().apply { | ||
publicKey = ByteString.copyFrom("ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9".toHexByteArray()) | ||
signature = "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG" | ||
message = "Hello world" | ||
}.build() | ||
|
||
assert(MessageSigner.verify(SOLANA, verifyingInput.toByteArray())) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// Copyright © 2017 Trust Wallet. | ||
|
||
#pragma once | ||
|
||
#include "TWBase.h" | ||
#include "TWPrivateKey.h" | ||
#include "TWString.h" | ||
|
||
TW_EXTERN_C_BEGIN | ||
|
||
/// Represents a message signer to sign custom messages for any blockchain. | ||
TW_EXPORT_CLASS | ||
struct TWMessageSigner; | ||
|
||
/// Signs an arbitrary message to prove ownership of an address for off-chain services. | ||
/// | ||
/// \param coin The given coin type to sign the message for. | ||
/// \param input The serialized data of a `MessageSigningInput` proto object, (e.g. `TW.Solana.Proto.MessageSigningInput`). | ||
/// \return The serialized data of a `MessageSigningOutput` proto object, (e.g. `TW.Solana.Proto.MessageSigningOutput`). | ||
TW_EXPORT_STATIC_METHOD | ||
TWData* _Nullable TWMessageSignerSign(enum TWCoinType coin, TWData* _Nonnull input); | ||
|
||
/// Verifies a signature for a message. | ||
/// | ||
/// \param coin The given coin type to sign the message for. | ||
/// \param input The serialized data of a verifying input (e.g. TW.Ethereum.Proto.MessageVerifyingInput). | ||
/// \return whether the signature is valid. | ||
TW_EXPORT_STATIC_METHOD | ||
bool TWMessageSignerVerify(enum TWCoinType coin, TWData* _Nonnull input); | ||
|
||
TW_EXTERN_C_END |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
rust/chains/tw_solana/src/modules/offchain_message_signer.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// Copyright © 2017 Trust Wallet. | ||
|
||
use crate::SOLANA_ALPHABET; | ||
use tw_coin_entry::coin_context::CoinContext; | ||
use tw_coin_entry::error::prelude::*; | ||
use tw_coin_entry::modules::message_signer::MessageSigner; | ||
use tw_coin_entry::signing_output_error; | ||
use tw_encoding::base58; | ||
use tw_keypair::ed25519; | ||
use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; | ||
use tw_misc::try_or_false; | ||
use tw_proto::Solana::Proto; | ||
use tw_proto::TxCompiler::Proto as CompilerProto; | ||
|
||
/// Currently, supports https://solana.com/developers/cookbook/wallets/sign-message only. | ||
pub struct OffchainMessageSigner; | ||
|
||
impl OffchainMessageSigner { | ||
pub fn sign_message_impl( | ||
_coin: &dyn CoinContext, | ||
input: Proto::MessageSigningInput, | ||
) -> SigningResult<Proto::MessageSigningOutput<'static>> { | ||
let private_key = ed25519::sha512::PrivateKey::try_from(input.private_key.as_ref())?; | ||
let sign = private_key.sign(input.message.as_bytes().to_vec())?; | ||
let base58_sign = base58::encode(sign.to_bytes().as_slice(), SOLANA_ALPHABET); | ||
Ok(Proto::MessageSigningOutput { | ||
signature: base58_sign.into(), | ||
..Proto::MessageSigningOutput::default() | ||
}) | ||
} | ||
} | ||
|
||
impl MessageSigner for OffchainMessageSigner { | ||
type MessageSigningInput<'a> = Proto::MessageSigningInput<'a>; | ||
type MessagePreSigningOutput = CompilerProto::PreSigningOutput<'static>; | ||
type MessageSigningOutput = Proto::MessageSigningOutput<'static>; | ||
type MessageVerifyingInput<'a> = Proto::MessageVerifyingInput<'a>; | ||
|
||
fn message_preimage_hashes( | ||
&self, | ||
_coin: &dyn CoinContext, | ||
input: Self::MessageSigningInput<'_>, | ||
) -> Self::MessagePreSigningOutput { | ||
CompilerProto::PreSigningOutput { | ||
data: input.message.as_bytes().to_vec().into(), | ||
..CompilerProto::PreSigningOutput::default() | ||
} | ||
} | ||
|
||
fn sign_message( | ||
&self, | ||
coin: &dyn CoinContext, | ||
input: Self::MessageSigningInput<'_>, | ||
) -> Self::MessageSigningOutput { | ||
Self::sign_message_impl(coin, input) | ||
.unwrap_or_else(|e| signing_output_error!(Proto::MessageSigningOutput, e)) | ||
} | ||
|
||
fn verify_message( | ||
&self, | ||
_coin: &dyn CoinContext, | ||
input: Self::MessageVerifyingInput<'_>, | ||
) -> bool { | ||
let sign = try_or_false!(base58::decode(&input.signature, SOLANA_ALPHABET)); | ||
let sign = try_or_false!(ed25519::Signature::try_from(sign.as_slice())); | ||
let public_key = try_or_false!(ed25519::sha512::PublicKey::try_from( | ||
input.public_key.as_ref() | ||
)); | ||
let message_utf8 = input.message.as_bytes().to_vec(); | ||
public_key.verify(sign, message_utf8) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// Copyright © 2017 Trust Wallet. | ||
|
||
use tw_any_coin::ffi::tw_message_signer::{ | ||
tw_message_signer_pre_image_hashes, tw_message_signer_sign, tw_message_signer_verify, | ||
}; | ||
use tw_coin_entry::error::prelude::SigningErrorType; | ||
use tw_coin_registry::coin_type::CoinType; | ||
use tw_encoding::hex::DecodeHex; | ||
use tw_memory::test_utils::tw_data_helper::TWDataHelper; | ||
use tw_proto::{deserialize, serialize, Solana, TxCompiler}; | ||
|
||
#[test] | ||
fn test_solana_message_signer_sign() { | ||
let input = Solana::Proto::MessageSigningInput { | ||
private_key: "44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d" | ||
.decode_hex() | ||
.unwrap() | ||
.into(), | ||
message: "Hello world".into(), | ||
}; | ||
|
||
let input_data = TWDataHelper::create(serialize(&input).unwrap()); | ||
let output = TWDataHelper::wrap(unsafe { | ||
tw_message_signer_sign(CoinType::Solana as u32, input_data.ptr()) | ||
}) | ||
.to_vec() | ||
.expect("!tw_message_signer_sign returned nullptr"); | ||
|
||
let output: Solana::Proto::MessageSigningOutput = deserialize(&output).unwrap(); | ||
assert_eq!(output.error, SigningErrorType::OK); | ||
assert!(output.error_message.is_empty()); | ||
assert_eq!( | ||
output.signature, | ||
"2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG" | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_solana_message_signer_verify() { | ||
let input = Solana::Proto::MessageVerifyingInput { | ||
public_key: "ee6d61a89fc8f9909585a996bb0d2b2ac69ae23b5acf39a19f32631239ba06f9" | ||
.decode_hex() | ||
.unwrap() | ||
.into(), | ||
message: "Hello world".into(), | ||
signature: "2iBZ6zrQRKHcbD8NWmm552gU5vGvh1dk3XV4jxnyEdRKm8up8AeQk1GFr9pJokSmchw7i9gMtNyFBdDt8tBxM1cG".into(), | ||
}; | ||
|
||
let input_data = TWDataHelper::create(serialize(&input).unwrap()); | ||
let verified = unsafe { tw_message_signer_verify(CoinType::Solana as u32, input_data.ptr()) }; | ||
assert!(verified); | ||
} | ||
|
||
#[test] | ||
fn test_solana_message_signer_pre_image_hashes() { | ||
let message = "Hello world"; | ||
|
||
let input = Solana::Proto::MessageSigningInput { | ||
private_key: "44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d" | ||
.decode_hex() | ||
.unwrap() | ||
.into(), | ||
message: message.into(), | ||
}; | ||
|
||
let input_data = TWDataHelper::create(serialize(&input).unwrap()); | ||
let output = TWDataHelper::wrap(unsafe { | ||
tw_message_signer_pre_image_hashes(CoinType::Solana as u32, input_data.ptr()) | ||
}) | ||
.to_vec() | ||
.expect("!tw_message_signer_sign returned nullptr"); | ||
|
||
let output: TxCompiler::Proto::PreSigningOutput = deserialize(&output).unwrap(); | ||
assert_eq!(output.error, SigningErrorType::OK); | ||
assert!(output.error_message.is_empty()); | ||
let actual_message = String::from_utf8(output.data.to_vec()).unwrap(); | ||
assert_eq!(actual_message, message); | ||
} |
Oops, something went wrong.