Skip to main content

soil_cli/commands/
sign.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Implementation of the `sign` subcommand
8use crate::{
9	error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams,
10};
11use array_bytes::bytes2hex;
12use clap::Parser;
13use std::io::{BufRead, Write};
14use subsoil::core::crypto::SecretString;
15
16/// The `sign` command
17#[derive(Debug, Clone, Parser)]
18#[command(name = "sign", about = "Sign a message, with a given (secret) key")]
19pub struct SignCmd {
20	/// The secret key URI.
21	/// If the value is a file, the file content is used as URI.
22	/// If not given, you will be prompted for the URI.
23	#[arg(long)]
24	suri: Option<String>,
25
26	#[allow(missing_docs)]
27	#[clap(flatten)]
28	pub message_params: MessageParams,
29
30	#[allow(missing_docs)]
31	#[clap(flatten)]
32	pub keystore_params: KeystoreParams,
33
34	#[allow(missing_docs)]
35	#[clap(flatten)]
36	pub crypto_scheme: CryptoSchemeFlag,
37}
38
39impl SignCmd {
40	/// Run the command
41	pub fn run(&self) -> error::Result<()> {
42		let sig = self.sign(|| std::io::stdin().lock())?;
43		std::io::stdout().lock().write_all(sig.as_bytes())?;
44		Ok(())
45	}
46
47	/// Sign a message.
48	///
49	/// The message can either be provided as immediate argument via CLI or otherwise read from the
50	/// reader created by `create_reader`. The reader will only be created in case that the message
51	/// is not passed as immediate.
52	pub(crate) fn sign<F, R>(&self, create_reader: F) -> error::Result<String>
53	where
54		R: BufRead,
55		F: FnOnce() -> R,
56	{
57		let message = self.message_params.message_from(create_reader)?;
58		let suri = utils::read_uri(self.suri.as_ref())?;
59		let password = self.keystore_params.read_password()?;
60
61		with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))
62	}
63}
64
65fn sign<P: subsoil::core::Pair>(
66	suri: &str,
67	password: Option<SecretString>,
68	message: Vec<u8>,
69) -> error::Result<String> {
70	let pair = utils::pair_from_suri::<P>(suri, password)?;
71	Ok(bytes2hex("0x", pair.sign(&message).as_ref()))
72}
73
74#[cfg(test)]
75mod test {
76	use super::*;
77
78	const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
79
80	#[test]
81	fn sign_arg() {
82		let cmd = SignCmd::parse_from(&[
83			"sign",
84			"--suri",
85			&SEED,
86			"--message",
87			&SEED,
88			"--password",
89			"12345",
90			"--hex",
91		]);
92		let sig = cmd.sign(|| std::io::stdin().lock()).expect("Must sign");
93
94		assert!(sig.starts_with("0x"), "Signature must start with 0x");
95		assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
96	}
97
98	#[test]
99	fn sign_stdin() {
100		let cmd = SignCmd::parse_from(&[
101			"sign",
102			"--suri",
103			SEED,
104			"--message",
105			&SEED,
106			"--password",
107			"12345",
108		]);
109		let sig = cmd.sign(|| SEED.as_bytes()).expect("Must sign");
110
111		assert!(sig.starts_with("0x"), "Signature must start with 0x");
112		assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
113	}
114}