1use crate::errors::CairnError;
8
9pub fn sign_eip4361_message(message: &str, private_key_hex: &str) -> Result<String, CairnError> {
12 use ethers::signers::{LocalWallet, Signer};
13 use ethers::core::types::Signature;
14
15 let wallet: LocalWallet = private_key_hex
16 .parse()
17 .map_err(|e: ethers::signers::WalletError| CairnError::SignatureError(e.to_string()))?;
18
19 let rt = tokio::runtime::Handle::current();
21 let sig: Signature = rt
22 .block_on(wallet.sign_message(message))
23 .map_err(|e| CairnError::SignatureError(e.to_string()))?;
24
25 Ok(format!("0x{}", sig))
26}
27
28pub fn ed25519_sign(payload: &[u8], secret_key_bytes: &[u8]) -> Result<Vec<u8>, CairnError> {
30 use ed25519_dalek::{SigningKey, Signer};
31
32 if secret_key_bytes.len() != 32 {
33 return Err(CairnError::SignatureError(
34 "Ed25519 secret key must be 32 bytes".to_string(),
35 ));
36 }
37
38 let mut key_bytes = [0u8; 32];
39 key_bytes.copy_from_slice(secret_key_bytes);
40 let signing_key = SigningKey::from_bytes(&key_bytes);
41 let signature = signing_key.sign(payload);
42
43 Ok(signature.to_bytes().to_vec())
44}
45
46pub fn read_key_file(path: &str) -> Result<Vec<u8>, CairnError> {
48 let content = std::fs::read_to_string(path)
49 .map_err(|e| CairnError::InvalidInput(format!("Cannot read key file '{}': {}", path, e)))?;
50
51 let trimmed = content.trim();
52
53 if trimmed.starts_with('[') {
55 if let Ok(bytes) = serde_json::from_str::<Vec<u8>>(trimmed) {
56 return Ok(bytes);
57 }
58 }
59
60 if let Ok(bytes) = hex::decode(trimmed) {
62 return Ok(bytes);
63 }
64
65 if let Ok(bytes) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, trimmed) {
67 return Ok(bytes);
68 }
69
70 Ok(trimmed.as_bytes().to_vec())
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn debug_solana_signature_mismatch() {
80 let wallet_file: Vec<u8> = vec![114,147,178,86,109,101,92,89,64,113,139,40,164,247,79,32,2,170,19,0,34,189,59,224,230,196,225,47,14,188,224,220,160,91,118,254,241,12,235,238,190,24,75,113,77,47,51,79,21,242,145,102,154,93,23,141,237,38,200,128,108,195,168,227];
81
82 let secret = &wallet_file[..32];
84
85 let wallet_address = bs58::encode(&wallet_file[32..]).into_string();
86 let expected_challenge = format!(
87 "Welcome to Backpac Agent Access.\n\n\
88 Please sign this message to verify ownership of this wallet and establish a session.\n\n\
89 Wallet: {}\n\
90 DID: {}\n\
91 Chain: {}\n\
92 Nonce: {}",
93 wallet_address.to_lowercase(),
94 "did:key:z6MkhaXgBZDvotDkL5257faiztiCEsJUTtcCjdReE7m1",
95 "solana:devnet",
96 "bb82335399edce14da9652840e11ea873f1f982664399c3c9000df07bced96d0"
97 );
98
99 let sig_bytes = ed25519_sign(expected_challenge.as_bytes(), secret).unwrap();
100 let sig_b58 = bs58::encode(sig_bytes).into_string();
101 println!("Rust Signature (Base58): {}", sig_b58);
102 assert_eq!(sig_b58, "5S2LiVAuHoMdLc12qAiyXVfYHBuU2qRFxbBgQV1hgwRz2KgSVeWzkLhDJVUiaepkKWPNYkiMJARiCDSxnPvsEDmR");
103 }
104}