use prikk_error::{PrikkError, Result};
use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType};
pub const SIGNATURE_DOMAIN: &[u8] = b"prikk.sig.v1";
pub const SIGNATURE_KEY_ID_MAX_LEN: usize = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum SignatureAlgorithm {
Ed25519 = 1,
}
impl SignatureAlgorithm {
#[must_use]
pub const fn code(self) -> u16 {
self as u16
}
pub fn from_code(code: u16) -> Result<Self> {
match code {
1 => Ok(Self::Ed25519),
other => Err(PrikkError::InvalidSignature(format!(
"unknown signature algorithm code: {other}"
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum SignerRole {
Author = 1,
Maintainer = 2,
Ci = 3,
Audit = 4,
}
impl SignerRole {
#[must_use]
pub const fn code(self) -> u16 {
self as u16
}
pub fn from_code(code: u16) -> Result<Self> {
match code {
1 => Ok(Self::Author),
2 => Ok(Self::Maintainer),
3 => Ok(Self::Ci),
4 => Ok(Self::Audit),
other => Err(PrikkError::InvalidSignature(format!(
"unknown signer role code: {other}"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub algorithm: SignatureAlgorithm,
pub key_id: String,
pub signature_bytes: Vec<u8>,
pub created_at: u64,
pub signer_role: SignerRole,
}
impl Signature {
pub fn validate_key_id(key_id: &str) -> Result<()> {
if key_id.is_empty() {
return Err(PrikkError::InvalidSignature(
"signature key_id must not be empty".to_string(),
));
}
if key_id.len() > SIGNATURE_KEY_ID_MAX_LEN {
return Err(PrikkError::InvalidSignature(format!(
"signature key_id must be at most {SIGNATURE_KEY_ID_MAX_LEN} bytes"
)));
}
if !key_id
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
{
return Err(PrikkError::InvalidSignature(
"signature key_id must contain only ASCII letters, digits, '-' or '_'".to_string(),
));
}
Ok(())
}
pub fn signed_bytes(
algorithm: SignatureAlgorithm,
object_type: ObjectType,
object_id: ObjectId,
signer_role: SignerRole,
key_id: &str,
) -> Result<Vec<u8>> {
Self::validate_key_id(key_id)?;
let key_id_len = u16::try_from(key_id.len()).map_err(|_| {
PrikkError::InvalidSignature(
"signature key_id is too long for the signature preimage length field".to_string(),
)
})?;
let mut out = Vec::with_capacity(SIGNATURE_DOMAIN.len() + 2 + 32 + 2 + 2 + key_id.len());
out.extend_from_slice(SIGNATURE_DOMAIN);
out.extend_from_slice(&algorithm.code().to_be_bytes());
out.extend_from_slice(&object_type.code().to_be_bytes());
out.extend_from_slice(object_id.as_bytes());
out.extend_from_slice(&signer_role.code().to_be_bytes());
out.extend_from_slice(&key_id_len.to_be_bytes());
out.extend_from_slice(key_id.as_bytes());
Ok(out)
}
pub fn validate(&self) -> Result<()> {
Self::validate_key_id(&self.key_id)?;
if self.signature_bytes.is_empty() {
return Err(PrikkError::InvalidSignature(
"signature bytes must not be empty".to_string(),
));
}
Ok(())
}
}
impl CanonicalEncode for Signature {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
writer.field_u32(1, self.algorithm.code() as u32)?;
writer.field_string(2, &self.key_id)?;
writer.field_bytes(3, &self.signature_bytes)?;
writer.field_u64(4, self.created_at)?;
writer.field_u32(5, self.signer_role.code() as u32)?;
Ok(())
}
}