1use crate::common::elgamal::Encoding as _;
2use anyhow::Result;
3
4#[derive(clap::Args, Debug)]
5pub struct ToolsArgs {
6 #[command(subcommand)]
7 command: Commands,
8}
9
10impl ToolsArgs {
11 pub fn run(self, _spec: &mut clap::Command) -> Result<()> {
12 match self.command {
13 Commands::Generate(args) => args.run(),
14 Commands::YiviEpoch(args) => args.run(),
15 }
16 }
17}
18
19#[derive(clap::Subcommand, Debug)]
20enum Commands {
21 Generate(generate::Args),
23
24 YiviEpoch(YiviEpochArgs),
26}
27
28#[derive(clap::Args, Debug)]
29struct YiviEpochArgs {
30 #[arg(long, value_name = "NUMBER", conflicts_with = "at")]
32 nr: Option<u64>,
33
34 #[arg(
36 long,
37 value_name = "TIMESTAMP",
38 value_parser = humantime::parse_rfc3339_weak,
39 conflicts_with = "nr"
40 )]
41 at: Option<std::time::SystemTime>,
42}
43
44impl YiviEpochArgs {
45 fn run(self) -> Result<()> {
46 let epoch = if let Some(nr) = self.nr {
47 crate::servers::yivi::Epoch::with_seqnr(nr)?
48 } else if let Some(at) = self.at {
49 let nd: crate::api::NumericDate = at.try_into()?;
50 crate::servers::yivi::Epoch::from(nd)
51 } else {
52 crate::servers::yivi::Epoch::current()
53 };
54
55 print!("{}", epoch);
56
57 Ok(())
58 }
59}
60
61mod generate {
63 use super::*;
64
65 #[derive(clap::Args, Debug)]
66 pub(super) struct Args {
67 #[command(subcommand)]
68 command: Commands,
69 }
70
71 impl Args {
72 pub(super) fn run(self) -> Result<()> {
73 match self.command {
74 Commands::Id(args) => args.run(),
75 Commands::Scalar(args) => args.run(),
76 Commands::SigningKey(args) => args.run(),
77 Commands::DecapKey(args) => args.run(),
78 }
79 }
80 }
81
82 #[derive(clap::Subcommand, Debug)]
83 enum Commands {
84 Id(IdArgs),
86
87 Scalar(ScalarArgs),
89
90 SigningKey(SigningKeyArgs),
92
93 DecapKey(DecapKeyArgs),
95 }
96
97 #[derive(clap::Args, Debug)]
98 struct IdArgs {}
99
100 impl IdArgs {
101 fn run(self) -> Result<()> {
102 println!("{}", crate::id::Id::random());
103
104 Ok(())
105 }
106 }
107
108 #[derive(clap::Args, Debug)]
109 struct ScalarArgs {}
110
111 impl ScalarArgs {
112 fn run(self) -> Result<()> {
113 let pk = crate::common::elgamal::PrivateKey::random();
114
115 println!("x (private key): {}", pk.to_hex());
116 println!("xB (public key): {}", pk.public_key().to_hex());
117
118 Ok(())
119 }
120 }
121
122 #[derive(clap::Args, Debug)]
123
124 struct SigningKeyArgs {}
125
126 impl SigningKeyArgs {
127 fn run(self) -> Result<()> {
128 let sk = crate::api::SigningKey::generate()
129 .map_err(|_| anyhow::anyhow!("failed to generate signing key"))?;
130
131 println!(" signing key: {}", serde_json::to_string(&sk.encode())?);
132 println!(
133 "verifying key: {}",
134 serde_json::to_string(&sk.verifying_key().encode())?
135 );
136
137 Ok(())
138 }
139 }
140
141 #[derive(clap::Args, Debug)]
142 struct DecapKeyArgs {}
143
144 impl DecapKeyArgs {
145 fn run(self) -> Result<()> {
146 let dk = crate::common::kem::DecapKey::generate()
147 .map_err(|_| anyhow::anyhow!("failed to generate decapsulation key"))?;
148
149 let encap_key_id = dk
150 .encap_key()
151 .encode()
152 .map_err(|_| anyhow::anyhow!("failed to encode encapsulation key"))?
153 .id();
154
155 let decap_key = dk
156 .encode()
157 .map_err(|_| anyhow::anyhow!("failed to encode decapsulation key"))?;
158
159 println!("# corresponding encapsulation key id: {encap_key_id}");
160 println!("{}", decap_key_config_snippet(&decap_key)?);
161
162 Ok(())
163 }
164 }
165
166 fn decap_key_config_snippet(decap_key: &crate::common::kem::DecapKeyBytes) -> Result<String> {
171 use core::fmt::Write as _;
172 use toml_writer::{TomlStringBuilder, TomlWrite as _};
173
174 const COLUMN_WIDTH: usize = 100;
176 const INDENT: &str = " ";
178
179 let chunk_width = COLUMN_WIDTH - INDENT.len() - 3;
181
182 fn folded(s: &str, indent: &str, chunk_width: usize) -> String {
186 let mut out = String::from("\"\"\"\\\n");
187 let mut rest = s;
188 while rest.len() > chunk_width {
189 let (head, tail) = rest.split_at(chunk_width);
190 out.push_str(indent);
191 out.push_str(head);
192 out.push_str("\\\n");
193 rest = tail;
194 }
195 out.push_str(indent);
196 out.push_str(rest);
197 out.push_str("\"\"\"");
198 out
199 }
200
201 let value = toml::Value::try_from(decap_key)
203 .map_err(|_| anyhow::anyhow!("failed to serialize decapsulation key"))?;
204 let table = value
205 .as_table()
206 .expect("a decapsulation key serializes to a table");
207
208 let mut out = String::new();
209 out.key("decap_key")?;
210 out.space()?;
211 out.keyval_sep()?;
212 out.space()?;
213 out.open_inline_table()?;
214 out.newline()?;
215 for (field, value) in table.iter() {
216 let value = value
217 .as_str()
218 .expect("decapsulation key fields serialize to strings");
219
220 out.write_str(" ")?;
221 out.key(field.as_str())?;
222 out.space()?;
223 out.keyval_sep()?;
224 out.space()?;
225
226 if value.len() <= chunk_width {
227 out.value(TomlStringBuilder::new(value).as_basic())?;
228 } else {
229 out.write_str(&folded(value, INDENT, chunk_width))?;
230 }
231
232 out.val_sep()?;
233 out.newline()?;
234 }
235 out.close_inline_table()?;
236
237 Ok(out)
238 }
239
240 #[cfg(test)]
241 mod tests {
242 use super::*;
243
244 #[test]
245 fn decap_key_snippet_reparses() {
246 let decap_key = crate::common::kem::DecapKey::generate()
247 .unwrap()
248 .encode()
249 .unwrap();
250
251 let snippet = decap_key_config_snippet(&decap_key).unwrap();
252
253 #[derive(serde::Deserialize)]
254 struct Parsed {
255 decap_key: crate::common::kem::DecapKeyBytes,
256 }
257 let parsed: Parsed = toml::from_str(&snippet).expect("snippet should reparse");
258
259 assert_eq!(
261 serde_json::to_string(&parsed.decap_key).unwrap(),
262 serde_json::to_string(&decap_key).unwrap(),
263 );
264 }
265 }
266}