Skip to main content

cesr/core/primitives/
siger.rs

1use alloc::borrow::Cow;
2#[cfg(feature = "alloc")]
3#[allow(
4    unused_imports,
5    reason = "alloc prelude items; subset used per cfg/feature combination"
6)]
7use alloc::{string::String, vec, vec::Vec};
8use core::fmt;
9
10use crate::core::indexer::Indexer;
11use crate::core::indexer::code::IndexedSigCode;
12
13use super::Verfer;
14
15/// An indexed signature primitive.
16///
17/// Wraps an [`Indexer`] (which holds the code, index, ondex, and raw signature
18/// bytes) and optionally carries the [`Verfer`] (verification key) associated
19/// with the signer index.
20///
21/// Construct a `Siger` by first building an [`Indexer`] via
22/// [`IndexerBuilder`](crate::core::indexer::IndexerBuilder), then passing it to
23/// [`Siger::new`].
24pub struct Siger<'a> {
25    indexer: Indexer<'a>,
26    verfer: Option<Verfer<'a>>,
27}
28
29// -- Manual trait implementations (Matter does not derive Clone/Debug/PartialEq/Eq) --
30
31impl fmt::Debug for Siger<'_> {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("Siger")
34            .field("code", &self.indexer.code())
35            .field("index", &self.indexer.index())
36            .field("ondex", &self.indexer.ondex())
37            .field("raw_len", &self.indexer.raw().len())
38            .field("has_verfer", &self.verfer.is_some())
39            .finish()
40    }
41}
42
43impl Clone for Siger<'_> {
44    fn clone(&self) -> Self {
45        let cloned_verfer = self.verfer.as_ref().map(|v| {
46            crate::core::matter::matter::Matter::new(
47                *v.code(),
48                Cow::Owned(v.raw().to_vec()),
49                Cow::Owned(String::from(v.soft())),
50            )
51        });
52        Self {
53            indexer: self.indexer.clone(),
54            verfer: cloned_verfer,
55        }
56    }
57}
58
59impl PartialEq for Siger<'_> {
60    fn eq(&self, other: &Self) -> bool {
61        if self.indexer != other.indexer {
62            return false;
63        }
64        match (&self.verfer, &other.verfer) {
65            (None, None) => true,
66            (Some(a), Some(b)) => a.code() == b.code() && a.raw() == b.raw(),
67            _ => false,
68        }
69    }
70}
71
72impl Eq for Siger<'_> {}
73
74impl<'a> Siger<'a> {
75    /// Creates a new `Siger` from an `Indexer`, with no attached verfer.
76    #[must_use]
77    pub const fn new(indexer: Indexer<'a>) -> Self {
78        Self {
79            indexer,
80            verfer: None,
81        }
82    }
83
84    /// Builder method: attaches a [`Verfer`] (verification key) to this siger.
85    #[must_use]
86    pub fn with_verfer(mut self, verfer: Verfer<'a>) -> Self {
87        self.verfer = Some(verfer);
88        self
89    }
90
91    /// Returns the CESR indexed signature code.
92    #[must_use]
93    pub const fn code(&self) -> IndexedSigCode {
94        self.indexer.code()
95    }
96
97    /// Returns the signer's index in the key list.
98    #[must_use]
99    pub const fn index(&self) -> u32 {
100        self.indexer.index()
101    }
102
103    /// Returns the "other index" (prior-next key list index), if present.
104    #[must_use]
105    pub const fn ondex(&self) -> Option<u32> {
106        self.indexer.ondex()
107    }
108
109    /// Returns the raw signature bytes.
110    #[must_use]
111    pub fn raw(&self) -> &[u8] {
112        self.indexer.raw()
113    }
114
115    /// Returns a reference to the attached verfer, if any.
116    #[must_use]
117    pub const fn verfer(&self) -> Option<&Verfer<'a>> {
118        self.verfer.as_ref()
119    }
120
121    /// Returns the full CESR-encoded size in characters.
122    #[must_use]
123    pub fn full_size(&self) -> usize {
124        self.indexer.full_size()
125    }
126
127    /// Encodes this indexed signature into its qualified Base64 (qb64) CESR
128    /// wire format.
129    #[must_use]
130    pub fn to_qb64(&self) -> String {
131        self.indexer.to_qb64()
132    }
133
134    /// Encodes this indexed signature into its qualified binary (qb2) CESR
135    /// wire format.
136    #[must_use]
137    pub fn to_qb2(&self) -> Vec<u8> {
138        self.indexer.to_qb2()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::core::indexer::IndexerBuilder;
146    use crate::core::indexer::code::IndexedSigCode;
147    use crate::core::matter::builder::MatterBuilder;
148    use crate::core::matter::code::VerKeyCode;
149
150    /// Helper: build an Ed25519 Indexer with the given index and raw bytes.
151    fn ed25519_indexer(index: u32, raw: &[u8]) -> Indexer<'_> {
152        IndexerBuilder::new()
153            .with_code(IndexedSigCode::Ed25519)
154            .with_index(index)
155            .unwrap()
156            .with_raw(raw)
157            .unwrap()
158    }
159
160    /// Helper: build an Ed25519 Verfer from 32 bytes.
161    fn ed25519_verfer(key_bytes: &[u8; 32]) -> Verfer<'_> {
162        MatterBuilder::new()
163            .with_code(VerKeyCode::Ed25519)
164            .with_raw(Cow::Borrowed(key_bytes.as_slice()))
165            .unwrap()
166            .build()
167            .unwrap()
168    }
169
170    #[test]
171    fn siger_delegates_to_indexer() {
172        let raw = [0xAB_u8; 64];
173        let indexer = ed25519_indexer(3, &raw);
174        let siger = Siger::new(indexer);
175
176        assert_eq!(siger.code(), IndexedSigCode::Ed25519);
177        assert_eq!(siger.index(), 3);
178        assert_eq!(siger.ondex(), Some(3));
179        assert_eq!(siger.raw(), &raw);
180    }
181
182    #[test]
183    fn siger_with_verfer_attached() {
184        let sig_bytes = [0u8; 64];
185        let key_bytes = [0u8; 32];
186
187        let indexer = ed25519_indexer(0, &sig_bytes);
188        let verfer = ed25519_verfer(&key_bytes);
189
190        let siger = Siger::new(indexer).with_verfer(verfer);
191
192        assert!(siger.verfer().is_some());
193        let v = siger.verfer().unwrap();
194        assert_eq!(*v.code(), VerKeyCode::Ed25519);
195        assert_eq!(v.raw(), &key_bytes);
196    }
197
198    #[test]
199    fn verfer_returns_none_when_absent() {
200        let indexer = ed25519_indexer(0, &[0u8; 64]);
201        let siger = Siger::new(indexer);
202        assert!(siger.verfer().is_none());
203    }
204
205    #[test]
206    fn to_qb64_delegation_returns_correct_size() {
207        let indexer = ed25519_indexer(0, &[0u8; 64]);
208        let siger = Siger::new(indexer);
209        let qb64 = siger.to_qb64();
210        // Ed25519 indexed sig qb64 is always 88 characters.
211        assert_eq!(qb64.len(), 88);
212    }
213
214    #[test]
215    fn full_size_delegation() {
216        let indexer = ed25519_indexer(0, &[0u8; 64]);
217        let siger = Siger::new(indexer);
218        assert_eq!(siger.full_size(), 88);
219    }
220
221    #[test]
222    fn owned_siger_is_static() {
223        let sig_bytes = vec![0xCDu8; 64];
224        let key_bytes = [0xEFu8; 32];
225
226        // Pass owned data → Indexer<'static> naturally via Cow
227        let indexer: Indexer<'static> = IndexerBuilder::new()
228            .with_code(IndexedSigCode::Ed25519)
229            .with_index(5)
230            .unwrap()
231            .with_raw(sig_bytes)
232            .unwrap();
233
234        let verfer: Verfer<'static> = MatterBuilder::new()
235            .with_code(VerKeyCode::Ed25519)
236            .with_raw(key_bytes.to_vec())
237            .unwrap()
238            .build()
239            .unwrap();
240
241        let siger: Siger<'static> = Siger::new(indexer).with_verfer(verfer);
242
243        assert_eq!(siger.code(), IndexedSigCode::Ed25519);
244        assert_eq!(siger.index(), 5);
245        assert_eq!(siger.raw(), &[0xCD; 64]);
246        assert!(siger.verfer().is_some());
247        assert_eq!(siger.verfer().unwrap().raw(), &[0xEF; 32]);
248    }
249
250    #[test]
251    fn to_qb2_delegation() {
252        let indexer = ed25519_indexer(0, &[0u8; 64]);
253        let siger = Siger::new(indexer);
254        let qb2 = siger.to_qb2();
255        // qb2 length = qb64 length * 3 / 4 = 88 * 3 / 4 = 66
256        assert_eq!(qb2.len(), 66);
257    }
258
259    #[test]
260    fn clone_preserves_all_fields() {
261        let indexer = ed25519_indexer(2, &[0u8; 64]);
262        let verfer = ed25519_verfer(&[0u8; 32]);
263        let siger = Siger::new(indexer).with_verfer(verfer);
264        let cloned = siger.clone();
265        assert_eq!(siger, cloned);
266    }
267}