1use crate::{ArtifactId, Error, Intent, KeyFingerprint, Result};
2use chrono::{DateTime, TimeZone, Utc};
3use std::io::{Read, Write};
4
5pub const MAGIC: &[u8; 4] = b"VWH\0";
6pub const VERSION_V1: u16 = 1;
7pub const VERSION_V2: u16 = 2;
8pub const ARTIFACT_SIZE_V1: usize = 128;
9pub const ARTIFACT_SIZE_V2: usize = 256;
10
11pub const FLAG_SEALED: u8 = 0b00000001;
13
14pub const ZERO_PUBKEY: [u8; 32] = [0u8; 32];
16pub const ZERO_HASH: [u8; 32] = [0u8; 32];
17pub const ZERO_SIGNATURE: [u8; 64] = [0u8; 64];
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ArtifactVersion {
22 V1,
23 V2,
24}
25
26impl std::fmt::Display for ArtifactVersion {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 ArtifactVersion::V1 => write!(f, "v1"),
30 ArtifactVersion::V2 => write!(f, "v2"),
31 }
32 }
33}
34
35impl ArtifactVersion {
36 pub fn as_u16(&self) -> u16 {
37 match self {
38 ArtifactVersion::V1 => VERSION_V1,
39 ArtifactVersion::V2 => VERSION_V2,
40 }
41 }
42
43 pub fn from_u16(v: u16) -> Result<Self> {
44 match v {
45 VERSION_V1 => Ok(ArtifactVersion::V1),
46 VERSION_V2 => Ok(ArtifactVersion::V2),
47 _ => Err(Error::UnsupportedVersion(v)),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum ArtifactState {
55 Draft,
57 Signed,
59 Sealed,
61}
62
63#[derive(Debug, Clone)]
65pub struct Artifact {
66 pub version: ArtifactVersion,
67
68 pub artifact_id: ArtifactId,
70 pub timestamp: DateTime<Utc>,
71 pub intent: Intent,
72 pub author_pubkey: [u8; 32],
73 pub author_signature: [u8; 64],
74
75 pub flags: u8, pub reserved_a: u8,
80 pub note_hash: [u8; 32],
81 pub seal_pubkey: [u8; 32],
82 pub seal_signature: [u8; 64],
83}
84
85impl Artifact {
86 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
88 if bytes.len() < 6 {
90 return Err(Error::FileTooSmall { expected: 6, actual: bytes.len() });
91 }
92
93 if &bytes[0..4] != MAGIC {
95 return Err(Error::InvalidMagic);
96 }
97
98 let version_u16 = u16::from_le_bytes([bytes[4], bytes[5]]);
100 let version = ArtifactVersion::from_u16(version_u16)?;
101
102 match version {
103 ArtifactVersion::V1 => Self::from_bytes_v1(bytes),
104 ArtifactVersion::V2 => Self::from_bytes_v2(bytes),
105 }
106 }
107
108 fn from_bytes_v1(bytes: &[u8]) -> Result<Self> {
110 if bytes.len() != ARTIFACT_SIZE_V1 {
111 return Err(Error::FileTooSmall {
112 expected: ARTIFACT_SIZE_V1,
113 actual: bytes.len(),
114 });
115 }
116
117 let mut cursor = 0;
118
119 cursor += 4;
121
122 cursor += 2;
124
125 let flags = bytes[cursor];
127 cursor += 1;
128
129 let mut artifact_id_bytes = [0u8; 16];
131 artifact_id_bytes.copy_from_slice(&bytes[cursor..cursor + 16]);
132 let artifact_id = ArtifactId::from_bytes(artifact_id_bytes);
133 cursor += 16;
134
135 let timestamp_secs =
137 u64::from_le_bytes(bytes[cursor..cursor + 8].try_into().unwrap());
138 let timestamp = Utc
139 .timestamp_opt(timestamp_secs as i64, 0)
140 .single()
141 .ok_or_else(|| Error::UnexpectedEof {
142 field: "timestamp".to_string(),
143 })?;
144 cursor += 8;
145
146 let intent = Intent::from_u8(bytes[cursor])?;
148 cursor += 1;
149
150 let mut author_pubkey = [0u8; 32];
152 author_pubkey.copy_from_slice(&bytes[cursor..cursor + 32]);
153 cursor += 32;
154
155 let mut author_signature = [0u8; 64];
157 author_signature.copy_from_slice(&bytes[cursor..cursor + 64]);
158
159 Ok(Artifact {
160 version: ArtifactVersion::V1,
161 artifact_id,
162 timestamp,
163 intent,
164 author_pubkey,
165 author_signature,
166 flags,
167 reserved_a: 0,
168 note_hash: ZERO_HASH,
169 seal_pubkey: ZERO_PUBKEY,
170 seal_signature: ZERO_SIGNATURE,
171 })
172 }
173
174 fn from_bytes_v2(bytes: &[u8]) -> Result<Self> {
176 if bytes.len() != ARTIFACT_SIZE_V2 {
177 return Err(Error::FileTooSmall {
178 expected: ARTIFACT_SIZE_V2,
179 actual: bytes.len(),
180 });
181 }
182
183 let mut cursor = 0;
184
185 cursor += 4;
187
188 cursor += 2;
190
191 let reserved_a = bytes[cursor];
193 cursor += 1;
194
195 let mut artifact_id_bytes = [0u8; 16];
197 artifact_id_bytes.copy_from_slice(&bytes[cursor..cursor + 16]);
198 let artifact_id = ArtifactId::from_bytes(artifact_id_bytes);
199 cursor += 16;
200
201 let timestamp_secs =
203 u64::from_le_bytes(bytes[cursor..cursor + 8].try_into().unwrap());
204 let timestamp = Utc
205 .timestamp_opt(timestamp_secs as i64, 0)
206 .single()
207 .ok_or_else(|| Error::UnexpectedEof {
208 field: "timestamp".to_string(),
209 })?;
210 cursor += 8;
211
212 let intent = Intent::from_u8(bytes[cursor])?;
214 cursor += 1;
215
216 let mut author_pubkey = [0u8; 32];
218 author_pubkey.copy_from_slice(&bytes[cursor..cursor + 32]);
219 cursor += 32;
220
221 let mut note_hash = [0u8; 32];
223 note_hash.copy_from_slice(&bytes[cursor..cursor + 32]);
224 cursor += 32;
225
226 let mut author_signature = [0u8; 64];
228 author_signature.copy_from_slice(&bytes[cursor..cursor + 64]);
229 cursor += 64;
230
231 let mut seal_pubkey = [0u8; 32];
233 seal_pubkey.copy_from_slice(&bytes[cursor..cursor + 32]);
234 cursor += 32;
235
236 let mut seal_signature = [0u8; 64];
238 seal_signature.copy_from_slice(&bytes[cursor..cursor + 64]);
239
240 Ok(Artifact {
241 version: ArtifactVersion::V2,
242 artifact_id,
243 timestamp,
244 intent,
245 author_pubkey,
246 author_signature,
247 flags: 0,
248 reserved_a,
249 note_hash,
250 seal_pubkey,
251 seal_signature,
252 })
253 }
254
255 pub fn author_signing_bytes(&self) -> Vec<u8> {
257 match self.version {
258 ArtifactVersion::V1 => {
259 let mut bytes = Vec::with_capacity(63);
261 bytes.extend_from_slice(MAGIC);
262 bytes.extend_from_slice(&VERSION_V1.to_le_bytes());
263 bytes.extend_from_slice(&self.artifact_id.0);
265 bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes());
266 bytes.push(self.intent.to_u8());
267 bytes.extend_from_slice(&self.author_pubkey);
268 bytes
269 },
270 ArtifactVersion::V2 => {
271 let mut bytes = Vec::with_capacity(103);
273 bytes.extend_from_slice(MAGIC); bytes.extend_from_slice(&VERSION_V2.to_le_bytes()); bytes.push(self.reserved_a); bytes.extend_from_slice(&self.artifact_id.0); bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes()); bytes.push(self.intent.to_u8()); bytes.extend_from_slice(&self.author_pubkey); bytes.extend_from_slice(&self.note_hash); bytes
282 },
283 }
284 }
285
286 pub fn seal_signing_bytes(&self) -> Result<Vec<u8>> {
289 if self.seal_pubkey == [0u8; 32] {
291 return Err(Error::NoSeal);
292 }
293
294 let mut bytes = Vec::with_capacity(192);
296 bytes.extend_from_slice(MAGIC); bytes.extend_from_slice(&VERSION_V2.to_le_bytes()); bytes.push(self.reserved_a); bytes.extend_from_slice(&self.artifact_id.0); bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes()); bytes.push(self.intent.to_u8()); bytes.extend_from_slice(&self.author_pubkey); bytes.extend_from_slice(&self.note_hash); bytes.extend_from_slice(&self.author_signature); bytes.extend_from_slice(&self.seal_pubkey); Ok(bytes)
307 }
308
309 pub fn to_bytes(&self) -> Vec<u8> {
311 match self.version {
312 ArtifactVersion::V1 => self.to_bytes_v1(),
313 ArtifactVersion::V2 => self.to_bytes_v2(),
314 }
315 }
316
317 fn to_bytes_v1(&self) -> Vec<u8> {
318 let mut bytes = Vec::with_capacity(ARTIFACT_SIZE_V1);
319
320 bytes.extend_from_slice(MAGIC);
321 bytes.extend_from_slice(&VERSION_V1.to_le_bytes());
322 bytes.push(self.flags);
323 bytes.extend_from_slice(&self.artifact_id.0);
324 bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes());
325 bytes.push(self.intent.to_u8());
326 bytes.extend_from_slice(&self.author_pubkey);
327 bytes.extend_from_slice(&self.author_signature);
328
329 bytes
330 }
331
332 fn to_bytes_v2(&self) -> Vec<u8> {
333 let mut bytes = Vec::with_capacity(ARTIFACT_SIZE_V2);
334
335 bytes.extend_from_slice(MAGIC); bytes.extend_from_slice(&VERSION_V2.to_le_bytes()); bytes.push(self.reserved_a); bytes.extend_from_slice(&self.artifact_id.0); bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes()); bytes.push(self.intent.to_u8()); bytes.extend_from_slice(&self.author_pubkey); bytes.extend_from_slice(&self.note_hash); bytes.extend_from_slice(&self.author_signature); bytes.extend_from_slice(&self.seal_pubkey); bytes.extend_from_slice(&self.seal_signature); bytes
348 }
349
350 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
352 writer.write_all(&self.to_bytes())?;
353 Ok(())
354 }
355
356 pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
358 let mut bytes = Vec::new();
359 reader.read_to_end(&mut bytes)?;
360 Self::from_bytes(&bytes)
361 }
362
363 pub fn author_fingerprint(&self) -> KeyFingerprint {
365 KeyFingerprint::new(&self.author_pubkey)
366 }
367
368 pub fn seal_fingerprint(&self) -> Option<KeyFingerprint> {
370 if self.version == ArtifactVersion::V2 && self.seal_pubkey != ZERO_PUBKEY {
371 Some(KeyFingerprint::new(&self.seal_pubkey))
372 } else {
373 None
374 }
375 }
376
377 pub fn is_sealed(&self) -> bool {
379 self.state() == ArtifactState::Sealed
380 }
381
382 pub fn has_author_signature(&self) -> bool {
384 self.author_signature != ZERO_SIGNATURE
385 }
386
387 pub fn has_signature(&self) -> bool {
389 self.has_author_signature()
390 }
391
392 pub fn has_author_pubkey(&self) -> bool {
394 self.author_pubkey != ZERO_PUBKEY
395 }
396
397 pub fn has_note_hash(&self) -> bool {
399 self.version == ArtifactVersion::V2 && self.note_hash != ZERO_HASH
400 }
401
402 pub fn state(&self) -> ArtifactState {
404 if !self.has_author_signature() {
405 ArtifactState::Draft
406 } else if self.is_sealed_raw() {
407 ArtifactState::Sealed
408 } else {
409 ArtifactState::Signed
410 }
411 }
412
413 fn is_sealed_raw(&self) -> bool {
415 match self.version {
416 ArtifactVersion::V1 => (self.flags & FLAG_SEALED) != 0,
417 ArtifactVersion::V2 => self.seal_signature != ZERO_SIGNATURE,
418 }
419 }
420
421 pub fn with_seal_flag(mut self) -> Self {
423 if self.version == ArtifactVersion::V1 {
424 self.flags |= FLAG_SEALED;
425 }
426 self
427 }
428
429 pub fn without_seal_flag(mut self) -> Self {
431 if self.version == ArtifactVersion::V1 {
432 self.flags &= !FLAG_SEALED;
433 }
434 self
435 }
436
437 pub fn without_author_signature(mut self) -> Self {
439 self.author_signature = ZERO_SIGNATURE;
440 self
441 }
442
443 pub fn without_signature(self) -> Self {
445 self.without_author_signature()
446 }
447
448 pub fn without_seal_signature(mut self) -> Self {
450 if self.version == ArtifactVersion::V2 {
451 self.seal_signature = ZERO_SIGNATURE;
452 self.seal_pubkey = ZERO_PUBKEY;
453 }
454 self
455 }
456
457 pub fn with_seal(mut self, seal_pubkey: [u8; 32], seal_signature: [u8; 64]) -> Self {
459 if self.version == ArtifactVersion::V2 {
460 self.seal_pubkey = seal_pubkey;
461 self.seal_signature = seal_signature;
462 }
463 self
464 }
465}
466
467pub struct Draft;
471pub struct Signed;
473pub struct Sealed;
475
476pub struct TypedArtifact<S> {
478 pub inner: Artifact,
479 _state: std::marker::PhantomData<S>,
480}
481
482fn state_name(s: ArtifactState) -> &'static str {
483 match s {
484 ArtifactState::Draft => "Draft",
485 ArtifactState::Signed => "Signed",
486 ArtifactState::Sealed => "Sealed",
487 }
488}
489
490impl TypedArtifact<Draft> {
491 pub fn new(artifact: Artifact) -> Result<Self> {
492 if artifact.state() != ArtifactState::Draft {
493 return Err(Error::InvalidState {
494 expected: "Draft",
495 actual: state_name(artifact.state()),
496 });
497 }
498 Ok(Self { inner: artifact, _state: std::marker::PhantomData })
499 }
500
501 pub fn into_signed(self, artifact: Artifact) -> Result<TypedArtifact<Signed>> {
503 if artifact.state() != ArtifactState::Signed {
504 return Err(Error::InvalidState {
505 expected: "Signed",
506 actual: state_name(artifact.state()),
507 });
508 }
509 Ok(TypedArtifact { inner: artifact, _state: std::marker::PhantomData })
510 }
511}
512
513impl TypedArtifact<Signed> {
514 pub fn new(artifact: Artifact) -> Result<Self> {
515 if artifact.state() != ArtifactState::Signed {
516 return Err(Error::InvalidState {
517 expected: "Signed",
518 actual: state_name(artifact.state()),
519 });
520 }
521 Ok(Self { inner: artifact, _state: std::marker::PhantomData })
522 }
523
524 pub fn into_sealed(self, artifact: Artifact) -> Result<TypedArtifact<Sealed>> {
526 if artifact.state() != ArtifactState::Sealed {
527 return Err(Error::InvalidState {
528 expected: "Sealed",
529 actual: state_name(artifact.state()),
530 });
531 }
532 Ok(TypedArtifact { inner: artifact, _state: std::marker::PhantomData })
533 }
534}
535
536impl TypedArtifact<Sealed> {
537 pub fn new(artifact: Artifact) -> Result<Self> {
538 if artifact.state() != ArtifactState::Sealed {
539 return Err(Error::InvalidState {
540 expected: "Sealed",
541 actual: state_name(artifact.state()),
542 });
543 }
544 Ok(Self { inner: artifact, _state: std::marker::PhantomData })
545 }
546}
547
548impl<S> TypedArtifact<S> {
549 pub fn artifact(&self) -> &Artifact {
550 &self.inner
551 }
552}
553
554pub struct ArtifactBuilder {
556 version: ArtifactVersion,
557 artifact_id: ArtifactId,
558 timestamp: DateTime<Utc>,
559 intent: Intent,
560 author_pubkey: [u8; 32],
561 flags: u8, note_hash: [u8; 32], }
564
565impl ArtifactBuilder {
566 pub fn new_v1(intent: Intent, author_pubkey: [u8; 32]) -> Self {
568 Self {
569 version: ArtifactVersion::V1,
570 artifact_id: ArtifactId::new(),
571 timestamp: Utc::now(),
572 intent,
573 author_pubkey,
574 flags: 0,
575 note_hash: ZERO_HASH,
576 }
577 }
578
579 pub fn new_v2(intent: Intent, author_pubkey: [u8; 32], note_hash: [u8; 32]) -> Self {
581 Self {
582 version: ArtifactVersion::V2,
583 artifact_id: ArtifactId::new(),
584 timestamp: Utc::now(),
585 intent,
586 author_pubkey,
587 flags: 0,
588 note_hash,
589 }
590 }
591
592 pub fn new(intent: Intent, author_pubkey: [u8; 32]) -> Self {
594 Self::new_v1(intent, author_pubkey)
595 }
596
597 pub fn with_artifact_id(mut self, id: ArtifactId) -> Self {
598 self.artifact_id = id;
599 self
600 }
601
602 pub fn with_timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
603 self.timestamp = timestamp;
604 self
605 }
606
607 pub fn with_flags(mut self, flags: u8) -> Self {
608 self.flags = flags;
609 self
610 }
611
612 pub fn with_note_hash(mut self, note_hash: [u8; 32]) -> Self {
613 self.note_hash = note_hash;
614 self
615 }
616
617 pub fn build_unsigned(&self) -> UnsignedArtifact {
619 UnsignedArtifact {
620 version: self.version,
621 flags: self.flags,
622 artifact_id: self.artifact_id,
623 timestamp: self.timestamp,
624 intent: self.intent,
625 author_pubkey: self.author_pubkey,
626 note_hash: self.note_hash,
627 }
628 }
629}
630
631pub struct UnsignedArtifact {
633 pub version: ArtifactVersion,
634 pub flags: u8, pub artifact_id: ArtifactId,
636 pub timestamp: DateTime<Utc>,
637 pub intent: Intent,
638 pub author_pubkey: [u8; 32],
639 pub note_hash: [u8; 32], }
641
642impl UnsignedArtifact {
643 pub fn author_signing_bytes(&self) -> Vec<u8> {
645 match self.version {
646 ArtifactVersion::V1 => {
647 let mut bytes = Vec::with_capacity(63);
649 bytes.extend_from_slice(MAGIC);
650 bytes.extend_from_slice(&VERSION_V1.to_le_bytes());
651 bytes.extend_from_slice(&self.artifact_id.0);
653 bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes());
654 bytes.push(self.intent.to_u8());
655 bytes.extend_from_slice(&self.author_pubkey);
656 bytes
657 },
658 ArtifactVersion::V2 => {
659 let mut bytes = Vec::with_capacity(103);
661 bytes.extend_from_slice(MAGIC);
662 bytes.extend_from_slice(&VERSION_V2.to_le_bytes());
663 bytes.push(0u8); bytes.extend_from_slice(&self.artifact_id.0);
665 bytes.extend_from_slice(&(self.timestamp.timestamp() as u64).to_le_bytes());
666 bytes.push(self.intent.to_u8());
667 bytes.extend_from_slice(&self.author_pubkey);
668 bytes.extend_from_slice(&self.note_hash);
669 bytes
670 },
671 }
672 }
673
674 pub fn with_author_signature(self, signature: [u8; 64]) -> Artifact {
676 Artifact {
677 version: self.version,
678 artifact_id: self.artifact_id,
679 timestamp: self.timestamp,
680 intent: self.intent,
681 author_pubkey: self.author_pubkey,
682 author_signature: signature,
683 flags: self.flags,
684 reserved_a: 0,
685 note_hash: self.note_hash,
686 seal_pubkey: ZERO_PUBKEY,
687 seal_signature: ZERO_SIGNATURE,
688 }
689 }
690
691 pub fn with_signature(self, signature: [u8; 64]) -> Artifact {
693 self.with_author_signature(signature)
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use crate::{crypto, Intent};
701 use ed25519_dalek::SigningKey;
702 use rand::rngs::OsRng;
703
704 fn make_author_key() -> SigningKey {
705 SigningKey::generate(&mut OsRng)
706 }
707
708 fn make_seal_key() -> SigningKey {
709 SigningKey::generate(&mut OsRng)
710 }
711
712 #[test]
715 fn test_v1_roundtrip() {
716 let author_key = make_author_key();
717 let author_pubkey = author_key.verifying_key().to_bytes();
718
719 let unsigned = ArtifactBuilder::new_v1(Intent::Lab, author_pubkey).build_unsigned();
720 let sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
721 let artifact = unsigned.with_signature(sig);
722
723 let parsed = Artifact::from_bytes(&artifact.to_bytes()).unwrap();
724
725 assert_eq!(parsed.version, ArtifactVersion::V1);
726 assert_eq!(parsed.artifact_id, artifact.artifact_id);
727 assert_eq!(parsed.intent, artifact.intent);
728 assert_eq!(parsed.author_pubkey, artifact.author_pubkey);
729 assert_eq!(parsed.author_signature, artifact.author_signature);
730 assert_eq!(parsed.flags, artifact.flags);
731 }
732
733 #[test]
736 fn test_v2_roundtrip_unsigned() {
737 let author_key = make_author_key();
738 let author_pubkey = author_key.verifying_key().to_bytes();
739 let note_hash = [0xabu8; 32];
740
741 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, note_hash).build_unsigned();
742 let sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
743 let artifact = unsigned.with_signature(sig);
744
745 let parsed = Artifact::from_bytes(&artifact.to_bytes()).unwrap();
746
747 assert_eq!(parsed.version, ArtifactVersion::V2);
748 assert_eq!(parsed.artifact_id, artifact.artifact_id);
749 assert_eq!(parsed.intent, artifact.intent);
750 assert_eq!(parsed.author_pubkey, artifact.author_pubkey);
751 assert_eq!(parsed.author_signature, artifact.author_signature);
752 assert_eq!(parsed.note_hash, note_hash);
753 assert_eq!(parsed.seal_pubkey, ZERO_PUBKEY);
754 assert_eq!(parsed.seal_signature, ZERO_SIGNATURE);
755 }
756
757 #[test]
758 fn test_v2_roundtrip_sealed() {
759 let author_key = make_author_key();
760 let seal_key = make_seal_key();
761 let author_pubkey = author_key.verifying_key().to_bytes();
762 let seal_pubkey = seal_key.verifying_key().to_bytes();
763 let note_hash = [0xbcu8; 32];
764
765 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, note_hash).build_unsigned();
766 let author_sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
767 let author_signed = unsigned.with_signature(author_sig);
768
769 let pre_seal = author_signed.with_seal(seal_pubkey, [0u8; 64]);
770 let seal_sig = crypto::sign(&seal_key, &pre_seal.seal_signing_bytes().unwrap());
771 let sealed = pre_seal.with_seal(seal_pubkey, seal_sig);
772
773 let parsed = Artifact::from_bytes(&sealed.to_bytes()).unwrap();
774
775 assert_eq!(parsed.version, ArtifactVersion::V2);
776 assert_eq!(parsed.note_hash, note_hash);
777 assert_eq!(parsed.author_pubkey, author_pubkey);
778 assert_eq!(parsed.author_signature, sealed.author_signature);
779 assert_eq!(parsed.seal_pubkey, seal_pubkey);
780 assert_eq!(parsed.seal_signature, sealed.seal_signature);
781 assert!(crypto::verify(&seal_pubkey, &parsed.seal_signing_bytes().unwrap(), &parsed.seal_signature).is_ok());
783 }
784
785 #[test]
788 fn test_v2_state_draft() {
789 let author_key = make_author_key();
790 let author_pubkey = author_key.verifying_key().to_bytes();
791 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
792 let artifact = unsigned.with_signature(ZERO_SIGNATURE);
793 assert_eq!(artifact.state(), ArtifactState::Draft);
794 assert!(!artifact.has_author_signature());
795 assert!(!artifact.is_sealed());
796 }
797
798 #[test]
799 fn test_v2_state_signed() {
800 let author_key = make_author_key();
801 let author_pubkey = author_key.verifying_key().to_bytes();
802 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
803 let sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
804 let artifact = unsigned.with_signature(sig);
805
806 assert_eq!(artifact.state(), ArtifactState::Signed);
807 assert!(artifact.has_author_signature());
808 assert!(!artifact.is_sealed());
809 }
810
811 #[test]
812 fn test_v2_state_sealed() {
813 let author_key = make_author_key();
814 let seal_key = make_seal_key();
815 let author_pubkey = author_key.verifying_key().to_bytes();
816 let seal_pubkey = seal_key.verifying_key().to_bytes();
817
818 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
819 let author_sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
820 let author_signed = unsigned.with_signature(author_sig);
821 let pre_seal = author_signed.with_seal(seal_pubkey, [0u8; 64]);
822 let seal_sig = crypto::sign(&seal_key, &pre_seal.seal_signing_bytes().unwrap());
823 let sealed = pre_seal.with_seal(seal_pubkey, seal_sig);
824
825 assert_eq!(sealed.state(), ArtifactState::Sealed);
826 assert!(sealed.has_author_signature());
827 assert!(sealed.is_sealed());
828 }
829
830 #[test]
833 fn test_v2_author_sig_covers_note_hash() {
834 let author_key = make_author_key();
835 let author_pubkey = author_key.verifying_key().to_bytes();
836 let note_hash = [0x11u8; 32];
837
838 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, note_hash).build_unsigned();
839 let sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
840 let mut artifact = unsigned.with_signature(sig);
841 artifact.note_hash = [0x22u8; 32]; let verify_bytes = artifact.author_signing_bytes();
844 assert!(crypto::verify(&author_pubkey, &verify_bytes, &artifact.author_signature).is_err());
845 }
846
847 #[test]
848 fn test_v2_seal_sig_covers_author_sig() {
849 let author_key = make_author_key();
850 let seal_key = make_seal_key();
851 let author_pubkey = author_key.verifying_key().to_bytes();
852 let seal_pubkey = seal_key.verifying_key().to_bytes();
853
854 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
855 let author_sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
856 let author_signed = unsigned.with_signature(author_sig);
857 let pre_seal = author_signed.with_seal(seal_pubkey, [0u8; 64]);
858 let seal_sig = crypto::sign(&seal_key, &pre_seal.seal_signing_bytes().unwrap());
859 let mut sealed = pre_seal.with_seal(seal_pubkey, seal_sig);
860 sealed.author_signature = [0xffu8; 64]; let verify_bytes = sealed.seal_signing_bytes().unwrap();
863 assert!(crypto::verify(&seal_pubkey, &verify_bytes, &sealed.seal_signature).is_err());
864 }
865
866 #[test]
867 fn test_v2_seal_key_can_differ_from_author_key() {
868 let author_key = make_author_key();
869 let seal_key = make_seal_key();
870 assert_ne!(
871 author_key.verifying_key().to_bytes(),
872 seal_key.verifying_key().to_bytes()
873 );
874 }
875
876 #[test]
879 fn test_v2_without_author_signature() {
880 let author_key = make_author_key();
881 let author_pubkey = author_key.verifying_key().to_bytes();
882 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
883 let sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
884 let artifact = unsigned.with_signature(sig).without_author_signature();
885
886 assert_eq!(artifact.author_signature, ZERO_SIGNATURE);
887 assert_eq!(artifact.state(), ArtifactState::Draft);
888 }
889
890 #[test]
891 fn test_v2_without_seal_signature() {
892 let author_key = make_author_key();
893 let seal_key = make_seal_key();
894 let author_pubkey = author_key.verifying_key().to_bytes();
895 let seal_pubkey = seal_key.verifying_key().to_bytes();
896
897 let unsigned = ArtifactBuilder::new_v2(Intent::Lab, author_pubkey, ZERO_HASH).build_unsigned();
898 let author_sig = crypto::sign(&author_key, &unsigned.author_signing_bytes());
899 let author_signed = unsigned.with_signature(author_sig);
900 let pre_seal = author_signed.with_seal(seal_pubkey, [0u8; 64]);
901 let seal_sig = crypto::sign(&seal_key, &pre_seal.seal_signing_bytes().unwrap());
902 let sealed = pre_seal.with_seal(seal_pubkey, seal_sig);
903 let unsealed = sealed.without_seal_signature();
904
905 assert_eq!(unsealed.seal_signature, ZERO_SIGNATURE);
906 assert_eq!(unsealed.seal_pubkey, ZERO_PUBKEY);
907 assert_eq!(unsealed.state(), ArtifactState::Signed);
908 assert!(unsealed.has_author_signature());
909 }
910
911 #[test]
914 fn test_invalid_magic() {
915 let mut bytes = vec![0u8; ARTIFACT_SIZE_V1];
916 bytes[0..4].copy_from_slice(b"NOPE");
917 assert!(matches!(Artifact::from_bytes(&bytes), Err(Error::InvalidMagic)));
918 }
919
920 #[test]
921 fn test_file_too_small() {
922 let bytes = vec![0u8; 5]; assert!(matches!(Artifact::from_bytes(&bytes), Err(Error::FileTooSmall { .. })));
924 }
925
926 #[test]
927 fn test_v1_wrong_size_rejected() {
928 let mut bytes = vec![0u8; ARTIFACT_SIZE_V1 - 1];
929 bytes[0..4].copy_from_slice(MAGIC);
930 bytes[4..6].copy_from_slice(&VERSION_V1.to_le_bytes());
931 assert!(Artifact::from_bytes(&bytes).is_err());
932 }
933
934 #[test]
935 fn test_v2_wrong_size_rejected() {
936 let mut bytes = vec![0u8; ARTIFACT_SIZE_V2 - 1];
937 bytes[0..4].copy_from_slice(MAGIC);
938 bytes[4..6].copy_from_slice(&VERSION_V2.to_le_bytes());
939 assert!(Artifact::from_bytes(&bytes).is_err());
940 }
941}
942