auths_cli/commands/
multi_sig.rs1use anyhow::{Context, Result, anyhow};
13use clap::{Args, Subcommand};
14use std::path::PathBuf;
15use std::sync::Arc;
16
17use auths_keri::{IndexedSignature, SignedEvent, Threshold};
18use auths_sdk::keychain::{KeyAlias, get_platform_keychain_with_config};
19use auths_sdk::workflows::multi_sig::{
20 begin_multi_sig_event, combine, read_partial, sign_partial, write_partial,
21};
22
23use crate::commands::executable::ExecutableCommand;
24use crate::config::CliConfig;
25
26#[derive(Args, Debug)]
29pub struct MultiSigCommand {
30 #[command(subcommand)]
31 pub subcommand: MultiSigSubcommand,
32}
33
34#[derive(Subcommand, Debug)]
35#[command(rename_all = "lowercase")]
36pub enum MultiSigSubcommand {
37 Begin {
39 #[arg(long)]
41 event: PathBuf,
42
43 #[arg(long, value_delimiter = ',')]
45 signers: Vec<String>,
46
47 #[arg(long)]
49 output: PathBuf,
50 },
51
52 Sign {
54 #[arg(long)]
56 unsigned: PathBuf,
57
58 #[arg(long)]
60 key_alias: String,
61
62 #[arg(long)]
64 index: u32,
65
66 #[arg(long)]
68 output: PathBuf,
69 },
70
71 Combine {
73 #[arg(long)]
75 unsigned: PathBuf,
76
77 #[arg(long, value_delimiter = ',')]
79 partials: Vec<PathBuf>,
80
81 #[arg(long)]
83 threshold: String,
84
85 #[arg(long)]
87 key_count: usize,
88
89 #[arg(long)]
91 output: PathBuf,
92 },
93}
94
95impl ExecutableCommand for MultiSigCommand {
96 fn execute(&self, ctx: &CliConfig) -> Result<()> {
97 match &self.subcommand {
98 MultiSigSubcommand::Begin {
99 event,
100 signers,
101 output,
102 } => {
103 let raw = std::fs::read(event)
104 .with_context(|| format!("reading event from {event:?}"))?;
105 let signed: SignedEvent = serde_json::from_slice(&raw)
106 .with_context(|| format!("parsing SignedEvent from {event:?}"))?;
107 let aliases: Vec<KeyAlias> = signers
108 .iter()
109 .map(|s| KeyAlias::new_unchecked(s.to_string()))
110 .collect();
111 let bundle = begin_multi_sig_event(&signed, &aliases, output)
112 .with_context(|| "begin_multi_sig_event failed")?;
113 println!(
114 "[OK] Unsigned bundle written to {:?} (SAID {}, {} signer slots)",
115 output,
116 bundle.said,
117 aliases.len()
118 );
119 Ok(())
120 }
121
122 &MultiSigSubcommand::Sign {
123 ref unsigned,
124 ref key_alias,
125 index,
126 ref output,
127 } => {
128 let keychain: Arc<dyn auths_sdk::keychain::KeyStorage + Send + Sync> = Arc::from(
129 get_platform_keychain_with_config(&ctx.env_config)
130 .context("Failed to access keychain")?,
131 );
132 let alias = KeyAlias::new_unchecked(key_alias.clone());
133 let partial = sign_partial(
134 unsigned,
135 &alias,
136 index,
137 ctx.passphrase_provider.as_ref(),
138 keychain.as_ref(),
139 )
140 .with_context(|| "sign_partial failed")?;
141 write_partial(&partial, output)
142 .with_context(|| format!("writing partial to {output:?}"))?;
143 println!(
144 "[OK] Partial signature written to {:?} (index {}, {} bytes)",
145 output,
146 partial.index,
147 partial.sig.len()
148 );
149 Ok(())
150 }
151
152 &MultiSigSubcommand::Combine {
153 ref unsigned,
154 ref partials,
155 ref threshold,
156 key_count,
157 ref output,
158 } => {
159 let expected_kt: Threshold =
160 crate::commands::init::parse_threshold_cli(threshold, key_count)?;
161 let loaded: Result<Vec<IndexedSignature>> = partials
162 .iter()
163 .map(|p| read_partial(p).map_err(|e| anyhow!("reading partial {:?}: {e}", p)))
164 .collect();
165 let loaded = loaded?;
166 let signed =
167 combine(unsigned, loaded, &expected_kt).with_context(|| "combine failed")?;
168 let body = serde_json::to_vec_pretty(&signed)
169 .with_context(|| "serializing SignedEvent")?;
170 std::fs::write(output, &body)
171 .with_context(|| format!("writing combined event to {output:?}"))?;
172 println!(
173 "[OK] Combined SignedEvent written to {:?} ({} signatures)",
174 output,
175 signed.signatures.len()
176 );
177 Ok(())
178 }
179 }
180 }
181}