diff --git a/rust/catalyst-contest/Cargo.toml b/rust/catalyst-contest/Cargo.toml index 2f17d8eec9..430aa87637 100644 --- a/rust/catalyst-contest/Cargo.toml +++ b/rust/catalyst-contest/Cargo.toml @@ -13,3 +13,8 @@ license.workspace = true workspace = true [dependencies] +minicbor = { version = "0.25.1", features = ["alloc", "derive", "half"] } + +cbork-utils = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cbork-utils-v0.0.2" } +# TODO: FIXME: Use tag instead of branch! +catalyst-voting = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", branch = "cbor-encoding-for-catalyst-voting-types" } diff --git a/rust/catalyst-contest/src/choices.rs b/rust/catalyst-contest/src/choices.rs new file mode 100644 index 0000000000..1d2b112de6 --- /dev/null +++ b/rust/catalyst-contest/src/choices.rs @@ -0,0 +1,153 @@ +//! Voters Choices. + +use catalyst_voting::crypto::elgamal::Ciphertext; +use cbork_utils::decode_helper::decode_array_len; +use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write}; + +use crate::row_proof::RowProof; + +/// Voters Choices. +/// +/// The CDDL schema: +/// ```cddl +/// choices = [ 0, clear-choices ] / +/// [ 1, elgamal-ristretto255-encrypted-choices ] +/// +/// clear-choices = ( +clear-choice ) +/// +/// clear-choice = int +/// +/// elgamal-ristretto255-encrypted-choices = [ +/// [+ elgamal-ristretto255-encrypted-choice] +/// ? row-proof +/// ] +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Choices { + /// A universal unencrypted set of choices. + Clear(Vec), + /// ElGamal/Ristretto255 encrypted choices. + Encrypted { + /// ElGamal/Ristretto255 encrypted choices. + choices: Vec, + /// A universal encrypted row proof. + row_proof: Option, + }, +} + +impl Decode<'_, ()> for Choices { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + let len = decode_array_len(d, "choices")?; + if len < 2 { + return Err(minicbor::decode::Error::message(format!( + "Unexpected choices array length {len}, expected at least 2" + ))); + } + match u8::decode(d, ctx)? { + 0 => Ok(Self::Clear(>::decode(d, ctx)?)), + 1 => { + let len = decode_array_len(d, "elgamal-ristretto255-encrypted-choices")?; + if !(1..=2).contains(&len) { + return Err(minicbor::decode::Error::message(format!( + "Unexpected elgamal-ristretto255-encrypted-choices array length {len}, expected 1 or 2" + ))); + } + let choices = >::decode(d, ctx)?; + let mut row_proof = None; + if len == 2 { + row_proof = Some(RowProof::decode(d, ctx)?); + } + Ok(Self::Encrypted { choices, row_proof }) + }, + val => { + Err(minicbor::decode::Error::message(format!( + "Unexpected choices value: {val}" + ))) + }, + } + } +} + +impl Encode<()> for Choices { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + match self { + Choices::Clear(choices) => { + e.array(choices.len() as u64 + 1)?; + 0.encode(e, ctx)?; + for choice in choices { + choice.encode(e, ctx)?; + } + }, + Choices::Encrypted { choices, row_proof } => { + e.array(2)?; + 1.encode(e, ctx)?; + e.array(choices.len() as u64 + u64::from(row_proof.is_some()))?; + choices.encode(e, ctx)?; + if let Some(row_proof) = row_proof { + row_proof.encode(e, ctx)?; + } + }, + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::row_proof::{ + ProofAnnouncement, ProofAnnouncementElement, ProofResponse, ProofScalar, + SingleSelectionProof, + }; + + #[test] + fn clear_roundtrip() { + let original = Choices::Clear(vec![1, 2, 3]); + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = Choices::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn elgamal_ristretto255_roundtrip() { + let bytes = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let original = Choices::Encrypted { + choices: vec![], + row_proof: Some(RowProof { + selections: vec![SingleSelectionProof { + announcement: ProofAnnouncement( + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ), + choice: Ciphertext::zero(), + response: ProofResponse( + ProofScalar(bytes), + ProofScalar(bytes), + ProofScalar(bytes), + ), + }], + scalar: ProofScalar(bytes), + }), + }; + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = Choices::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } +} diff --git a/rust/catalyst-contest/src/contest_ballot.rs b/rust/catalyst-contest/src/contest_ballot.rs new file mode 100644 index 0000000000..7318476cc3 --- /dev/null +++ b/rust/catalyst-contest/src/contest_ballot.rs @@ -0,0 +1,184 @@ +//! An individual Ballot cast in a Contest by a registered user. + +use std::collections::BTreeMap; + +use cbork_utils::decode_helper::decode_map_len; +use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write}; + +use crate::{Choices, EncryptedChoices}; + +/// An individual Ballot cast in a Contest by a registered user. +/// +/// The CDDL schema: +/// ```cddl +/// contest-ballot-payload = { +/// + uint => choices +/// ? "column-proof" : column-proof +/// ? "matrix-proof" : matrix-proof +/// ? "voter-choice" : voter-choice +/// } +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ContentBallot { + /// A map of voters choices. + pub choices: BTreeMap, + /// A universal encrypted column proof. + /// + /// This is a placeholder for now and should always be `None`. + pub column_proof: Option<()>, + /// A universal encrypted matrix proof. + /// + /// This is a placeholder for now and should always be `None`. + pub matrix_proof: Option<()>, + /// An encrypted voter choice payload. + pub voter_choices: Option, +} + +impl Decode<'_, ()> for ContentBallot { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + use minicbor::data::Type; + + let len = decode_map_len(d, "content ballot")?; + + let mut choices = BTreeMap::new(); + let column_proof = None; + let matrix_proof = None; + let mut voter_choices = None; + for _ in 0..len { + match d.datatype()? { + Type::U64 => { + let key = d.u64()?; + let val = Choices::decode(d, ctx)?; + choices.insert(key, val); + }, + Type::String => { + match d.str()? { + "column-proof" => { + return Err(minicbor::decode::Error::message( + "column-proof is a placeholder and shouldn't be used", + )); + }, + "matrix-proof" => { + return Err(minicbor::decode::Error::message( + "matrix-proof is a placeholder and shouldn't be used", + )); + }, + "voter-choices" => voter_choices = Some(EncryptedChoices::decode(d, ctx)?), + key => { + return Err(minicbor::decode::Error::message(format!( + "Unexpected content ballot key value: {key:?}" + ))); + }, + } + }, + t => { + return Err(minicbor::decode::Error::message(format!( + "Unexpected content ballot key type: {t:?}" + ))); + }, + } + } + + Ok(Self { + choices, + column_proof, + matrix_proof, + voter_choices, + }) + } +} + +impl Encode<()> for ContentBallot { + fn encode( + &self, + e: &mut Encoder, + _ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + let len = self.choices.len() as u64 + + u64::from(self.column_proof.is_some()) + + u64::from(self.matrix_proof.is_some()) + + u64::from(self.voter_choices.is_some()); + e.map(len)?; + + for (&key, val) in &self.choices { + e.u64(key)?.encode(val)?; + } + if let Some(column_proof) = self.column_proof.as_ref() { + e.str("column-proof")?.encode(column_proof)?; + } + if let Some(matrix_proof) = self.matrix_proof.as_ref() { + e.str("matrix-proof")?.encode(matrix_proof)?; + } + if let Some(voter_choices) = self.voter_choices.as_ref() { + e.str("voter-choices")?.encode(voter_choices)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use catalyst_voting::crypto::elgamal::Ciphertext; + + use super::*; + use crate::{ + EncryptedBlock, RowProof, + row_proof::{ + ProofAnnouncement, ProofAnnouncementElement, ProofResponse, ProofScalar, + SingleSelectionProof, + }, + }; + + #[test] + fn roundtrip() { + let bytes = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let original = ContentBallot { + choices: [ + (1, Choices::Clear(vec![1, 2, 3, -4, -5])), + (2, Choices::Encrypted { + choices: vec![Ciphertext::zero()], + row_proof: None, + }), + (3, Choices::Encrypted { + choices: vec![Ciphertext::zero()], + row_proof: Some(RowProof { + selections: vec![SingleSelectionProof { + announcement: ProofAnnouncement( + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ), + choice: Ciphertext::zero(), + response: ProofResponse( + ProofScalar(bytes), + ProofScalar(bytes), + ProofScalar(bytes), + ), + }], + scalar: ProofScalar(bytes), + }), + }), + ] + .into(), + column_proof: None, + matrix_proof: None, + voter_choices: Some(EncryptedChoices(vec![ + EncryptedBlock([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + EncryptedBlock([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), + ])), + }; + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = ContentBallot::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } +} diff --git a/rust/catalyst-contest/src/encrypted_choices.rs b/rust/catalyst-contest/src/encrypted_choices.rs new file mode 100644 index 0000000000..8d0245cbd0 --- /dev/null +++ b/rust/catalyst-contest/src/encrypted_choices.rs @@ -0,0 +1,117 @@ +//! Encrypted voter choices. + +use cbork_utils::decode_helper::decode_array_len; +use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write}; + +/// A length of the encrypted block array. +const ENCRYPTED_BLOCK_ARRAY_LEN: u64 = 16; + +/// Encrypted voter choices. +/// +/// The CDDL schema: +/// ```cddl +/// voter-choice = [ 0, aes-ctr-encrypted-choices ] +/// +/// aes-ctr-encrypted-choices = +aes-ctr-encrypted-block +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct EncryptedChoices(pub Vec); + +/// An AES-CTR encrypted data block. +/// +/// The CDDL schema: +/// ```cddl +/// aes-ctr-encrypted-block = bytes .size 16 +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct EncryptedBlock(pub [u8; ENCRYPTED_BLOCK_ARRAY_LEN as usize]); + +impl Decode<'_, ()> for EncryptedChoices { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + let len = decode_array_len(d, "encrypted choices")?; + if len < 2 { + return Err(minicbor::decode::Error::message(format!( + "Unexpected encrypted choices array length: {len}, expected at least 2" + ))); + } + let version = u64::decode(d, ctx)?; + if version != 0 { + return Err(minicbor::decode::Error::message(format!( + "Unexpected encrypted choices version value: {version}, expected 0" + ))); + } + + let mut blocks = Vec::with_capacity(len as usize - 1); + for _ in 1..len { + blocks.push(EncryptedBlock::decode(d, ctx)?); + } + + Ok(Self(blocks)) + } +} + +impl Encode<()> for EncryptedChoices { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(self.0.len() as u64 + 1)?; + 0.encode(e, ctx)?; + for block in &self.0 { + block.encode(e, ctx)?; + } + Ok(()) + } +} + +impl Decode<'_, ()> for EncryptedBlock { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + <[u8; ENCRYPTED_BLOCK_ARRAY_LEN as usize]>::decode(d, ctx).map(Self) + } +} + +impl Encode<()> for EncryptedBlock { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encrypted_block_roundtrip() { + let original = EncryptedBlock([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32]); + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = EncryptedBlock::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn encrypted_choices_roundtrip() { + let original = EncryptedChoices(vec![EncryptedBlock([ + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, + ])]); + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = EncryptedChoices::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } +} diff --git a/rust/catalyst-contest/src/lib.rs b/rust/catalyst-contest/src/lib.rs index 096d5740eb..90d9b6a9d1 100644 --- a/rust/catalyst-contest/src/lib.rs +++ b/rust/catalyst-contest/src/lib.rs @@ -3,3 +3,18 @@ //! See the [documentation] for more information. //! //! [documentation]: https://docs.dev.projectcatalyst.io/libs/main/architecture/08_concepts/signed_doc/docs/contest_ballot/ + +// TODO: FIXME: +//#![allow(unused_variables)] + +mod choices; +mod contest_ballot; +mod encrypted_choices; +mod row_proof; + +pub use crate::{ + choices::Choices, + contest_ballot::ContentBallot, + encrypted_choices::{EncryptedBlock, EncryptedChoices}, + row_proof::RowProof, +}; diff --git a/rust/catalyst-contest/src/row_proof.rs b/rust/catalyst-contest/src/row_proof.rs new file mode 100644 index 0000000000..b3076ac9c4 --- /dev/null +++ b/rust/catalyst-contest/src/row_proof.rs @@ -0,0 +1,335 @@ +//! A universal encrypted row proof. + +use catalyst_voting::crypto::elgamal::Ciphertext; +use cbork_utils::decode_helper::decode_array_len; +use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write}; + +/// A length of the underlying CBOR array of the `ProofScalar` type. +const SCALAR_PROOF_LEN: u64 = 32; + +/// A length of the underlying CBOR array of the `ProofAnnouncementElement` type. +const PROOF_ANNOUNCEMENT_ELEMENT_LEN: u64 = 32; + +/// A minimal length (number of elements) of the +/// `zkproof-elgamal-ristretto255-unit-vector-with-single-selection` array. +/// +/// +/// The number of elements consists of the following: +/// - 7 (zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item) +/// - 3 (zkproof-elgamal-announcement = x3 zkproof-elgamal-group-element) +/// - 1 (elgamal-ristretto255-encrypted-choice) +/// - 3 (zkproof-ed25519-r-response = x3 zkproof-ed25519-scalar) +/// - 1 (zkproof-ed25519-scalar) +const MIN_SELECTION_LEN: u64 = 8; + +/// A universal encrypted row proof. +/// +/// The CDDL schema: +/// ```cddl +/// row-proof = [0, zkproof-elgamal-ristretto255-unit-vector-with-single-selection ] +/// +/// zkproof-elgamal-ristretto255-unit-vector-with-single-selection = [ +zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item, zkproof-ed25519-scalar ] +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RowProof { + /// A list of a single selection proofs. + pub selections: Vec, + /// An individual Ed25519 scalar used in ZK proofs. + pub scalar: ProofScalar, +} + +/// A proof that the row is a unit vector with a single selection. +/// +/// The CDDL schema: +/// ```cddl +/// zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item = ( zkproof-elgamal-announcement, ~elgamal-ristretto255-encrypted-choice, zkproof-ed25519-r-response ) +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SingleSelectionProof { + /// A ZK proof announcement values for Elgamal. + pub announcement: ProofAnnouncement, + /// An elgamal encrypted ciphertext. + pub choice: Ciphertext, + /// A ZK proof response values for Ed25519. + pub response: ProofResponse, +} + +/// An individual Ed25519 scalar used in ZK proofs. +/// +/// The CDDL schema: +/// ```cddl +/// zkproof-ed25519-scalar = bytes .size 32 +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProofScalar(pub [u8; SCALAR_PROOF_LEN as usize]); + +/// A ZK proof announcement values for Elgamal. +/// +/// The CDDL schema: +/// ```cddl +/// zkproof-elgamal-announcement = ( zkproof-elgamal-group-element, zkproof-elgamal-group-element, zkproof-elgamal-group-element ) +/// +/// zkproof-elgamal-group-element = bytes .size 32 +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProofAnnouncement( + pub ProofAnnouncementElement, + pub ProofAnnouncementElement, + pub ProofAnnouncementElement, +); + +/// An individual Elgamal group element used in ZK proofs. +/// +/// The CDDL schema: +/// ```cddl +/// zkproof-elgamal-group-element = bytes .size 32 +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProofAnnouncementElement(pub [u8; PROOF_ANNOUNCEMENT_ELEMENT_LEN as usize]); + +/// A ZK proof response values for Ed25519. +/// +/// The CDDL schema: +/// +/// ```cddl +/// zkproof-ed25519-r-response = ( zkproof-ed25519-scalar, zkproof-ed25519-scalar, zkproof-ed25519-scalar ) +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProofResponse(pub ProofScalar, pub ProofScalar, pub ProofScalar); + +impl Decode<'_, ()> for RowProof { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + let len = decode_array_len(d, "row proof")?; + if len != 2 { + return Err(minicbor::decode::Error::message(format!( + "Unexpected row proof array length {len}, expected 2" + ))); + } + let version = u64::decode(d, ctx)?; + if version != 0 { + return Err(minicbor::decode::Error::message(format!( + "Unexpected row proof version value: {version}, expected 0" + ))); + } + + let len = decode_array_len(d, "row proof single selection")?; + if len < MIN_SELECTION_LEN || !len.is_multiple_of(MIN_SELECTION_LEN) { + return Err(minicbor::decode::Error::message(format!( + "Unexpected row proof single selection array length {len}, expected multiplier of {MIN_SELECTION_LEN}" + ))); + } + + let mut selections = Vec::with_capacity(len as usize - 1); + for _ in 0..len / MIN_SELECTION_LEN { + selections.push(SingleSelectionProof::decode(d, ctx)?); + } + let scalar = ProofScalar::decode(d, ctx)?; + + Ok(Self { selections, scalar }) + } +} + +impl Encode<()> for RowProof { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(2)?; + 0.encode(e, ctx)?; + + e.array(MIN_SELECTION_LEN * self.selections.len() as u64 + 1)?; + for selection in &self.selections { + selection.encode(e, ctx)?; + } + self.scalar.encode(e, ctx) + } +} + +impl Decode<'_, ()> for SingleSelectionProof { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + let announcement = ProofAnnouncement::decode(d, ctx)?; + let choice = Ciphertext::decode(d, ctx)?; + let response = ProofResponse::decode(d, ctx)?; + + Ok(Self { + announcement, + choice, + response, + }) + } +} + +impl Encode<()> for SingleSelectionProof { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.announcement.encode(e, ctx)?; + self.choice.encode(e, ctx)?; + self.response.encode(e, ctx) + } +} + +impl Decode<'_, ()> for ProofScalar { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + <[u8; SCALAR_PROOF_LEN as usize]>::decode(d, ctx).map(Self) + } +} + +impl Encode<()> for ProofScalar { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx) + } +} + +impl Decode<'_, ()> for ProofAnnouncement { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + Ok(Self( + ProofAnnouncementElement::decode(d, ctx)?, + ProofAnnouncementElement::decode(d, ctx)?, + ProofAnnouncementElement::decode(d, ctx)?, + )) + } +} + +impl Encode<()> for ProofAnnouncement { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx)?; + self.1.encode(e, ctx)?; + self.2.encode(e, ctx) + } +} + +impl Decode<'_, ()> for ProofResponse { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + Ok(Self( + ProofScalar::decode(d, ctx)?, + ProofScalar::decode(d, ctx)?, + ProofScalar::decode(d, ctx)?, + )) + } +} + +impl Encode<()> for ProofResponse { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx)?; + self.1.encode(e, ctx)?; + self.2.encode(e, ctx) + } +} + +impl Decode<'_, ()> for ProofAnnouncementElement { + fn decode( + d: &mut Decoder<'_>, + ctx: &mut (), + ) -> Result { + <[u8; PROOF_ANNOUNCEMENT_ELEMENT_LEN as usize]>::decode(d, ctx).map(Self) + } +} + +impl Encode<()> for ProofAnnouncementElement { + fn encode( + &self, + e: &mut Encoder, + ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn row_proof_roundtrip() { + let bytes = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let original = RowProof { + selections: vec![SingleSelectionProof { + announcement: ProofAnnouncement( + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ), + choice: Ciphertext::zero(), + response: ProofResponse(ProofScalar(bytes), ProofScalar(bytes), ProofScalar(bytes)), + }], + scalar: ProofScalar(bytes), + }; + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = RowProof::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn single_selection_proof_roundtrip() { + let bytes = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let original = SingleSelectionProof { + announcement: ProofAnnouncement( + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ProofAnnouncementElement(bytes), + ), + choice: Ciphertext::zero(), + response: ProofResponse(ProofScalar(bytes), ProofScalar(bytes), ProofScalar(bytes)), + }; + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = SingleSelectionProof::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn proof_scalar_roundtrip() { + let original = ProofScalar([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + ]); + let mut buffer = Vec::new(); + original + .encode(&mut Encoder::new(&mut buffer), &mut ()) + .unwrap(); + let decoded = ProofScalar::decode(&mut Decoder::new(&buffer), &mut ()).unwrap(); + assert_eq!(original, decoded); + } +}