prikk_object/
signature.rs1use prikk_error::{PrikkError, Result};
4
5use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType};
6
7pub const SIGNATURE_DOMAIN: &[u8] = b"prikk.sig.v1";
9
10pub const SIGNATURE_KEY_ID_MAX_LEN: usize = 128;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(u16)]
16pub enum SignatureAlgorithm {
17 Ed25519 = 1,
19}
20
21impl SignatureAlgorithm {
22 #[must_use]
24 pub const fn code(self) -> u16 {
25 self as u16
26 }
27
28 pub fn from_code(code: u16) -> Result<Self> {
30 match code {
31 1 => Ok(Self::Ed25519),
32 other => Err(PrikkError::InvalidSignature(format!(
33 "unknown signature algorithm code: {other}"
34 ))),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[repr(u16)]
42pub enum SignerRole {
43 Author = 1,
45 Maintainer = 2,
47 Ci = 3,
49 Audit = 4,
51}
52
53impl SignerRole {
54 #[must_use]
56 pub const fn code(self) -> u16 {
57 self as u16
58 }
59
60 pub fn from_code(code: u16) -> Result<Self> {
62 match code {
63 1 => Ok(Self::Author),
64 2 => Ok(Self::Maintainer),
65 3 => Ok(Self::Ci),
66 4 => Ok(Self::Audit),
67 other => Err(PrikkError::InvalidSignature(format!(
68 "unknown signer role code: {other}"
69 ))),
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct Signature {
77 pub algorithm: SignatureAlgorithm,
79 pub key_id: String,
81 pub signature_bytes: Vec<u8>,
83 pub created_at: u64,
85 pub signer_role: SignerRole,
87}
88
89impl Signature {
90 pub fn validate_key_id(key_id: &str) -> Result<()> {
92 if key_id.is_empty() {
93 return Err(PrikkError::InvalidSignature(
94 "signature key_id must not be empty".to_string(),
95 ));
96 }
97 if key_id.len() > SIGNATURE_KEY_ID_MAX_LEN {
98 return Err(PrikkError::InvalidSignature(format!(
99 "signature key_id must be at most {SIGNATURE_KEY_ID_MAX_LEN} bytes"
100 )));
101 }
102 if !key_id
103 .bytes()
104 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
105 {
106 return Err(PrikkError::InvalidSignature(
107 "signature key_id must contain only ASCII letters, digits, '-' or '_'".to_string(),
108 ));
109 }
110 Ok(())
111 }
112
113 pub fn signed_bytes(
115 algorithm: SignatureAlgorithm,
116 object_type: ObjectType,
117 object_id: ObjectId,
118 signer_role: SignerRole,
119 key_id: &str,
120 ) -> Result<Vec<u8>> {
121 Self::validate_key_id(key_id)?;
122 let key_id_len = u16::try_from(key_id.len()).map_err(|_| {
123 PrikkError::InvalidSignature(
124 "signature key_id is too long for the signature preimage length field".to_string(),
125 )
126 })?;
127 let mut out = Vec::with_capacity(SIGNATURE_DOMAIN.len() + 2 + 32 + 2 + 2 + key_id.len());
128 out.extend_from_slice(SIGNATURE_DOMAIN);
129 out.extend_from_slice(&algorithm.code().to_be_bytes());
130 out.extend_from_slice(&object_type.code().to_be_bytes());
131 out.extend_from_slice(object_id.as_bytes());
132 out.extend_from_slice(&signer_role.code().to_be_bytes());
133 out.extend_from_slice(&key_id_len.to_be_bytes());
134 out.extend_from_slice(key_id.as_bytes());
135 Ok(out)
136 }
137
138 pub fn validate(&self) -> Result<()> {
140 Self::validate_key_id(&self.key_id)?;
141 if self.signature_bytes.is_empty() {
142 return Err(PrikkError::InvalidSignature(
143 "signature bytes must not be empty".to_string(),
144 ));
145 }
146 Ok(())
147 }
148}
149
150impl CanonicalEncode for Signature {
151 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
152 writer.field_u32(1, self.algorithm.code() as u32)?;
153 writer.field_string(2, &self.key_id)?;
154 writer.field_bytes(3, &self.signature_bytes)?;
155 writer.field_u64(4, self.created_at)?;
156 writer.field_u32(5, self.signer_role.code() as u32)?;
157 Ok(())
158 }
159}