1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Implementation of the `sign` subcommand
use crate::{
	error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams,
};
use array_bytes::bytes2hex;
use clap::Parser;
use sp_core::crypto::SecretString;
use std::io::{BufRead, Write};

/// The `sign` command
#[derive(Debug, Clone, Parser)]
#[command(name = "sign", about = "Sign a message, with a given (secret) key")]
pub struct SignCmd {
	/// The secret key URI.
	/// If the value is a file, the file content is used as URI.
	/// If not given, you will be prompted for the URI.
	#[arg(long)]
	suri: Option<String>,

	#[allow(missing_docs)]
	#[clap(flatten)]
	pub message_params: MessageParams,

	#[allow(missing_docs)]
	#[clap(flatten)]
	pub keystore_params: KeystoreParams,

	#[allow(missing_docs)]
	#[clap(flatten)]
	pub crypto_scheme: CryptoSchemeFlag,
}

impl SignCmd {
	/// Run the command
	pub fn run(&self) -> error::Result<()> {
		let sig = self.sign(|| std::io::stdin().lock())?;
		std::io::stdout().lock().write_all(sig.as_bytes())?;
		Ok(())
	}

	/// Sign a message.
	///
	/// The message can either be provided as immediate argument via CLI or otherwise read from the
	/// reader created by `create_reader`. The reader will only be created in case that the message
	/// is not passed as immediate.
	pub(crate) fn sign<F, R>(&self, create_reader: F) -> error::Result<String>
	where
		R: BufRead,
		F: FnOnce() -> R,
	{
		let message = self.message_params.message_from(create_reader)?;
		let suri = utils::read_uri(self.suri.as_ref())?;
		let password = self.keystore_params.read_password()?;

		with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))
	}
}

fn sign<P: sp_core::Pair>(
	suri: &str,
	password: Option<SecretString>,
	message: Vec<u8>,
) -> error::Result<String> {
	let pair = utils::pair_from_suri::<P>(suri, password)?;
	Ok(bytes2hex("0x", pair.sign(&message).as_ref()))
}

#[cfg(test)]
mod test {
	use super::*;

	const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";

	#[test]
	fn sign_arg() {
		let cmd = SignCmd::parse_from(&[
			"sign",
			"--suri",
			&SEED,
			"--message",
			&SEED,
			"--password",
			"12345",
			"--hex",
		]);
		let sig = cmd.sign(|| std::io::stdin().lock()).expect("Must sign");

		assert!(sig.starts_with("0x"), "Signature must start with 0x");
		assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
	}

	#[test]
	fn sign_stdin() {
		let cmd = SignCmd::parse_from(&[
			"sign",
			"--suri",
			SEED,
			"--message",
			&SEED,
			"--password",
			"12345",
		]);
		let sig = cmd.sign(|| SEED.as_bytes()).expect("Must sign");

		assert!(sig.starts_with("0x"), "Signature must start with 0x");
		assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
	}
}