|
1 | 1 | use std::{ |
| 2 | + borrow::Cow, |
2 | 3 | cmp, |
3 | 4 | ops::{Index, IndexMut}, |
4 | 5 | }; |
@@ -521,6 +522,124 @@ impl IndexMut<ValueIndex> for ValueVec { |
521 | 522 | } |
522 | 523 | } |
523 | 524 |
|
| 525 | +/// Public witness data for zero-knowledge proofs. |
| 526 | +/// |
| 527 | +/// This structure holds the public portion of witness data that needs to be shared |
| 528 | +/// with verifiers. It uses `Cow<[Word]>` to avoid unnecessary clones while supporting |
| 529 | +/// both borrowed and owned data. |
| 530 | +/// |
| 531 | +/// The public witness consists of: |
| 532 | +/// - Constants: Fixed values defined in the constraint system |
| 533 | +/// - Inputs/Outputs: Public values that are part of the statement being proven |
| 534 | +#[derive(Clone, Debug, PartialEq, Eq)] |
| 535 | +pub struct PublicWitness<'a> { |
| 536 | + data: Cow<'a, [Word]>, |
| 537 | +} |
| 538 | + |
| 539 | +impl<'a> PublicWitness<'a> { |
| 540 | + /// Serialization format version for compatibility checking |
| 541 | + pub const SERIALIZATION_VERSION: u32 = 1; |
| 542 | + |
| 543 | + /// Create a new PublicWitness from borrowed data |
| 544 | + pub fn borrowed(data: &'a [Word]) -> Self { |
| 545 | + Self { |
| 546 | + data: Cow::Borrowed(data), |
| 547 | + } |
| 548 | + } |
| 549 | + |
| 550 | + /// Create a new PublicWitness from owned data |
| 551 | + pub fn owned(data: Vec<Word>) -> Self { |
| 552 | + Self { |
| 553 | + data: Cow::Owned(data), |
| 554 | + } |
| 555 | + } |
| 556 | + |
| 557 | + /// Get the public witness data as a slice |
| 558 | + pub fn as_slice(&self) -> &[Word] { |
| 559 | + &self.data |
| 560 | + } |
| 561 | + |
| 562 | + /// Get the number of words in the public witness |
| 563 | + pub fn len(&self) -> usize { |
| 564 | + self.data.len() |
| 565 | + } |
| 566 | + |
| 567 | + /// Check if the public witness is empty |
| 568 | + pub fn is_empty(&self) -> bool { |
| 569 | + self.data.is_empty() |
| 570 | + } |
| 571 | + |
| 572 | + /// Convert to owned data, consuming self |
| 573 | + pub fn into_owned(self) -> Vec<Word> { |
| 574 | + self.data.into_owned() |
| 575 | + } |
| 576 | + |
| 577 | + /// Convert to owned version of PublicWitness |
| 578 | + pub fn to_owned(&self) -> PublicWitness<'static> { |
| 579 | + PublicWitness { |
| 580 | + data: Cow::Owned(self.data.to_vec()), |
| 581 | + } |
| 582 | + } |
| 583 | +} |
| 584 | + |
| 585 | +impl<'a> SerializeBytes for PublicWitness<'a> { |
| 586 | + fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> { |
| 587 | + Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?; |
| 588 | + |
| 589 | + self.data.as_ref().serialize(write_buf) |
| 590 | + } |
| 591 | +} |
| 592 | + |
| 593 | +impl DeserializeBytes for PublicWitness<'static> { |
| 594 | + fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError> |
| 595 | + where |
| 596 | + Self: Sized, |
| 597 | + { |
| 598 | + let version = u32::deserialize(&mut read_buf)?; |
| 599 | + if version != Self::SERIALIZATION_VERSION { |
| 600 | + return Err(SerializationError::InvalidConstruction { |
| 601 | + name: "PublicWitness::version", |
| 602 | + }); |
| 603 | + } |
| 604 | + |
| 605 | + let data = Vec::<Word>::deserialize(read_buf)?; |
| 606 | + |
| 607 | + Ok(PublicWitness::owned(data)) |
| 608 | + } |
| 609 | +} |
| 610 | + |
| 611 | +impl<'a> From<&'a [Word]> for PublicWitness<'a> { |
| 612 | + fn from(data: &'a [Word]) -> Self { |
| 613 | + PublicWitness::borrowed(data) |
| 614 | + } |
| 615 | +} |
| 616 | + |
| 617 | +impl From<Vec<Word>> for PublicWitness<'static> { |
| 618 | + fn from(data: Vec<Word>) -> Self { |
| 619 | + PublicWitness::owned(data) |
| 620 | + } |
| 621 | +} |
| 622 | + |
| 623 | +impl<'a> From<&'a ValueVec> for PublicWitness<'a> { |
| 624 | + fn from(value_vec: &'a ValueVec) -> Self { |
| 625 | + PublicWitness::borrowed(value_vec.public()) |
| 626 | + } |
| 627 | +} |
| 628 | + |
| 629 | +impl<'a> AsRef<[Word]> for PublicWitness<'a> { |
| 630 | + fn as_ref(&self) -> &[Word] { |
| 631 | + self.as_slice() |
| 632 | + } |
| 633 | +} |
| 634 | + |
| 635 | +impl<'a> std::ops::Deref for PublicWitness<'a> { |
| 636 | + type Target = [Word]; |
| 637 | + |
| 638 | + fn deref(&self) -> &Self::Target { |
| 639 | + self.as_slice() |
| 640 | + } |
| 641 | +} |
| 642 | + |
524 | 643 | #[cfg(test)] |
525 | 644 | mod serialization_tests { |
526 | 645 | use rand::{RngCore, SeedableRng, rngs::StdRng}; |
@@ -853,27 +972,10 @@ mod serialization_tests { |
853 | 972 | fn test_deserialize_from_reference_binary_file() { |
854 | 973 | let test_data_path = std::path::Path::new("crates/core/test_data/constraint_system_v1.bin"); |
855 | 974 |
|
856 | | - // If the reference file doesn't exist, create it first |
857 | | - if !test_data_path.exists() { |
858 | | - let constraint_system = create_test_constraint_system(); |
859 | | - let mut buf = Vec::new(); |
860 | | - constraint_system.serialize(&mut buf).unwrap(); |
861 | | - |
862 | | - // Create directory if it doesn't exist |
863 | | - if let Some(parent) = test_data_path.parent() { |
864 | | - std::fs::create_dir_all(parent).unwrap(); |
865 | | - } |
866 | | - |
867 | | - std::fs::write(test_data_path, &buf).unwrap(); |
868 | | - } |
869 | | - |
870 | | - // Read the reference binary file |
871 | 975 | let binary_data = std::fs::read(test_data_path).unwrap(); |
872 | 976 |
|
873 | | - // Deserialize and verify it works |
874 | 977 | let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap(); |
875 | 978 |
|
876 | | - // Verify the deserialized data has expected structure |
877 | 979 | assert_eq!(deserialized.value_vec_layout.n_const, 3); |
878 | 980 | assert_eq!(deserialized.value_vec_layout.n_inout, 2); |
879 | 981 | assert_eq!(deserialized.value_vec_layout.n_witness, 10); |
@@ -901,32 +1003,117 @@ mod serialization_tests { |
901 | 1003 | ); |
902 | 1004 | } |
903 | 1005 |
|
904 | | - /// Test that demonstrates what happens when there's a version mismatch. |
905 | | - /// This serves as documentation for the version compatibility system. |
906 | 1006 | #[test] |
907 | | - fn test_version_compatibility_documentation() { |
908 | | - // Create a constraint system and serialize it |
| 1007 | + fn test_public_witness_from_value_vec() { |
909 | 1008 | let constraint_system = create_test_constraint_system(); |
| 1009 | + let value_vec = constraint_system.new_value_vec(); |
| 1010 | + |
| 1011 | + let public_witness: PublicWitness = (&value_vec).into(); |
| 1012 | + |
| 1013 | + assert_eq!(public_witness.len(), value_vec.public().len()); |
| 1014 | + assert_eq!(public_witness.as_slice(), value_vec.public()); |
| 1015 | + } |
| 1016 | + |
| 1017 | + #[test] |
| 1018 | + fn test_public_witness_serialization_round_trip_owned() { |
| 1019 | + let data = vec![ |
| 1020 | + Word::from_u64(1), |
| 1021 | + Word::from_u64(42), |
| 1022 | + Word::from_u64(0xDEADBEEF), |
| 1023 | + Word::from_u64(0x1234567890ABCDEF), |
| 1024 | + ]; |
| 1025 | + let witness = PublicWitness::owned(data.clone()); |
| 1026 | + |
910 | 1027 | let mut buf = Vec::new(); |
911 | | - constraint_system.serialize(&mut buf).unwrap(); |
| 1028 | + witness.serialize(&mut buf).unwrap(); |
912 | 1029 |
|
913 | | - // Verify current version works |
914 | | - let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap(); |
915 | | - assert_eq!(constraint_system.constants.len(), deserialized.constants.len()); |
| 1030 | + let deserialized = PublicWitness::deserialize(&mut buf.as_slice()).unwrap(); |
| 1031 | + assert_eq!(witness, deserialized); |
| 1032 | + assert_eq!(deserialized.as_slice(), data.as_slice()); |
| 1033 | + } |
| 1034 | + |
| 1035 | + #[test] |
| 1036 | + fn test_public_witness_serialization_round_trip_borrowed() { |
| 1037 | + let data = vec![Word::from_u64(123), Word::from_u64(456)]; |
| 1038 | + let witness = PublicWitness::borrowed(&data); |
| 1039 | + |
| 1040 | + let mut buf = Vec::new(); |
| 1041 | + witness.serialize(&mut buf).unwrap(); |
916 | 1042 |
|
917 | | - // Now simulate an older version by modifying the version bytes |
918 | | - let mut modified_buf = buf.clone(); |
919 | | - let old_version = 0u32; // Simulate version 0 |
920 | | - modified_buf[0..4].copy_from_slice(&old_version.to_le_bytes()); |
| 1043 | + let deserialized = PublicWitness::deserialize(&mut buf.as_slice()).unwrap(); |
| 1044 | + assert_eq!(witness, deserialized); |
| 1045 | + assert_eq!(deserialized.as_slice(), data.as_slice()); |
| 1046 | + } |
| 1047 | + |
| 1048 | + #[test] |
| 1049 | + fn test_public_witness_version_mismatch() { |
| 1050 | + let mut buf = Vec::new(); |
| 1051 | + 999u32.serialize(&mut buf).unwrap(); // Wrong version |
| 1052 | + vec![Word::from_u64(1)].serialize(&mut buf).unwrap(); // Some data |
921 | 1053 |
|
922 | | - // This should fail with version mismatch |
923 | | - let result = ConstraintSystem::deserialize(&mut modified_buf.as_slice()); |
| 1054 | + let result = PublicWitness::deserialize(&mut buf.as_slice()); |
924 | 1055 | assert!(result.is_err()); |
925 | 1056 | match result.unwrap_err() { |
926 | 1057 | SerializationError::InvalidConstruction { name } => { |
927 | | - assert_eq!(name, "ConstraintSystem::version"); |
| 1058 | + assert_eq!(name, "PublicWitness::version"); |
928 | 1059 | } |
929 | 1060 | _ => panic!("Expected version mismatch error"), |
930 | 1061 | } |
931 | 1062 | } |
| 1063 | + |
| 1064 | + /// Helper function to create or update the reference binary file for PublicWitness version |
| 1065 | + /// compatibility testing. |
| 1066 | + #[test] |
| 1067 | + #[ignore] // Use `cargo test -- --ignored create_public_witness_reference_binary` to run this |
| 1068 | + fn create_public_witness_reference_binary_file() { |
| 1069 | + let data = vec![ |
| 1070 | + Word::from_u64(1), |
| 1071 | + Word::from_u64(42), |
| 1072 | + Word::from_u64(0xDEADBEEF), |
| 1073 | + Word::from_u64(0x1234567890ABCDEF), |
| 1074 | + ]; |
| 1075 | + let public_witness = PublicWitness::owned(data); |
| 1076 | + |
| 1077 | + let mut buf = Vec::new(); |
| 1078 | + public_witness.serialize(&mut buf).unwrap(); |
| 1079 | + |
| 1080 | + let test_data_path = std::path::Path::new("crates/core/test_data/public_witness_v1.bin"); |
| 1081 | + |
| 1082 | + if let Some(parent) = test_data_path.parent() { |
| 1083 | + std::fs::create_dir_all(parent).unwrap(); |
| 1084 | + } |
| 1085 | + |
| 1086 | + std::fs::write(test_data_path, &buf).unwrap(); |
| 1087 | + |
| 1088 | + println!("Created PublicWitness reference binary file at: {:?}", test_data_path); |
| 1089 | + println!("Binary data length: {} bytes", buf.len()); |
| 1090 | + } |
| 1091 | + |
| 1092 | + /// Test deserialization from a reference binary file to ensure PublicWitness version |
| 1093 | + /// compatibility. This test will fail if breaking changes are made without incrementing the |
| 1094 | + /// version. |
| 1095 | + #[test] |
| 1096 | + fn test_public_witness_deserialize_from_reference_binary_file() { |
| 1097 | + let test_data_path = std::path::Path::new("crates/core/test_data/public_witness_v1.bin"); |
| 1098 | + |
| 1099 | + let binary_data = std::fs::read(test_data_path).unwrap(); |
| 1100 | + |
| 1101 | + let deserialized = PublicWitness::deserialize(&mut binary_data.as_slice()).unwrap(); |
| 1102 | + |
| 1103 | + assert_eq!(deserialized.len(), 4); |
| 1104 | + assert_eq!(deserialized.as_slice()[0].as_u64(), 1); |
| 1105 | + assert_eq!(deserialized.as_slice()[1].as_u64(), 42); |
| 1106 | + assert_eq!(deserialized.as_slice()[2].as_u64(), 0xDEADBEEF); |
| 1107 | + assert_eq!(deserialized.as_slice()[3].as_u64(), 0x1234567890ABCDEF); |
| 1108 | + |
| 1109 | + // Verify that the version is what we expect |
| 1110 | + // This is implicitly checked during deserialization, but we can also verify |
| 1111 | + // the file starts with the correct version bytes |
| 1112 | + let version_bytes = &binary_data[0..4]; // First 4 bytes should be version |
| 1113 | + let expected_version_bytes = 1u32.to_le_bytes(); // Version 1 in little-endian |
| 1114 | + assert_eq!( |
| 1115 | + version_bytes, expected_version_bytes, |
| 1116 | + "PublicWitness binary file version mismatch. If you made breaking changes, increment PublicWitness::SERIALIZATION_VERSION" |
| 1117 | + ); |
| 1118 | + } |
932 | 1119 | } |
0 commit comments