embed miniquorum signer into icepick
This commit is contained in:
parent
e49d6be339
commit
b91a55b93d
|
@ -1866,21 +1866,17 @@ dependencies = [
|
|||
name = "icepick"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"card-backend-pcsc",
|
||||
"chrono",
|
||||
"clap",
|
||||
"icepick-module",
|
||||
"icepick-workflow",
|
||||
"keyfork-derive-util",
|
||||
"keyforkd-client",
|
||||
"keyforkd-models",
|
||||
"openpgp-card",
|
||||
"openpgp-card-sequoia",
|
||||
"sequoia-openpgp",
|
||||
"miniquorum",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sha3",
|
||||
"thiserror 2.0.11",
|
||||
"toml 0.8.19",
|
||||
]
|
||||
|
@ -2545,6 +2541,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"card-backend-pcsc",
|
||||
"chrono",
|
||||
"clap",
|
||||
"keyfork-prompt",
|
||||
"openpgp-card",
|
||||
|
|
|
@ -4,12 +4,14 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.39", default-features = false, features = ["now", "serde", "std"] }
|
||||
clap = { version = "4.5.20", features = ["cargo", "derive", "string"] }
|
||||
icepick-module = { version = "0.1.0", path = "../icepick-module" }
|
||||
icepick-workflow = { version = "0.1.0", path = "../icepick-workflow" }
|
||||
keyfork-derive-util = { version = "0.2.1", registry = "distrust" }
|
||||
keyforkd-client = { version = "0.2.1", registry = "distrust" }
|
||||
keyforkd-models = { version = "0.2.0", registry = "distrust" }
|
||||
miniquorum = { version = "0.1.0", path = "../miniquorum", default-features = false }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = "0.9.34"
|
||||
|
|
|
@ -187,7 +187,13 @@ pub fn do_cli_thing() {
|
|||
let workflows = workflows.leak();
|
||||
let mut workflow_command = clap::Command::new("workflow")
|
||||
.about("Run a pre-defined Icepick workflow")
|
||||
.arg(clap::arg!(--"simulate-workflow").global(true));
|
||||
.arg(clap::arg!(--"simulate-workflow").global(true))
|
||||
.arg(clap::arg!(--"export-for-quorum").global(true))
|
||||
.arg(
|
||||
clap::arg!(--"sign")
|
||||
.global(true)
|
||||
.requires_if(clap::builder::ArgPredicate::IsPresent, "export-for-quorum"),
|
||||
);
|
||||
for module in workflows.iter() {
|
||||
let mut module_subcommand = clap::Command::new(module.0.as_str());
|
||||
for workflow in &module.1 {
|
||||
|
@ -236,7 +242,7 @@ pub fn do_cli_thing() {
|
|||
.find(|(module, _)| module == module_name)
|
||||
.and_then(|(_, workflows)| workflows.iter().find(|x| x.name == workflow_name))
|
||||
.expect("workflow from CLI should match config");
|
||||
workflow::handle(workflow, matches, commands, &config.modules);
|
||||
workflow::handle(workflow, module_name, matches, commands, &config.modules);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -172,6 +172,7 @@ fn load_operations(commands: Commands, config: &[ModuleConfig]) -> Vec<CLIOperat
|
|||
|
||||
pub fn handle(
|
||||
workflow: &Workflow,
|
||||
module_name: &str,
|
||||
matches: &clap::ArgMatches,
|
||||
modules: Commands,
|
||||
config: &[ModuleConfig],
|
||||
|
@ -194,6 +195,19 @@ pub fn handle(
|
|||
return;
|
||||
}
|
||||
|
||||
if matches.get_flag("export-for-quorum") {
|
||||
let mut payload = miniquorum::Payload::new(
|
||||
serde_json::to_value(data).unwrap(),
|
||||
module_name,
|
||||
&workflow.name,
|
||||
);
|
||||
if matches.get_flag("sign") {
|
||||
payload.add_signature().unwrap();
|
||||
}
|
||||
println!("{}", serde_json::to_string_pretty(&payload).unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
let result = workflow
|
||||
.run_workflow(data, &operations, &derive_keys)
|
||||
.expect("Invocation failure");
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
name: withdraw-rewards
|
||||
inputs:
|
||||
- delegate_address
|
||||
- validator_address
|
||||
- chain_name
|
||||
optional_inputs:
|
||||
- gas_factor
|
||||
step:
|
||||
- type: cosmos-get-chain-info
|
||||
inputs:
|
||||
chain_name: chain_name
|
||||
outputs:
|
||||
blockchain_config: blockchain_config
|
||||
- type: internal-load-file
|
||||
values:
|
||||
filename: "account_info.json"
|
||||
outputs:
|
||||
account_number: account_number
|
||||
sequence_number: sequence_number
|
||||
- type: cosmos-withdraw-rewards
|
||||
inputs:
|
||||
delegate_address: delegate_address
|
||||
validator_address: validator_address
|
||||
blockchain_config: blockchain_config
|
||||
gas_factor: gas_factor
|
||||
outputs:
|
||||
fee: fee
|
||||
tx_messages: tx_messages
|
||||
- type: cosmos-sign
|
||||
inputs:
|
||||
fee: fee
|
||||
tx_messages: tx_messages
|
||||
account_number: account_number
|
||||
sequence_number: sequence_number
|
||||
blockchain_config: blockchain_config
|
||||
outputs:
|
||||
transaction: signed_transaction
|
||||
- type: internal-save-file
|
||||
values:
|
||||
filename: "transaction.json"
|
||||
inputs:
|
||||
transaction: signed_transaction
|
|
@ -9,6 +9,7 @@ default = ["clap"]
|
|||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
card-backend-pcsc = "0.5.0"
|
||||
chrono = { version = "0.4.39", default-features = false, features = ["std", "now", "serde"] }
|
||||
clap = { version = "4.5.27", features = ["derive", "wrap_help"], optional = true }
|
||||
keyfork-prompt = { version = "0.2.0", registry = "distrust", default-features = false }
|
||||
openpgp-card = "0.4"
|
||||
|
|
|
@ -14,6 +14,7 @@ use sequoia_openpgp::{
|
|||
types::SignatureType,
|
||||
Cert, Fingerprint,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{collections::BTreeMap, fs::File, io::Read, path::Path};
|
||||
|
@ -55,6 +56,10 @@ pub enum BaseError {
|
|||
/// A Payload was provided when an inner [`serde_json::Value`] was expected.
|
||||
#[error("a payload was provided when a non-payload JSON value was expected")]
|
||||
UnexpectedPayloadProvided,
|
||||
|
||||
/// The JSON object is not a valid value.
|
||||
#[error("the JSON object is not a valid value")]
|
||||
InvalidJSONValue,
|
||||
}
|
||||
|
||||
impl BaseError {
|
||||
|
@ -87,14 +92,17 @@ fn canonicalize(value: Value) -> Value {
|
|||
}
|
||||
|
||||
fn unhashed(value: Value) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let value = canonicalize(value);
|
||||
let Value::Object(mut value) = value else {
|
||||
return Err(BaseError::InvalidJSONValue.into());
|
||||
};
|
||||
value.remove("signatures");
|
||||
let value = canonicalize(Value::Object(value));
|
||||
let bincoded = bincode::serialize(&value)?;
|
||||
Ok(bincoded)
|
||||
}
|
||||
|
||||
fn hash(value: Value) -> Result<Box<dyn Digest>, Box<dyn std::error::Error>> {
|
||||
let value = canonicalize(value);
|
||||
let bincoded = bincode::serialize(&value)?;
|
||||
let bincoded = unhashed(value)?;
|
||||
let mut digest = openpgp::types::HashAlgorithm::SHA512.context()?;
|
||||
digest.update(&bincoded);
|
||||
|
||||
|
@ -103,7 +111,10 @@ fn hash(value: Value) -> Result<Box<dyn Digest>, Box<dyn std::error::Error>> {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Payload {
|
||||
workflow: [String; 2],
|
||||
values: Value,
|
||||
datetime: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
signatures: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -184,6 +195,16 @@ fn format_name(input: impl AsRef<str>) -> String {
|
|||
}
|
||||
|
||||
impl Payload {
|
||||
/// Create a new Payload, using the current system's time, in UTC.
|
||||
pub fn new(values: serde_json::Value, module_name: impl AsRef<str>, workflow_name: impl AsRef<str>) -> Self {
|
||||
Self {
|
||||
workflow: [module_name.as_ref().to_string(), workflow_name.as_ref().to_string()],
|
||||
values,
|
||||
datetime: Utc::now(),
|
||||
signatures: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a Payload and the relevant certificates.
|
||||
///
|
||||
/// # Errors
|
||||
|
@ -209,21 +230,13 @@ impl Payload {
|
|||
Ok((payload, certs))
|
||||
}
|
||||
|
||||
/// Create an unsigned Payload from a [`serde_json::Value`].
|
||||
pub fn new(value: serde_json::Value) -> Self {
|
||||
Self {
|
||||
values: value,
|
||||
signatures: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a signature from an OpenPGP card.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The method may error if a signature could not be created.
|
||||
pub fn add_signature(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let unhashed = unhashed(self.values.clone())?;
|
||||
let unhashed = unhashed(serde_json::to_value(&self)?)?;
|
||||
let builder = SignatureBuilder::new(SignatureType::Binary);
|
||||
let mut prompt_handler = default_handler()?;
|
||||
let pin_validator = PinValidator {
|
||||
|
@ -319,7 +332,7 @@ impl Payload {
|
|||
))?;
|
||||
}
|
||||
|
||||
let hashed = hash(self.values.clone())?;
|
||||
let hashed = hash(serde_json::to_value(self)?)?;
|
||||
|
||||
let PayloadVerification {
|
||||
mut threshold,
|
||||
|
|
|
@ -32,9 +32,9 @@ enum MiniQuorum {
|
|||
AddSignature {
|
||||
/// The file to use as input.
|
||||
///
|
||||
/// If no file is provided, standard input of a payload value is used. If a file is
|
||||
/// provided and no output file is provided, it will be used in-place as the output file
|
||||
/// with the additional signature added.
|
||||
/// If no file is provided, standard input is used. If a file is provided and no output
|
||||
/// file is provided, it will be used in-place as the output file with the additional
|
||||
/// signature added.
|
||||
input_file: Option<PathBuf>,
|
||||
|
||||
/// The file to use as output.
|
||||
|
@ -68,29 +68,28 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
};
|
||||
|
||||
let policy = PayloadVerification::new().with_threshold(certs.len().try_into()?);
|
||||
let inner_value = payload.verify_signatures(&certs, &policy, fingerprint)?;
|
||||
payload.verify_signatures(&certs, &policy, fingerprint)?;
|
||||
|
||||
if let Some(output_file) = output_file {
|
||||
let file = File::create(output_file)?;
|
||||
serde_json::to_writer_pretty(file, inner_value)?;
|
||||
serde_json::to_writer_pretty(file, &payload)?;
|
||||
} else {
|
||||
let stdout = std::io::stdout();
|
||||
serde_json::to_writer_pretty(stdout, inner_value)?;
|
||||
serde_json::to_writer_pretty(stdout, &payload)?;
|
||||
}
|
||||
}
|
||||
MiniQuorum::AddSignature {
|
||||
input_file,
|
||||
output_file,
|
||||
} => {
|
||||
let mut payload = match &input_file {
|
||||
let mut payload: Payload = match &input_file {
|
||||
Some(input_file) => {
|
||||
let input_file = File::open(input_file)?;
|
||||
let payload: Payload = serde_json::from_reader(input_file)?;
|
||||
payload
|
||||
serde_json::from_reader(input_file)?
|
||||
}
|
||||
None => {
|
||||
let stdin = std::io::stdin();
|
||||
let value: serde_json::Value = serde_json::from_reader(stdin)?;
|
||||
Payload::new(value)
|
||||
serde_json::from_reader(stdin)?
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue