1use thiserror::Error;
97
98use crate::cbor::{decode_cbor, encode_cbor, DecodeError};
99use crate::hash::Hash;
100use crate::identity::{PrivateKey, PublicKey, Signature};
101use crate::Extensions;
102
103pub type RawOperation = (Vec<u8>, Option<Vec<u8>>);
105
106#[derive(Clone, Debug)]
108pub struct Operation<E = ()> {
109 pub hash: Hash,
110 pub header: Header<E>,
111 pub body: Option<Body>,
112}
113
114impl<E> PartialEq for Operation<E> {
115 fn eq(&self, other: &Self) -> bool {
116 self.hash.eq(&other.hash)
117 }
118}
119
120impl<E> Eq for Operation<E> {}
121
122impl<E> PartialOrd for Operation<E> {
123 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
124 Some(self.hash.cmp(&other.hash))
125 }
126}
127
128impl<E> Ord for Operation<E> {
129 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
130 self.hash.cmp(&other.hash)
131 }
132}
133
134#[derive(Clone, Debug, PartialEq)]
165#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
166pub struct Header<E = ()> {
167 pub version: u64,
169
170 pub public_key: PublicKey,
172
173 pub signature: Option<Signature>,
175
176 pub payload_size: u64,
178
179 pub payload_hash: Option<Hash>,
185
186 pub timestamp: u64,
188
189 pub seq_num: u64,
192
193 pub backlink: Option<Hash>,
196
197 pub previous: Vec<Hash>,
201
202 pub extensions: Option<E>,
204}
205
206impl<E> Default for Header<E> {
207 fn default() -> Self {
208 Self {
209 version: 1,
210 public_key: PublicKey::default(),
211 signature: None,
212 payload_size: 0,
213 payload_hash: None,
214 timestamp: 0,
215 seq_num: 0,
216 backlink: None,
217 previous: vec![],
218 extensions: None,
219 }
220 }
221}
222
223impl<E> Header<E>
224where
225 E: Extensions,
226{
227 pub fn to_bytes(&self) -> Vec<u8> {
229 encode_cbor(self)
230 .expect("CBOR encoder failed due to an critical IO error")
233 }
234
235 pub fn sign(&mut self, private_key: &PrivateKey) {
240 self.signature = None;
242
243 let bytes = self.to_bytes();
244 self.signature = Some(private_key.sign(&bytes));
245 }
246
247 pub fn verify(&self) -> bool {
250 match self.signature {
251 Some(claimed_signature) => {
252 let mut unsigned_header = self.clone();
253 unsigned_header.signature = None;
254 let unsigned_bytes = unsigned_header.to_bytes();
255 self.public_key.verify(&unsigned_bytes, &claimed_signature)
256 }
257 None => false,
258 }
259 }
260
261 pub fn hash(&self) -> Hash {
265 Hash::new(self.to_bytes())
266 }
267}
268
269impl<E> Header<E> {
270 pub(crate) fn field_count(&self) -> usize {
274 let mut count = 6;
276
277 if self.signature.is_some() {
278 count += 1;
279 }
280
281 if self.payload_hash.is_some() {
282 count += 1;
283 }
284
285 if self.backlink.is_some() {
286 count += 1;
287 }
288
289 if self.extensions.is_some() {
290 count += 1;
291 }
292
293 count
294 }
295}
296
297impl TryFrom<&[u8]> for Header {
298 type Error = DecodeError;
299
300 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
301 decode_cbor(value)
302 }
303}
304
305#[derive(Clone, Debug, PartialEq)]
307pub struct Body(pub(super) Vec<u8>);
308
309impl Body {
310 pub fn new(bytes: &[u8]) -> Self {
312 Self(bytes.to_vec())
313 }
314
315 pub fn to_bytes(&self) -> Vec<u8> {
317 self.0.clone()
318 }
319
320 pub fn hash(&self) -> Hash {
322 Hash::new(&self.0)
323 }
324
325 pub fn size(&self) -> u64 {
327 self.0.len() as u64
328 }
329}
330
331impl From<&[u8]> for Body {
332 fn from(value: &[u8]) -> Self {
333 Body::new(value)
334 }
335}
336
337impl From<Vec<u8>> for Body {
338 fn from(value: Vec<u8>) -> Self {
339 Body(value)
340 }
341}
342
343#[derive(Clone, Debug, Error)]
344pub enum OperationError {
345 #[error("operation version {0} is not supported, needs to be <= {1}")]
346 UnsupportedVersion(u64, u64),
347
348 #[error("operation needs to be signed")]
349 MissingSignature,
350
351 #[error("signature does not match claimed public key")]
352 SignatureMismatch,
353
354 #[error("sequence number can't be 0 when backlink is given")]
355 SeqNumMismatch,
356
357 #[error("payload hash and -size need to be defined together")]
358 InconsistentPayloadInfo,
359
360 #[error("needs payload hash in header when body is given")]
361 MissingPayloadHash,
362
363 #[error("payload hash and size do not match given body")]
364 PayloadMismatch,
365
366 #[error("logs can not contain operations of different authors")]
367 TooManyAuthors,
368
369 #[error("expected sequence number {0} but found {1}")]
370 SeqNumNonIncremental(u64, u64),
371
372 #[error("expected backlink but none was given")]
373 BacklinkMissing,
374
375 #[error("given backlink did not match previous operation")]
376 BacklinkMismatch,
377}
378
379pub fn validate_operation<E>(operation: &Operation<E>) -> Result<(), OperationError>
390where
391 E: Extensions,
392{
393 validate_header(&operation.header)?;
394
395 let claimed_payload_size = operation.header.payload_size;
396 let claimed_payload_hash: Option<Hash> = match claimed_payload_size {
397 0 => None,
398 _ => {
399 let hash = operation
400 .header
401 .payload_hash
402 .ok_or(OperationError::MissingPayloadHash)?;
403 Some(hash)
404 }
405 };
406
407 if let Some(body) = &operation.body {
408 if claimed_payload_hash != Some(body.hash()) || claimed_payload_size != body.size() {
409 return Err(OperationError::PayloadMismatch);
410 }
411 }
412
413 Ok(())
414}
415
416pub fn validate_header<E>(header: &Header<E>) -> Result<(), OperationError>
424where
425 E: Extensions,
426{
427 if !header.verify() {
428 return Err(OperationError::SignatureMismatch);
429 }
430
431 if header.version != 1 {
432 return Err(OperationError::UnsupportedVersion(header.version, 1));
433 }
434
435 if (header.payload_hash.is_some() && header.payload_size == 0)
436 || (header.payload_hash.is_none() && header.payload_size > 0)
437 {
438 return Err(OperationError::InconsistentPayloadInfo);
439 }
440
441 if header.backlink.is_some() && header.seq_num == 0 {
442 return Err(OperationError::SeqNumMismatch);
443 }
444
445 if header.backlink.is_none() && header.seq_num > 0 {
446 return Err(OperationError::BacklinkMissing);
447 }
448
449 Ok(())
450}
451
452pub fn validate_backlink<E>(
460 past_header: &Header<E>,
461 header: &Header<E>,
462) -> Result<(), OperationError>
463where
464 E: Extensions,
465{
466 if past_header.public_key != header.public_key {
467 return Err(OperationError::TooManyAuthors);
468 }
469
470 if past_header.seq_num + 1 != header.seq_num {
471 return Err(OperationError::SeqNumNonIncremental(
472 past_header.seq_num + 1,
473 header.seq_num,
474 ));
475 }
476
477 match header.backlink {
478 Some(backlink) => {
479 if past_header.hash() != backlink {
480 return Err(OperationError::BacklinkMismatch);
481 }
482 }
483 None => {
484 return Err(OperationError::BacklinkMissing);
485 }
486 }
487
488 Ok(())
489}
490
491#[cfg(test)]
492mod tests {
493 use serde::{Deserialize, Serialize};
494
495 use crate::{Extension, PrivateKey};
496
497 use super::*;
498
499 #[test]
500 fn simple_extension_type_parameter() {
501 let private_key = PrivateKey::new();
502 let body = Body::new("Hello, Sloth!".as_bytes());
503 let mut header = Header {
504 version: 1,
505 public_key: private_key.public_key(),
506 signature: None,
507 payload_size: body.size(),
508 payload_hash: Some(body.hash()),
509 timestamp: 0,
510 seq_num: 0,
511 backlink: None,
512 previous: vec![],
513 extensions: None::<()>,
514 };
515
516 header.sign(&private_key);
517 }
518
519 #[test]
520 fn sign_and_verify() {
521 let private_key = PrivateKey::new();
522 let body = Body::new("Hello, Sloth!".as_bytes());
523 type CustomExtensions = ();
524
525 let mut header = Header {
526 version: 1,
527 public_key: private_key.public_key(),
528 signature: None,
529 payload_size: body.size(),
530 payload_hash: Some(body.hash()),
531 timestamp: 0,
532 seq_num: 0,
533 backlink: None,
534 previous: vec![],
535 extensions: None::<CustomExtensions>,
536 };
537 assert!(!header.verify());
538
539 header.sign(&private_key);
540 assert!(header.verify());
541
542 let operation = Operation {
543 hash: header.hash(),
544 header,
545 body: Some(body),
546 };
547 assert!(validate_operation(&operation).is_ok());
548 }
549
550 #[test]
551 fn valid_backlink_header() {
552 let private_key = PrivateKey::new();
553
554 let mut header_0 = Header::<()> {
555 version: 1,
556 public_key: private_key.public_key(),
557 signature: None,
558 payload_size: 0,
559 payload_hash: None,
560 timestamp: 0,
561 seq_num: 0,
562 backlink: None,
563 previous: vec![],
564 extensions: None,
565 };
566 header_0.sign(&private_key);
567 assert!(validate_header(&header_0).is_ok());
568
569 let mut header_1 = Header::<()> {
570 version: 1,
571 public_key: private_key.public_key(),
572 signature: None,
573 payload_size: 0,
574 payload_hash: None,
575 timestamp: 0,
576 seq_num: 1,
577 backlink: Some(header_0.hash()),
578 previous: vec![],
579 extensions: None,
580 };
581 header_1.sign(&private_key);
582 assert!(validate_header(&header_1).is_ok());
583
584 assert!(validate_backlink(&header_0, &header_1).is_ok());
585 }
586
587 #[test]
588 fn invalid_operations() {
589 let private_key = PrivateKey::new();
590 let body: Body = Body::new("Hello, Sloth!".as_bytes());
591
592 let header_base = Header::<()> {
593 version: 1,
594 public_key: private_key.public_key(),
595 signature: None,
596 payload_size: body.size(),
597 payload_hash: Some(body.hash()),
598 timestamp: 0,
599 seq_num: 0,
600 backlink: None,
601 previous: vec![],
602 extensions: None,
603 };
604
605 let mut header = header_base.clone();
607 header.version = 0;
608 header.sign(&private_key);
609 assert!(matches!(
610 validate_header(&header),
611 Err(OperationError::UnsupportedVersion(0, 1))
612 ));
613
614 let mut header = header_base.clone();
616 header.public_key = PrivateKey::new().public_key();
617 header.sign(&private_key);
618 assert!(matches!(
619 validate_header(&header),
620 Err(OperationError::SignatureMismatch)
621 ));
622
623 let mut header = header_base.clone();
625 header.seq_num = 1;
626 header.sign(&private_key);
627 assert!(matches!(
628 validate_header(&header),
629 Err(OperationError::BacklinkMissing)
630 ));
631
632 let mut header = header_base.clone();
634 header.backlink = Some(Hash::new(vec![4, 5, 6]));
635 header.sign(&private_key);
636 assert!(matches!(
637 validate_header(&header),
638 Err(OperationError::SeqNumMismatch)
639 ));
640
641 let mut header = header_base.clone();
643 header.payload_size = 11;
644 header.sign(&private_key);
645 assert!(matches!(
646 validate_operation(&Operation {
647 hash: header.hash(),
648 header,
649 body: Some(body.clone()),
650 }),
651 Err(OperationError::PayloadMismatch)
652 ));
653
654 let mut header = header_base.clone();
656 header.payload_hash = Some(Hash::new(vec![4, 5, 6]));
657 header.sign(&private_key);
658 assert!(matches!(
659 validate_operation(&Operation {
660 hash: header.hash(),
661 header,
662 body: Some(body.clone()),
663 }),
664 Err(OperationError::PayloadMismatch)
665 ));
666 }
667
668 #[test]
669 fn extensions() {
670 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
671 struct LogId(u64);
672
673 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
674 struct Expiry(u64);
675
676 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
677 struct CustomExtensions {
678 log_id: LogId,
679 expires: Expiry,
680 }
681
682 impl Extension<LogId> for CustomExtensions {
683 fn extract(&self) -> Option<LogId> {
684 Some(self.log_id.to_owned())
685 }
686 }
687
688 impl Extension<Expiry> for CustomExtensions {
689 fn extract(&self) -> Option<Expiry> {
690 Some(self.expires.to_owned())
691 }
692 }
693
694 let extensions = CustomExtensions {
695 log_id: LogId(0),
696 expires: Expiry(0123456),
697 };
698
699 let log_id = Extension::<LogId>::extract(&extensions).unwrap();
700 let expiry = Extension::<Expiry>::extract(&extensions).unwrap();
701
702 assert_eq!(extensions.log_id.0, log_id.0);
703 assert_eq!(extensions.expires.0, expiry.0);
704
705 let private_key = PrivateKey::new();
706 let body: Body = Body::new("Hello, Sloth!".as_bytes());
707
708 let mut header = Header {
709 version: 1,
710 public_key: private_key.public_key(),
711 signature: None,
712 payload_size: body.size(),
713 payload_hash: Some(body.hash()),
714 timestamp: 0,
715 seq_num: 0,
716 backlink: None,
717 previous: vec![],
718 extensions: Some(extensions.clone()),
719 };
720
721 header.sign(&private_key);
722
723 let log_id = Extension::<LogId>::extract(&header).unwrap();
726 let expiry = Extension::<Expiry>::extract(&header).unwrap();
727
728 assert_eq!(extensions.log_id.0, log_id.0);
729 assert_eq!(extensions.expires.0, expiry.0);
730 }
731}