kaspa_cli_lib/modules/
message.rs

1use 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}