diff --git a/packages/kos-mobile/src/lib.rs b/packages/kos-mobile/src/lib.rs index af5d3c7..84c4547 100644 --- a/packages/kos-mobile/src/lib.rs +++ b/packages/kos-mobile/src/lib.rs @@ -1,10 +1,9 @@ use hex::FromHexError; use hex::ToHex; - use kos_crypto::cipher; use kos_crypto::cipher::CipherAlgo; use kos_sdk::chain::Chain; -use kos_sdk::models::PathOptions; +use kos_sdk::models::{PathOptions, Transaction}; use kos_sdk::wallet::Wallet; use kos_types::error::Error as KosError; @@ -41,6 +40,32 @@ struct KOSAccount { pub path: String, } +#[derive(uniffi::Record)] +struct KOSTransaction { + pub chain_id: i32, + pub raw: String, + pub sender: String, + pub signature: String, +} + +#[uniffi::export] +fn sign_transaction(account: KOSAccount, raw: String) -> Result { + let chain = get_chain_by(account.chain_id)?; + let wallet = Wallet::from_private_key(chain, account.private_key.to_string())?; + let transaction = Transaction::from_raw(chain, &raw)?; + let signed_transaction = wallet.sign(transaction)?; + let signature = signed_transaction + .get_signature() + .ok_or(KOSError::KOSDelegate("Signature not found".to_string()))?; + + Ok(KOSTransaction { + chain_id: account.chain_id, + raw: signed_transaction.get_raw()?, + sender: signed_transaction.sender, + signature, + }) +} + #[uniffi::export] fn generate_mnemonic(size: i32) -> Result { Ok(kos_crypto::mnemonic::generate_mnemonic(size as usize)?.to_phrase()) @@ -319,4 +344,34 @@ mod tests { Err(e) => assert!(matches!(e, KOSError::KOSDelegate(..)), "Invalid error"), } } + + #[test] + fn should_sign_raw_transaction() { + let chain_id = 38; + + let raw = "{\"RawData\":{\"BandwidthFee\":1000000,\"ChainID\":\"MTAwNDIw\",\"Contract\":[{\"Parameter\":{\"type_url\":\"type.googleapis.com/proto.TransferContract\",\"value\":\"CiAysyg0Aj8xj/rr5XGU6iJ+ATI29mnRHS0W0BrC1vz0CBgK\"}}],\"KAppFee\":500000,\"Nonce\":39,\"Sender\":\"5BsyOlcf2VXgnNQWYP9EZcP0RpPIfy+upKD8QIcnyOo=\",\"Version\":1}}"; + + let account = generate_wallet_from_mnemonic( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(), + chain_id, + 0, + false + ).unwrap(); + + let transaction = sign_transaction(account, raw.to_string()).unwrap(); + + assert_eq!(transaction.chain_id, chain_id, "The chain_id doesn't match"); + assert_eq!( + transaction.sender, "klv1usdnywjhrlv4tcyu6stxpl6yvhplg35nepljlt4y5r7yppe8er4qujlazy", + "The sender doesn't match" + ); + assert_eq!( + transaction.raw, "{\"RawData\":{\"Nonce\":39,\"Sender\":\"5BsyOlcf2VXgnNQWYP9EZcP0RpPIfy+upKD8QIcnyOo=\",\"Contract\":[{\"Parameter\":{\"typeUrl\":\"type.googleapis.com/proto.TransferContract\",\"value\":\"CiAysyg0Aj8xj/rr5XGU6iJ+ATI29mnRHS0W0BrC1vz0CBgK\"}}],\"KAppFee\":500000,\"BandwidthFee\":1000000,\"Version\":1,\"ChainID\":\"MTAwNDIw\"},\"Signature\":[\"gUZDIPSxSq40QjTBM38/DAAuWTm7D1THo2KWVqhiTYCum5O+OSWwTYlgIU0RgJ6ungg1cuCJPcmYWNgjDKA/DA==\"]}", + "The raw doesn't match" + ); + assert_eq!( + transaction.signature, "gUZDIPSxSq40QjTBM38/DAAuWTm7D1THo2KWVqhiTYCum5O+OSWwTYlgIU0RgJ6ungg1cuCJPcmYWNgjDKA/DA==", + "The signature doesn't match" + ); + } } diff --git a/packages/kos-sdk/src/chains/bitcoin/mod.rs b/packages/kos-sdk/src/chains/bitcoin/mod.rs index 8047be4..a32bac6 100644 --- a/packages/kos-sdk/src/chains/bitcoin/mod.rs +++ b/packages/kos-sdk/src/chains/bitcoin/mod.rs @@ -137,9 +137,12 @@ impl BTC { // redeem script btc_tx.finalize()?; + let signature = btc_tx.get_signature().unwrap_or("".to_string()); + Ok(Transaction { hash: btc_tx.txid_hash()?, data: Some(TransactionRaw::Bitcoin(btc_tx)), + signature: Some(signature), ..tx }) } @@ -267,6 +270,7 @@ impl BTC { sender, hash: Hash::new(&tx.txid().to_string())?, data: Some(TransactionRaw::Bitcoin(tx)), + signature: None, }) } diff --git a/packages/kos-sdk/src/chains/bitcoin/transaction.rs b/packages/kos-sdk/src/chains/bitcoin/transaction.rs index c9c8f20..5024ca4 100644 --- a/packages/kos-sdk/src/chains/bitcoin/transaction.rs +++ b/packages/kos-sdk/src/chains/bitcoin/transaction.rs @@ -46,6 +46,11 @@ impl BTCTransaction { bitcoin::consensus::encode::serialize_hex(&self.tx.clone().extract_tx()) } + pub fn get_signature(&self) -> Result { + let sig = self.tx.clone().extract_tx().wtxid(); + Ok(sig.to_string()) + } + pub fn finalize(&mut self) -> Result<(), Error> { for inp_idx in 0..self.tx.inputs.len() { // todo!("multi sig and redeem type"); diff --git a/packages/kos-sdk/src/chains/ethereum/mod.rs b/packages/kos-sdk/src/chains/ethereum/mod.rs index 312a4a6..502eed3 100644 --- a/packages/kos-sdk/src/chains/ethereum/mod.rs +++ b/packages/kos-sdk/src/chains/ethereum/mod.rs @@ -139,6 +139,7 @@ impl ETH { sender: tx.sender, hash: Hash::from_vec(new_hash)?, data: Some(TransactionRaw::Ethereum(new_tx)), + signature: Some(hex::encode(sig)), }; Ok(result) @@ -284,6 +285,7 @@ impl ETH { sender, hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Ethereum(tx)), + signature: None, }) } @@ -444,12 +446,16 @@ impl ETH { } }; + let signature = tx.signature.map(|sig| sig.to_standard().to_string()); + let digest = hash_transaction(&tx)?; + Ok(crate::models::Transaction { chain: chain::Chain::ETH, sender: "".to_string(), //TODO: implement sender on eth decode hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Ethereum(tx)), + signature, }) } @@ -465,11 +471,14 @@ impl ETH { None => "".to_string(), }; + let signature = tx.signature.map(|sig| sig.to_standard().to_string()); + Ok(crate::models::Transaction { chain: chain::Chain::ETH, sender, hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Ethereum(tx)), + signature, }) } } diff --git a/packages/kos-sdk/src/chains/klever/klever_test.rs b/packages/kos-sdk/src/chains/klever/klever_test.rs index 48edd25..3ab6849 100644 --- a/packages/kos-sdk/src/chains/klever/klever_test.rs +++ b/packages/kos-sdk/src/chains/klever/klever_test.rs @@ -58,6 +58,7 @@ mod klever_test { hash: Hash::new("0x0000000000000000000000000000000000000000000000000000000000000000") .unwrap(), data: Some(TransactionRaw::Klever(klv_tx)), + signature: None, }; let result = tokio_test::block_on(KLV::broadcast(to_broadcast, None)); diff --git a/packages/kos-sdk/src/chains/klever/mod.rs b/packages/kos-sdk/src/chains/klever/mod.rs index fdacad9..00e9229 100644 --- a/packages/kos-sdk/src/chains/klever/mod.rs +++ b/packages/kos-sdk/src/chains/klever/mod.rs @@ -8,6 +8,7 @@ use crate::{ use kos_crypto::{ed25519::Ed25519KeyPair, keypair::KeyPair}; use kos_types::{error::Error, hash::Hash, number::BigNumber}; +use pbjson::private::base64; use std::str::FromStr; use wasm_bindgen::prelude::*; @@ -102,12 +103,15 @@ impl KLV { let digest = KLV::hash_transaction(&klv_tx)?; let sig = KLV::sign_digest(digest.as_slice(), keypair)?; + let signature = base64::encode(sig.clone()); + new_tx.signature.push(sig); let result = Transaction { chain: tx.chain, sender: tx.sender, hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Klever(new_tx)), + signature: Some(signature), }; Ok(result) @@ -210,6 +214,7 @@ impl KLV { sender: tx.sender, hash: tx_hash, data: tx.data, + signature: None, })) } None => match result.get("error") { @@ -282,12 +287,14 @@ impl KLV { let sender = address::Address::from_bytes(&data.sender); let digest = KLV::hash_transaction(&tx)?; + let signature = tx.signature.first().map(|sig| base64::encode(sig.clone())); Ok(crate::models::Transaction { chain: crate::chain::Chain::KLV, sender: sender.to_string(), hash: Hash::from_slice(&digest)?, data: Some(TransactionRaw::Klever(tx)), + signature, }) } } diff --git a/packages/kos-sdk/src/chains/klever/requests.rs b/packages/kos-sdk/src/chains/klever/requests.rs index e6496b6..318625f 100644 --- a/packages/kos-sdk/src/chains/klever/requests.rs +++ b/packages/kos-sdk/src/chains/klever/requests.rs @@ -84,5 +84,6 @@ pub async fn make_request( sender, hash: Hash::new(&tx.tx_hash)?, data: Some(TransactionRaw::Klever(tx.tx)), + signature: None, }) } diff --git a/packages/kos-sdk/src/chains/tron/mod.rs b/packages/kos-sdk/src/chains/tron/mod.rs index 0a0eafa..6a3867f 100644 --- a/packages/kos-sdk/src/chains/tron/mod.rs +++ b/packages/kos-sdk/src/chains/tron/mod.rs @@ -116,6 +116,7 @@ impl TRX { let mut new_tx = trx_tx.clone(); let digest = TRX::hash_transaction(&trx_tx)?; let sig = TRX::sign_digest(digest.as_slice(), keypair)?; + let hex_sig = hex::encode(sig.clone()); new_tx.signature.push(sig); let result = Transaction { @@ -123,6 +124,7 @@ impl TRX { sender: tx.sender, hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Tron(new_tx)), + signature: Some(hex_sig), }; Ok(result) @@ -328,6 +330,7 @@ impl TRX { sender, hash: Hash::from_vec(digest)?, data: Some(TransactionRaw::Tron(tx)), + signature: None, }) } @@ -361,6 +364,7 @@ impl TRX { sender: tx.sender, hash: tx.hash, data: tx.data, + signature: tx.signature, })) } @@ -405,6 +409,39 @@ impl TRX { let bytes = kos_proto::write_message(&raw_data); Ok(hex::encode(bytes)) } + + pub fn tx_from_raw(data: &str) -> Result { + let raw_data_bytes = hex::decode(data)?; + let raw_data: kos_proto::tron::transaction::Raw = + kos_proto::from_bytes(raw_data_bytes).unwrap(); + + let parameter: kos_proto::tron::TransferContract = + kos_proto::unpack_from_option_any(&raw_data.contract[0].parameter).ok_or( + Error::InvalidTransaction("Failed to unpack transfer contract".to_string()), + )?; + + let tx = kos_proto::tron::Transaction { + raw_data: Some(raw_data), + signature: Vec::new(), + ret: Vec::new(), + }; + + let hash = Hash::from_vec(TRX::hash_transaction(&tx)?)?; + + let sender = address::Address::from_bytes(parameter.owner_address.as_slice()).to_string(); + + let binding = tx.clone(); + let sig = binding.signature.first(); + let signature = sig.map(hex::encode); + + Ok(Transaction { + chain: chain::Chain::TRX, + sender, + hash, + data: Some(TransactionRaw::Tron(tx)), + signature, + }) + } } #[cfg(test)] @@ -630,4 +667,18 @@ mod tests { let result = TRX::serialize_raw_data_into_hex_string(invalid_tx); assert!(result.is_err()); } + + #[test] + fn test_tx_from_raw() { + let tx = "0a02d8372208e9c73b516bcd78844088c6e8ad9a325a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a1541e825d52582eec346c839b4875376117904a76cbc12154120ab1300cf70c048e4cf5d5b1b33f59653ed662618c0843d70fdfee4ad9a32"; + let result = TRX::tx_from_raw(tx); + assert!(result.is_ok()); + let t = result.unwrap(); + assert_eq!(t.chain, chain::Chain::TRX); + assert_eq!(t.sender, "TX8h6Df74VpJsXF6sTDz1QJsq3Ec8dABc3"); + assert_eq!( + t.hash.to_string(), + "3e6c463b2e88d78e912dee3aa31a14aa71c0e56bbdfbdbba012c8af4e45b8834" + ); + } } diff --git a/packages/kos-sdk/src/models.rs b/packages/kos-sdk/src/models.rs index a9be80d..70fdd51 100644 --- a/packages/kos-sdk/src/models.rs +++ b/packages/kos-sdk/src/models.rs @@ -1,10 +1,9 @@ -// use crate::klever; +use crate::chain::Chain; +use crate::chains::{ETH, KLV, TRX}; use kos_types::{error::Error, hash::Hash}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -use crate::chain::Chain; - #[derive(Debug, Clone, Serialize)] #[wasm_bindgen] pub struct BroadcastResult { @@ -149,6 +148,8 @@ pub struct Transaction { pub hash: Hash, #[wasm_bindgen(skip)] pub data: Option, + #[wasm_bindgen(skip)] + pub signature: Option, } #[wasm_bindgen] @@ -184,8 +185,12 @@ impl Transaction { None => Err(Error::InvalidTransaction("no data found".to_string())), } } -} + #[wasm_bindgen(js_name = getSignature)] + pub fn get_signature(&self) -> Option { + self.signature.clone() + } +} impl Transaction { pub fn new_data(&self, chain: Chain, data: TransactionRaw) -> Transaction { let mut tx = self.clone(); @@ -195,6 +200,25 @@ impl Transaction { } } +#[wasm_bindgen] +impl Transaction { + #[wasm_bindgen(js_name = fromRaw)] + pub fn from_raw(chain: Chain, data: &str) -> Result { + match chain { + Chain::KLV => KLV::tx_from_raw(data), + Chain::TRX => TRX::tx_from_raw(data), + Chain::ETH => ETH::tx_from_json(data), + Chain::MATIC => Err(Error::InvalidTransaction( + "MATIC chain not implemented".to_string(), + )), + Chain::BTC => Err(Error::InvalidTransaction( + "BTC chain not implemented".to_string(), + )), + Chain::NONE => Err(Error::InvalidTransaction("Invalid chain".to_string())), + } + } +} + #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[wasm_bindgen] pub struct AddressOptions {