sc_cli/commands/
sign.rs

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