diff --git a/Cargo.lock b/Cargo.lock index 6faba48cd..78f3bacc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3348,6 +3348,7 @@ dependencies = [ "crc", "futures", "gem_client", + "gem_hash", "hex", "num-bigint", "primitives", diff --git a/crates/gem_ton/Cargo.toml b/crates/gem_ton/Cargo.toml index 133e693c7..75c016eb3 100644 --- a/crates/gem_ton/Cargo.toml +++ b/crates/gem_ton/Cargo.toml @@ -11,7 +11,7 @@ rpc = [ "dep:chain_traits", "dep:futures", ] -signer = ["dep:signer"] +signer = ["dep:signer", "dep:gem_hash"] reqwest = ["gem_client/reqwest"] chain_integration_tests = ["rpc", "reqwest", "settings/testkit"] @@ -35,6 +35,7 @@ futures = { workspace = true, optional = true } # Optional signer dependencies signer = { path = "../signer", optional = true } +gem_hash = { path = "../gem_hash", optional = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/crates/gem_ton/src/address.rs b/crates/gem_ton/src/address.rs index 0cfcef4da..65925e101 100644 --- a/crates/gem_ton/src/address.rs +++ b/crates/gem_ton/src/address.rs @@ -1,4 +1,4 @@ -use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine}; +use base64::prelude::{BASE64_STANDARD_NO_PAD, BASE64_URL_SAFE_NO_PAD, Engine}; use crc::Crc; type Workchain = i32; @@ -34,10 +34,30 @@ impl Address { Self { workchain, hash_part } } + pub fn workchain(&self) -> Workchain { + self.workchain + } + pub fn get_hash_part(&self) -> &HashPart { &self.hash_part } + pub fn from_base64_url(base64: &str) -> Result { + let bytes = BASE64_URL_SAFE_NO_PAD + .decode(base64) + .or_else(|_| BASE64_STANDARD_NO_PAD.decode(base64)) + .map_err(|_| ParseError("Invalid base64".to_string()))?; + + if bytes.len() != 36 { + return Err(ParseError("Invalid base64 address length".to_string())); + } + + let workchain = bytes[1] as i8 as i32; + let hash_part: HashPart = bytes[2..34].try_into().map_err(|_| ParseError("Invalid hash length".to_string()))?; + + Ok(Self { workchain, hash_part }) + } + pub fn from_hex_str(hex_str: S) -> Result where S: AsRef, @@ -73,21 +93,8 @@ pub fn hex_to_base64_address(hex_str: String) -> Result Result> { - use base64::prelude::{BASE64_STANDARD_NO_PAD, BASE64_URL_SAFE_NO_PAD}; - - let bytes = BASE64_URL_SAFE_NO_PAD - .decode(&base64_str) - .or_else(|_| BASE64_STANDARD_NO_PAD.decode(&base64_str)) - .map_err(|_| ParseError("Invalid base64".to_string()))?; - - if bytes.len() != 36 { - return Err(ParseError("Invalid base64 length".to_string()).into()); - } - - let workchain = bytes[1] as i32; - let hash = &bytes[2..34]; - - Ok(format!("{}:{}", workchain, hex::encode(hash))) + let address = Address::from_base64_url(&base64_str)?; + Ok(format!("{}:{}", address.workchain(), hex::encode(address.get_hash_part()))) } impl std::error::Error for ParseError {} @@ -164,6 +171,14 @@ mod tests { assert_eq!(hex, "0:8e874b7ad9bbebbfc48810b8939c98f50580246f19982040dbcb253c4c3daf78"); } + #[test] + fn test_from_base64_url() { + let addr = Address::from_base64_url("UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg").unwrap(); + + assert_eq!(addr.workchain(), 0); + assert_eq!(hex::encode(addr.get_hash_part()), "58d5c54fbb8488af7eaad0cdc759ca8f6ff79fc9555106c1339b037ec0a40347"); + } + #[test] fn test_round_trip_conversion() { let original_hex = "0:0e97797708411c29a3cb1f3f810ef4f83f41d990838f7f93ce7082c4ff9aa026"; diff --git a/crates/gem_ton/src/signer/chain_signer.rs b/crates/gem_ton/src/signer/chain_signer.rs index a240f88ce..496a438f3 100644 --- a/crates/gem_ton/src/signer/chain_signer.rs +++ b/crates/gem_ton/src/signer/chain_signer.rs @@ -1,3 +1,5 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + use primitives::{ChainSigner, SignerError, TransactionLoadInput}; use super::signature::sign_personal; @@ -7,8 +9,12 @@ pub struct TonChainSigner; impl ChainSigner for TonChainSigner { fn sign_message(&self, message: &[u8], private_key: &[u8]) -> Result { - let (signature, _public_key) = sign_personal(message, private_key)?; - Ok(base64::Engine::encode(&base64::engine::general_purpose::STANDARD, signature)) + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| SignerError::InvalidInput(e.to_string()))? + .as_secs(); + let result = sign_personal(message, private_key, timestamp)?; + Ok(base64::Engine::encode(&base64::engine::general_purpose::STANDARD, result.signature)) } fn sign_transfer(&self, input: &TransactionLoadInput, private_key: &[u8]) -> Result { diff --git a/crates/gem_ton/src/signer/mod.rs b/crates/gem_ton/src/signer/mod.rs index e2cea077a..a0a7b3ded 100644 --- a/crates/gem_ton/src/signer/mod.rs +++ b/crates/gem_ton/src/signer/mod.rs @@ -1,7 +1,9 @@ mod chain_signer; mod signature; +#[cfg(test)] +pub(crate) mod testkit; mod types; pub use chain_signer::TonChainSigner; pub use signature::sign_personal; -pub use types::{TonSignDataPayload, TonSignDataResponse, TonSignMessageData}; +pub use types::{TonSignDataPayload, TonSignDataResponse, TonSignMessageData, TonSignResult}; diff --git a/crates/gem_ton/src/signer/signature.rs b/crates/gem_ton/src/signer/signature.rs index 55492f883..3abb03baa 100644 --- a/crates/gem_ton/src/signer/signature.rs +++ b/crates/gem_ton/src/signer/signature.rs @@ -1,41 +1,48 @@ use primitives::SignerError; use signer::Signer; -use super::types::TonSignMessageData; +use super::types::{TonSignMessageData, TonSignResult}; -pub fn sign_personal(data: &[u8], private_key: &[u8]) -> Result<(Vec, Vec), SignerError> { +pub fn sign_personal(data: &[u8], private_key: &[u8], timestamp: u64) -> Result { let ton_data = TonSignMessageData::from_bytes(data)?; - let digest = ton_data.payload.hash(); + let digest = ton_data.hash(timestamp)?; - Signer::sign_ed25519_with_public_key(&digest, private_key).map_err(|e| SignerError::InvalidInput(e.to_string())) + let (signature, public_key) = Signer::sign_ed25519_with_public_key(&digest, private_key).map_err(|e| SignerError::InvalidInput(e.to_string()))?; + Ok(TonSignResult { signature, public_key, timestamp }) } #[cfg(test)] mod tests { use super::*; use crate::signer::TonSignDataPayload; + use crate::signer::testkit::TEST_ADDRESS; #[test] fn test_sign_ton_personal() { let payload = TonSignDataPayload::Text { text: "Hello TON".to_string() }; - let ton_data = TonSignMessageData::new(payload, "example.com".to_string()); + let ton_data = TonSignMessageData::new(payload, "example.com".to_string(), TEST_ADDRESS.to_string()); let data = ton_data.to_bytes(); - let private_key = hex::decode("1e9d38b5274152a78dff1a86fa464ceadc1f4238ca2c17060c3c507349424a34").expect("valid hex"); + let private_key = hex::decode("1e9d38b5274152a78dff1a86fa464ceadc1f4238ca2c17060c3c507349424a34").unwrap(); + let timestamp = 1234567890u64; - let (signature, public_key) = sign_personal(&data, &private_key).expect("signing succeeds"); + let result = sign_personal(&data, &private_key, timestamp).unwrap(); - assert_eq!(signature.len(), 64, "Ed25519 signature should be 64 bytes"); - assert_eq!(public_key.len(), 32, "Ed25519 public key should be 32 bytes"); + assert_eq!( + hex::encode(&result.signature), + "3fe42db1d77534ba52d43240cf6b84b36eb1c53a28e3370c5872f37558cee9b758b9f93a8740c84ee4190b99de83901dcb9d5b42b1c7826b3836236ef5cd3a0f" + ); + assert_eq!(hex::encode(&result.public_key), "d369452197c2a56481e5e2d3e8bf03de2349f67a63151956822208c2334adee2"); + assert_eq!(result.timestamp, timestamp); } #[test] fn test_sign_ton_personal_rejects_invalid_key() { let payload = TonSignDataPayload::Text { text: "Hello TON".to_string() }; - let ton_data = TonSignMessageData::new(payload, "example.com".to_string()); + let ton_data = TonSignMessageData::new(payload, "example.com".to_string(), TEST_ADDRESS.to_string()); let data = ton_data.to_bytes(); - let result = sign_personal(&data, &[0u8; 16]); + let result = sign_personal(&data, &[0u8; 16], 1234567890); assert!(result.is_err()); } } diff --git a/crates/gem_ton/src/signer/testkit.rs b/crates/gem_ton/src/signer/testkit.rs new file mode 100644 index 000000000..f71818526 --- /dev/null +++ b/crates/gem_ton/src/signer/testkit.rs @@ -0,0 +1 @@ +pub const TEST_ADDRESS: &str = "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg"; diff --git a/crates/gem_ton/src/signer/types.rs b/crates/gem_ton/src/signer/types.rs index e4946982d..ef1933cfe 100644 --- a/crates/gem_ton/src/signer/types.rs +++ b/crates/gem_ton/src/signer/types.rs @@ -1,6 +1,13 @@ +use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64; +use gem_hash::sha2::sha256; use primitives::SignerError; use serde::{Deserialize, Serialize}; +use crate::address::Address; + +const SIGN_DATA_PREFIX: &[u8] = b"\xff\xffton-connect/sign-data/"; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum TonSignDataPayload { @@ -18,8 +25,12 @@ impl TonSignDataPayload { } } - pub fn hash(&self) -> Vec { - self.data().as_bytes().to_vec() + pub fn encode(&self) -> Result<(&str, Vec), SignerError> { + match self { + Self::Text { text } => Ok(("txt", text.as_bytes().to_vec())), + Self::Binary { bytes } => Ok(("bin", BASE64.decode(bytes).map_err(|e| SignerError::InvalidInput(e.to_string()))?)), + Self::Cell { .. } => Err(SignerError::InvalidInput("Cell payload not supported for sign-data".to_string())), + } } } @@ -28,6 +39,7 @@ impl TonSignDataPayload { pub struct TonSignDataResponse { signature: String, public_key: String, + address: String, timestamp: u64, domain: String, payload: TonSignDataPayload, @@ -37,16 +49,17 @@ pub struct TonSignDataResponse { pub struct TonSignMessageData { pub payload: TonSignDataPayload, pub domain: String, + pub address: String, } impl TonSignMessageData { - pub fn new(payload: TonSignDataPayload, domain: String) -> Self { - Self { payload, domain } + pub fn new(payload: TonSignDataPayload, domain: String, address: String) -> Self { + Self { payload, domain, address } } - pub fn from_value(payload: serde_json::Value, domain: String) -> Result { + pub fn from_value(payload: serde_json::Value, domain: String, address: String) -> Result { let payload: TonSignDataPayload = serde_json::from_value(payload).map_err(SignerError::from)?; - Ok(Self { payload, domain }) + Ok(Self { payload, domain, address }) } pub fn from_bytes(data: &[u8]) -> Result { @@ -56,13 +69,39 @@ impl TonSignMessageData { pub fn to_bytes(&self) -> Vec { serde_json::to_vec(self).unwrap_or_default() } + + pub fn hash(&self, timestamp: u64) -> Result, SignerError> { + let address = Address::from_base64_url(&self.address).map_err(|e| SignerError::InvalidInput(e.to_string()))?; + let domain_bytes = self.domain.as_bytes(); + let (type_prefix, payload_bytes) = self.payload.encode()?; + + let mut msg = Vec::new(); + msg.extend_from_slice(SIGN_DATA_PREFIX); + msg.extend_from_slice(&address.workchain().to_be_bytes()); + msg.extend_from_slice(address.get_hash_part()); + msg.extend_from_slice(&(domain_bytes.len() as u32).to_be_bytes()); + msg.extend_from_slice(domain_bytes); + msg.extend_from_slice(×tamp.to_be_bytes()); + msg.extend_from_slice(type_prefix.as_bytes()); + msg.extend_from_slice(&(payload_bytes.len() as u32).to_be_bytes()); + msg.extend_from_slice(&payload_bytes); + + Ok(sha256(&msg).to_vec()) + } +} + +pub struct TonSignResult { + pub signature: Vec, + pub public_key: Vec, + pub timestamp: u64, } impl TonSignDataResponse { - pub fn new(signature: String, public_key: String, timestamp: u64, domain: String, payload: TonSignDataPayload) -> Self { + pub fn new(signature: String, public_key: String, address: String, timestamp: u64, domain: String, payload: TonSignDataPayload) -> Self { Self { signature, public_key, + address, timestamp, domain, payload, @@ -77,6 +116,7 @@ impl TonSignDataResponse { #[cfg(test)] mod tests { use super::*; + use crate::signer::testkit::TEST_ADDRESS; #[test] fn test_parse_payload_text() { @@ -84,7 +124,6 @@ mod tests { let parsed: TonSignDataPayload = serde_json::from_str(json).unwrap(); assert_eq!(parsed, TonSignDataPayload::Text { text: "Hello TON".to_string() }); - assert_eq!(b"Hello TON".to_vec(), parsed.hash()); } #[test] @@ -93,7 +132,6 @@ mod tests { let parsed: TonSignDataPayload = serde_json::from_str(json).unwrap(); assert_eq!(parsed, TonSignDataPayload::Binary { bytes: "SGVsbG8=".to_string() }); - assert_eq!("SGVsbG8=".as_bytes().to_vec(), parsed.hash()); } #[test] @@ -102,20 +140,27 @@ mod tests { let parsed: TonSignDataPayload = serde_json::from_str(json).unwrap(); assert_eq!(parsed, TonSignDataPayload::Cell { cell: "te6c".to_string() }); - assert_eq!("te6c".as_bytes().to_vec(), parsed.hash()); } #[test] fn test_response_to_json() { let payload = TonSignDataPayload::Text { text: "Hello TON".to_string() }; - let response = TonSignDataResponse::new("c2lnbmF0dXJl".to_string(), "cHVibGljS2V5".to_string(), 1234567890, "example.com".to_string(), payload); + let response = TonSignDataResponse::new( + "c2lnbmF0dXJl".to_string(), + "abcdef01".to_string(), + "0:58d5c54fbb8488af7eaad0cdc759ca8f6ff79fc9555106c1339b037ec0a40347".to_string(), + 1234567890, + "example.com".to_string(), + payload, + ); let json = response.to_json().unwrap(); let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(parsed["signature"], "c2lnbmF0dXJl"); - assert_eq!(parsed["publicKey"], "cHVibGljS2V5"); + assert_eq!(parsed["publicKey"], "abcdef01"); + assert_eq!(parsed["address"], "0:58d5c54fbb8488af7eaad0cdc759ca8f6ff79fc9555106c1339b037ec0a40347"); assert_eq!(parsed["timestamp"], 1234567890); assert_eq!(parsed["domain"], "example.com"); assert_eq!(parsed["payload"]["type"], "text"); @@ -125,20 +170,31 @@ mod tests { #[test] fn test_ton_sign_message_data() { let payload = TonSignDataPayload::Text { text: "Hello TON".to_string() }; - let data = TonSignMessageData::new(payload.clone(), "example.com".to_string()); + let data = TonSignMessageData::new(payload.clone(), "example.com".to_string(), TEST_ADDRESS.to_string()); let bytes = data.to_bytes(); let parsed = TonSignMessageData::from_bytes(&bytes).unwrap(); assert_eq!(parsed.payload, payload); assert_eq!(parsed.domain, "example.com"); + assert_eq!(parsed.address, TEST_ADDRESS); } #[test] - fn test_ton_sign_message_data_get_payload() { + fn test_build_sign_data_hash() { let payload = TonSignDataPayload::Text { text: "Hello TON".to_string() }; - let data = TonSignMessageData::new(payload, "example.com".to_string()); + let data = TonSignMessageData::new(payload, "example.com".to_string(), TEST_ADDRESS.to_string()); + + let hash = data.hash(1234567890).unwrap(); + + assert_eq!(hash.len(), 32); + } + + #[test] + fn test_build_sign_data_hash_cell_unsupported() { + let payload = TonSignDataPayload::Cell { cell: "te6c".to_string() }; + let data = TonSignMessageData::new(payload, "example.com".to_string(), TEST_ADDRESS.to_string()); - assert_eq!(data.payload, TonSignDataPayload::Text { text: "Hello TON".to_string() }); + assert!(data.hash(1234567890).is_err()); } } diff --git a/crates/gem_wallet_connect/src/request_handler/ton.rs b/crates/gem_wallet_connect/src/request_handler/ton.rs index aeea26564..51f8d7173 100644 --- a/crates/gem_wallet_connect/src/request_handler/ton.rs +++ b/crates/gem_wallet_connect/src/request_handler/ton.rs @@ -13,8 +13,9 @@ fn extract_host(url: &str) -> String { impl TonRequestHandler { pub fn parse_sign_message(_chain: Chain, params: Value, domain: &str) -> Result { let payload = params.at(0)?.clone(); + let from = payload.get_value("from")?.string()?.to_string(); let host = extract_host(domain); - let ton_data = TonSignMessageData::from_value(payload, host).map_err(|e| e.to_string())?; + let ton_data = TonSignMessageData::from_value(payload, host, from).map_err(|e| e.to_string())?; let data = String::from_utf8(ton_data.to_bytes()).map_err(|e| format!("Failed to encode TonSignMessageData: {}", e))?; Ok(WalletConnectAction::SignMessage { chain: Chain::Ton, @@ -54,7 +55,7 @@ mod tests { #[test] fn test_parse_sign_message() { - let params = serde_json::from_str(r#"[{"type":"text","text":"Hello TON"}]"#).unwrap(); + let params = serde_json::from_str(r#"[{"type":"text","text":"Hello TON","from":"UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg"}]"#).unwrap(); let action = TonRequestHandler::parse_sign_message(Chain::Ton, params, "https://react-app.walletconnect.com").unwrap(); let WalletConnectAction::SignMessage { chain, sign_type, data } = action else { panic!("Expected SignMessage action") @@ -64,12 +65,13 @@ mod tests { let parsed: TonSignMessageData = serde_json::from_str(&data).unwrap(); assert_eq!(parsed.domain, "react-app.walletconnect.com"); + assert_eq!(parsed.address, "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg"); assert_eq!(parsed.payload, TonSignDataPayload::Text { text: "Hello TON".to_string() }); } #[test] fn test_parse_sign_message_extracts_host() { - let params = serde_json::from_str(r#"[{"type":"text","text":"Test"}]"#).unwrap(); + let params = serde_json::from_str(r#"[{"type":"text","text":"Test","from":"UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg"}]"#).unwrap(); let action = TonRequestHandler::parse_sign_message(Chain::Ton, params, "https://example.com/path?query=1").unwrap(); let WalletConnectAction::SignMessage { data, .. } = action else { panic!("Expected SignMessage action") diff --git a/crates/gem_wallet_connect/src/validator.rs b/crates/gem_wallet_connect/src/validator.rs index 6d485c3cb..beed258ce 100644 --- a/crates/gem_wallet_connect/src/validator.rs +++ b/crates/gem_wallet_connect/src/validator.rs @@ -176,7 +176,11 @@ mod tests { ); // Valid: text payload - let ton_data = TonSignMessageData::new(TonSignDataPayload::Text { text: "Hello".to_string() }, "example.com".to_string()); + let ton_data = TonSignMessageData::new( + TonSignDataPayload::Text { text: "Hello".to_string() }, + "example.com".to_string(), + "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg".to_string(), + ); assert!( validate_sign_message(&sign_validation( Chain::Ton, @@ -188,7 +192,11 @@ mod tests { ); // Valid: binary payload - let ton_data = TonSignMessageData::new(TonSignDataPayload::Binary { bytes: "SGVsbG8=".to_string() }, "example.com".to_string()); + let ton_data = TonSignMessageData::new( + TonSignDataPayload::Binary { bytes: "SGVsbG8=".to_string() }, + "example.com".to_string(), + "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg".to_string(), + ); assert!( validate_sign_message(&sign_validation( Chain::Ton, @@ -200,7 +208,11 @@ mod tests { ); // Valid: cell payload - let ton_data = TonSignMessageData::new(TonSignDataPayload::Cell { cell: "te6c".to_string() }, "example.com".to_string()); + let ton_data = TonSignMessageData::new( + TonSignDataPayload::Cell { cell: "te6c".to_string() }, + "example.com".to_string(), + "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg".to_string(), + ); assert!( validate_sign_message(&sign_validation( Chain::Ton, diff --git a/gemstone/src/message/signer.rs b/gemstone/src/message/signer.rs index f7d726faf..5825b2d5e 100644 --- a/gemstone/src/message/signer.rs +++ b/gemstone/src/message/signer.rs @@ -5,9 +5,11 @@ use base64::engine::general_purpose::STANDARD as BASE64; use bs58; use gem_evm::{ETHEREUM_RECOVERY_ID_OFFSET, RECOVERY_ID_INDEX, SIGNATURE_LENGTH, message::eip191_hash_message}; use gem_sui::signer as sui_signer; -use gem_ton::signer::{TonSignDataResponse, TonSignMessageData, sign_personal as ton_sign_personal}; +use gem_ton::address::base64_to_hex_address; +use gem_ton::signer::{TonSignDataResponse, TonSignMessageData, TonSignResult, sign_personal as ton_sign_personal}; use primitives::hex::encode_with_0x; use signer::{SignatureScheme, Signer, hash_eip712}; +use std::time::{SystemTime, UNIX_EPOCH}; use sui_types::PersonalMessage; use super::{ @@ -19,6 +21,13 @@ use gem_bitcoin::signer::{BitcoinSignMessageData, sign_personal as bitcoin_sign_ use gem_tron::signer::tron_hash_message; use zeroize::Zeroizing; +fn current_timestamp() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .map_err(|e| GemstoneError::from(e.to_string())) +} + #[derive(Debug, PartialEq, uniffi::Enum)] pub enum MessagePreview { Text(String), @@ -113,7 +122,8 @@ impl MessageSigner { SignDigestType::TonPersonal => { let string = String::from_utf8(self.message.data.clone())?; let ton_data = TonSignMessageData::from_bytes(string.as_bytes())?; - Ok(ton_data.payload.hash()) + let timestamp = current_timestamp()?; + Ok(ton_data.hash(timestamp)?) } SignDigestType::TronPersonal => Ok(tron_hash_message(&self.message.data).to_vec()), SignDigestType::Eip191 | SignDigestType::Siwe => Ok(eip191_hash_message(&self.message.data).to_vec()), @@ -148,25 +158,15 @@ impl MessageSigner { } } - pub fn get_ton_result(&self, signature: &[u8], public_key: &[u8]) -> Result { - let string = String::from_utf8(self.message.data.clone())?; - let ton_data = TonSignMessageData::from_bytes(string.as_bytes())?; - let payload = ton_data.payload; - let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0); - - let response = TonSignDataResponse::new(BASE64.encode(signature), BASE64.encode(public_key), timestamp, ton_data.domain, payload); - - Ok(response.to_json()?) - } - pub fn sign(&self, private_key: Vec) -> Result { let private_key = Zeroizing::new(private_key); let hash = self.hash()?; match &self.message.sign_type { SignDigestType::SuiPersonal => sui_signer::sign_digest(&hash, &private_key).map_err(GemstoneError::from), SignDigestType::TonPersonal => { - let (signature, public_key) = ton_sign_personal(&self.message.data, &private_key)?; - self.get_ton_result(&signature, &public_key) + let timestamp = current_timestamp()?; + let result = ton_sign_personal(&self.message.data, &private_key, timestamp)?; + self.get_ton_result(&result) } SignDigestType::Eip191 | SignDigestType::Eip712 | SignDigestType::Siwe | SignDigestType::TronPersonal => { let signed = Signer::sign_digest(SignatureScheme::Secp256k1, hash, private_key.to_vec())?; @@ -181,6 +181,25 @@ impl MessageSigner { } } +impl MessageSigner { + fn get_ton_result(&self, result: &TonSignResult) -> Result { + let string = String::from_utf8(self.message.data.clone())?; + let data = TonSignMessageData::from_bytes(string.as_bytes())?; + let raw_address = base64_to_hex_address(data.address.clone())?; + + let response = TonSignDataResponse::new( + BASE64.encode(&result.signature), + hex::encode(&result.public_key), + raw_address, + result.timestamp, + data.domain, + data.payload, + ); + + Ok(response.to_json()?) + } +} + #[cfg(test)] mod tests { use super::*; @@ -492,8 +511,9 @@ mod tests { #[test] fn test_ton_personal_preview() { let ton_data = TonSignMessageData::from_value( - serde_json::json!({"type": "text", "text": "Hello TON", "from": "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg"}), + serde_json::json!({"type": "text", "text": "Hello TON"}), "example.com".to_string(), + "UQBY1cVPu4SIr36q0M3HWcqPb_efyVVRBsEzmwN-wKQDR6zg".to_string(), ) .unwrap(); let data = String::from_utf8(ton_data.to_bytes()).unwrap();