-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cli command for message sign/verify (#280)
* Add cli command for message sign/verify * Handle different account types for get_address_private_key * Compare address to created data directly As opposed to storing temporarily to a map * Get value by index after len check * Code refactoring to match
- Loading branch information
1 parent
cfa54c9
commit 41d14dd
Showing
2 changed files
with
157 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
use kaspa_addresses::Version; | ||
use kaspa_bip32::secp256k1::XOnlyPublicKey; | ||
use kaspa_wallet_core::message::{sign_message, verify_message, PersonalMessage}; | ||
|
||
use crate::imports::*; | ||
|
||
#[derive(Default)] | ||
pub struct Message; | ||
|
||
#[async_trait] | ||
impl Handler for Message { | ||
fn verb(&self, _ctx: &Arc<dyn Context>) -> Option<&'static str> { | ||
Some("message") | ||
} | ||
|
||
fn help(&self, _ctx: &Arc<dyn Context>) -> &'static str { | ||
"Sign a message or verify a message signature" | ||
} | ||
|
||
async fn handle(self: Arc<Self>, ctx: &Arc<dyn Context>, argv: Vec<String>, cmd: &str) -> cli::Result<()> { | ||
let ctx = ctx.clone().downcast_arc::<KaspaCli>()?; | ||
self.main(ctx, argv, cmd).await.map_err(|e| e.into()) | ||
} | ||
} | ||
|
||
impl Message { | ||
async fn main(self: Arc<Self>, ctx: Arc<KaspaCli>, argv: Vec<String>, _cmd: &str) -> Result<()> { | ||
if argv.is_empty() { | ||
return self.display_help(ctx, argv).await; | ||
} | ||
|
||
match argv.get(0).unwrap().as_str() { | ||
"sign" => { | ||
if argv.len() != 2 { | ||
return self.display_help(ctx, argv).await; | ||
} | ||
|
||
let kaspa_address = argv[1].as_str(); | ||
let asked_message = ctx.term().ask(false, "Message: ").await?; | ||
let message = asked_message.as_str(); | ||
|
||
self.sign(ctx, kaspa_address, message).await?; | ||
} | ||
"verify" => { | ||
if argv.len() != 3 { | ||
return self.display_help(ctx, argv).await; | ||
} | ||
let kaspa_address = argv[1].as_str(); | ||
let signature = argv[2].as_str(); | ||
let asked_message = ctx.term().ask(false, "Message: ").await?; | ||
let message = asked_message.as_str(); | ||
|
||
self.verify(ctx, kaspa_address, signature, message).await?; | ||
} | ||
v => { | ||
tprintln!(ctx, "unknown command: '{v}'\r\n"); | ||
return self.display_help(ctx, argv).await; | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
async fn display_help(self: Arc<Self>, ctx: Arc<KaspaCli>, _argv: Vec<String>) -> Result<()> { | ||
ctx.term().help( | ||
&[ | ||
("sign <kaspa_address>", "Sign a message with the private key that matches the given address. Prompts for message."), | ||
( | ||
"verify <kaspa_address> <signature>", | ||
"Verify the signature against the message and kaspa_address. Prompts for message.", | ||
), | ||
], | ||
None, | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
async fn sign(self: Arc<Self>, ctx: Arc<KaspaCli>, kaspa_address: &str, message: &str) -> Result<()> { | ||
let kaspa_address = Address::try_from(kaspa_address)?; | ||
if kaspa_address.version != Version::PubKey { | ||
return Err(Error::custom("Address not supported for message signing. Only supports PubKey addresses")); | ||
} | ||
|
||
let pm = PersonalMessage(message); | ||
let privkey = self.get_address_private_key(&ctx, kaspa_address).await?; | ||
|
||
let sig_result = sign_message(&pm, &privkey); | ||
|
||
match sig_result { | ||
Ok(signature) => { | ||
let sig_hex = faster_hex::hex_string(signature.as_slice()); | ||
tprintln!(ctx, "Signature: {}", sig_hex); | ||
Ok(()) | ||
} | ||
Err(_) => Err(Error::custom("Message signing failed")), | ||
} | ||
} | ||
|
||
async fn verify(self: Arc<Self>, ctx: Arc<KaspaCli>, kaspa_address: &str, signature: &str, message: &str) -> Result<()> { | ||
let kaspa_address = Address::try_from(kaspa_address)?; | ||
if kaspa_address.version != Version::PubKey { | ||
return Err(Error::custom("Address not supported for message signing. Only supports PubKey addresses")); | ||
} | ||
|
||
let pubkey = XOnlyPublicKey::from_slice(&kaspa_address.payload[0..32]).unwrap(); | ||
|
||
let mut signature_hex = [0u8; 64]; | ||
faster_hex::hex_decode(signature.as_bytes(), &mut signature_hex)?; | ||
|
||
let pm = PersonalMessage(message); | ||
let verify_result = verify_message(&pm, &signature_hex.to_vec(), &pubkey); | ||
|
||
match verify_result { | ||
Ok(()) => { | ||
tprintln!(ctx, "Message verified successfully!"); | ||
} | ||
Err(_) => { | ||
return Err(Error::custom("Verification failed")); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
async fn get_address_private_key(self: Arc<Self>, ctx: &Arc<KaspaCli>, kaspa_address: Address) -> Result<[u8; 32]> { | ||
let account = ctx.wallet().account()?; | ||
|
||
match account.account_kind() { | ||
AccountKind::Bip32 => { | ||
let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; | ||
let keydata = account.prv_key_data(wallet_secret).await?; | ||
let account = account.clone().as_derivation_capable().expect("expecting derivation capable"); | ||
|
||
let (receive, change) = account.derivation().addresses_indexes(&[&kaspa_address])?; | ||
let private_keys = account.create_private_keys(&keydata, &payment_secret, &receive, &change)?; | ||
for (address, private_key) in private_keys { | ||
if kaspa_address == *address { | ||
return Ok(private_key.secret_bytes()); | ||
} | ||
} | ||
|
||
Err(Error::custom("Could not find address in any derivation path in account")) | ||
} | ||
AccountKind::Keypair => { | ||
let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; | ||
let keydata = account.prv_key_data(wallet_secret).await?; | ||
let decrypted_privkey = keydata.payload.decrypt(payment_secret.as_ref()).unwrap(); | ||
let secretkey = decrypted_privkey.as_secret_key()?.unwrap(); | ||
Ok(secretkey.secret_bytes()) | ||
} | ||
_ => Err(Error::custom("Unsupported account kind")), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters