Bootstrap geth Rust workspace

This commit is contained in:
Eric Wendland 2026-05-15 15:08:20 +02:00
commit 26f81ff1ef
73 changed files with 4835 additions and 0 deletions

View file

@ -0,0 +1,118 @@
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand_core::OsRng;
use std::path::Path;
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("invalid hex key: {0}")]
Hex(#[from] hex::FromHexError),
#[error("invalid ed25519 key material")]
InvalidKey,
#[error("signature verification failed")]
Verify,
}
pub struct AgentKey {
signing_key: SigningKey,
}
impl AgentKey {
#[must_use]
pub fn generate() -> Self {
Self {
signing_key: SigningKey::generate(&mut OsRng),
}
}
pub fn load_or_create(path: &Path) -> Result<Self, CryptoError> {
if path.exists() {
return Self::load(path);
}
let key = Self::generate();
key.save(path)?;
Ok(key)
}
pub fn load(path: &Path) -> Result<Self, CryptoError> {
let hex_key = std::fs::read_to_string(path)?;
let bytes = hex::decode(hex_key.trim())?;
let key_bytes: [u8; 32] = bytes.try_into().map_err(|_| CryptoError::InvalidKey)?;
Ok(Self {
signing_key: SigningKey::from_bytes(&key_bytes),
})
}
pub fn save(&self, path: &Path) -> Result<(), CryptoError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, hex::encode(self.signing_key.to_bytes()))?;
std::fs::rename(tmp, path)?;
Ok(())
}
#[must_use]
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
#[must_use]
pub fn public_key_hex(&self) -> String {
hex::encode(self.verifying_key().to_bytes())
}
#[must_use]
pub fn agent_id(&self) -> geth_types::AgentId {
geth_types::AgentId::new(key_fingerprint(&self.verifying_key().to_bytes()))
}
#[must_use]
pub fn sign(&self, bytes: &[u8]) -> Vec<u8> {
self.signing_key.sign(bytes).to_bytes().to_vec()
}
}
pub fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
let key_bytes: [u8; 32] = public_key.try_into().map_err(|_| CryptoError::InvalidKey)?;
let verifying_key =
VerifyingKey::from_bytes(&key_bytes).map_err(|_| CryptoError::InvalidKey)?;
let sig = Signature::from_slice(signature).map_err(|_| CryptoError::InvalidKey)?;
verifying_key
.verify(message, &sig)
.map_err(|_| CryptoError::Verify)
}
#[must_use]
pub fn blake3_hex(bytes: &[u8]) -> String {
blake3::hash(bytes).to_hex().to_string()
}
#[must_use]
pub fn key_fingerprint(public_key: &[u8]) -> String {
format!("ed25519:{}", blake3_hex(public_key))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agent_identity_persists() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("agent.ed25519");
let first = AgentKey::load_or_create(&path).expect("create");
let second = AgentKey::load_or_create(&path).expect("load");
assert_eq!(first.agent_id(), second.agent_id());
}
#[test]
fn blake3_helper_matches_known_hash() {
assert_eq!(
blake3_hex(b"hello geth"),
"3a4aa805ade0d4694a1bb69ad5b9a2f1dffcd4de2136df9a465023722d26e325"
);
}
}