prikk_object/
signature.rs1use prikk_error::{PrikkError, Result};
4use std::cmp::Ordering;
5
6use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType};
7
8pub const SIGNATURE_DOMAIN: &[u8] = b"prikk.sig.v1";
10
11pub const SIGNATURE_KEY_ID_MAX_LEN: usize = 128;
13
14pub const ED25519_SIGNATURE_LEN: usize = 64;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(u16)]
20pub enum SignatureAlgorithm {
21 Ed25519 = 1,
23}
24
25impl SignatureAlgorithm {
26 #[must_use]
28 pub const fn code(self) -> u16 {
29 self as u16
30 }
31
32 pub fn from_code(code: u16) -> Result<Self> {
34 match code {
35 1 => Ok(Self::Ed25519),
36 other => Err(PrikkError::InvalidSignature(format!(
37 "unknown signature algorithm code: {other}"
38 ))),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45#[repr(u16)]
46pub enum SignerRole {
47 Author = 1,
49 Maintainer = 2,
51 Ci = 3,
53 Audit = 4,
55}
56
57impl SignerRole {
58 #[must_use]
60 pub const fn code(self) -> u16 {
61 self as u16
62 }
63
64 pub fn from_code(code: u16) -> Result<Self> {
66 match code {
67 1 => Ok(Self::Author),
68 2 => Ok(Self::Maintainer),
69 3 => Ok(Self::Ci),
70 4 => Ok(Self::Audit),
71 other => Err(PrikkError::InvalidSignature(format!(
72 "unknown signer role code: {other}"
73 ))),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct Signature {
84 pub algorithm: SignatureAlgorithm,
86 pub key_id: String,
88 pub signature_bytes: Vec<u8>,
90 pub created_at: u64,
92 pub signer_role: SignerRole,
94}
95
96impl Signature {
97 pub fn validate_key_id(key_id: &str) -> Result<()> {
99 if key_id.is_empty() {
100 return Err(PrikkError::InvalidSignature(
101 "signature key_id must not be empty".to_string(),
102 ));
103 }
104 if key_id.len() > SIGNATURE_KEY_ID_MAX_LEN {
105 return Err(PrikkError::InvalidSignature(format!(
106 "signature key_id must be at most {SIGNATURE_KEY_ID_MAX_LEN} bytes"
107 )));
108 }
109 if !key_id
110 .bytes()
111 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
112 {
113 return Err(PrikkError::InvalidSignature(
114 "signature key_id must contain only ASCII letters, digits, '-' or '_'".to_string(),
115 ));
116 }
117 Ok(())
118 }
119
120 pub fn signed_bytes(
122 algorithm: SignatureAlgorithm,
123 object_type: ObjectType,
124 object_id: ObjectId,
125 signer_role: SignerRole,
126 key_id: &str,
127 ) -> Result<Vec<u8>> {
128 Self::validate_key_id(key_id)?;
129 let key_id_len = u16::try_from(key_id.len()).map_err(|_| {
130 PrikkError::InvalidSignature(
131 "signature key_id is too long for the signature preimage length field".to_string(),
132 )
133 })?;
134 let mut out =
135 Vec::with_capacity(SIGNATURE_DOMAIN.len() + 2 + 2 + 32 + 2 + 2 + key_id.len());
136 out.extend_from_slice(SIGNATURE_DOMAIN);
137 out.extend_from_slice(&algorithm.code().to_be_bytes());
138 out.extend_from_slice(&object_type.code().to_be_bytes());
139 out.extend_from_slice(object_id.as_bytes());
140 out.extend_from_slice(&signer_role.code().to_be_bytes());
141 out.extend_from_slice(&key_id_len.to_be_bytes());
142 out.extend_from_slice(key_id.as_bytes());
143 Ok(out)
144 }
145
146 pub fn validate(&self) -> Result<()> {
148 Self::validate_key_id(&self.key_id)?;
149 if self.signature_bytes.is_empty() {
150 return Err(PrikkError::InvalidSignature(
151 "signature bytes must not be empty".to_string(),
152 ));
153 }
154 Ok(())
155 }
156
157 pub fn validate_shape(&self) -> Result<()> {
159 match self.algorithm {
160 SignatureAlgorithm::Ed25519 if self.signature_bytes.len() == ED25519_SIGNATURE_LEN => {
161 Ok(())
162 }
163 SignatureAlgorithm::Ed25519 => Err(PrikkError::InvalidSignature(format!(
164 "Ed25519 signature must be {ED25519_SIGNATURE_LEN} bytes, got {}",
165 self.signature_bytes.len()
166 ))),
167 }
168 }
169
170 #[must_use]
172 pub fn canonical_cmp(&self, other: &Self) -> Ordering {
173 self.key_id
174 .as_bytes()
175 .cmp(other.key_id.as_bytes())
176 .then_with(|| self.signer_role.code().cmp(&other.signer_role.code()))
177 .then_with(|| self.algorithm.code().cmp(&other.algorithm.code()))
178 .then_with(|| self.signature_bytes.cmp(&other.signature_bytes))
179 }
180}
181
182impl CanonicalEncode for Signature {
183 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
184 writer.field_u32(1, self.algorithm.code() as u32)?;
185 writer.field_string(2, &self.key_id)?;
186 writer.field_bytes(3, &self.signature_bytes)?;
187 writer.field_u64(4, self.created_at)?;
188 writer.field_u32(5, self.signer_role.code() as u32)?;
189 Ok(())
190 }
191}