keri_core/event_message/
signed_event_message.rs

1use cesrox::{group::Group, ParsedData};
2use serde::{ser::SerializeStruct, Deserialize, Serialize};
3
4use super::{msg::KeriEvent, serializer::to_string, signature::Nontransferable};
5#[cfg(feature = "query")]
6use crate::query::{query_event::SignedQueryMessage, reply_event::SignedReply};
7use crate::{
8    error::Error,
9    event::{
10        receipt::Receipt,
11        sections::seal::{EventSeal, SourceSeal},
12        KeyEvent,
13    },
14    prefix::{IdentifierPrefix, IndexedSignature},
15    state::{EventSemantics, IdentifierState},
16};
17
18#[cfg(feature = "mailbox")]
19use crate::mailbox::exchange::SignedExchange;
20
21#[derive(Clone, Debug, PartialEq)]
22pub enum Message {
23    Notice(Notice),
24    #[cfg(feature = "query")]
25    Op(Op),
26}
27
28#[derive(Clone, Debug, PartialEq)]
29pub enum Notice {
30    Event(SignedEventMessage),
31    // Rct's have an alternative appended signature structure,
32    // use SignedNontransferableReceipt and SignedTransferableReceipt
33    NontransferableRct(SignedNontransferableReceipt),
34    TransferableRct(SignedTransferableReceipt),
35}
36
37#[cfg(any(feature = "query", feature = "oobi"))]
38#[derive(Clone, Debug, PartialEq)]
39pub enum Op {
40    #[cfg(feature = "mailbox")]
41    Exchange(SignedExchange),
42    #[cfg(feature = "query")]
43    Reply(SignedReply),
44    #[cfg(feature = "query")]
45    Query(SignedQueryMessage),
46}
47
48impl From<Message> for ParsedData {
49    fn from(message: Message) -> Self {
50        match message {
51            Message::Notice(notice) => ParsedData::from(notice),
52            #[cfg(any(feature = "query", feature = "oobi"))]
53            Message::Op(op) => ParsedData::from(op),
54        }
55    }
56}
57
58impl From<Notice> for ParsedData {
59    fn from(notice: Notice) -> Self {
60        match notice {
61            Notice::Event(event) => ParsedData::from(&event),
62            Notice::NontransferableRct(rct) => ParsedData::from(rct),
63            Notice::TransferableRct(rct) => ParsedData::from(rct),
64        }
65    }
66}
67
68#[cfg(feature = "query")]
69impl From<Op> for ParsedData {
70    fn from(op: Op) -> Self {
71        match op {
72            #[cfg(feature = "query")]
73            Op::Reply(ksn) => ParsedData::from(ksn),
74            #[cfg(feature = "query")]
75            Op::Query(qry) => ParsedData::from(qry),
76            // #[cfg(feature = "query")]
77            // Op::MailboxQuery(qry) => ParsedData::from(qry),
78            #[cfg(feature = "mailbox")]
79            Op::Exchange(exn) => ParsedData::from(exn),
80        }
81    }
82}
83
84impl Message {
85    pub fn to_cesr(&self) -> Result<Vec<u8>, Error> {
86        ParsedData::from(self.clone())
87            .to_cesr()
88            .map_err(|_e| Error::CesrError)
89    }
90
91    pub fn get_prefix(&self) -> IdentifierPrefix {
92        match self {
93            Message::Notice(notice) => notice.get_prefix(),
94            #[cfg(any(feature = "query", feature = "oobi"))]
95            Message::Op(op) => op.get_prefix(),
96        }
97    }
98}
99
100impl Notice {
101    pub fn get_prefix(&self) -> IdentifierPrefix {
102        match self {
103            Notice::Event(ev) => ev.event_message.data.get_prefix(),
104            Notice::NontransferableRct(rct) => rct.body.prefix.clone(),
105            Notice::TransferableRct(rct) => rct.body.prefix.clone(),
106        }
107    }
108}
109
110#[cfg(feature = "query")]
111impl Op {
112    pub fn get_prefix(&self) -> IdentifierPrefix {
113        match self {
114            Op::Reply(reply) => reply.reply.get_prefix(),
115            Op::Query(qry) => qry.prefix(),
116            #[cfg(feature = "mailbox")]
117            // returns exchange message recipient id
118            Op::Exchange(exn) => exn.exchange_message.data.data.get_prefix(),
119        }
120    }
121}
122
123// KERI serializer should be used to serialize this
124#[derive(Debug, Clone, Deserialize)]
125pub struct SignedEventMessage {
126    pub event_message: KeriEvent<KeyEvent>,
127    #[serde(skip_serializing)]
128    pub signatures: Vec<IndexedSignature>,
129    #[serde(skip_serializing)]
130    pub witness_receipts: Option<Vec<Nontransferable>>,
131    #[serde(skip_serializing)]
132    pub delegator_seal: Option<SourceSeal>,
133}
134
135impl Serialize for SignedEventMessage {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: serde::Serializer,
139    {
140        // if JSON - we pack qb64 KERI
141        if serializer.is_human_readable() {
142            let mut em = serializer.serialize_struct("EventMessage", 4)?;
143            em.serialize_field("", &self.event_message)?;
144            let att_sigs = Group::IndexedControllerSignatures(
145                self.signatures
146                    .iter()
147                    .map(|sig| sig.clone().into())
148                    .collect(),
149            );
150            em.serialize_field("-", &att_sigs.to_cesr_str())?;
151            if let Some(ref receipts) = self.witness_receipts {
152                let att_receipts = receipts
153                    .iter()
154                    .map(|rct| match rct {
155                        Nontransferable::Indexed(indexed) => {
156                            let signatures =
157                                indexed.iter().map(|sig| (sig.clone()).into()).collect();
158                            Group::IndexedWitnessSignatures(signatures).to_cesr_str()
159                        }
160                        Nontransferable::Couplet(couplets) => {
161                            let couples = couplets
162                                .iter()
163                                .map(|(bp, sp)| ((bp.clone()).into(), (sp.clone()).into()))
164                                .collect();
165                            Group::NontransReceiptCouples(couples).to_cesr_str()
166                        }
167                    })
168                    .collect::<Vec<_>>()
169                    .join("");
170                em.serialize_field("", &att_receipts)?;
171            }
172            if let Some(ref seal) = self.delegator_seal {
173                let att_seal =
174                    Group::SourceSealCouples(vec![(seal.sn, seal.digest.said.clone().into())]);
175                em.serialize_field("", &att_seal.to_cesr_str())?;
176            }
177
178            em.end()
179        // . else - we pack as it is for DB / CBOR purpose
180        } else {
181            let mut em = serializer.serialize_struct("SignedEventMessage", 4)?;
182            em.serialize_field("event_message", &self.event_message)?;
183            em.serialize_field("signatures", &self.signatures)?;
184            em.serialize_field("witness_receipts", &self.witness_receipts)?;
185            em.serialize_field("delegator_seal", &self.delegator_seal)?;
186            em.end()
187        }
188    }
189}
190
191impl PartialEq for SignedEventMessage {
192    fn eq(&self, other: &Self) -> bool {
193        self.event_message == other.event_message && self.signatures == other.signatures
194    }
195}
196
197impl SignedEventMessage {
198    pub fn new(
199        message: &KeriEvent<KeyEvent>,
200        sigs: Vec<IndexedSignature>,
201        witness_receipts: Option<Vec<Nontransferable>>,
202        delegator_seal: Option<SourceSeal>,
203    ) -> Self {
204        Self {
205            event_message: message.clone(),
206            signatures: sigs,
207            witness_receipts,
208            delegator_seal,
209        }
210    }
211
212    pub fn encode(&self) -> Result<Vec<u8>, Error> {
213        Ok(to_string(&self)?.as_bytes().to_vec())
214    }
215}
216
217impl EventSemantics for SignedEventMessage {
218    fn apply_to(&self, state: IdentifierState) -> Result<IdentifierState, Error> {
219        self.event_message.apply_to(state)
220    }
221}
222
223/// Signed Transferrable Receipt
224///
225/// Event Receipt which is suitable for creation by Transferable
226/// Identifiers. Provides both the signatures and a commitment to
227/// the latest establishment event of the receipt creator.
228/// Mostly intended for use by Validators
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub struct SignedTransferableReceipt {
231    pub body: Receipt,
232    pub validator_seal: EventSeal,
233    pub signatures: Vec<IndexedSignature>,
234}
235
236impl SignedTransferableReceipt {
237    pub fn new(message: Receipt, event_seal: EventSeal, sigs: Vec<IndexedSignature>) -> Self {
238        Self {
239            body: message,
240            validator_seal: event_seal,
241            signatures: sigs,
242        }
243    }
244}
245
246/// Signed Non-Transferrable Receipt
247///
248/// A receipt created by an Identifier of a non-transferrable type.
249/// Mostly intended for use by Witnesses.
250/// NOTE: This receipt has a unique structure to it's appended
251/// signatures
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
253pub struct SignedNontransferableReceipt {
254    pub body: Receipt,
255    // pub couplets: Option<Vec<(BasicPrefix, SelfSigningPrefix)>>,
256    // pub indexed_sigs: Option<Vec<AttachedSignaturePrefix>>,
257    pub signatures: Vec<Nontransferable>,
258}
259
260impl SignedNontransferableReceipt {
261    pub fn new(message: &Receipt, signatures: Vec<Nontransferable>) -> Self {
262        Self {
263            body: message.clone(),
264            signatures,
265        }
266    }
267}
268
269#[cfg(test)]
270pub mod tests {
271    use std::convert::TryFrom;
272
273    use cesrox::{parse, ParsedData};
274
275    use crate::{
276        actor::prelude::Message,
277        event_message::{signature::Nontransferable, signed_event_message::Notice},
278    };
279
280    #[test]
281    fn test_stream1() {
282        // taken from KERIPY: tests/core/test_kevery.py#62
283        let stream = br#"{"v":"KERI10JSON00012b_","t":"icp","d":"ECwI3rbyMMCCBrjBcZW-qIh4SFeY1ri6fl6nFNZ6_LPn","i":"DEzolW_U9CTatBFey9LL9e4_FOekoAJdTbReEstNEl-D","s":"0","kt":"1","k":["DEzolW_U9CTatBFey9LL9e4_FOekoAJdTbReEstNEl-D"],"nt":"1","n":["EL0nWR23_LnKW6OAXJauX2oz6N2V_QZfWeT4tsK-y3jZ"],"bt":"0","b":[],"c":[],"a":[]}-AABAAB7Ro77feCA8A0B632ThEzVKGHwUrEx-TGyV8VdXKZvxPivaWqR__Exa7n02sjJkNlrQcOqs7cXsJ6IDopxkbEC"#;
284
285        let parsed = parse(stream).unwrap().1;
286        let msg = Message::try_from(parsed).unwrap();
287        assert!(matches!(msg, Message::Notice(Notice::Event(_))));
288
289        match msg {
290            Message::Notice(Notice::Event(signed_event)) => {
291                assert_eq!(
292                    signed_event.event_message.encode().unwrap().len(),
293                    signed_event.event_message.serialization_info.size
294                );
295
296                let serialized_again = signed_event.encode();
297                assert!(serialized_again.is_ok());
298                let stringified = String::from_utf8(serialized_again.unwrap()).unwrap();
299                assert_eq!(stream, stringified.as_bytes());
300            }
301            _ => assert!(false),
302        }
303    }
304
305    #[test]
306    fn test_stream2() {
307        // taken from KERIPY: tests/core/test_eventing.py::test_multisig_digprefix#2256
308        let stream = br#"{"v":"KERI10JSON0001e7_","t":"icp","d":"EBfxc4RiVY6saIFmUfEtETs1FcqmktZW88UkbnOg0Qen","i":"EBfxc4RiVY6saIFmUfEtETs1FcqmktZW88UkbnOg0Qen","s":"0","kt":"2","k":["DErocgXD2RGSyvn3MObcx59jeOsEQhv2TqHirVkzrp0Q","DFXLiTjiRdSBPLL6hLa0rskIxk3dh4XwJLfctkJFLRSS","DE9YgIQVgpLwocTVrG8tidKScsQSMWwLWywNC48fhq4f"],"nt":"2","n":["EDJk5EEpC4-tQ7YDwBiKbpaZahh1QCyQOnZRF7p2i8k8","EAXfDjKvUFRj-IEB_o4y-Y_qeJAjYfZtOMD9e7vHNFss","EN8l6yJC2PxribTN0xfri6bLz34Qvj-x3cNwcV3DvT2m"],"bt":"0","b":[],"c":[],"a":[]}-AADAAD4SyJSYlsQG22MGXzRGz2PTMqpkgOyUfq7cS99sC2BCWwdVmEMKiTEeWe5kv-l_d9auxdadQuArLtAGEArW8wEABD0z_vQmFImZXfdR-0lclcpZFfkJJJNXDcUNrf7a-mGsxNLprJo-LROwDkH5m7tVrb-a1jcor2dHD9Jez-r4bQIACBFeU05ywfZycLdR0FxCvAR9BfV9im8tWe1DglezqJLf-vHRQSChY1KafbYNc96hYYpbuN90WzuCRMgV8KgRsEC"#;
309        let parsed = parse(stream).unwrap().1;
310        let msg = Message::try_from(parsed);
311        assert!(msg.is_ok());
312        assert!(matches!(msg, Ok(Message::Notice(Notice::Event(_)))));
313
314        match msg.unwrap() {
315            Message::Notice(Notice::Event(signed_event)) => {
316                assert_eq!(
317                    signed_event.event_message.encode().unwrap().len(),
318                    signed_event.event_message.serialization_info.size
319                );
320                let serialized_again = signed_event.encode();
321                assert!(serialized_again.is_ok());
322                let stringified = String::from_utf8(serialized_again.unwrap()).unwrap();
323                assert_eq!(stream, stringified.as_bytes())
324            }
325            _ => assert!(false),
326        }
327    }
328
329    #[test]
330    fn test_deserialize_signed_receipt() {
331        // Taken from keripy/tests/core/test_eventing.py::test_direct_mode
332        let trans_receipt_event = br#"{"v":"KERI10JSON000091_","t":"rct","d":"EsZuhYAPBDnexP3SOl9YsGvWBrYkjYcRjomUYmCcLAYY","i":"EsZuhYAPBDnexP3SOl9YsGvWBrYkjYcRjomUYmCcLAYY","s":"0"}-FABE7pB5IKuaYh3aIWKxtexyYFhpSjDNTEGSQuxeJbWiylg0AAAAAAAAAAAAAAAAAAAAAAAE7pB5IKuaYh3aIWKxtexyYFhpSjDNTEGSQuxeJbWiylg-AABAAlIts3z2kNyis9l0Pfu54HhVN_yZHEV7NWIVoSTzl5IABelbY8xi7VRyW42ZJvBaaFTGtiqwMOywloVNpG_ZHAQ"#;
333        let parsed_trans_receipt = parse(trans_receipt_event).unwrap().1;
334        let msg = Message::try_from(parsed_trans_receipt);
335        assert!(matches!(
336            msg,
337            Ok(Message::Notice(Notice::TransferableRct(_)))
338        ));
339        assert!(msg.is_ok());
340
341        // Taken from keripy/core/test_witness.py::test_nonindexed_witness_receipts
342        let nontrans_rcp = br#"{"v":"KERI10JSON000091_","t":"rct","d":"E77aKmmdHtYKuJeBOYWRHbi8C6dYqzG-ESfdvlUAptlo","i":"EHz9RXAr9JiJn-3wkBvsUo1Qq3hvMQPaITxzcfJND8NM","s":"2"}-CABB389hKezugU2LFKiFVbitoHAxXqJh6HQ8Rn9tH7fxd680Bpx_cu_UoMtD0ES-bS9Luh-b2A_AYmM3PmVNfgFrFXls4IE39-_D14dS46NEMqCf0vQmqDcQmhY-UOpgoyFS2Bw"#;
343        let parsed_nontrans_receipt = parse(nontrans_rcp).unwrap().1;
344        let msg = Message::try_from(parsed_nontrans_receipt);
345        assert!(msg.is_ok());
346        assert!(matches!(
347            msg,
348            Ok(Message::Notice(Notice::NontransferableRct(_)))
349        ));
350
351        // takien from keripy/tests/core/test_witness.py::test_indexed_witness_reply
352        let witness_receipts = br#"{"v":"KERI10JSON000091_","t":"rct","d":"EHz9RXAr9JiJn-3wkBvsUo1Qq3hvMQPaITxzcfJND8NM","i":"EHz9RXAr9JiJn-3wkBvsUo1Qq3hvMQPaITxzcfJND8NM","s":"0"}-BADAAdgQkf11JTyF2WVA1Vji1ZhXD8di4AJsfro-sN_jURM1SUioeOleik7w8lkDldKtg0-Nr1X32V9Q8tk8RvBGxDgABZmkRun-qNliRA8WR2fIUnVeB8eFLF7aLFtn2hb31iW7wYSYafR0kT3fV_r1wNNdjm9dkBw-_2xsxThTGfO5UAwACRGJiRPFe4ClvpqZL3LHcEAeT396WVrYV10EaTdt0trINT8rPbz96deSFT32z3myNPVwLlNcq4FzIaQCooM2HDQ"#;
353        let parsed_witness_receipt: ParsedData = parse(witness_receipts).unwrap().1;
354
355        let msg = Message::try_from(parsed_witness_receipt);
356        assert!(msg.is_ok());
357        if let Ok(Message::Notice(Notice::NontransferableRct(rct))) = msg {
358            match &rct.signatures[0] {
359                Nontransferable::Indexed(indexed) => {
360                    assert_eq!(3, indexed.len());
361                }
362                Nontransferable::Couplet(_) => {
363                    unreachable!()
364                }
365            };
366        } else {
367            assert!(false)
368        };
369    }
370
371    #[cfg(feature = "mailbox")]
372    #[test]
373    fn test_deserialize_signed_exchange() {
374        use crate::event_message::signed_event_message::Op;
375        let exn_event = br#"{"v":"KERI10JSON0002f1_","t":"exn","d":"EBLqTGJXK8ViUGXMOO8_LXbetpjJX8CY_SbA134RIZmf","dt":"2022-10-25T09:53:04.119676+00:00","r":"/fwd","q":{"pre":"EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4","topic":"multisig"},"a":{"v":"KERI10JSON000215_","t":"icp","d":"EC61gZ9lCKmHAS7U5ehUfEbGId5rcY0D7MirFZHDQcE2","i":"EC61gZ9lCKmHAS7U5ehUfEbGId5rcY0D7MirFZHDQcE2","s":"0","kt":"2","k":["DOZlWGPfDHLMf62zSFzE8thHmnQUOgA3_Y-KpOyF9ScG","DHGb2qY9WwZ1sBnC9Ip0F-M8QjTM27ftI-3jTGF9mc6K"],"nt":"2","n":["EBvD5VIVvf6NpP9GRmTqu_Cd1KN0RKrKNfPJ-uhIxurj","EHlpcaxffvtcpoUUMTc6tpqAVtb2qnOYVk_3HRsZ34PH"],"bt":"3","b":["BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha","BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM","BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX"],"c":[],"a":[]}}-HABEJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1-AABAAArUSuSpts5zDQ7CgPcy305IxhAG8lOjf-r_d5yYQXp18OD9No_gd2McOOjGWMfjyLVjDK529pQcbvNv9Uwc6gH-LAZ5AABAA-a-AABAABYHc_lpuYF3SPNWvyPjzek7yquw69Csc6pLv5vrXHkFAFDcwNNTVxq7ZpxpqOO0CAIS-9Qj1zMor-cwvMHAmkE"#;
376
377        let parsed_exn = parse(exn_event).unwrap().1;
378        let msg = Message::try_from(parsed_exn).unwrap();
379        assert!(matches!(msg, Message::Op(Op::Exchange(_))));
380        assert_eq!(msg.to_cesr().unwrap(), exn_event);
381    }
382}