embed miniquorum signer into icepick
This commit is contained in:
parent
e49d6be339
commit
b91a55b93d
|
@ -1866,21 +1866,17 @@ dependencies = [
|
||||||
name = "icepick"
|
name = "icepick"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"chrono",
|
||||||
"card-backend-pcsc",
|
|
||||||
"clap",
|
"clap",
|
||||||
"icepick-module",
|
"icepick-module",
|
||||||
"icepick-workflow",
|
"icepick-workflow",
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
"keyforkd-client",
|
"keyforkd-client",
|
||||||
"keyforkd-models",
|
"keyforkd-models",
|
||||||
"openpgp-card",
|
"miniquorum",
|
||||||
"openpgp-card-sequoia",
|
|
||||||
"sequoia-openpgp",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sha3",
|
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"toml 0.8.19",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
@ -2545,6 +2541,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"card-backend-pcsc",
|
"card-backend-pcsc",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"keyfork-prompt",
|
"keyfork-prompt",
|
||||||
"openpgp-card",
|
"openpgp-card",
|
||||||
|
|
|
@ -4,12 +4,14 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = { version = "0.4.39", default-features = false, features = ["now", "serde", "std"] }
|
||||||
clap = { version = "4.5.20", features = ["cargo", "derive", "string"] }
|
clap = { version = "4.5.20", features = ["cargo", "derive", "string"] }
|
||||||
icepick-module = { version = "0.1.0", path = "../icepick-module" }
|
icepick-module = { version = "0.1.0", path = "../icepick-module" }
|
||||||
icepick-workflow = { version = "0.1.0", path = "../icepick-workflow" }
|
icepick-workflow = { version = "0.1.0", path = "../icepick-workflow" }
|
||||||
keyfork-derive-util = { version = "0.2.1", registry = "distrust" }
|
keyfork-derive-util = { version = "0.2.1", registry = "distrust" }
|
||||||
keyforkd-client = { version = "0.2.1", registry = "distrust" }
|
keyforkd-client = { version = "0.2.1", registry = "distrust" }
|
||||||
keyforkd-models = { version = "0.2.0", 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 = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
|
|
|
@ -187,7 +187,13 @@ pub fn do_cli_thing() {
|
||||||
let workflows = workflows.leak();
|
let workflows = workflows.leak();
|
||||||
let mut workflow_command = clap::Command::new("workflow")
|
let mut workflow_command = clap::Command::new("workflow")
|
||||||
.about("Run a pre-defined Icepick 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() {
|
for module in workflows.iter() {
|
||||||
let mut module_subcommand = clap::Command::new(module.0.as_str());
|
let mut module_subcommand = clap::Command::new(module.0.as_str());
|
||||||
for workflow in &module.1 {
|
for workflow in &module.1 {
|
||||||
|
@ -236,7 +242,7 @@ pub fn do_cli_thing() {
|
||||||
.find(|(module, _)| module == module_name)
|
.find(|(module, _)| module == module_name)
|
||||||
.and_then(|(_, workflows)| workflows.iter().find(|x| x.name == workflow_name))
|
.and_then(|(_, workflows)| workflows.iter().find(|x| x.name == workflow_name))
|
||||||
.expect("workflow from CLI should match config");
|
.expect("workflow from CLI should match config");
|
||||||
workflow::handle(workflow, matches, commands, &config.modules);
|
workflow::handle(workflow, module_name, matches, commands, &config.modules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,7 @@ fn load_operations(commands: Commands, config: &[ModuleConfig]) -> Vec<CLIOperat
|
||||||
|
|
||||||
pub fn handle(
|
pub fn handle(
|
||||||
workflow: &Workflow,
|
workflow: &Workflow,
|
||||||
|
module_name: &str,
|
||||||
matches: &clap::ArgMatches,
|
matches: &clap::ArgMatches,
|
||||||
modules: Commands,
|
modules: Commands,
|
||||||
config: &[ModuleConfig],
|
config: &[ModuleConfig],
|
||||||
|
@ -194,6 +195,19 @@ pub fn handle(
|
||||||
return;
|
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
|
let result = workflow
|
||||||
.run_workflow(data, &operations, &derive_keys)
|
.run_workflow(data, &operations, &derive_keys)
|
||||||
.expect("Invocation failure");
|
.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]
|
[dependencies]
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
card-backend-pcsc = "0.5.0"
|
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 }
|
clap = { version = "4.5.27", features = ["derive", "wrap_help"], optional = true }
|
||||||
keyfork-prompt = { version = "0.2.0", registry = "distrust", default-features = false }
|
keyfork-prompt = { version = "0.2.0", registry = "distrust", default-features = false }
|
||||||
openpgp-card = "0.4"
|
openpgp-card = "0.4"
|
||||||
|
|
|
@ -14,6 +14,7 @@ use sequoia_openpgp::{
|
||||||
types::SignatureType,
|
types::SignatureType,
|
||||||
Cert, Fingerprint,
|
Cert, Fingerprint,
|
||||||
};
|
};
|
||||||
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{collections::BTreeMap, fs::File, io::Read, path::Path};
|
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.
|
/// 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")]
|
#[error("a payload was provided when a non-payload JSON value was expected")]
|
||||||
UnexpectedPayloadProvided,
|
UnexpectedPayloadProvided,
|
||||||
|
|
||||||
|
/// The JSON object is not a valid value.
|
||||||
|
#[error("the JSON object is not a valid value")]
|
||||||
|
InvalidJSONValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseError {
|
impl BaseError {
|
||||||
|
@ -87,14 +92,17 @@ fn canonicalize(value: Value) -> Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unhashed(value: Value) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
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)?;
|
let bincoded = bincode::serialize(&value)?;
|
||||||
Ok(bincoded)
|
Ok(bincoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(value: Value) -> Result<Box<dyn Digest>, Box<dyn std::error::Error>> {
|
fn hash(value: Value) -> Result<Box<dyn Digest>, Box<dyn std::error::Error>> {
|
||||||
let value = canonicalize(value);
|
let bincoded = unhashed(value)?;
|
||||||
let bincoded = bincode::serialize(&value)?;
|
|
||||||
let mut digest = openpgp::types::HashAlgorithm::SHA512.context()?;
|
let mut digest = openpgp::types::HashAlgorithm::SHA512.context()?;
|
||||||
digest.update(&bincoded);
|
digest.update(&bincoded);
|
||||||
|
|
||||||
|
@ -103,7 +111,10 @@ fn hash(value: Value) -> Result<Box<dyn Digest>, Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
|
workflow: [String; 2],
|
||||||
values: Value,
|
values: Value,
|
||||||
|
datetime: DateTime<Utc>,
|
||||||
|
#[serde(default)]
|
||||||
signatures: Vec<String>,
|
signatures: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +195,16 @@ fn format_name(input: impl AsRef<str>) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Payload {
|
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.
|
/// Load a Payload and the relevant certificates.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
@ -209,21 +230,13 @@ impl Payload {
|
||||||
Ok((payload, certs))
|
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.
|
/// Attach a signature from an OpenPGP card.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// The method may error if a signature could not be created.
|
/// The method may error if a signature could not be created.
|
||||||
pub fn add_signature(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
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 builder = SignatureBuilder::new(SignatureType::Binary);
|
||||||
let mut prompt_handler = default_handler()?;
|
let mut prompt_handler = default_handler()?;
|
||||||
let pin_validator = PinValidator {
|
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 {
|
let PayloadVerification {
|
||||||
mut threshold,
|
mut threshold,
|
||||||
|
|
|
@ -32,9 +32,9 @@ enum MiniQuorum {
|
||||||
AddSignature {
|
AddSignature {
|
||||||
/// The file to use as input.
|
/// The file to use as input.
|
||||||
///
|
///
|
||||||
/// If no file is provided, standard input of a payload value is used. If a file is
|
/// If no file is provided, standard input is used. If a file is provided and no output
|
||||||
/// provided and no output file is provided, it will be used in-place as the output file
|
/// file is provided, it will be used in-place as the output file with the additional
|
||||||
/// with the additional signature added.
|
/// signature added.
|
||||||
input_file: Option<PathBuf>,
|
input_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// The file to use as output.
|
/// 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 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 {
|
if let Some(output_file) = output_file {
|
||||||
let file = File::create(output_file)?;
|
let file = File::create(output_file)?;
|
||||||
serde_json::to_writer_pretty(file, inner_value)?;
|
serde_json::to_writer_pretty(file, &payload)?;
|
||||||
} else {
|
} else {
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
serde_json::to_writer_pretty(stdout, inner_value)?;
|
serde_json::to_writer_pretty(stdout, &payload)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MiniQuorum::AddSignature {
|
MiniQuorum::AddSignature {
|
||||||
input_file,
|
input_file,
|
||||||
output_file,
|
output_file,
|
||||||
} => {
|
} => {
|
||||||
let mut payload = match &input_file {
|
let mut payload: Payload = match &input_file {
|
||||||
Some(input_file) => {
|
Some(input_file) => {
|
||||||
let input_file = File::open(input_file)?;
|
let input_file = File::open(input_file)?;
|
||||||
let payload: Payload = serde_json::from_reader(input_file)?;
|
serde_json::from_reader(input_file)?
|
||||||
payload
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
let value: serde_json::Value = serde_json::from_reader(stdin)?;
|
serde_json::from_reader(stdin)?
|
||||||
Payload::new(value)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue