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
This commit is contained in:
Gavin Wood
2019-04-24 12:23:59 +02:00
committed by GitHub
parent bf9d7957d8
commit babe638be7
5 changed files with 123 additions and 7 deletions
+5
View File
@@ -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)",
+16 -6
View File
@@ -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<Iter: Iterator<Item=DeriveJunction>>(&self, _path: Iter) -> Option<Self> { None }
fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, _path: Iter) -> Option<Self> {
None
}
}
#[cfg(feature = "std")]
@@ -266,11 +268,19 @@ impl<T: AsMut<[u8]> + 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)
}
}
}
+5
View File
@@ -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 = []
+24
View File
@@ -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:
+73 -1
View File
@@ -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<C: Crypto<Seed=[u8; 32]>>(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::<Balance>(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>(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);
}
}