use crate::agents::{decode_base64, encode_base64};
use crate::errors::AtomicResult;
pub const GENESIS_VERSION_V1: u8 = 0x01;
const FLAG_HAS_STATE_HASH: u8 = 0b0000_0001;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GenesisCert {
pub signer_pubkey: [u8; 32],
pub created_at: i64,
pub nonce: [u8; 16],
pub state_hash: Option<[u8; 32]>,
pub parent: String,
pub drive: String,
}
impl GenesisCert {
pub fn encode(&self) -> Vec<u8> {
let parent_bytes = self.parent.as_bytes();
let drive_bytes = self.drive.as_bytes();
let mut out = Vec::with_capacity(
2 + 32 + 8 + 16 + 32 + 2 + parent_bytes.len() + 2 + drive_bytes.len(),
);
out.push(GENESIS_VERSION_V1);
let mut flags = 0u8;
if self.state_hash.is_some() {
flags |= FLAG_HAS_STATE_HASH;
}
out.push(flags);
out.extend_from_slice(&self.signer_pubkey);
out.extend_from_slice(&self.created_at.to_le_bytes());
out.extend_from_slice(&self.nonce);
if let Some(hash) = &self.state_hash {
out.extend_from_slice(hash);
}
let parent_len: u16 = parent_bytes
.len()
.try_into()
.expect("genesis parent subject exceeds 65535 bytes");
out.extend_from_slice(&parent_len.to_le_bytes());
out.extend_from_slice(parent_bytes);
let drive_len: u16 = drive_bytes
.len()
.try_into()
.expect("genesis drive subject exceeds 65535 bytes");
out.extend_from_slice(&drive_len.to_le_bytes());
out.extend_from_slice(drive_bytes);
out
}
pub fn decode(bytes: &[u8]) -> AtomicResult<Self> {
fn take<'a>(bytes: &'a [u8], cursor: &mut usize, n: usize) -> AtomicResult<&'a [u8]> {
let end = *cursor + n;
if end > bytes.len() {
return Err("Genesis certificate is truncated".into());
}
let slice = &bytes[*cursor..end];
*cursor = end;
Ok(slice)
}
let mut cursor = 0;
let header = take(bytes, &mut cursor, 2)?;
let version = header[0];
if version != GENESIS_VERSION_V1 {
return Err(format!("Unsupported genesis certificate version {version}").into());
}
let flags = header[1];
let mut signer_pubkey = [0u8; 32];
signer_pubkey.copy_from_slice(take(bytes, &mut cursor, 32)?);
let created_at = i64::from_le_bytes(take(bytes, &mut cursor, 8)?.try_into().unwrap());
let mut nonce = [0u8; 16];
nonce.copy_from_slice(take(bytes, &mut cursor, 16)?);
let state_hash = if flags & FLAG_HAS_STATE_HASH != 0 {
let mut hash = [0u8; 32];
hash.copy_from_slice(take(bytes, &mut cursor, 32)?);
Some(hash)
} else {
None
};
let parent_len =
u16::from_le_bytes(take(bytes, &mut cursor, 2)?.try_into().unwrap()) as usize;
let parent = String::from_utf8(take(bytes, &mut cursor, parent_len)?.to_vec())
.map_err(|e| format!("Genesis parent is not valid UTF-8: {e}"))?;
let drive_len =
u16::from_le_bytes(take(bytes, &mut cursor, 2)?.try_into().unwrap()) as usize;
let drive = String::from_utf8(take(bytes, &mut cursor, drive_len)?.to_vec())
.map_err(|e| format!("Genesis drive is not valid UTF-8: {e}"))?;
if cursor != bytes.len() {
return Err("Genesis certificate has trailing bytes".into());
}
Ok(Self {
signer_pubkey,
created_at,
nonce,
state_hash,
parent,
drive,
})
}
pub fn signer_did(&self) -> String {
format!("did:ad:agent:{}", encode_base64(&self.signer_pubkey))
}
pub fn subject_for_signature(signature: &str) -> String {
format!("did:ad:{signature}")
}
pub fn sign(&self, private_key: &str) -> AtomicResult<String> {
use ed25519_dalek::{Signer, SigningKey};
let seed: [u8; 32] = decode_base64(private_key)?
.try_into()
.map_err(|_| "Ed25519 private key must be 32 bytes")?;
let signing_key = SigningKey::from_bytes(&seed);
if signing_key.verifying_key().as_bytes() != &self.signer_pubkey {
return Err("Genesis signer pubkey does not match the signing key".into());
}
let signature = signing_key.sign(&self.encode());
Ok(encode_base64(&signature.to_bytes()))
}
pub fn verify(&self, signature: &str) -> AtomicResult<()> {
use ed25519_dalek::Verifier;
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&self.signer_pubkey)
.map_err(|e| format!("Invalid genesis signer pubkey: {e}"))?;
let sig_bytes: [u8; 64] = decode_base64(signature)?
.try_into()
.map_err(|_| "Ed25519 signature must be 64 bytes")?;
let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
verifying_key
.verify(&self.encode(), &sig)
.map_err(|_| "Genesis certificate signature is invalid".into())
}
}
#[cfg(test)]
mod test {
use super::*;
use ed25519_dalek::SigningKey;
fn test_key(seed_byte: u8) -> (String, [u8; 32]) {
let seed = [seed_byte; 32];
let signing_key = SigningKey::from_bytes(&seed);
let pubkey = *signing_key.verifying_key().as_bytes();
(encode_base64(&seed), pubkey)
}
fn sample(pubkey: [u8; 32], state_hash: Option<[u8; 32]>) -> GenesisCert {
GenesisCert {
signer_pubkey: pubkey,
created_at: 1_780_000_123_456,
nonce: [7u8; 16],
state_hash,
parent: "https://example.com/parent".to_string(),
drive: "https://example.com/drive".to_string(),
}
}
#[test]
fn encode_decode_roundtrip_without_state_hash() {
let (_pk, pubkey) = test_key(1);
let cert = sample(pubkey, None);
let decoded = GenesisCert::decode(&cert.encode()).unwrap();
assert_eq!(cert, decoded);
assert_eq!(
cert.encode().len(),
2 + 32
+ 8
+ 16
+ 2
+ "https://example.com/parent".len()
+ 2
+ "https://example.com/drive".len()
);
}
#[test]
fn known_byte_vector_v1() {
let cert = GenesisCert {
signer_pubkey: [1u8; 32],
created_at: 1,
nonce: [2u8; 16],
state_hash: None,
parent: "x".to_string(),
drive: "d".to_string(),
};
let mut expected = vec![0x01u8, 0x00]; expected.extend_from_slice(&[1u8; 32]); expected.extend_from_slice(&1i64.to_le_bytes()); expected.extend_from_slice(&[2u8; 16]); expected.extend_from_slice(&1u16.to_le_bytes()); expected.push(b'x');
expected.extend_from_slice(&1u16.to_le_bytes()); expected.push(b'd');
assert_eq!(cert.encode(), expected);
}
#[test]
fn cross_lang_signature_vector_v1() {
let seed = [7u8; 32];
let signing_key = SigningKey::from_bytes(&seed);
let pubkey = *signing_key.verifying_key().as_bytes();
assert_eq!(
encode_base64(&pubkey),
"6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw"
);
let cert = GenesisCert {
signer_pubkey: pubkey,
created_at: 1,
nonce: [2u8; 16],
state_hash: None,
parent: "x".to_string(),
drive: "d".to_string(),
};
let sig = cert.sign(&encode_base64(&seed)).unwrap();
assert_eq!(
sig,
"71Igt-CKD2nhZZn4aKCe8tetVUTCgMMqJ67d97Wrb3pT3LFazyP1lGJjAw2Gg9KY0daGHhHPXj3xFMWEmYVdCw"
);
assert_eq!(
GenesisCert::subject_for_signature(&sig),
"did:ad:71Igt-CKD2nhZZn4aKCe8tetVUTCgMMqJ67d97Wrb3pT3LFazyP1lGJjAw2Gg9KY0daGHhHPXj3xFMWEmYVdCw"
);
cert.verify(&sig).unwrap();
}
#[test]
fn encode_decode_roundtrip_with_state_hash() {
let (_pk, pubkey) = test_key(2);
let cert = sample(pubkey, Some([9u8; 32]));
let bytes = cert.encode();
assert_eq!(bytes[1] & FLAG_HAS_STATE_HASH, FLAG_HAS_STATE_HASH);
assert_eq!(GenesisCert::decode(&bytes).unwrap(), cert);
}
#[test]
fn sign_then_verify_succeeds_and_derives_subject() {
let (private_key, pubkey) = test_key(3);
let cert = sample(pubkey, Some([1u8; 32]));
let signature = cert.sign(&private_key).unwrap();
cert.verify(&signature).unwrap();
let subject = GenesisCert::subject_for_signature(&signature);
assert!(subject.starts_with("did:ad:"));
assert_eq!(
cert.signer_did(),
format!("did:ad:agent:{}", encode_base64(&pubkey))
);
}
#[test]
fn signing_with_wrong_key_is_rejected() {
let (_pk1, pubkey1) = test_key(4);
let (private_key2, _pubkey2) = test_key(5);
let cert = sample(pubkey1, None);
assert!(cert.sign(&private_key2).is_err());
}
#[test]
fn tampered_cert_fails_verification() {
let (private_key, pubkey) = test_key(6);
let cert = sample(pubkey, None);
let signature = cert.sign(&private_key).unwrap();
let mut tampered = cert.clone();
tampered.created_at += 1;
assert!(tampered.verify(&signature).is_err());
let mut tampered2 = cert.clone();
tampered2.parent = "https://example.com/evil".to_string();
assert!(tampered2.verify(&signature).is_err());
}
#[test]
fn decode_rejects_bad_version_truncation_and_trailing() {
let (_pk, pubkey) = test_key(7);
let cert = sample(pubkey, None);
let bytes = cert.encode();
let mut bad_version = bytes.clone();
bad_version[0] = 0xFF;
assert!(GenesisCert::decode(&bad_version).is_err());
assert!(GenesisCert::decode(&bytes[..bytes.len() - 3]).is_err());
let mut trailing = bytes.clone();
trailing.push(0);
assert!(GenesisCert::decode(&trailing).is_err());
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn unhex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
struct VectorInput {
seed: u8,
created_at: i64,
nonce: [u8; 16],
state_hash: Option<[u8; 32]>,
parent: &'static str,
drive: &'static str,
}
fn golden_inputs() -> Vec<VectorInput> {
let ascending = {
let mut n = [0u8; 16];
for (i, b) in n.iter_mut().enumerate() {
*b = i as u8;
}
n
};
vec![
VectorInput {
seed: 1,
created_at: 1_700_000_000_000,
nonce: [0x11; 16],
state_hash: None,
parent: "did:ad:parentAAAA",
drive: "did:ad:driveAAAA",
},
VectorInput {
seed: 2,
created_at: 0,
nonce: ascending,
state_hash: None,
parent: "",
drive: "did:ad:driveBBBB",
},
VectorInput {
seed: 3,
created_at: 1_699_999_999_999,
nonce: [0xAB; 16],
state_hash: Some([0xCD; 32]),
parent: "https://example.com/parent",
drive: "did:ad:driveCCCC",
},
]
}
fn cert_for(input: &VectorInput) -> (String, GenesisCert) {
let (private_key, pubkey) = test_key(input.seed);
(
private_key,
GenesisCert {
signer_pubkey: pubkey,
created_at: input.created_at,
nonce: input.nonce,
state_hash: input.state_hash,
parent: input.parent.into(),
drive: input.drive.into(),
},
)
}
#[test]
#[ignore = "generator: run with --nocapture to regenerate the fixture"]
fn generate_golden_vectors() {
let vectors: Vec<_> = golden_inputs()
.iter()
.map(|input| {
let (private_key, cert) = cert_for(input);
let signature = cert.sign(&private_key).unwrap();
serde_json::json!({
"seedByte": input.seed,
"privateKeyBase64": private_key,
"pubKeyHex": hex(&cert.signer_pubkey),
"createdAt": cert.created_at,
"nonceHex": hex(&cert.nonce),
"stateHashHex": cert.state_hash.map(|h| hex(&h)),
"parent": cert.parent,
"drive": cert.drive,
"certBytesHex": hex(&cert.encode()),
"signature": signature,
"did": GenesisCert::subject_for_signature(&signature),
"signerDid": cert.signer_did(),
})
})
.collect();
let doc = serde_json::json!({
"_comment": "Golden cross-language vectors for the v1 genesis certificate. \
Both the Rust GenesisCert and the browser TS implementation MUST reproduce \
every field. Byte fields are hex; signature / did / signerDid use \
base64url-no-pad. Seeds are [seedByte; 32] Ed25519 seeds — TEST KEYS ONLY. \
Regenerate via `cargo test -p atomic_lib genesis::test::generate_golden_vectors \
-- --ignored --nocapture`.",
"version": 1,
"vectors": vectors,
});
println!(
"GOLDEN_START\n{}\nGOLDEN_END",
serde_json::to_string_pretty(&doc).unwrap()
);
}
#[test]
fn matches_the_golden_vectors() {
let fixture: serde_json::Value =
serde_json::from_str(include_str!("genesis_test_vectors.json")).unwrap();
let vectors = fixture["vectors"].as_array().unwrap();
let inputs = golden_inputs();
assert_eq!(
vectors.len(),
inputs.len(),
"fixture vector count drifted from golden_inputs — regenerate the fixture"
);
for (input, expected) in inputs.iter().zip(vectors) {
let (private_key, cert) = cert_for(input);
let signature = cert.sign(&private_key).unwrap();
let did = GenesisCert::subject_for_signature(&signature);
assert_eq!(
hex(&cert.encode()),
expected["certBytesHex"].as_str().unwrap(),
"cert bytes differ for seed {}",
input.seed
);
assert_eq!(signature, expected["signature"].as_str().unwrap());
assert_eq!(did, expected["did"].as_str().unwrap());
assert_eq!(cert.signer_did(), expected["signerDid"].as_str().unwrap());
assert_eq!(
GenesisCert::decode(&unhex(expected["certBytesHex"].as_str().unwrap())).unwrap(),
cert
);
}
}
}