Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit aceb761

Browse files
ggwpeznuke-web3
authored andcommitted
subkey: only decode hex if requested, CLI 0x prefixed hex for all stdout (#13258)
* Only decode hex if requested Signed-off-by: Oliver Tale-Yazdi <[email protected]> * Cleanup code Signed-off-by: Oliver Tale-Yazdi <[email protected]> * Add some tests Signed-off-by: Oliver Tale-Yazdi <[email protected]> * Add license Signed-off-by: Oliver Tale-Yazdi <[email protected]> * Docs Signed-off-by: Oliver Tale-Yazdi <[email protected]> * Clippy Signed-off-by: Oliver Tale-Yazdi <[email protected]> * bump version, breaking (tiny) change in output. * Move integration tests to own folder Signed-off-by: Oliver Tale-Yazdi <[email protected]> --------- Signed-off-by: Oliver Tale-Yazdi <[email protected]> Co-authored-by: Dan Shields <[email protected]>
1 parent a056cd4 commit aceb761

File tree

10 files changed

+412
-53
lines changed

10 files changed

+412
-53
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/utils/subkey/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "subkey"
3-
version = "2.0.2"
3+
version = "3.0.0"
44
authors = ["Parity Technologies <[email protected]>"]
55
edition = "2021"
66
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"

client/cli/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod purge_chain_cmd;
3131
mod revert_cmd;
3232
mod run_cmd;
3333
mod sign;
34+
mod test;
3435
pub mod utils;
3536
mod vanity;
3637
mod verify;

client/cli/src/commands/sign.rs

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
//! Implementation of the `sign` subcommand
20-
use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams};
20+
use crate::{
21+
error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams,
22+
};
23+
use array_bytes::bytes2hex;
2124
use clap::Parser;
2225
use sp_core::crypto::SecretString;
26+
use std::io::{BufRead, Write};
2327

2428
/// The `sign` command
2529
#[derive(Debug, Clone, Parser)]
@@ -31,14 +35,9 @@ pub struct SignCmd {
3135
#[arg(long)]
3236
suri: Option<String>,
3337

34-
/// Message to sign, if not provided you will be prompted to
35-
/// pass the message via STDIN
36-
#[arg(long)]
37-
message: Option<String>,
38-
39-
/// The message on STDIN is hex-encoded data
40-
#[arg(long)]
41-
hex: bool,
38+
#[allow(missing_docs)]
39+
#[clap(flatten)]
40+
pub message_params: MessageParams,
4241

4342
#[allow(missing_docs)]
4443
#[clap(flatten)]
@@ -52,15 +51,26 @@ pub struct SignCmd {
5251
impl SignCmd {
5352
/// Run the command
5453
pub fn run(&self) -> error::Result<()> {
55-
let message = utils::read_message(self.message.as_ref(), self.hex)?;
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)?;
5670
let suri = utils::read_uri(self.suri.as_ref())?;
5771
let password = self.keystore_params.read_password()?;
5872

59-
let signature =
60-
with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))?;
61-
62-
println!("{}", signature);
63-
Ok(())
73+
with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))
6474
}
6575
}
6676

@@ -70,26 +80,47 @@ fn sign<P: sp_core::Pair>(
7080
message: Vec<u8>,
7181
) -> error::Result<String> {
7282
let pair = utils::pair_from_suri::<P>(suri, password)?;
73-
Ok(array_bytes::bytes2hex("", pair.sign(&message).as_ref()))
83+
Ok(bytes2hex("0x", pair.sign(&message).as_ref()))
7484
}
7585

7686
#[cfg(test)]
7787
mod test {
7888
use super::*;
7989

90+
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
91+
8092
#[test]
81-
fn sign() {
82-
let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
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+
}
83109

84-
let sign = SignCmd::parse_from(&[
110+
#[test]
111+
fn sign_stdin() {
112+
let cmd = SignCmd::parse_from(&[
85113
"sign",
86114
"--suri",
87-
seed,
115+
SEED,
88116
"--message",
89-
&seed[2..],
117+
&SEED,
90118
"--password",
91119
"12345",
92120
]);
93-
assert!(sign.run().is_ok());
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");
94125
}
95126
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2023 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+
//! Integration tests for subkey commands.
20+
21+
mod sig_verify;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2023 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+
#![cfg(test)]
20+
21+
//! Integration test that the `sign` and `verify` sub-commands work together.
22+
23+
use crate::*;
24+
use clap::Parser;
25+
26+
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
27+
const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
28+
const BOB: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
29+
30+
/// Sign a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
31+
fn sign(msg: &str, hex: bool, stdin: bool) -> String {
32+
sign_raw(msg.as_bytes(), hex, stdin)
33+
}
34+
35+
/// Sign a raw message which can be `hex` and passed either via `stdin` or as an argument.
36+
fn sign_raw(msg: &[u8], hex: bool, stdin: bool) -> String {
37+
let mut args = vec!["sign", "--suri", SEED];
38+
if !stdin {
39+
args.push("--message");
40+
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
41+
}
42+
if hex {
43+
args.push("--hex");
44+
}
45+
let cmd = SignCmd::parse_from(&args);
46+
cmd.sign(|| msg).expect("Static data is good; Must sign; qed")
47+
}
48+
49+
/// Verify a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
50+
fn verify(msg: &str, hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
51+
verify_raw(msg.as_bytes(), hex, stdin, who, sig)
52+
}
53+
54+
/// Verify a raw message which can be `hex` and passed either via `stdin` or as an argument.
55+
fn verify_raw(msg: &[u8], hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
56+
let mut args = vec!["verify", sig, who];
57+
if !stdin {
58+
args.push("--message");
59+
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
60+
}
61+
if hex {
62+
args.push("--hex");
63+
}
64+
let cmd = VerifyCmd::parse_from(&args);
65+
cmd.verify(|| msg).is_ok()
66+
}
67+
68+
/// Test that sig/verify works with UTF-8 bytes passed as arg.
69+
#[test]
70+
fn sig_verify_arg_utf8_work() {
71+
let sig = sign("Something", false, false);
72+
73+
assert!(verify("Something", false, false, ALICE, &sig));
74+
assert!(!verify("Something", false, false, BOB, &sig));
75+
76+
assert!(!verify("Wrong", false, false, ALICE, &sig));
77+
assert!(!verify("Not hex", true, false, ALICE, &sig));
78+
assert!(!verify("0x1234", true, false, ALICE, &sig));
79+
assert!(!verify("Wrong", false, false, BOB, &sig));
80+
assert!(!verify("Not hex", true, false, BOB, &sig));
81+
assert!(!verify("0x1234", true, false, BOB, &sig));
82+
}
83+
84+
/// Test that sig/verify works with UTF-8 bytes passed via stdin.
85+
#[test]
86+
fn sig_verify_stdin_utf8_work() {
87+
let sig = sign("Something", false, true);
88+
89+
assert!(verify("Something", false, true, ALICE, &sig));
90+
assert!(!verify("Something", false, true, BOB, &sig));
91+
92+
assert!(!verify("Wrong", false, true, ALICE, &sig));
93+
assert!(!verify("Not hex", true, true, ALICE, &sig));
94+
assert!(!verify("0x1234", true, true, ALICE, &sig));
95+
assert!(!verify("Wrong", false, true, BOB, &sig));
96+
assert!(!verify("Not hex", true, true, BOB, &sig));
97+
assert!(!verify("0x1234", true, true, BOB, &sig));
98+
}
99+
100+
/// Test that sig/verify works with hex bytes passed as arg.
101+
#[test]
102+
fn sig_verify_arg_hex_work() {
103+
let sig = sign("0xaabbcc", true, false);
104+
105+
assert!(verify("0xaabbcc", true, false, ALICE, &sig));
106+
assert!(verify("aabBcc", true, false, ALICE, &sig));
107+
assert!(verify("0xaAbbCC", true, false, ALICE, &sig));
108+
assert!(!verify("0xaabbcc", true, false, BOB, &sig));
109+
110+
assert!(!verify("0xaabbcc", false, false, ALICE, &sig));
111+
}
112+
113+
/// Test that sig/verify works with hex bytes passed via stdin.
114+
#[test]
115+
fn sig_verify_stdin_hex_work() {
116+
let sig = sign("0xaabbcc", true, true);
117+
118+
assert!(verify("0xaabbcc", true, true, ALICE, &sig));
119+
assert!(verify("aabBcc", true, true, ALICE, &sig));
120+
assert!(verify("0xaAbbCC", true, true, ALICE, &sig));
121+
assert!(!verify("0xaabbcc", true, true, BOB, &sig));
122+
123+
assert!(!verify("0xaabbcc", false, true, ALICE, &sig));
124+
}
125+
126+
/// Test that sig/verify works with random bytes.
127+
#[test]
128+
fn sig_verify_stdin_non_utf8_work() {
129+
use rand::RngCore;
130+
let mut rng = rand::thread_rng();
131+
132+
for _ in 0..100 {
133+
let mut raw = [0u8; 32];
134+
rng.fill_bytes(&mut raw);
135+
let sig = sign_raw(&raw, false, true);
136+
137+
assert!(verify_raw(&raw, false, true, ALICE, &sig));
138+
assert!(!verify_raw(&raw, false, true, BOB, &sig));
139+
}
140+
}
141+
142+
/// Test that sig/verify works with invalid UTF-8 bytes.
143+
#[test]
144+
fn sig_verify_stdin_invalid_utf8_work() {
145+
let raw = vec![192u8, 193];
146+
assert!(String::from_utf8(raw.clone()).is_err(), "Must be invalid UTF-8");
147+
148+
let sig = sign_raw(&raw, false, true);
149+
150+
assert!(verify_raw(&raw, false, true, ALICE, &sig));
151+
assert!(!verify_raw(&raw, false, true, BOB, &sig));
152+
}

client/cli/src/commands/utils.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use sp_core::{
3131
Pair,
3232
};
3333
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
34-
use std::{io::Read, path::PathBuf};
34+
use std::path::PathBuf;
3535

3636
/// Public key type for Runtime
3737
pub type PublicFor<P> = <P as sp_core::Pair>::Public;
@@ -273,23 +273,6 @@ where
273273
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
274274
}
275275

276-
/// checks if message is Some, otherwise reads message from stdin and optionally decodes hex
277-
pub fn read_message(msg: Option<&String>, should_decode: bool) -> Result<Vec<u8>, Error> {
278-
let mut message = vec![];
279-
match msg {
280-
Some(m) => {
281-
message = array_bytes::hex2bytes(m.as_str())?;
282-
},
283-
None => {
284-
std::io::stdin().lock().read_to_end(&mut message)?;
285-
if should_decode {
286-
message = array_bytes::hex2bytes(array_bytes::hex_bytes2hex_str(&message)?)?;
287-
}
288-
},
289-
}
290-
Ok(message)
291-
}
292-
293276
/// Allows for calling $method with appropriate crypto impl.
294277
#[macro_export]
295278
macro_rules! with_crypto_scheme {

0 commit comments

Comments
 (0)