Skip to main content

prikk_object/
signature.rs

1//! Signature metadata and signed-byte construction.
2
3use prikk_error::{PrikkError, Result};
4
5use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType};
6
7/// Signature domain string.
8pub const SIGNATURE_DOMAIN: &[u8] = b"prikk.sig.v1";
9
10/// Supported signature algorithms.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(u16)]
13pub enum SignatureAlgorithm {
14    /// Ed25519. The only v1 signing and verification algorithm.
15    Ed25519 = 1,
16}
17
18impl SignatureAlgorithm {
19    /// Stable u16 code.
20    #[must_use]
21    pub const fn code(self) -> u16 {
22        self as u16
23    }
24
25    /// Parse a stable u16 code.
26    pub fn from_code(code: u16) -> Result<Self> {
27        match code {
28            1 => Ok(Self::Ed25519),
29            other => Err(PrikkError::InvalidSignature(format!(
30                "unknown signature algorithm code: {other}"
31            ))),
32        }
33    }
34}
35
36/// Role bound into signature preimages.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38#[repr(u16)]
39pub enum SignerRole {
40    /// Author of a patch.
41    Author = 1,
42    /// Maintainer publishing/sealing a block or ref state.
43    Maintainer = 2,
44    /// Continuous-integration actor.
45    Ci = 3,
46    /// Audit plugin or audit policy signer.
47    Audit = 4,
48}
49
50impl SignerRole {
51    /// Stable u16 code.
52    #[must_use]
53    pub const fn code(self) -> u16 {
54        self as u16
55    }
56
57    /// Parse a stable u16 code.
58    pub fn from_code(code: u16) -> Result<Self> {
59        match code {
60            1 => Ok(Self::Author),
61            2 => Ok(Self::Maintainer),
62            3 => Ok(Self::Ci),
63            4 => Ok(Self::Audit),
64            other => Err(PrikkError::InvalidSignature(format!(
65                "unknown signer role code: {other}"
66            ))),
67        }
68    }
69}
70
71/// Signature attached to an object envelope.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Signature {
74    /// Signature algorithm.
75    pub algorithm: SignatureAlgorithm,
76    /// Key identifier.
77    pub key_id: String,
78    /// Raw signature bytes.
79    pub signature_bytes: Vec<u8>,
80    /// Advisory signing timestamp. Do not use as authoritative audit time.
81    pub created_at: u64,
82    /// Signer role.
83    pub signer_role: SignerRole,
84}
85
86impl Signature {
87    /// Build the bytes to sign for an object ID and role.
88    #[must_use]
89    pub fn signed_bytes(
90        algorithm: SignatureAlgorithm,
91        object_type: ObjectType,
92        object_id: ObjectId,
93        signer_role: SignerRole,
94        key_id: &str,
95    ) -> Vec<u8> {
96        let mut out = Vec::with_capacity(SIGNATURE_DOMAIN.len() + 2 + 32 + 2 + 2 + key_id.len());
97        out.extend_from_slice(SIGNATURE_DOMAIN);
98        out.extend_from_slice(&algorithm.code().to_be_bytes());
99        out.extend_from_slice(&object_type.code().to_be_bytes());
100        out.extend_from_slice(object_id.as_bytes());
101        out.extend_from_slice(&signer_role.code().to_be_bytes());
102        out.extend_from_slice(&(key_id.len() as u16).to_be_bytes());
103        out.extend_from_slice(key_id.as_bytes());
104        out
105    }
106
107    /// Validate local structural constraints.
108    pub fn validate(&self) -> Result<()> {
109        if self.key_id.is_empty() {
110            return Err(PrikkError::InvalidSignature(
111                "signature key_id must not be empty".to_string(),
112            ));
113        }
114        if self.signature_bytes.is_empty() {
115            return Err(PrikkError::InvalidSignature(
116                "signature bytes must not be empty".to_string(),
117            ));
118        }
119        Ok(())
120    }
121}
122
123impl CanonicalEncode for Signature {
124    fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
125        writer.field_u32(1, self.algorithm.code() as u32)?;
126        writer.field_string(2, &self.key_id)?;
127        writer.field_bytes(3, &self.signature_bytes)?;
128        writer.field_u64(4, self.created_at)?;
129        writer.field_u32(5, self.signer_role.code() as u32)?;
130        Ok(())
131    }
132}