From fe15c5249c40563832a7014c4a3f5f251dbd007a Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 08:36:41 -0300 Subject: [PATCH 1/6] add legacy option to sign message --- packages/kos-hardware/src/lib.rs | 3 +- packages/kos-mobile/src/lib.rs | 7 ++-- packages/kos-web/src/wallet.rs | 8 ++--- packages/kos/src/chains/ada/mod.rs | 27 ++++++-------- packages/kos/src/chains/apt/mod.rs | 13 ++++--- packages/kos/src/chains/atom/mod.rs | 27 ++++++++------ packages/kos/src/chains/bch/mod.rs | 13 ++++--- packages/kos/src/chains/btc/mod.rs | 27 ++++++++++++-- packages/kos/src/chains/eth/mod.rs | 29 +++++++++++++-- packages/kos/src/chains/icp/mod.rs | 16 +++++++-- packages/kos/src/chains/klv/mod.rs | 27 +++++++++++++- packages/kos/src/chains/mod.rs | 7 +++- packages/kos/src/chains/sol/mod.rs | 13 ++++--- packages/kos/src/chains/substrate/mod.rs | 22 +++++++++++- packages/kos/src/chains/sui/mod.rs | 33 +++++++++++++++-- packages/kos/src/chains/trx/mod.rs | 27 +++++++++++++- packages/kos/src/chains/xrp/mod.rs | 45 ++++++++++++++++++++++-- 17 files changed, 280 insertions(+), 64 deletions(-) diff --git a/packages/kos-hardware/src/lib.rs b/packages/kos-hardware/src/lib.rs index e7a4309b..f8ef8e37 100644 --- a/packages/kos-hardware/src/lib.rs +++ b/packages/kos-hardware/src/lib.rs @@ -177,6 +177,7 @@ pub extern "C" fn rs_sign_message( node: &mut CNodeStruct, message: &mut CBuffer, sig: &mut CBuffer, + legacy: bool, ) -> bool { let pvk = node.read_pvk(); let c = match chains::get_chain_by_params(chain.to_chain_type()) { @@ -191,7 +192,7 @@ pub extern "C" fn rs_sign_message( let message = message.read(); - let signature = match c.sign_message(pvk, message) { + let signature = match c.sign_message(pvk, message, legacy) { Ok(s) => s, Err(e) => { unsafe { diff --git a/packages/kos-mobile/src/lib.rs b/packages/kos-mobile/src/lib.rs index cf502fe4..be8919ee 100644 --- a/packages/kos-mobile/src/lib.rs +++ b/packages/kos-mobile/src/lib.rs @@ -316,10 +316,11 @@ fn sign_transaction( } #[uniffi::export] -fn sign_message(account: KOSAccount, hex: String) -> Result, KOSError> { +fn sign_message(account: KOSAccount, hex: String, legacy: bool) -> Result, KOSError> { let chain = get_chain_by(account.chain_id)?; let message = hex::decode(hex)?; - let signature = chain.sign_message(hex::decode(account.private_key).unwrap(), message)?; + let signature = + chain.sign_message(hex::decode(account.private_key).unwrap(), message, legacy)?; Ok(signature) } @@ -635,7 +636,7 @@ mod tests { false ).unwrap(); - let signature = sign_message(account, message).unwrap(); + let signature = sign_message(account, message, true).unwrap(); assert_eq!(signature.len(), 64, "The signature length doesn't match"); } diff --git a/packages/kos-web/src/wallet.rs b/packages/kos-web/src/wallet.rs index 52115966..18bd0597 100644 --- a/packages/kos-web/src/wallet.rs +++ b/packages/kos-web/src/wallet.rs @@ -325,7 +325,7 @@ impl Wallet { impl Wallet { #[wasm_bindgen(js_name = "signMessage")] /// sign message with keypair - pub fn sign_message(&self, message: &[u8]) -> Result, Error> { + pub fn sign_message(&self, message: &[u8], legacy: bool) -> Result, Error> { match self.private_key { Some(ref pk_hex) => { let pk_bytes = hex::decode(pk_hex)?; @@ -333,7 +333,7 @@ impl Wallet { .ok_or_else(|| Error::WalletManager("Invalid chain".to_string()))?; chain - .sign_message(pk_bytes, message.to_vec()) + .sign_message(pk_bytes, message.to_vec(), legacy) .map_err(|e| Error::WalletManager(format!("sign message: {}", e))) } None => Err(Error::WalletManager("no keypair".to_string())), @@ -490,7 +490,7 @@ mod tests { let wallet = Wallet::from_private_key(chain_id, TEST_PRIVATE_KEY.to_string()).unwrap(); - let signature = wallet.sign_message(message).unwrap(); + let signature = wallet.sign_message(message, true).unwrap(); assert!(!signature.is_empty()); } @@ -556,7 +556,7 @@ mod tests { // Signing operations should fail let message = b"test message"; - assert!(wallet.sign_message(message).is_err()); + assert!(wallet.sign_message(message, true).is_err()); } #[test] diff --git a/packages/kos/src/chains/ada/mod.rs b/packages/kos/src/chains/ada/mod.rs index 8a317141..6544fb44 100644 --- a/packages/kos/src/chains/ada/mod.rs +++ b/packages/kos/src/chains/ada/mod.rs @@ -77,18 +77,7 @@ impl Chain for ADA { cc.copy_from_slice(&private_key[64..]); let vk = self.get_pbk(pvk.to_vec())?; - if !self.extended_key { - return Ok(vk); - } - - let mut xvk = Vec::new(); - xvk.append(&mut vk.to_vec()); - xvk.append(&mut cc.to_vec()); - - pvk.fill(0); - cc.fill(0); - - Ok(xvk) + Ok(vk) } _ => Err(ChainError::InvalidPrivateKey), } @@ -137,7 +126,12 @@ impl Chain for ADA { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let sig = self.sign_raw(private_key.clone(), message)?; let pbk = self.get_pbk(private_key)?; @@ -207,11 +201,10 @@ mod test { let path = ada.get_path(0, false); let pvk = ada.derive(seed, path).unwrap(); + let message = "test message".as_bytes().to_vec(); - let message = "hello world".as_bytes().to_vec(); - - let sig = ada.sign_message(pvk, message).unwrap(); + let sig = ada.sign_message(pvk, message, false).unwrap(); - assert_eq!(hex::encode(sig), "c9343740a90a19a4ffac066357297c41401dd90710266445803a010a53cc041c3cb5cbbdb6a7ec2e41f8bc9c640876458d3ae31652abe2de2086ea34676923007ea09a34aebb13c9841c71397b1cabfec5ddf950405293dee496cac2f437480a".to_string()) + assert_eq!(hex::encode(sig), "3a958c70d7e86c7beae52eba5e0738ee4ede4f27bb2ce79b8082373cb9ac16ea3988a1500b6cc47cde5769d7f7fee886c998ab5e5ea4646024682fadb1adb3057ea09a34aebb13c9841c71397b1cabfec5ddf950405293dee496cac2f437480a88848e8af62a27a57e982215741c9eac17e6e45cbfd6ea65a0e0dcc03bb777b2".to_string()) } } diff --git a/packages/kos/src/chains/apt/mod.rs b/packages/kos/src/chains/apt/mod.rs index a565fd49..59e4dac1 100644 --- a/packages/kos/src/chains/apt/mod.rs +++ b/packages/kos/src/chains/apt/mod.rs @@ -73,7 +73,12 @@ impl Chain for APT { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let sig = self.sign_raw(private_key.clone(), message)?; let pbk = self.get_pbk(private_key)?; @@ -138,10 +143,10 @@ mod test { let pvk = ada.derive(seed, path).unwrap(); - let message = "hello world".as_bytes().to_vec(); + let message = "test message".as_bytes().to_vec(); - let sig = ada.sign_message(pvk, message).unwrap(); + let sig = ada.sign_message(pvk, message, true).unwrap(); - assert_eq!(hex::encode(sig), "7c2879913c2939e6e62d45cd3c30fbed11dd37cc147a38e8dbd12b6dee537342f7404632535522156331a44992753ef35982456aed78f7345d85f8c63718cf01a686f0309ab80312979606cfccc10ea2740147ae6888351488d11c46f08fbf60".to_string()) + assert_eq!(hex::encode(sig), "8f64c4f4717b60b4ab14633636aab83fa0d7b41455b9c3133224816e2cae32ee9b3221f67ab8698126cd165a841d9e8b2ce0044e5a33deac57125c5233a05e0ea686f0309ab80312979606cfccc10ea2740147ae6888351488d11c46f08fbf60".to_string()) } } diff --git a/packages/kos/src/chains/atom/mod.rs b/packages/kos/src/chains/atom/mod.rs index 4262bfec..af8c3473 100644 --- a/packages/kos/src/chains/atom/mod.rs +++ b/packages/kos/src/chains/atom/mod.rs @@ -120,19 +120,27 @@ impl Chain for ATOM { mut tx: Transaction, ) -> Result { let prepared_tx = ATOM::prepare_transaction(tx.raw_data.clone()).to_vec(); - tx.signature = self.sign_raw(private_key, prepared_tx)?; + let signature = self.sign_raw(private_key, prepared_tx)?; + + tx.signature = signature[..64].to_vec(); Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let prepared_msg: [u8; 32] = ATOM::prepare_message(message); let signature = self.sign_raw(private_key, prepared_msg.to_vec())?; - let mut sig_vec = Vec::new(); // <(byte of 27+public key solution)+4 if compressed >< padded bytes for signature R> - sig_vec.extend_from_slice(&[27]); - sig_vec.extend_from_slice(signature.as_ref()); + let mut sig_vec = Vec::new(); + let rec_byte = signature[64]; + sig_vec.extend_from_slice(&[27 + rec_byte]); + sig_vec.extend_from_slice(&signature[..64]); Ok(sig_vec.to_vec()) } @@ -143,10 +151,7 @@ impl Chain for ATOM { let sig: [u8; 65] = secp256k1::Secp256K1::sign(&payload_bytes, &pvk_bytes)?; - let mut sig_vec = [0; 64]; - sig_vec.copy_from_slice(&sig[..64]); - - Ok(sig_vec.to_vec()) + Ok(sig.to_vec()) } fn get_tx_info(&self, _raw_tx: Vec) -> Result { @@ -190,11 +195,11 @@ mod test { let message_bytes = "test message".as_bytes().to_vec(); - let signature = atom.sign_message(pvk, message_bytes).unwrap(); + let signature = atom.sign_message(pvk, message_bytes, false).unwrap(); assert_eq!( hex::encode(signature), - "1bd48bf6446d3cd53869ff9ab787548fd04648fe4a1bc72cab594f9cd9a525d88e6a1b0c6252247f11dd8361629635df0e98f2ceedfee06bb6a116d18f5c5da150" + "1cd48bf6446d3cd53869ff9ab787548fd04648fe4a1bc72cab594f9cd9a525d88e6a1b0c6252247f11dd8361629635df0e98f2ceedfee06bb6a116d18f5c5da150" ); } diff --git a/packages/kos/src/chains/bch/mod.rs b/packages/kos/src/chains/bch/mod.rs index 9858fec8..9c7fc4e2 100644 --- a/packages/kos/src/chains/bch/mod.rs +++ b/packages/kos/src/chains/bch/mod.rs @@ -172,9 +172,14 @@ impl Chain for BCH { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let btc = BTC::new(); - btc.sign_message(private_key, message) + btc.sign_message(private_key, message, legacy) } fn sign_raw(&self, private_key: Vec, payload: Vec) -> Result, ChainError> { @@ -257,9 +262,9 @@ mod test { let pvk = bch.derive(seed, path).unwrap(); let result = bch - .sign_message(pvk, "test message".as_bytes().to_vec()) + .sign_message(pvk, "test message".as_bytes().to_vec(), true) .unwrap(); - assert_eq!(hex::encode(result), "303a181697a1b5d5b4f5adac6f42a44a660c893589c4b52ef71385ccc301a4d27b6c9068e30b84e5f3d08ca314cd45563c3114b4f42216945de1304f85e0617b00"); + assert_eq!(hex::encode(result), "1ba94d065712b8c35814d67a2923caae4626d902c852c1be95bb038e689b647ae97b0fdd5ea813da9c261c90d406f6bd2a42854a355fbdcdbb074223461dee2032"); } } diff --git a/packages/kos/src/chains/btc/mod.rs b/packages/kos/src/chains/btc/mod.rs index a0d5e730..7040281a 100644 --- a/packages/kos/src/chains/btc/mod.rs +++ b/packages/kos/src/chains/btc/mod.rs @@ -114,6 +114,10 @@ impl BTC { sha256_digest(&msg[..]) } + + pub fn prepare_message_legacy(message: Vec) -> [u8; 32] { + sha256_digest(&sha256_digest(&message)) + } } impl Chain for BTC { @@ -279,7 +283,26 @@ impl Chain for BTC { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { + if legacy { + let prepared_message = BTC::prepare_message_legacy(message); + let signature = self.sign_raw(private_key, prepared_message.to_vec())?; + + // <(byte of 27+public key solution)+4 if compressed >< padded bytes for signature R> + let mut sig_vec = Vec::new(); + let rec_byte = signature[64]; + sig_vec.extend_from_slice(&[27 + rec_byte]); + sig_vec.extend_from_slice(&signature[..64]); + + return Ok(sig_vec); + } + let prepared_message = BTC::prepare_message(message); let signature = self.sign_raw(private_key, prepared_message.to_vec())?; Ok(signature) @@ -416,7 +439,7 @@ mod test { let seed = btc.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let pvk = btc.derive(seed, path).unwrap(); let message = "Hello, World!".as_bytes().to_vec(); - let signature = btc.sign_message(pvk, message).unwrap(); + let signature = btc.sign_message(pvk, message, false).unwrap(); assert_eq!(hex::encode(signature.clone()), "9d561a0ba6ea562e61606e7f3b6a92c889246eec2c05e86e3f465f43469ae9436d7e46accdcfaea848460e42c83c52238b6956c4bfb192e67023b6024e95bdcf01"); assert_eq!(signature.len(), 65); } diff --git a/packages/kos/src/chains/eth/mod.rs b/packages/kos/src/chains/eth/mod.rs index 27fc0cb9..29e9ac9c 100644 --- a/packages/kos/src/chains/eth/mod.rs +++ b/packages/kos/src/chains/eth/mod.rs @@ -149,7 +149,12 @@ impl Chain for ETH { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { #[cfg(not(feature = "ksafe"))] { if let Ok(data) = std::str::from_utf8(&message) { @@ -332,7 +337,27 @@ mod test { .unwrap(); let message = data.as_bytes(); - let signature = eth.sign_message(pvk, message.to_vec()).unwrap(); + let signature = eth.sign_message(pvk, message.to_vec(), false).unwrap(); assert_eq!(signature.len(), 65); } + + #[test] + fn test_sign_message() { + let mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); + + let eth = super::ETH::new(); + let seed = eth.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); + let path = eth.get_path(0, false); + let pvk = eth.derive(seed, path).unwrap(); + + let message_bytes = "test message".as_bytes().to_vec(); + + let signature = eth.sign_message(pvk, message_bytes, false).unwrap(); + + assert_eq!( + hex::encode(signature), + "960e9bb7f2cdfa4325661e11218c28ab2804b8966d6529b86073886a95142c881a965b3608a573ff035a780039afcbca13be25ee57ac175dd5ca7b82b79948c61c" + ); + } } diff --git a/packages/kos/src/chains/icp/mod.rs b/packages/kos/src/chains/icp/mod.rs index c1a39342..23059c43 100644 --- a/packages/kos/src/chains/icp/mod.rs +++ b/packages/kos/src/chains/icp/mod.rs @@ -121,7 +121,12 @@ impl Chain for ICP { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let public_key = self.get_pbk(private_key.clone())?; let signature = self.sign_raw(private_key, message)?; @@ -249,9 +254,14 @@ mod test { let pvk = icp.derive(seed, path).unwrap(); - let message = "Hello, World!".as_bytes().to_vec(); - let signature = icp.sign_message(pvk, message).unwrap(); + let message = "test message".as_bytes().to_vec(); + let signature = icp.sign_message(pvk, message, true).unwrap(); assert_eq!(signature.len(), 96, "Signature length should be 96"); + + assert_eq!( + hex::encode(signature), + "db41e41de474e2cb6d997ae5aa5de9aa81512a19d1337881363a3c481431935992a118ba863b6d00612c638b5caf7bac65cb2cf31a7d30f9c5473fcb97bf620bc006bf0760963c13c1c1478adbc326b96338060f03487ebd1c3b261dbccd8daf" + ); } } diff --git a/packages/kos/src/chains/klv/mod.rs b/packages/kos/src/chains/klv/mod.rs index 24e0ae4d..96380e03 100644 --- a/packages/kos/src/chains/klv/mod.rs +++ b/packages/kos/src/chains/klv/mod.rs @@ -113,7 +113,12 @@ impl Chain for KLV { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let prepared_messafe = KLV::prepare_message(message); let signature = self.sign_raw(private_key, prepared_messafe.to_vec())?; Ok(signature) @@ -418,4 +423,24 @@ mod test { "klv16sd7crk4jlc8csrv7lwskqrpjgjklvcsmlhexuesa9p6a3dm57rs5vh0hq" ); } + + #[test] + fn test_sign_message() { + let mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); + + let chain = super::KLV {}; + let seed = chain.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); + let path = chain.get_path(0, false); + let pvk = chain.derive(seed, path).unwrap(); + + let message_bytes = "test message".as_bytes().to_vec(); + + let signature = chain.sign_message(pvk, message_bytes, false).unwrap(); + + assert_eq!( + hex::encode(signature), + "458fc2752927ae5ae68e6c0c95a4b408d6329ffc13de19c29bacf3d7efb12500567ca14d67460197ea6097fc93f1fd7c9b41c02a4aedcf1ab15d28b50935bb07" + ); + } } diff --git a/packages/kos/src/chains/mod.rs b/packages/kos/src/chains/mod.rs index 14f6e7af..ce2aa299 100644 --- a/packages/kos/src/chains/mod.rs +++ b/packages/kos/src/chains/mod.rs @@ -386,7 +386,12 @@ pub trait Chain { fn get_pbk(&self, private_key: Vec) -> Result, ChainError>; fn get_address(&self, public_key: Vec) -> Result; fn sign_tx(&self, private_key: Vec, tx: Transaction) -> Result; - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError>; + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError>; fn sign_raw(&self, private_key: Vec, payload: Vec) -> Result, ChainError>; fn get_tx_info(&self, raw_tx: Vec) -> Result; fn get_chain_type(&self) -> ChainType; diff --git a/packages/kos/src/chains/sol/mod.rs b/packages/kos/src/chains/sol/mod.rs index f10cea2b..eab9e520 100644 --- a/packages/kos/src/chains/sol/mod.rs +++ b/packages/kos/src/chains/sol/mod.rs @@ -91,7 +91,12 @@ impl Chain for SOL { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { self.sign_raw(private_key, message) } @@ -267,10 +272,10 @@ mod test { let seed = sol.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let pvk = sol.derive(seed, "m/44'/501'/0'/0'/0'".to_string()).unwrap(); - let message = "Hello, World!".as_bytes().to_vec(); - let result = sol.sign_message(pvk.clone(), message).unwrap(); + let message = "test message".as_bytes().to_vec(); + let result = sol.sign_message(pvk.clone(), message, false).unwrap(); // Same transaction signed with same key should produce same signature and hash - assert_eq!(hex::encode(&result), "e8ebd3bf665fe5b57e421c477fa4187ef5f1275ddc8dbf693dd684a0164f11aef22bb98416e2e765d39dbb38451d8996fea135baa9e9fd13890286e8be0e8200"); + assert_eq!(hex::encode(&result), "a3c211cc274707367d89ee4ecdab99fa99d856c4ccbc03591bddcaf325da2f3b64f74f4e692da212d3ce157bea6277195c66765e4f552c42ea63d513a07d8907"); } } diff --git a/packages/kos/src/chains/substrate/mod.rs b/packages/kos/src/chains/substrate/mod.rs index a3e59e1f..214bea85 100644 --- a/packages/kos/src/chains/substrate/mod.rs +++ b/packages/kos/src/chains/substrate/mod.rs @@ -184,7 +184,12 @@ impl Chain for Substrate { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { self.sign_raw(private_key, message) } @@ -707,4 +712,19 @@ mod test { let tx_info = dot.get_tx_info(raw_data_hex); assert!(tx_info.is_ok()); } + + #[test] + fn test_sign_message() { + let dot = super::Substrate::new(21, 0, "Polkadot", "DOT"); + let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); + let seed = dot.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); + let path = dot.get_path(0, false); + let pvk = dot.derive(seed, path).unwrap(); + + let message = "test message".as_bytes().to_vec(); + let result = dot.sign_message(pvk.clone(), message, false).unwrap(); + + // Same transaction signed with same key should produce same signature and hash + assert_eq!(hex::encode(&result), "2099af82b371ef0f2409e56369a65c03cb92962efbca766099c6f3dccda5cb64a6f8c2c74153ae0dc4dd05e911daea242f9d593635af23f07495f55e16afc783"); + } } diff --git a/packages/kos/src/chains/sui/mod.rs b/packages/kos/src/chains/sui/mod.rs index 00283a72..ddd1b66f 100644 --- a/packages/kos/src/chains/sui/mod.rs +++ b/packages/kos/src/chains/sui/mod.rs @@ -68,12 +68,17 @@ impl Chain for SUI { mut tx: Transaction, ) -> Result { let raw = tx.raw_data.clone(); - let signature = self.sign_message(private_key, raw)?; + let signature = self.sign_message(private_key, raw, false)?; tx.signature = signature; Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { let mut intent_message = Vec::new(); intent_message.append(&mut [0; 3].to_vec()); intent_message.append(&mut message.clone()); @@ -82,7 +87,9 @@ impl Chain for SUI { let mut pvk_bytes = private_key_from_vec(&private_key)?; let mut response = Vec::new(); - response.push(0x00); + if !legacy { + response.push(0x00); + } let signature = Ed25519::sign(&pvk_bytes, &check_sum)?; response.append(&mut signature.clone()); @@ -131,4 +138,24 @@ mod test { "0x5e93a736d04fbb25737aa40bee40171ef79f65fae833749e3c089fe7cc2161f1" ); } + + #[test] + fn test_sign_message() { + let mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); + + let chain = super::SUI {}; + let seed = chain.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); + let path = chain.get_path(0, false); + let pvk = chain.derive(seed, path).unwrap(); + + let message_bytes = "test message".as_bytes().to_vec(); + + let signature = chain.sign_message(pvk, message_bytes, false).unwrap(); + + assert_eq!( + hex::encode(signature), + "73b5a37df5ae989ec52a970547fdee96e4e76f0c668159b40a4864f2e06637cac485bfcd3e5af9a29a383243c41549c7c5b2ba645ad68aa849c6b08a53a18b02900b4d81eecea3df2f74b14200c4f4cf3f49afaca7a634ffd2cf6ff82bdaecf2" + ); + } } diff --git a/packages/kos/src/chains/trx/mod.rs b/packages/kos/src/chains/trx/mod.rs index d7a56e0b..8a53c647 100644 --- a/packages/kos/src/chains/trx/mod.rs +++ b/packages/kos/src/chains/trx/mod.rs @@ -146,7 +146,12 @@ impl Chain for TRX { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { if private_key.len() != 32 { return Err(ChainError::InvalidPrivateKey); } @@ -343,4 +348,24 @@ mod test { r#"{"receiver":"TCwwZeH6so1X4R5kcdbKqa4GWuzF53xPqG","sender":"TX8h6Df74VpJsXF6sTDz1QJsq3Ec8dABc3","tx_type":"Transfer","value":0.00001}"# ); } + + #[test] + fn test_sign_message() { + let mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); + + let chain = super::TRX {}; + let seed = chain.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); + let path = chain.get_path(0, false); + let pvk = chain.derive(seed, path).unwrap(); + + let message_bytes = "test message".as_bytes().to_vec(); + + let signature = chain.sign_message(pvk, message_bytes, false).unwrap(); + + assert_eq!( + hex::encode(signature), + "cf7b64342bc41955671164c8d5fe7ee992e9497ff4bedcad02f7236be994e4821e2e3fe5807f88ff7099ab3ce8973e4399ccb24bcb41c8d92c64675a32c77e7101" + ); + } } diff --git a/packages/kos/src/chains/xrp/mod.rs b/packages/kos/src/chains/xrp/mod.rs index 1ffedf13..fedd803a 100644 --- a/packages/kos/src/chains/xrp/mod.rs +++ b/packages/kos/src/chains/xrp/mod.rs @@ -32,6 +32,11 @@ impl XRP { sha512_half } + fn prepare_message_legacy(message: Vec) -> [u8; 32] { + let sha256_hash: [u8; 32] = sha256_digest(&sha256_digest(&message)); + sha256_hash + } + fn prepare_transaction(message: Vec) -> [u8; 32] { let mut buffer: Vec = Vec::new(); buffer.extend_from_slice(&HASH_PREFIX_UNSIGNED_TRANSACTION_SINGLE); @@ -44,6 +49,27 @@ impl XRP { sha512_half } + + fn sign_message_legacy( + &self, + private_key: Vec, + message: Vec, + ) -> Result, ChainError> { + let prepared_msg: [u8; 32] = XRP::prepare_message_legacy(message); + let pvk_bytes = private_key_from_vec(&private_key)?; + let payload_bytes = slice_from_vec(&prepared_msg)?; + + let signature = Secp256K1::sign(&payload_bytes, &pvk_bytes)?; + + let rec_byte = signature[64]; + + let mut sig_vec = Vec::new(); + // <(byte of 27+public key solution)+4 if compressed >< padded bytes for signature R> + sig_vec.extend_from_slice(&[27 + rec_byte]); + sig_vec.extend_from_slice(&signature[..64]); + + Ok(sig_vec) + } } impl Chain for XRP { @@ -118,7 +144,16 @@ impl Chain for XRP { Ok(tx) } - fn sign_message(&self, private_key: Vec, message: Vec) -> Result, ChainError> { + fn sign_message( + &self, + private_key: Vec, + message: Vec, + legacy: bool, + ) -> Result, ChainError> { + if legacy { + return self.sign_message_legacy(private_key, message); + } + let prepared_msg: [u8; 32] = XRP::prepare_message(message); self.sign_raw(private_key, prepared_msg.to_vec()) } @@ -171,9 +206,15 @@ mod test { let pvk = xrp.derive(seed, path).unwrap(); let message = "test message".as_bytes().to_vec(); - let signature = xrp.sign_message(pvk, message).unwrap(); + let signature = xrp + .sign_message(pvk.clone(), message.clone(), false) + .unwrap(); assert_eq!(hex::encode(signature).to_uppercase(), "3045022100E10177E86739A9C38B485B6AA04BF2B9AA00E79189A1132E7172B70F400ED1170220566BD64AA3F01DDE8D99DFFF0523D165E7DD2B9891ABDA1944E2F3A52CCCB83A"); + + let signature_legacy = xrp.sign_message(pvk, message, true).unwrap(); + + assert_eq!(hex::encode(signature_legacy), "1cbab59f99b3ff9571f3b8ad534b3a15d342459fef6c4660bba18554e8bc54dec566c3e864a8c3e1f818b8979b4fd864db474c261151e48f42e4a7a76687237376"); } #[test] From 5ed577dbb6c75ff5ea6707ba58c66f01cfe2ef43 Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 10:13:18 -0300 Subject: [PATCH 2/6] legacy true --- packages/kos/src/chains/eth/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kos/src/chains/eth/mod.rs b/packages/kos/src/chains/eth/mod.rs index 29e9ac9c..bd580262 100644 --- a/packages/kos/src/chains/eth/mod.rs +++ b/packages/kos/src/chains/eth/mod.rs @@ -353,7 +353,7 @@ mod test { let message_bytes = "test message".as_bytes().to_vec(); - let signature = eth.sign_message(pvk, message_bytes, false).unwrap(); + let signature = eth.sign_message(pvk, message_bytes, true).unwrap(); assert_eq!( hex::encode(signature), From 456a310a8e1fe89b25063f593cdbdcd23e4358e5 Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 10:28:21 -0300 Subject: [PATCH 3/6] adjust due checks --- packages/kos/src/chains/ada/mod.rs | 24 ++++++++++++------------ packages/kos/src/chains/apt/mod.rs | 2 +- packages/kos/src/chains/atom/mod.rs | 2 +- packages/kos/src/chains/eth/mod.rs | 2 +- packages/kos/src/chains/icp/mod.rs | 2 +- packages/kos/src/chains/klv/mod.rs | 2 +- packages/kos/src/chains/mod.rs | 2 +- packages/kos/src/chains/sol/mod.rs | 2 +- packages/kos/src/chains/substrate/mod.rs | 2 +- packages/kos/src/chains/trx/mod.rs | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/kos/src/chains/ada/mod.rs b/packages/kos/src/chains/ada/mod.rs index 6544fb44..b1a33972 100644 --- a/packages/kos/src/chains/ada/mod.rs +++ b/packages/kos/src/chains/ada/mod.rs @@ -12,13 +12,17 @@ use alloc::vec::Vec; pub const BASE_ID: u32 = 20; -pub struct ADA { - extended_key: bool, -} +pub struct ADA {} impl ADA { - pub fn new(extended_key: bool) -> Self { - ADA { extended_key } + pub fn new() -> Self { + ADA {} + } +} + +impl Default for ADA { + fn default() -> Self { + Self::new() } } @@ -130,7 +134,7 @@ impl Chain for ADA { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { let sig = self.sign_raw(private_key.clone(), message)?; @@ -171,9 +175,7 @@ mod test { let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" .to_string(); - let ada = super::ADA { - extended_key: false, - }; + let ada = super::ADA {}; let seed = ada.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let path = ada.get_path(0, false); @@ -193,9 +195,7 @@ mod test { let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" .to_string(); - let ada = super::ADA { - extended_key: false, - }; + let ada = super::ADA {}; let seed = ada.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let path = ada.get_path(0, false); diff --git a/packages/kos/src/chains/apt/mod.rs b/packages/kos/src/chains/apt/mod.rs index 59e4dac1..dcf267c9 100644 --- a/packages/kos/src/chains/apt/mod.rs +++ b/packages/kos/src/chains/apt/mod.rs @@ -77,7 +77,7 @@ impl Chain for APT { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { let sig = self.sign_raw(private_key.clone(), message)?; diff --git a/packages/kos/src/chains/atom/mod.rs b/packages/kos/src/chains/atom/mod.rs index af8c3473..faf4fb2d 100644 --- a/packages/kos/src/chains/atom/mod.rs +++ b/packages/kos/src/chains/atom/mod.rs @@ -131,7 +131,7 @@ impl Chain for ATOM { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { let prepared_msg: [u8; 32] = ATOM::prepare_message(message); let signature = self.sign_raw(private_key, prepared_msg.to_vec())?; diff --git a/packages/kos/src/chains/eth/mod.rs b/packages/kos/src/chains/eth/mod.rs index bd580262..cbf74abf 100644 --- a/packages/kos/src/chains/eth/mod.rs +++ b/packages/kos/src/chains/eth/mod.rs @@ -153,7 +153,7 @@ impl Chain for ETH { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { #[cfg(not(feature = "ksafe"))] { diff --git a/packages/kos/src/chains/icp/mod.rs b/packages/kos/src/chains/icp/mod.rs index 23059c43..cce41f9b 100644 --- a/packages/kos/src/chains/icp/mod.rs +++ b/packages/kos/src/chains/icp/mod.rs @@ -125,7 +125,7 @@ impl Chain for ICP { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { let public_key = self.get_pbk(private_key.clone())?; diff --git a/packages/kos/src/chains/klv/mod.rs b/packages/kos/src/chains/klv/mod.rs index 96380e03..307bb0c3 100644 --- a/packages/kos/src/chains/klv/mod.rs +++ b/packages/kos/src/chains/klv/mod.rs @@ -117,7 +117,7 @@ impl Chain for KLV { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { let prepared_messafe = KLV::prepare_message(message); let signature = self.sign_raw(private_key, prepared_messafe.to_vec())?; diff --git a/packages/kos/src/chains/mod.rs b/packages/kos/src/chains/mod.rs index ce2aa299..479f038f 100644 --- a/packages/kos/src/chains/mod.rs +++ b/packages/kos/src/chains/mod.rs @@ -658,7 +658,7 @@ impl ChainRegistry { ( constants::ADA, ChainInfo { - factory: || Box::new(ADA::new(false)), + factory: || Box::new(ADA::new()), supported: true, }, ), diff --git a/packages/kos/src/chains/sol/mod.rs b/packages/kos/src/chains/sol/mod.rs index eab9e520..f6c4c468 100644 --- a/packages/kos/src/chains/sol/mod.rs +++ b/packages/kos/src/chains/sol/mod.rs @@ -95,7 +95,7 @@ impl Chain for SOL { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { self.sign_raw(private_key, message) } diff --git a/packages/kos/src/chains/substrate/mod.rs b/packages/kos/src/chains/substrate/mod.rs index 214bea85..be6da641 100644 --- a/packages/kos/src/chains/substrate/mod.rs +++ b/packages/kos/src/chains/substrate/mod.rs @@ -188,7 +188,7 @@ impl Chain for Substrate { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { self.sign_raw(private_key, message) } diff --git a/packages/kos/src/chains/trx/mod.rs b/packages/kos/src/chains/trx/mod.rs index 8a53c647..d366af64 100644 --- a/packages/kos/src/chains/trx/mod.rs +++ b/packages/kos/src/chains/trx/mod.rs @@ -150,7 +150,7 @@ impl Chain for TRX { &self, private_key: Vec, message: Vec, - legacy: bool, + _legacy: bool, ) -> Result, ChainError> { if private_key.len() != 32 { return Err(ChainError::InvalidPrivateKey); From 38690be46fc425b58e66adf47a0eb0b9089c81a6 Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 15:46:26 -0300 Subject: [PATCH 4/6] fix test --- packages/kos-codec/src/chains/ada/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kos-codec/src/chains/ada/mod.rs b/packages/kos-codec/src/chains/ada/mod.rs index 3b3542ae..6623f57f 100644 --- a/packages/kos-codec/src/chains/ada/mod.rs +++ b/packages/kos-codec/src/chains/ada/mod.rs @@ -112,7 +112,7 @@ mod test { let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" .to_string(); - let ada = ADA::new(false); + let ada = ADA::new(); let seed = ada.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let path = ada.get_path(0, false); From 0e2311fd019b344319beb9b2520aa637305d99d9 Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 16:57:13 -0300 Subject: [PATCH 5/6] normlize ada sign message --- packages/kos-codec/src/chains/ada/mod.rs | 8 ++++++-- packages/kos/src/chains/ada/mod.rs | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/kos-codec/src/chains/ada/mod.rs b/packages/kos-codec/src/chains/ada/mod.rs index 6623f57f..2ab9fc3b 100644 --- a/packages/kos-codec/src/chains/ada/mod.rs +++ b/packages/kos-codec/src/chains/ada/mod.rs @@ -59,9 +59,13 @@ pub fn encode_for_broadcast( let tx_body = TransactionBody::decode_fragment(metadata.as_slice()) .map_err(|e| ChainError::InvalidData(e.to_string()))?; - let public_key = + let mut public_key = hex::decode(account.public_key.clone()).map_err(|_| ChainError::InvalidPublicKey)?; + if public_key.len() > 32 { + public_key = public_key[..32].to_vec(); + } + let v_key_witness = VKeyWitness { vkey: public_key.into(), signature: transaction.signature.clone().into(), @@ -117,7 +121,7 @@ mod test { let seed = ada.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let path = ada.get_path(0, false); - let pvk = ada.derive(seed, path).unwrap(); + let pvk: Vec = ada.derive(seed, path).unwrap(); let transaction = Transaction { raw_data: simple_base64_decode("gnkBNmE0MDA4MTgyNTgyMGQxOWMwNTQwOTlkODllMjJiNWJlNTU3ZTI0YzAyMzE0ZGU3YWM5M2Q3ZDFlNjAyZDNiYmZjODY4NDY3OWQzYzEwMDAxODI4MjU4MzkwMWFmMDZmYTVmMWIyOGM5MGJkYzFjODdiYmI2NzMwYmMwZGE5ODY0MjBjNGJkMDBmZDRlNWRkMWYyYWViMGM3NDdjNjhhNDAzYzJlY2UwNWE3OTg4MWVmZTk0YWVjMmVjOTIyZmU0YmQxYzA4ZTNkNjMxYTAwMGY0MjQwODI1ODFkNjFkNTVmNDUzZjkzOTU0NzU1OTEzOTkxZDIxMTk1MmU0YmRkZmNjZDllZWE3ZTQyNDk2N2E3NzlmNDFhMDEwZjcxYTEwMjFhMDAwMzM2ZGYwMzFhMDhmNzFlOTWham9wZXJhdGlvbnOBpnRvcGVyYXRpb25faWRlbnRpZmllcqFlaW5kZXgAZHR5cGVlaW5wdXRmc3RhdHVzYGdhY2NvdW50oWdhZGRyZXNzeDphZGRyMXY4MjQ3M2ZsancyNXc0djM4eGdheXl2NDllOWFtbHhkbm00OHVzamZ2N25obmFxOXYyNTl1ZmFtb3VudKJldmFsdWVoMTkwMDAwMDBoY3VycmVuY3miZnN5bWJvbGNBREFoZGVjaW1hbHMGa2NvaW5fY2hhbmdlom9jb2luX2lkZW50aWZpZXKhamlkZW50aWZpZXJ4QmQxOWMwNTQwOTlkODllMjJiNWJlNTU3ZTI0YzAyMzE0ZGU3YWM5M2Q3ZDFlNjAyZDNiYmZjODY4NDY3OWQzYzE6MGtjb2luX2FjdGlvbmpjb2luX3NwZW50").unwrap(), diff --git a/packages/kos/src/chains/ada/mod.rs b/packages/kos/src/chains/ada/mod.rs index b1a33972..928cb16f 100644 --- a/packages/kos/src/chains/ada/mod.rs +++ b/packages/kos/src/chains/ada/mod.rs @@ -81,7 +81,14 @@ impl Chain for ADA { cc.copy_from_slice(&private_key[64..]); let vk = self.get_pbk(pvk.to_vec())?; - Ok(vk) + let mut xvk = Vec::new(); + xvk.append(&mut vk.to_vec()); + xvk.append(&mut cc.to_vec()); + + pvk.fill(0); + cc.fill(0); + + Ok(xvk) } _ => Err(ChainError::InvalidPrivateKey), } From 5719f03e001a13a31a03ca3b5d7724b9fe49261d Mon Sep 17 00:00:00 2001 From: Gustavo Britto Date: Thu, 10 Apr 2025 17:20:30 -0300 Subject: [PATCH 6/6] fix tests --- packages/kos/src/chains/substrate/mod.rs | 26 ++++++++++++++++++++---- packages/kos/src/chains/sui/mod.rs | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/kos/src/chains/substrate/mod.rs b/packages/kos/src/chains/substrate/mod.rs index be6da641..e602df4d 100644 --- a/packages/kos/src/chains/substrate/mod.rs +++ b/packages/kos/src/chains/substrate/mod.rs @@ -226,8 +226,8 @@ mod test { use crate::crypto::base64::simple_base64_decode; use alloc::string::{String, ToString}; use alloc::vec::Vec; + use schnorrkel; use serde::Deserialize; - #[derive(Deserialize)] struct TxBrowser { #[serde(rename = "specVersion")] @@ -720,11 +720,29 @@ mod test { let seed = dot.mnemonic_to_seed(mnemonic, "".to_string()).unwrap(); let path = dot.get_path(0, false); let pvk = dot.derive(seed, path).unwrap(); + let pbk = dot.get_pbk(pvk.clone()).unwrap(); let message = "test message".as_bytes().to_vec(); - let result = dot.sign_message(pvk.clone(), message, false).unwrap(); + let result = dot + .sign_message(pvk.clone(), message.clone(), false) + .unwrap(); + + let secret_key = schnorrkel::SecretKey::from_bytes(&pvk).unwrap(); + let public_key = schnorrkel::PublicKey::from_bytes(&pbk).unwrap(); + + let key_pair = schnorrkel::Keypair { + secret: secret_key, + public: public_key, + }; + + let signature = schnorrkel::Signature::from_bytes(&result).unwrap(); + + let substrate_ctx: &[u8; 9] = b"substrate"; + + let verify_result = key_pair + .verify_simple(substrate_ctx, &message, &signature) + .unwrap(); - // Same transaction signed with same key should produce same signature and hash - assert_eq!(hex::encode(&result), "2099af82b371ef0f2409e56369a65c03cb92962efbca766099c6f3dccda5cb64a6f8c2c74153ae0dc4dd05e911daea242f9d593635af23f07495f55e16afc783"); + assert_eq!(true, verify_result == ()); } } diff --git a/packages/kos/src/chains/sui/mod.rs b/packages/kos/src/chains/sui/mod.rs index ddd1b66f..b38df464 100644 --- a/packages/kos/src/chains/sui/mod.rs +++ b/packages/kos/src/chains/sui/mod.rs @@ -151,7 +151,7 @@ mod test { let message_bytes = "test message".as_bytes().to_vec(); - let signature = chain.sign_message(pvk, message_bytes, false).unwrap(); + let signature = chain.sign_message(pvk, message_bytes, true).unwrap(); assert_eq!( hex::encode(signature),