Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["zk-sdk", "zk-sdk-wasm-js"]
members = ["zk-sdk", "zk-sdk-pod", "zk-sdk-wasm-js"]

[workspace.package]
authors = ["Anza Maintainers <[email protected]>"]
Expand Down Expand Up @@ -53,6 +53,7 @@ solana-seed-phrase = "3.0.0"
solana-signature = { version = "3.0.0", default-features = false }
solana-signer = "3.0.0"
solana-zk-sdk = { path = "zk-sdk", version = "4.0.0" }
solana-zk-sdk-pod = { path = "zk-sdk-pod", version = "0.1.0" }
solana-zk-sdk-wasm-js = { path = "zk-sdk-wasm-js", version = "0.1.0" }
subtle = "2.6.1"
thiserror = "2.0.17"
Expand Down
25 changes: 25 additions & 0 deletions zk-sdk-pod/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "solana-zk-sdk-pod"
description = "Solana ZK SDK Plain Old Data (Pod)"
documentation = "https://docs.rs/solana-zk-sdk-pod"
version = "0.1.0"
authors = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
edition = { workspace = true }

[dependencies]
base64 = { workspace = true }
bytemuck = { workspace = true }
bytemuck_derive = { workspace = true }
thiserror = { workspace = true }

[lib]
crate-type = ["rlib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[lints]
workspace = true
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
//! Plain Old Data types for the AES128-GCM-SIV authenticated encryption scheme.

#[cfg(not(target_os = "solana"))]
use crate::{encryption::auth_encryption::AeCiphertext, errors::AuthenticatedEncryptionError};
use {
crate::{
encryption::AE_CIPHERTEXT_LEN,
pod::{impl_from_bytes, impl_from_str},
macros::{impl_from_bytes, impl_from_str},
},
base64::{prelude::BASE64_STANDARD, Engine},
bytemuck::{Pod, Zeroable},
Expand All @@ -18,7 +16,7 @@ const AE_CIPHERTEXT_MAX_BASE64_LEN: usize = 48;
/// The `AeCiphertext` type as a `Pod`.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodAeCiphertext(pub(crate) [u8; AE_CIPHERTEXT_LEN]);
pub struct PodAeCiphertext(pub [u8; AE_CIPHERTEXT_LEN]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in zk-sdk-pod is mostly a copy of the original pod logic in the zk-sdk. The only main difference is that the enclosed byte array has a pub visibility while it used to have pub(crate) visibility inside the zk-sdk.


// `PodAeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However,
// the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two
Expand Down Expand Up @@ -51,35 +49,3 @@ impl Default for PodAeCiphertext {
Self::zeroed()
}
}

#[cfg(not(target_os = "solana"))]
impl From<AeCiphertext> for PodAeCiphertext {
fn from(decoded_ciphertext: AeCiphertext) -> Self {
Self(decoded_ciphertext.to_bytes())
}
}

#[cfg(not(target_os = "solana"))]
impl TryFrom<PodAeCiphertext> for AeCiphertext {
type Error = AuthenticatedEncryptionError;

fn try_from(pod_ciphertext: PodAeCiphertext) -> Result<Self, Self::Error> {
Self::from_bytes(&pod_ciphertext.0).ok_or(AuthenticatedEncryptionError::Deserialization)
}
}

#[cfg(test)]
mod tests {
use {super::*, crate::encryption::auth_encryption::AeKey, std::str::FromStr};

#[test]
fn ae_ciphertext_fromstr() {
let ae_key = AeKey::new_rand();
let expected_ae_ciphertext: PodAeCiphertext = ae_key.encrypt(0_u64).into();

let ae_ciphertext_base64_str = format!("{}", expected_ae_ciphertext);
let computed_ae_ciphertext = PodAeCiphertext::from_str(&ae_ciphertext_base64_str).unwrap();

assert_eq!(expected_ae_ciphertext, computed_ae_ciphertext);
}
}
104 changes: 104 additions & 0 deletions zk-sdk-pod/src/encryption/elgamal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Plain Old Data types for the ElGamal encryption scheme.

use {
crate::{
encryption::{DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_PUBKEY_LEN},
macros::{impl_from_bytes, impl_from_str},
},
base64::{prelude::BASE64_STANDARD, Engine},
bytemuck::Zeroable,
std::fmt,
};

/// Maximum length of a base64 encoded ElGamal public key
const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44;

/// Maximum length of a base64 encoded ElGamal ciphertext
const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88;

/// Maximum length of a base64 encoded ElGamal decrypt handle
const DECRYPT_HANDLE_MAX_BASE64_LEN: usize = 44;

/// The `ElGamalCiphertext` type as a `Pod`.
#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]);

impl fmt::Debug for PodElGamalCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl fmt::Display for PodElGamalCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.0))
}
}

impl Default for PodElGamalCiphertext {
fn default() -> Self {
Self::zeroed()
}
}

impl_from_str!(
TYPE = PodElGamalCiphertext,
BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN,
BASE64_LEN = ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN
);

impl_from_bytes!(
TYPE = PodElGamalCiphertext,
BYTES_LEN = ELGAMAL_CIPHERTEXT_LEN
);

/// The `ElGamalPubkey` type as a `Pod`.
#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]);

impl fmt::Debug for PodElGamalPubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl fmt::Display for PodElGamalPubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.0))
}
}

impl_from_str!(
TYPE = PodElGamalPubkey,
BYTES_LEN = ELGAMAL_PUBKEY_LEN,
BASE64_LEN = ELGAMAL_PUBKEY_MAX_BASE64_LEN
);

impl_from_bytes!(TYPE = PodElGamalPubkey, BYTES_LEN = ELGAMAL_PUBKEY_LEN);

/// The `DecryptHandle` type as a `Pod`.
#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodDecryptHandle(pub [u8; DECRYPT_HANDLE_LEN]);

impl fmt::Debug for PodDecryptHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl fmt::Display for PodDecryptHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.0))
}
}

impl_from_str!(
TYPE = PodDecryptHandle,
BYTES_LEN = DECRYPT_HANDLE_LEN,
BASE64_LEN = DECRYPT_HANDLE_MAX_BASE64_LEN
);

impl_from_bytes!(TYPE = PodDecryptHandle, BYTES_LEN = DECRYPT_HANDLE_LEN);
138 changes: 138 additions & 0 deletions zk-sdk-pod/src/encryption/grouped_elgamal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Plain Old Data types for the Grouped ElGamal encryption scheme.

use {
crate::{
encryption::{
elgamal::PodElGamalCiphertext, pedersen::PodPedersenCommitment, DECRYPT_HANDLE_LEN,
ELGAMAL_CIPHERTEXT_LEN, PEDERSEN_COMMITMENT_LEN,
},
macros::{impl_from_bytes, impl_from_str},
},
base64::{prelude::BASE64_STANDARD, Engine},
bytemuck::Zeroable,
std::fmt,
};

/// Maximum length of a base64 encoded grouped ElGamal ciphertext with 2 handles
const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES_MAX_BASE64_LEN: usize = 132;

/// Maximum length of a base64 encoded grouped ElGamal ciphertext with 3 handles
const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES_MAX_BASE64_LEN: usize = 176;

macro_rules! impl_extract {
(TYPE = $type:ident) => {
impl $type {
/// Extract the commitment component from a grouped ciphertext
pub fn extract_commitment(&self) -> PodPedersenCommitment {
// `GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES` guaranteed to be at least `PEDERSEN_COMMITMENT_LEN`
let commitment = self.0[..PEDERSEN_COMMITMENT_LEN].try_into().unwrap();
PodPedersenCommitment(commitment)
}

/// Extract a regular ElGamal ciphertext using the decrypt handle at a specified index.
pub fn try_extract_ciphertext(
&self,
index: usize,
) -> Result<PodElGamalCiphertext, crate::errors::PodParseError> {
let mut ciphertext_bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN];
ciphertext_bytes[..PEDERSEN_COMMITMENT_LEN]
.copy_from_slice(&self.0[..PEDERSEN_COMMITMENT_LEN]);

let handle_start = DECRYPT_HANDLE_LEN
.checked_mul(index)
.and_then(|n| n.checked_add(PEDERSEN_COMMITMENT_LEN))
.ok_or(crate::errors::PodParseError::GroupedCiphertextIndexOutOfBounds)?;
let handle_end = handle_start
.checked_add(DECRYPT_HANDLE_LEN)
.ok_or(crate::errors::PodParseError::GroupedCiphertextIndexOutOfBounds)?;
ciphertext_bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice(
self.0
.get(handle_start..handle_end)
.ok_or(crate::errors::PodParseError::GroupedCiphertextIndexOutOfBounds)?,
);

Ok(PodElGamalCiphertext(ciphertext_bytes))
}
}
};
}

/// Byte length of a grouped ElGamal ciphertext with 2 handles
const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES: usize =
PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;

/// Byte length of a grouped ElGamal ciphertext with 3 handles
const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize =
PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;

/// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod`
#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodGroupedElGamalCiphertext2Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES]);

impl fmt::Debug for PodGroupedElGamalCiphertext2Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl Default for PodGroupedElGamalCiphertext2Handles {
fn default() -> Self {
Self::zeroed()
}
}

impl fmt::Display for PodGroupedElGamalCiphertext2Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.0))
}
}

impl_from_str!(
TYPE = PodGroupedElGamalCiphertext2Handles,
BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES,
BASE64_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES_MAX_BASE64_LEN
);

impl_from_bytes!(
TYPE = PodGroupedElGamalCiphertext2Handles,
BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES
);

impl_extract!(TYPE = PodGroupedElGamalCiphertext2Handles);

/// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod`
#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)]
#[repr(transparent)]
pub struct PodGroupedElGamalCiphertext3Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES]);

impl fmt::Debug for PodGroupedElGamalCiphertext3Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl Default for PodGroupedElGamalCiphertext3Handles {
fn default() -> Self {
Self::zeroed()
}
}

impl fmt::Display for PodGroupedElGamalCiphertext3Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", BASE64_STANDARD.encode(self.0))
}
}

impl_from_str!(
TYPE = PodGroupedElGamalCiphertext3Handles,
BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES,
BASE64_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES_MAX_BASE64_LEN
);

impl_from_bytes!(
TYPE = PodGroupedElGamalCiphertext3Handles,
BYTES_LEN = GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES
);

impl_extract!(TYPE = PodGroupedElGamalCiphertext3Handles);
Loading
Loading