From babe638be7d71598c059ad7cff6c5e2847fa6b78 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 24 Apr 2019 12:23:59 +0200 Subject: [PATCH] Subkey can construct and sign transfer txs (#2109) * First effort * Fix for encoding * !fixed subkey xfer creation (still brittle because of double-hardcoded genesis_hash (#2221) * CLI genesis hash * Add test * Slightly nicer text * Fix Elm hash * Update lock file --- substrate/Cargo.lock | 5 ++ substrate/core/primitives/src/crypto.rs | 22 ++++++-- substrate/subkey/Cargo.toml | 5 ++ substrate/subkey/src/cli.yml | 24 ++++++++ substrate/subkey/src/main.rs | 74 ++++++++++++++++++++++++- 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 9069255fcc..452da42fb6 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3731,9 +3731,14 @@ version = "1.0.0" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "node-primitives 1.0.0", + "node-runtime 1.0.0", + "parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "schnorrkel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-primitives 1.0.0", "substrate-bip39 0.2.1 (git+https://github.com/paritytech/substrate-bip39)", "substrate-primitives 1.0.0", "tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/substrate/core/primitives/src/crypto.rs b/substrate/core/primitives/src/crypto.rs index e0ecd4ce42..dc886c7b38 100644 --- a/substrate/core/primitives/src/crypto.rs +++ b/substrate/core/primitives/src/crypto.rs @@ -215,7 +215,9 @@ pub trait Derive: Sized { /// Derive a child key from a series of given junctions. /// /// Will be `None` for public keys if there are any hard junctions in there. - fn derive>(&self, _path: Iter) -> Option { None } + fn derive>(&self, _path: Iter) -> Option { + None + } } #[cfg(feature = "std")] @@ -266,11 +268,19 @@ impl + AsRef<[u8]> + Default + Derive> Ss58Codec for T { let cap = re.captures(s).ok_or(PublicError::InvalidFormat)?; let re_junction = Regex::new(r"/(/?[^/]+)") .expect("constructed from known-good static value; qed"); - let path = re_junction.captures_iter(&cap["path"]) - .map(|f| DeriveJunction::from(&f[1])); - Self::from_ss58check(cap.name("ss58").map(|r| r.as_str()).unwrap_or(DEV_ADDRESS))? - .derive(path) - .ok_or(PublicError::InvalidPath) + let addr = Self::from_ss58check( + cap.name("ss58") + .map(|r| r.as_str()) + .unwrap_or(DEV_ADDRESS) + )?; + if cap["path"].is_empty() { + Ok(addr) + } else { + let path = re_junction.captures_iter(&cap["path"]) + .map(|f| DeriveJunction::from(&f[1])); + addr.derive(path) + .ok_or(PublicError::InvalidPath) + } } } diff --git a/substrate/subkey/Cargo.toml b/substrate/subkey/Cargo.toml index 12ae7bdbbe..280f18887e 100644 --- a/substrate/subkey/Cargo.toml +++ b/substrate/subkey/Cargo.toml @@ -6,6 +6,9 @@ edition = "2018" [dependencies] substrate-primitives = { version = "*", path = "../core/primitives" } +node-runtime = { version = "*", path = "../node/runtime" } +node-primitives = { version = "*", path = "../node/primitives" } +sr-primitives = { version = "*", path = "../core/sr-primitives" } rand = "0.6" clap = { version = "~2.32", features = ["yaml"] } tiny-bip39 = "0.6.0" @@ -13,6 +16,8 @@ rustc-hex = "2.0" substrate-bip39 = { git = "https://github.com/paritytech/substrate-bip39" } schnorrkel = "0.1" hex = "0.3" +hex-literal = "0.1" +parity-codec = "3.2" [features] bench = [] diff --git a/substrate/subkey/src/cli.yml b/substrate/subkey/src/cli.yml index cc131703eb..8b839cd443 100644 --- a/substrate/subkey/src/cli.yml +++ b/substrate/subkey/src/cli.yml @@ -40,6 +40,30 @@ subcommands: long: hex help: The message on STDIN is hex-encoded data takes_value: false + - transfer: + about: Author and sign a Node balances::Transfer transaction with a given (secret) key + args: + - from: + index: 1 + required: true + help: The signing secret key URI. + - to: + index: 2 + required: true + help: The destination account public key URI. + - amount: + index: 3 + required: true + help: The number of units to transfer. + - index: + index: 4 + required: true + help: The signing account's transaction index. + - genesis: + short: g + long: genesis + help: The genesis hash or a recognised chain identifier (dev, elm, alex). + takes_value: true - verify: about: Verify a signature for a message, provided on STDIN, with a given (public or secret) key args: diff --git a/substrate/subkey/src/main.rs b/substrate/subkey/src/main.rs index 5caf58d45b..2aa8609623 100644 --- a/substrate/subkey/src/main.rs +++ b/substrate/subkey/src/main.rs @@ -20,14 +20,19 @@ extern crate test; extern crate substrate_bip39; extern crate rustc_hex; +#[macro_use] extern crate hex_literal; use std::io::{stdin, Read}; use clap::load_yaml; use rand::{RngCore, rngs::OsRng}; use substrate_bip39::mini_secret_from_entropy; use bip39::{Mnemonic, Language, MnemonicType}; -use substrate_primitives::{ed25519, sr25519, hexdisplay::HexDisplay, Pair, crypto::Ss58Codec}; +use substrate_primitives::{ed25519, sr25519, hexdisplay::HexDisplay, Pair, crypto::Ss58Codec, blake2_256}; +use parity_codec::{Encode, Decode, Compact}; +use sr_primitives::generic::Era; use schnorrkel::keys::MiniSecretKey; +use node_primitives::{Balance, Index, Hash}; +use node_runtime::{Call, UncheckedExtrinsic, BalancesCall}; mod vanity; @@ -173,6 +178,54 @@ fn execute>(matches: clap::ArgMatches) where let sig = pair.sign(&message); println!("{}", hex::encode(&sig)); } + ("transfer", Some(matches)) => { + let signer = matches.value_of("from") + .expect("parameter is required; thus it can't be None; qed"); + let signer = Sr25519::pair_from_suri(signer, password); + + let to = matches.value_of("to") + .expect("parameter is required; thus it can't be None; qed"); + let to = sr25519::Public::from_string(to).ok().or_else(|| + sr25519::Pair::from_string(to, password).ok().map(|p| p.public()) + ).expect("Invalid 'to' URI; expecting either a secret URI or a public URI."); + + let amount = matches.value_of("amount") + .expect("parameter is required; thus it can't be None; qed"); + let amount = str::parse::(amount) + .expect("Invalid 'amount' parameter; expecting an integer."); + + let index = matches.value_of("index") + .expect("parameter is required; thus it can't be None; qed"); + let index = str::parse::(index) + .expect("Invalid 'amount' parameter; expecting an integer."); + + let function = Call::Balances(BalancesCall::transfer(to.into(), amount)); + + let genesis_hash: Hash = match matches.value_of("genesis").unwrap_or("alex") { + "elm" => hex!["10c08714a10c7da78f40a60f6f732cf0dba97acfb5e2035445b032386157d5c3"].into(), + "alex" => hex!["dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b"].into(), + h => hex::decode(h).ok().and_then(|x| Decode::decode(&mut &x[..])).expect("Invalid genesis hash or unrecognised chain identifier"), + }; + + println!("Using a genesis hash of {}", HexDisplay::from(&genesis_hash.as_ref())); + + let era = Era::immortal(); + let raw_payload = (Compact(index), function, era, genesis_hash); + let signature = raw_payload.using_encoded(|payload| if payload.len() > 256 { + signer.sign(&blake2_256(payload)[..]) + } else { + println!("Signing {}", HexDisplay::from(&payload)); + signer.sign(payload) + }); + let extrinsic = UncheckedExtrinsic::new_signed( + index, + raw_payload.1, + signer.public().into(), + signature.into(), + era, + ); + println!("0x{}", hex::encode(&extrinsic.encode())); + } ("verify", Some(matches)) => { let sig_data = matches.value_of("sig") .expect("signature parameter is required; thus it can't be None; qed"); @@ -218,3 +271,22 @@ fn main() { fn print_usage(matches: &clap::ArgMatches) { println!("{}", matches.usage()); } + +#[cfg(test)] +mod tests { + use super::{Hash, Decode}; + #[test] + fn should_work() { + let s = "0123456789012345678901234567890123456789012345678901234567890123"; + + let d1: Hash = hex::decode(s).ok().and_then(|x| Decode::decode(&mut &x[..])).unwrap(); + + let d2: Hash = { + let mut gh: [u8; 32] = Default::default(); + gh.copy_from_slice(hex::decode(s).unwrap().as_ref()); + Hash::from(gh) + }; + + assert_eq!(d1, d2); + } +}