homestar_runtime/settings/
pubkey_config.rs1use anyhow::{anyhow, Context};
4use clap::ValueEnum;
5use libp2p::{identity, identity::secp256k1};
6use rand::{Rng, SeedableRng};
7use sec1::{der::Decode, pkcs8::DecodePrivateKey};
8use serde::{Deserialize, Serialize};
9use serde_with::{base64::Base64, serde_as};
10use std::{
11 fmt::Display,
12 io::Read,
13 path::{Path, PathBuf},
14};
15use tracing::info;
16
17#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
18pub enum PubkeyConfig {
20 #[serde(rename = "random")]
22 Random,
23 #[serde(rename = "random_seed")]
25 GenerateFromSeed(RNGSeed),
26 #[serde(rename = "existing")]
28 Existing(ExistingKeyPath),
29}
30
31#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, ValueEnum)]
33pub enum KeyType {
34 #[default]
36 #[serde(rename = "ed25519")]
37 Ed25519,
38 #[serde(rename = "secp256k1")]
40 Secp256k1,
41}
42
43impl Display for KeyType {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 KeyType::Ed25519 => f.write_str("Ed25519"),
47 KeyType::Secp256k1 => f.write_str("Secp256k1"),
48 }
49 }
50}
51
52#[serde_as]
54#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
55pub struct RNGSeed {
56 #[serde(default)]
57 key_type: KeyType,
58 #[serde_as(as = "Base64")]
59 seed: [u8; 32],
60}
61
62impl RNGSeed {
63 pub fn new(key_type: KeyType, seed: [u8; 32]) -> Self {
65 Self { key_type, seed }
66 }
67}
68
69#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
71pub struct ExistingKeyPath {
72 #[serde(default)]
73 key_type: KeyType,
74 path: PathBuf,
75}
76
77impl ExistingKeyPath {
78 pub fn new(key_type: KeyType, path: PathBuf) -> Self {
80 Self { key_type, path }
81 }
82}
83
84impl PubkeyConfig {
85 pub(crate) fn keypair(&self) -> anyhow::Result<identity::Keypair> {
88 match self {
89 PubkeyConfig::Random => {
90 info!(
91 subject = "pubkey_config.random",
92 category = "pubkey_config",
93 "generating random ed25519 key"
94 );
95 Ok(identity::Keypair::generate_ed25519())
96 }
97 PubkeyConfig::GenerateFromSeed(RNGSeed { key_type, seed }) => {
98 let mut r = rand::prelude::StdRng::from_seed(*seed);
100 let mut new_key: [u8; 32] = r.gen();
101
102 match key_type {
103 KeyType::Ed25519 => {
104 info!(
105 subject = "pubkey_config.random_seed.ed25519",
106 category = "pubkey_config",
107 "generating random ed25519 key from seed"
108 );
109
110 identity::Keypair::ed25519_from_bytes(new_key).map_err(|e| {
111 anyhow!("failed to generate ed25519 key from random: {:?}", e)
112 })
113 }
114 KeyType::Secp256k1 => {
115 info!(
116 subject = "pubkey_config.random_seed.secp256k1",
117 category = "pubkey_config",
118 "generating random secp256k1 key from seed"
119 );
120
121 let sk =
122 secp256k1::SecretKey::try_from_bytes(&mut new_key).map_err(|e| {
123 anyhow!("failed to generate secp256k1 key from random: {:?}", e)
124 })?;
125 let kp = secp256k1::Keypair::from(sk);
126 Ok(identity::Keypair::from(kp))
127 }
128 }
129 }
130 PubkeyConfig::Existing(ExistingKeyPath { key_type, path }) => {
131 let path = Path::new(&path);
132
133 let mut file = std::fs::File::open(path).context("unable to read key file")?;
134
135 let mut buf = Vec::new();
136 file.read_to_end(&mut buf)
137 .context("unable to read bytes from file, is the file corrupted?")?;
138
139 match key_type {
140 KeyType::Ed25519 => {
141 info!(
142 subject = "pubkey_config.path.ed25519",
143 category = "pubkey_config",
144 "importing ed25519 key from: {}",
145 path.display()
146 );
147
148 String::from_utf8(buf.clone()).with_context(|| {
149 "unable to read PEM, file contained invalid UTF-8 code points"
150 })
151 .and_then(|pem| ed25519_dalek::SigningKey::from_pkcs8_pem(&pem).with_context(|| "unable to deserialize ed25119 key from PEM"))
152 .and_then(|pem| {
153 let mut key = pem.to_bytes();
154
155 identity::Keypair::ed25519_from_bytes(&mut key).with_context(|| "imported key material was invalid for ed25519")
156 })
157 .or_else(|_| {
158 const PEM_HEADER: &str = "PRIVATE KEY";
161
162 let (tag, mut key) = sec1::der::pem::decode_vec(&buf)
164 .map_err(|e| anyhow!("key file must be PEM formatted: {:#?}", e))?;
165 if tag != PEM_HEADER {
166 return Err(anyhow!("imported key file had a header of '{tag}', expected '{PEM_HEADER}' for ed25519"));
167 }
168
169 identity::Keypair::ed25519_from_bytes(&mut key)
170 .with_context(|| "imported key material was invalid for ed25519")
171 })
172 }
173 KeyType::Secp256k1 => {
174 info!(
175 subject = "pubkey_config.path.secp256k1",
176 category = "pubkey_config",
177 "importing secp256k1 key from: {}",
178 path.display()
179 );
180
181 let sk = match path.extension().and_then(|ext| ext.to_str()) {
182 Some("der") => sec1::EcPrivateKey::from_der(buf.as_slice()).map_err(|e| anyhow!("failed to parse DER encoded secp256k1 key: {e:#?}")),
183 Some("pem") => {
184 Err(anyhow!("PEM encoded secp256k1 keys are unsupported at the moment. Please file an issue if you require this."))
185 },
186 _ => Err(anyhow!("please disambiguate file from either PEM or DER with a file extension."))
187 }?;
188 let kp = secp256k1::SecretKey::try_from_bytes(sk.private_key.to_vec())
189 .map(secp256k1::Keypair::from)
190 .map_err(|e| anyhow!("failed to import secp256k1 key: {:#?}", e))?;
191 Ok(identity::Keypair::from(kp))
192 }
193 }
194 }
195 }
196 }
197}