kaspa_cli_lib/modules/
message.rs1use kaspa_addresses::Version;
2use kaspa_bip32::secp256k1::XOnlyPublicKey;
3use kaspa_wallet_core::{
4 account::{BIP32_ACCOUNT_KIND, KEYPAIR_ACCOUNT_KIND},
5 message::{sign_message, verify_message, PersonalMessage},
6};
7
8use crate::imports::*;
9
10#[derive(Default)]
11pub struct Message;
12
13#[async_trait]
14impl Handler for Message {
15 fn verb(&self, _ctx: &Arc<dyn Context>) -> Option<&'static str> {
16 Some("message")
17 }
18
19 fn help(&self, _ctx: &Arc<dyn Context>) -> &'static str {
20 "Sign a message or verify a message signature"
21 }
22
23 async fn handle(self: Arc<Self>, ctx: &Arc<dyn Context>, argv: Vec<String>, cmd: &str) -> cli::Result<()> {
24 let ctx = ctx.clone().downcast_arc::<KaspaCli>()?;
25 self.main(ctx, argv, cmd).await.map_err(|e| e.into())
26 }
27}
28
29impl Message {
30 async fn main(self: Arc<Self>, ctx: Arc<KaspaCli>, argv: Vec<String>, _cmd: &str) -> Result<()> {
31 if argv.is_empty() {
32 return self.display_help(ctx, argv).await;
33 }
34
35 match argv.first().unwrap().as_str() {
36 "sign" => {
37 if argv.len() != 2 {
38 return self.display_help(ctx, argv).await;
39 }
40
41 let kaspa_address = argv[1].as_str();
42 let asked_message = ctx.term().ask(false, "Message: ").await?;
43 let message = asked_message.as_str();
44
45 self.sign(ctx, kaspa_address, message).await?;
46 }
47 "verify" => {
48 if argv.len() != 3 {
49 return self.display_help(ctx, argv).await;
50 }
51 let kaspa_address = argv[1].as_str();
52 let signature = argv[2].as_str();
53 let asked_message = ctx.term().ask(false, "Message: ").await?;
54 let message = asked_message.as_str();
55
56 self.verify(ctx, kaspa_address, signature, message).await?;
57 }
58 v => {
59 tprintln!(ctx, "unknown command: '{v}'\r\n");
60 return self.display_help(ctx, argv).await;
61 }
62 }
63
64 Ok(())
65 }
66
67 async fn display_help(self: Arc<Self>, ctx: Arc<KaspaCli>, _argv: Vec<String>) -> Result<()> {
68 ctx.term().help(
69 &[
70 ("sign <kaspa_address>", "Sign a message with the private key that matches the given address. Prompts for message."),
71 (
72 "verify <kaspa_address> <signature>",
73 "Verify the signature against the message and kaspa_address. Prompts for message.",
74 ),
75 ],
76 None,
77 )?;
78
79 Ok(())
80 }
81
82 async fn sign(self: Arc<Self>, ctx: Arc<KaspaCli>, kaspa_address: &str, message: &str) -> Result<()> {
83 let kaspa_address = Address::try_from(kaspa_address)?;
84 if kaspa_address.version != Version::PubKey {
85 return Err(Error::custom("Address not supported for message signing. Only supports PubKey addresses"));
86 }
87
88 let pm = PersonalMessage(message);
89 let privkey = self.get_address_private_key(&ctx, kaspa_address).await?;
90
91 let sig_result = sign_message(&pm, &privkey);
92
93 match sig_result {
94 Ok(signature) => {
95 let sig_hex = faster_hex::hex_string(signature.as_slice());
96 tprintln!(ctx, "Signature: {}", sig_hex);
97 Ok(())
98 }
99 Err(_) => Err(Error::custom("Message signing failed")),
100 }
101 }
102
103 async fn verify(self: Arc<Self>, ctx: Arc<KaspaCli>, kaspa_address: &str, signature: &str, message: &str) -> Result<()> {
104 let kaspa_address = Address::try_from(kaspa_address)?;
105 if kaspa_address.version != Version::PubKey {
106 return Err(Error::custom("Address not supported for message signing. Only supports PubKey addresses"));
107 }
108
109 let pubkey = XOnlyPublicKey::from_slice(&kaspa_address.payload[0..32]).unwrap();
110
111 let mut signature_hex = [0u8; 64];
112 faster_hex::hex_decode(signature.as_bytes(), &mut signature_hex)?;
113
114 let pm = PersonalMessage(message);
115 let verify_result = verify_message(&pm, &signature_hex.to_vec(), &pubkey);
116
117 match verify_result {
118 Ok(()) => {
119 tprintln!(ctx, "Message verified successfully!");
120 }
121 Err(_) => {
122 return Err(Error::custom("Verification failed"));
123 }
124 }
125
126 Ok(())
127 }
128
129 async fn get_address_private_key(self: Arc<Self>, ctx: &Arc<KaspaCli>, kaspa_address: Address) -> Result<[u8; 32]> {
130 let account = ctx.wallet().account()?;
131
132 match account.account_kind().as_ref() {
133 BIP32_ACCOUNT_KIND => {
134 let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?;
135 let keydata = account.prv_key_data(wallet_secret).await?;
136 let account = account.clone().as_derivation_capable().expect("expecting derivation capable");
137
138 let (receive, change) = account.derivation().addresses_indexes(&[&kaspa_address])?;
139 let private_keys = account.create_private_keys(&keydata, &payment_secret, &receive, &change)?;
140 for (address, private_key) in private_keys {
141 if kaspa_address == *address {
142 return Ok(private_key.secret_bytes());
143 }
144 }
145
146 Err(Error::custom("Could not find address in any derivation path in account"))
147 }
148 KEYPAIR_ACCOUNT_KIND => {
149 let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?;
150 let keydata = account.prv_key_data(wallet_secret).await?;
151 let decrypted_privkey = keydata.payload.decrypt(payment_secret.as_ref()).unwrap();
152 let secretkey = decrypted_privkey.as_secret_key()?.unwrap();
153 Ok(secretkey.secret_bytes())
154 }
155 _ => Err(Error::custom("Unsupported account kind")),
156 }
157 }
158}