use std::collections::HashSet;
use std::fmt;
use std::io::{BufRead, Write};
use std::path::Path;
use sha2::{Digest, Sha256};
use crate::{AgentId, Error, ID_SPACE};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Chain {
Bitcoin,
Ethereum,
}
impl fmt::Display for Chain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Chain::Bitcoin => "bitcoin",
Chain::Ethereum => "ethereum",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChainAnchor {
pub chain: Chain,
pub height: u64,
pub block_hash: [u8; 32],
}
impl ChainAnchor {
pub fn from_hex(
chain: Chain,
height: u64,
hash_hex: &str,
) -> Result<Self, Error> {
let hex = hash_hex.trim().trim_start_matches("0x");
if hex.len() != 64 {
return Err(Error::InvalidHex(hash_hex.to_string()));
}
let mut block_hash = [0u8; 32];
for (i, byte) in block_hash.iter_mut().enumerate() {
*byte = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16)
.map_err(|_| Error::InvalidHex(hash_hex.to_string()))?;
}
Ok(ChainAnchor { chain, height, block_hash })
}
}
pub fn derive_id(anchor: &ChainAnchor, minter: &[u8], nonce: u32) -> AgentId {
let mut h = Sha256::new();
h.update(b"agenticon/v1");
h.update(anchor.chain.to_string().as_bytes());
h.update(anchor.height.to_be_bytes());
h.update(anchor.block_hash);
h.update((minter.len() as u64).to_be_bytes());
h.update(minter);
h.update(nonce.to_be_bytes());
let digest = h.finalize();
let mut first = [0u8; 8];
first.copy_from_slice(&digest[..8]);
AgentId::new(u64::from_be_bytes(first) % ID_SPACE)
.expect("value reduced modulo ID_SPACE")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Minted {
pub id: AgentId,
pub nonce: u32,
}
#[derive(Clone, Debug, Default)]
pub struct Registry {
taken: HashSet<AgentId>,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.taken.len()
}
pub fn is_empty(&self) -> bool {
self.taken.is_empty()
}
pub fn contains(&self, id: AgentId) -> bool {
self.taken.contains(&id)
}
pub fn insert(&mut self, id: AgentId) -> bool {
self.taken.insert(id)
}
pub fn mint(&mut self, anchor: &ChainAnchor, minter: &[u8]) -> Minted {
let mut nonce = 0u32;
loop {
let id = derive_id(anchor, minter, nonce);
if self.taken.insert(id) {
return Minted { id, nonce };
}
nonce += 1;
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
let file = std::fs::File::open(path)?;
let mut reg = Registry::new();
for line in std::io::BufReader::new(file).lines() {
let line = line?;
if !line.trim().is_empty() {
reg.taken.insert(line.trim().parse()?);
}
}
Ok(reg)
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), Error> {
let mut ids: Vec<_> = self.taken.iter().copied().collect();
ids.sort();
let mut f = std::io::BufWriter::new(std::fs::File::create(path)?);
for id in ids {
writeln!(f, "{id}")?;
}
Ok(())
}
}