diff --git a/substrate/subkey/README.adoc b/substrate/subkey/README.adoc index 7fe194eb82..52770e78ec 100644 --- a/substrate/subkey/README.adoc +++ b/substrate/subkey/README.adoc @@ -53,3 +53,18 @@ You can use the included vanity generator to find a seed that provides an addres ```bash subkey vanity 1337 ``` + +=== Signing a transaction + +Sign a transaction from an encoded `Call`. + +```bash +subkey sign-transaction \ + --call \ + --nonce 0 \ + --suri \ + --password \ + --prior-block-hash +``` + +Will output a signed and encoded `UncheckedMortalCompactExtrinsic` as hex. diff --git a/substrate/subkey/src/cli.yml b/substrate/subkey/src/cli.yml index 8b839cd443..eedd475737 100644 --- a/substrate/subkey/src/cli.yml +++ b/substrate/subkey/src/cli.yml @@ -92,3 +92,36 @@ subcommands: help: Number of keys to generate takes_value: true default_value: "1" + - sign-transaction: + about: Sign transaction from encoded Call. Returns a signed and encoded UncheckedMortalCompactExtrinsic as hex. + args: + - call: + short: c + long: call + help: The call, hex-encoded. + takes_value: true + required: true + - nonce: + short: n + long: nonce + help: The nonce. + takes_value: true + required: true + - suri: + long: suri + short: s + help: The secret key URI. + takes_value: true + required: true + - password: + short: p + long: password + takes_value: true + help: The password for the key. + required: true + - prior-block-hash: + short: h + long: prior-block-hash + help: The prior block hash, hex-encoded. + takes_value: true + required: true diff --git a/substrate/subkey/src/main.rs b/substrate/subkey/src/main.rs index b0ad1b281d..1b9afb8cd7 100644 --- a/substrate/subkey/src/main.rs +++ b/substrate/subkey/src/main.rs @@ -223,6 +223,48 @@ fn execute>(matches: clap::ArgMatches) where ); println!("0x{}", hex::encode(&extrinsic.encode())); } + ("sign-transaction", Some(matches)) => { + let s = matches.value_of("suri") + .expect("secret URI parameter is required; thus it can't be None; qed"); + let signer = Sr25519::pair_from_suri(s, password); + + let index = matches.value_of("nonce") + .expect("nonce is required; thus it can't be None; qed"); + let index = str::parse::(index) + .expect("Invalid 'index' parameter; expecting an integer."); + + let call = matches.value_of("call") + .expect("call is required; thus it can't be None; qed"); + let function: Call = hex::decode(&call).ok() + .and_then(|x| Decode::decode(&mut &x[..])).unwrap(); + + let h = matches.value_of("prior-block-hash") + .expect("prior-block-hash is required; thus it can't be None; qed"); + let prior_block_hash: Hash = hex::decode(h).ok() + .and_then(|x| Decode::decode(&mut &x[..])) + .expect("Invalid prior block hash"); + + let era = Era::immortal(); + + let raw_payload = (Compact(index), function, era, prior_block_hash); + let signature = raw_payload.using_encoded(|payload| + if payload.len() > 256 { + signer.sign(&blake2_256(payload)[..]) + } else { + 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");