use super::{
PublicKeyHex, Sha256Hex, SignatureHex, TrustedHead, canonical_json,
};
use crate::crypto::Signature;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
pub const ROOT_DOMAIN: &[u8] = b"agora-governance-root-v1\n";
pub const KEY_CERT_VERSION: u32 = 1;
pub const ROOT_KEYS: &[&str] = &[
"d29ed152161d23d75cec48ade38859db07f48f3dc15a337179a8f20b13f12cd5",
"200e8efe32391d7f4a1d763c4de0739acfe2e63e69d8be7e19b29d3f5075fb53",
];
pub const ROOT_THRESHOLD: usize = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(rename_all = "snake_case")]
pub enum CertPurpose {
Genesis,
Routine,
Compromise,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(deny_unknown_fields)]
pub struct KeyCertStatement {
pub agora_governance_key_cert: u32,
pub key: PublicKeyHex,
pub purpose: CertPurpose,
pub from_seq: u64,
pub prev_hash: Option<Sha256Hex>,
pub last_trusted: Option<TrustedHead>,
}
impl KeyCertStatement {
pub fn genesis(key: PublicKeyHex) -> Self {
Self {
agora_governance_key_cert: KEY_CERT_VERSION,
key,
purpose: CertPurpose::Genesis,
from_seq: 1,
prev_hash: None,
last_trusted: None,
}
}
pub fn routine(
key: PublicKeyHex,
seq: u64,
prev_hash: Option<Sha256Hex>,
) -> Self {
Self {
agora_governance_key_cert: KEY_CERT_VERSION,
key,
purpose: CertPurpose::Routine,
from_seq: seq + 1,
prev_hash,
last_trusted: None,
}
}
pub fn compromise(
key: PublicKeyHex,
seq: u64,
prev_hash: Option<Sha256Hex>,
last_trusted: TrustedHead,
) -> Self {
Self {
agora_governance_key_cert: KEY_CERT_VERSION,
key,
purpose: CertPurpose::Compromise,
from_seq: seq,
prev_hash,
last_trusted: Some(last_trusted),
}
}
pub fn signed_bytes(&self) -> Vec<u8> {
let value = serde_json::to_value(self)
.expect("a KeyCertStatement always serializes");
let mut out = ROOT_DOMAIN.to_vec();
out.extend(canonical_json(&value));
out
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(deny_unknown_fields)]
pub struct RootSignature {
pub root_key: PublicKeyHex,
pub signature: SignatureHex,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(deny_unknown_fields)]
pub struct KeyCertificate {
pub statement: KeyCertStatement,
pub signatures: Vec<RootSignature>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum CertificateError {
#[error("agora_governance_key_cert is {0}, not {KEY_CERT_VERSION}")]
UnsupportedVersion(u32),
#[error(
"the certificate is for a different key, purpose or chain position"
)]
WrongStatement,
#[error(
"{valid} valid root signature(s) where {needed} are needed; \
unknown and repeated signers count for nothing"
)]
BelowThreshold { valid: usize, needed: usize },
}
impl KeyCertificate {
pub fn unsigned(statement: KeyCertStatement) -> Self {
Self {
statement,
signatures: Vec::new(),
}
}
pub fn with(mut self, signature: RootSignature) -> Self {
self.signatures.push(signature);
self
}
pub fn verify(&self, roots: &RootSet) -> Result<(), CertificateError> {
let version = self.statement.agora_governance_key_cert;
if version != KEY_CERT_VERSION {
return Err(CertificateError::UnsupportedVersion(version));
}
let message = self.statement.signed_bytes();
let mut signers = HashSet::new();
for s in &self.signatures {
if !roots.contains(&s.root_key) {
continue;
}
let Ok(key) = s.root_key.to_verifying_key() else {
continue;
};
if key
.verify_strict(&message, &Signature::from(&s.signature))
.is_ok()
{
signers.insert(s.root_key);
}
}
if signers.len() >= roots.threshold() {
Ok(())
} else {
Err(CertificateError::BelowThreshold {
valid: signers.len(),
needed: roots.threshold(),
})
}
}
pub fn verify_for(
&self,
expected: &KeyCertStatement,
roots: &RootSet,
) -> Result<(), CertificateError> {
if self.statement != *expected {
return Err(CertificateError::WrongStatement);
}
self.verify(roots)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootSet {
keys: HashSet<PublicKeyHex>,
threshold: usize,
}
impl RootSet {
pub fn published() -> Self {
Self::new(
ROOT_KEYS.iter().map(|k| {
k.parse().expect("ROOT_KEYS are valid 32-byte hex keys")
}),
ROOT_THRESHOLD,
)
}
pub fn new(
keys: impl IntoIterator<Item = PublicKeyHex>,
threshold: usize,
) -> Self {
Self {
keys: keys.into_iter().collect(),
threshold: threshold.max(1),
}
}
pub fn contains(&self, key: &PublicKeyHex) -> bool {
self.keys.contains(key)
}
pub fn threshold(&self) -> usize {
self.threshold
}
pub fn keys(&self) -> impl Iterator<Item = &PublicKeyHex> {
self.keys.iter()
}
}