[zk-token-sdk] rename and restructure `CloseAccount` and `WithdrawWithheld` proof instructions (#31608)
* rename and restruct ProofInstruction::VerifyCloseAccount to ProofInstruction::VerifyZeroBalance * rename `PubkeySigmaProof` to `PubkeyValidityProof` * refactor equality proofs into separate modules * rename and restructure ProofInstruction::VerifyWithdrawWithheld to ProofInstruction::VerifyCiphertextCiphertextEquality * add and reorg documentation for the proof instructions * generalize ciphertext-ciphertext equality proof data constructor * Update zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs Co-authored-by: Jon Cinque <joncinque@pm.me> --------- Co-authored-by: Jon Cinque <joncinque@pm.me>
This commit is contained in:
parent
bd8289e114
commit
f9b0691eb4
|
@ -9,69 +9,79 @@ use {
|
|||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
solana_zk_token_sdk::{
|
||||
encryption::elgamal::ElGamalKeypair, instruction::*, zk_token_proof_instruction::*,
|
||||
zk_token_proof_program, zk_token_proof_state::ProofContextState,
|
||||
encryption::{elgamal::ElGamalKeypair, pedersen::PedersenOpening},
|
||||
instruction::*,
|
||||
zk_token_proof_instruction::*,
|
||||
zk_token_proof_program,
|
||||
zk_token_proof_state::ProofContextState,
|
||||
},
|
||||
std::mem::size_of,
|
||||
};
|
||||
|
||||
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [
|
||||
ProofInstruction::VerifyCloseAccount,
|
||||
ProofInstruction::VerifyZeroBalance,
|
||||
ProofInstruction::VerifyWithdraw,
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens,
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||
ProofInstruction::VerifyTransfer,
|
||||
ProofInstruction::VerifyTransferWithFee,
|
||||
ProofInstruction::VerifyPubkeyValidity,
|
||||
];
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_close_account() {
|
||||
async fn test_zero_balance() {
|
||||
let elgamal_keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
let zero_ciphertext = elgamal_keypair.public.encrypt(0_u64);
|
||||
let success_proof_data = CloseAccountData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
|
||||
let success_proof_data = ZeroBalanceProofData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
|
||||
|
||||
let incorrect_keypair = ElGamalKeypair {
|
||||
public: ElGamalKeypair::new_rand().public,
|
||||
secret: ElGamalKeypair::new_rand().secret,
|
||||
};
|
||||
let fail_proof_data = CloseAccountData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
|
||||
let fail_proof_data = ZeroBalanceProofData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
|
||||
|
||||
test_verify_proof_without_context(
|
||||
ProofInstruction::VerifyCloseAccount,
|
||||
ProofInstruction::VerifyZeroBalance,
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_verify_proof_with_context(
|
||||
ProofInstruction::VerifyCloseAccount,
|
||||
size_of::<ProofContextState<CloseAccountProofContext>>(),
|
||||
ProofInstruction::VerifyZeroBalance,
|
||||
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_close_context_state(
|
||||
ProofInstruction::VerifyCloseAccount,
|
||||
size_of::<ProofContextState<CloseAccountProofContext>>(),
|
||||
ProofInstruction::VerifyZeroBalance,
|
||||
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
|
||||
&success_proof_data,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_withdraw_withheld_tokens() {
|
||||
let elgamal_keypair = ElGamalKeypair::new_rand();
|
||||
async fn test_ciphertext_ciphertext_equality() {
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
let amount: u64 = 0;
|
||||
let withdraw_withheld_authority_ciphertext = elgamal_keypair.public.encrypt(amount);
|
||||
let source_ciphertext = source_keypair.public.encrypt(amount);
|
||||
|
||||
let success_proof_data = WithdrawWithheldTokensData::new(
|
||||
&elgamal_keypair,
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(amount, &destination_opening);
|
||||
|
||||
let success_proof_data = CiphertextCiphertextEqualityProofData::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&destination_opening,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -80,32 +90,34 @@ async fn test_withdraw_withheld_tokens() {
|
|||
public: ElGamalKeypair::new_rand().public,
|
||||
secret: ElGamalKeypair::new_rand().secret,
|
||||
};
|
||||
let fail_proof_data = WithdrawWithheldTokensData::new(
|
||||
let fail_proof_data = CiphertextCiphertextEqualityProofData::new(
|
||||
&incorrect_keypair,
|
||||
&destination_keypair.public,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&destination_opening,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_verify_proof_without_context(
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens,
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_verify_proof_with_context(
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens,
|
||||
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_close_context_state(
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens,
|
||||
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
|
||||
&success_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -135,14 +135,14 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
|
|||
ic_msg!(invoke_context, "CloseContextState");
|
||||
process_close_proof_context(invoke_context)
|
||||
}
|
||||
ProofInstruction::VerifyCloseAccount => {
|
||||
ProofInstruction::VerifyZeroBalance => {
|
||||
if native_programs_consume_cu {
|
||||
invoke_context
|
||||
.consume_checked(6_012)
|
||||
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
|
||||
}
|
||||
ic_msg!(invoke_context, "VerifyCloseAccount");
|
||||
process_verify_proof::<CloseAccountData, CloseAccountProofContext>(invoke_context)
|
||||
ic_msg!(invoke_context, "VerifyZeroBalance");
|
||||
process_verify_proof::<ZeroBalanceProofData, ZeroBalanceProofContext>(invoke_context)
|
||||
}
|
||||
ProofInstruction::VerifyWithdraw => {
|
||||
if native_programs_consume_cu {
|
||||
|
@ -153,16 +153,17 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
|
|||
ic_msg!(invoke_context, "VerifyWithdraw");
|
||||
process_verify_proof::<WithdrawData, WithdrawProofContext>(invoke_context)
|
||||
}
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens => {
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality => {
|
||||
if native_programs_consume_cu {
|
||||
invoke_context
|
||||
.consume_checked(7_943)
|
||||
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
|
||||
}
|
||||
ic_msg!(invoke_context, "VerifyWithdrawWithheldTokens");
|
||||
process_verify_proof::<WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext>(
|
||||
invoke_context,
|
||||
)
|
||||
ic_msg!(invoke_context, "VerifyCiphertextCiphertextEquality");
|
||||
process_verify_proof::<
|
||||
CiphertextCiphertextEqualityProofData,
|
||||
CiphertextCiphertextEqualityProofContext,
|
||||
>(invoke_context)
|
||||
}
|
||||
ProofInstruction::VerifyTransfer => {
|
||||
if native_programs_consume_cu {
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
||||
errors::ProofError,
|
||||
sigma_proofs::zero_balance_proof::ZeroBalanceProof,
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
std::convert::TryInto,
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
instruction::{ProofType, ZkProofData},
|
||||
zk_token_elgamal::pod,
|
||||
},
|
||||
bytemuck::{Pod, Zeroable},
|
||||
};
|
||||
|
||||
/// This struct includes the cryptographic proof *and* the account data information needed to verify
|
||||
/// the proof
|
||||
///
|
||||
/// - The pre-instruction should call CloseAccountData::verify_proof(&self)
|
||||
/// - The actual program should check that `balance` is consistent with what is
|
||||
/// currently stored in the confidential token account
|
||||
///
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct CloseAccountData {
|
||||
/// The context data for the close account proof
|
||||
pub context: CloseAccountProofContext,
|
||||
|
||||
/// Proof that the source account available balance is zero
|
||||
pub proof: CloseAccountProof, // 96 bytes
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct CloseAccountProofContext {
|
||||
/// The source account ElGamal pubkey
|
||||
pub pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
/// The source account available balance in encrypted form
|
||||
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CloseAccountData {
|
||||
pub fn new(
|
||||
keypair: &ElGamalKeypair,
|
||||
ciphertext: &ElGamalCiphertext,
|
||||
) -> Result<Self, ProofError> {
|
||||
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
|
||||
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
|
||||
|
||||
let context = CloseAccountProofContext {
|
||||
pubkey: pod_pubkey,
|
||||
ciphertext: pod_ciphertext,
|
||||
};
|
||||
|
||||
let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
|
||||
let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
|
||||
|
||||
Ok(CloseAccountData { context, proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZkProofData<CloseAccountProofContext> for CloseAccountData {
|
||||
const PROOF_TYPE: ProofType = ProofType::CloseAccount;
|
||||
|
||||
fn context_data(&self) -> &CloseAccountProofContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript =
|
||||
CloseAccountProof::transcript_new(&self.context.pubkey, &self.context.ciphertext);
|
||||
|
||||
let pubkey = self.context.pubkey.try_into()?;
|
||||
let ciphertext = self.context.ciphertext.try_into()?;
|
||||
self.proof.verify(&pubkey, &ciphertext, &mut transcript)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents the cryptographic proof component that certifies that the encrypted
|
||||
/// balance is zero
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct CloseAccountProof {
|
||||
pub proof: pod::ZeroBalanceProof,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CloseAccountProof {
|
||||
fn transcript_new(
|
||||
pubkey: &pod::ElGamalPubkey,
|
||||
ciphertext: &pod::ElGamalCiphertext,
|
||||
) -> Transcript {
|
||||
let mut transcript = Transcript::new(b"CloseAccountProof");
|
||||
|
||||
transcript.append_pubkey(b"pubkey", pubkey);
|
||||
transcript.append_ciphertext(b"ciphertext", ciphertext);
|
||||
|
||||
transcript
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
keypair: &ElGamalKeypair,
|
||||
ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
|
||||
|
||||
Self {
|
||||
proof: proof.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
pubkey: &ElGamalPubkey,
|
||||
ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
let proof: ZeroBalanceProof = self.proof.try_into()?;
|
||||
proof.verify(pubkey, ciphertext, transcript)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_close_account_correctness() {
|
||||
let keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
// general case: encryption of 0
|
||||
let ciphertext = keypair.public.encrypt(0_u64);
|
||||
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
|
||||
assert!(close_account_data.verify_proof().is_ok());
|
||||
|
||||
// general case: encryption of > 0
|
||||
let ciphertext = keypair.public.encrypt(1_u64);
|
||||
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
|
||||
assert!(close_account_data.verify_proof().is_err());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
//! The ciphertext-ciphertext equality proof instruction.
|
||||
//!
|
||||
//! A ciphertext-ciphertext equality proof is defined with respect to two twisted ElGamal
|
||||
//! ciphertexts. The proof certifies that the two ciphertexts encrypt the same message. To generate
|
||||
//! the proof, a prover must provide the decryption key for the first ciphertext and the randomness
|
||||
//! used to generate the second ciphertext.
|
||||
//!
|
||||
//! The first ciphertext associated with the proof is referred to as the "source" ciphertext. The
|
||||
//! second ciphertext associated with the proof is referred to as the "destination" ciphertext.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::{
|
||||
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
||||
pedersen::PedersenOpening,
|
||||
},
|
||||
errors::ProofError,
|
||||
sigma_proofs::ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof,
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
std::convert::TryInto,
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
instruction::{ProofType, ZkProofData},
|
||||
zk_token_elgamal::pod,
|
||||
},
|
||||
bytemuck::{Pod, Zeroable},
|
||||
};
|
||||
|
||||
/// The instruction data that is needed for the
|
||||
/// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction.
|
||||
///
|
||||
/// It includes the cryptographic proof as well as the context data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct CiphertextCiphertextEqualityProofData {
|
||||
pub context: CiphertextCiphertextEqualityProofContext,
|
||||
|
||||
pub proof: pod::CiphertextCiphertextEqualityProof,
|
||||
}
|
||||
|
||||
/// The context data needed to verify a ciphertext-ciphertext equality proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct CiphertextCiphertextEqualityProofContext {
|
||||
pub source_pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
pub source_ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
|
||||
pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CiphertextCiphertextEqualityProofData {
|
||||
pub fn new(
|
||||
source_keypair: &ElGamalKeypair,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
destination_ciphertext: &ElGamalCiphertext,
|
||||
destination_opening: &PedersenOpening,
|
||||
amount: u64,
|
||||
) -> Result<Self, ProofError> {
|
||||
let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.public.to_bytes());
|
||||
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
|
||||
let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes());
|
||||
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
|
||||
|
||||
let context = CiphertextCiphertextEqualityProofContext {
|
||||
source_pubkey: pod_source_pubkey,
|
||||
destination_pubkey: pod_destination_pubkey,
|
||||
source_ciphertext: pod_source_ciphertext,
|
||||
destination_ciphertext: pod_destination_ciphertext,
|
||||
};
|
||||
|
||||
let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
|
||||
&pod_source_pubkey,
|
||||
&pod_destination_pubkey,
|
||||
&pod_source_ciphertext,
|
||||
&pod_destination_ciphertext,
|
||||
);
|
||||
|
||||
let proof = CiphertextCiphertextEqualityProof::new(
|
||||
source_keypair,
|
||||
destination_pubkey,
|
||||
source_ciphertext,
|
||||
destination_opening,
|
||||
amount,
|
||||
&mut transcript,
|
||||
)
|
||||
.into();
|
||||
|
||||
Ok(Self { context, proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZkProofData<CiphertextCiphertextEqualityProofContext>
|
||||
for CiphertextCiphertextEqualityProofData
|
||||
{
|
||||
const PROOF_TYPE: ProofType = ProofType::CiphertextCiphertextEquality;
|
||||
|
||||
fn context_data(&self) -> &CiphertextCiphertextEqualityProofContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
|
||||
&self.context.source_pubkey,
|
||||
&self.context.destination_pubkey,
|
||||
&self.context.source_ciphertext,
|
||||
&self.context.destination_ciphertext,
|
||||
);
|
||||
|
||||
let source_pubkey = self.context.source_pubkey.try_into()?;
|
||||
let destination_pubkey = self.context.destination_pubkey.try_into()?;
|
||||
let source_ciphertext = self.context.source_ciphertext.try_into()?;
|
||||
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
|
||||
let proof: CiphertextCiphertextEqualityProof = self.proof.try_into()?;
|
||||
|
||||
proof
|
||||
.verify(
|
||||
&source_pubkey,
|
||||
&destination_pubkey,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut transcript,
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CiphertextCiphertextEqualityProof {
|
||||
fn transcript_new(
|
||||
source_pubkey: &pod::ElGamalPubkey,
|
||||
destination_pubkey: &pod::ElGamalPubkey,
|
||||
source_ciphertext: &pod::ElGamalCiphertext,
|
||||
destination_ciphertext: &pod::ElGamalCiphertext,
|
||||
) -> Transcript {
|
||||
let mut transcript = Transcript::new(b"CiphertextCiphertextEqualityProof");
|
||||
|
||||
transcript.append_pubkey(b"pubkey-source", source_pubkey);
|
||||
transcript.append_pubkey(b"pubkey-dest", destination_pubkey);
|
||||
|
||||
transcript.append_ciphertext(b"ciphertext-source", source_ciphertext);
|
||||
transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);
|
||||
|
||||
transcript
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ciphertext_ciphertext_instruction_correctness() {
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
let amount: u64 = 0;
|
||||
let source_ciphertext = source_keypair.public.encrypt(amount);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(amount, &destination_opening);
|
||||
|
||||
let proof_data = CiphertextCiphertextEqualityProofData::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&destination_opening,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof_data.verify_proof().is_ok());
|
||||
|
||||
let amount: u64 = 55;
|
||||
let source_ciphertext = source_keypair.public.encrypt(amount);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(amount, &destination_opening);
|
||||
|
||||
let proof_data = CiphertextCiphertextEqualityProofData::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&destination_opening,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof_data.verify_proof().is_ok());
|
||||
|
||||
let amount = u64::max_value();
|
||||
let source_ciphertext = source_keypair.public.encrypt(amount);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(amount, &destination_opening);
|
||||
|
||||
let proof_data = CiphertextCiphertextEqualityProofData::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&destination_opening,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof_data.verify_proof().is_ok());
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
pub mod close_account;
|
||||
pub mod ctxt_ctxt_equality;
|
||||
pub mod pubkey_validity;
|
||||
pub mod transfer;
|
||||
pub mod transfer_with_fee;
|
||||
pub mod withdraw;
|
||||
pub mod withdraw_withheld;
|
||||
pub mod zero_balance;
|
||||
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
|
@ -19,12 +19,14 @@ use {
|
|||
};
|
||||
pub use {
|
||||
bytemuck::Pod,
|
||||
close_account::{CloseAccountData, CloseAccountProofContext},
|
||||
ctxt_ctxt_equality::{
|
||||
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
|
||||
},
|
||||
pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext},
|
||||
transfer::{TransferData, TransferProofContext},
|
||||
transfer_with_fee::{FeeParameters, TransferWithFeeData, TransferWithFeeProofContext},
|
||||
withdraw::{WithdrawData, WithdrawProofContext},
|
||||
withdraw_withheld::{WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext},
|
||||
zero_balance::{ZeroBalanceProofContext, ZeroBalanceProofData},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
|
||||
|
@ -32,9 +34,9 @@ pub use {
|
|||
pub enum ProofType {
|
||||
/// Empty proof type used to distinguish if a proof context account is initialized
|
||||
Uninitialized,
|
||||
CloseAccount,
|
||||
ZeroBalance,
|
||||
Withdraw,
|
||||
WithdrawWithheldTokens,
|
||||
CiphertextCiphertextEquality,
|
||||
Transfer,
|
||||
TransferWithFee,
|
||||
PubkeyValidity,
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
//! The public-key validity proof instruction.
|
||||
//!
|
||||
//! A public-key validity proof system is defined with respect to an ElGamal public key. The proof
|
||||
//! certifies that a given public key is a valid ElGamal public key (i.e. the prover knows a
|
||||
//! corresponding secret key). To generate the proof, a prover must provide the secret key for the
|
||||
//! public key.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::elgamal::{ElGamalKeypair, ElGamalPubkey},
|
||||
errors::ProofError,
|
||||
sigma_proofs::pubkey_proof::PubkeySigmaProof,
|
||||
transcript::TranscriptProtocol,
|
||||
encryption::elgamal::ElGamalKeypair, errors::ProofError,
|
||||
sigma_proofs::pubkey_proof::PubkeyValidityProof, transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
std::convert::TryInto,
|
||||
|
@ -17,22 +22,22 @@ use {
|
|||
bytemuck::{Pod, Zeroable},
|
||||
};
|
||||
|
||||
/// This struct includes the cryptographic proof *and* the account data information needed to
|
||||
/// verify the proof
|
||||
/// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity`
|
||||
/// instruction.
|
||||
///
|
||||
/// - The pre-instruction should call PubkeyValidityData::verify_proof(&self)
|
||||
/// - The actual program should check that the public key in this struct is consistent with what is
|
||||
/// stored in the confidential token account
|
||||
/// It includes the cryptographic proof as well as the context data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct PubkeyValidityData {
|
||||
/// The context data for the public key validity proof
|
||||
pub context: PubkeyValidityProofContext,
|
||||
pub context: PubkeyValidityProofContext, // 32 bytes
|
||||
|
||||
/// Proof that the public key is well-formed
|
||||
pub proof: PubkeyValidityProof, // 64 bytes
|
||||
pub proof: pod::PubkeyValidityProof, // 64 bytes
|
||||
}
|
||||
|
||||
/// The context data needed to verify a pubkey validity proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct PubkeyValidityProofContext {
|
||||
|
@ -48,7 +53,7 @@ impl PubkeyValidityData {
|
|||
let context = PubkeyValidityProofContext { pubkey: pod_pubkey };
|
||||
|
||||
let mut transcript = PubkeyValidityProof::transcript_new(&pod_pubkey);
|
||||
let proof = PubkeyValidityProof::new(keypair, &mut transcript);
|
||||
let proof = PubkeyValidityProof::new(keypair, &mut transcript).into();
|
||||
|
||||
Ok(PubkeyValidityData { context, proof })
|
||||
}
|
||||
|
@ -65,18 +70,11 @@ impl ZkProofData<PubkeyValidityProofContext> for PubkeyValidityData {
|
|||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript = PubkeyValidityProof::transcript_new(&self.context.pubkey);
|
||||
let pubkey = self.context.pubkey.try_into()?;
|
||||
self.proof.verify(&pubkey, &mut transcript)
|
||||
let proof: PubkeyValidityProof = self.proof.try_into()?;
|
||||
proof.verify(&pubkey, &mut transcript).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct PubkeyValidityProof {
|
||||
/// Associated public-key sigma proof
|
||||
pub proof: pod::PubkeySigmaProof,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl PubkeyValidityProof {
|
||||
|
@ -85,23 +83,6 @@ impl PubkeyValidityProof {
|
|||
transcript.append_pubkey(b"pubkey", pubkey);
|
||||
transcript
|
||||
}
|
||||
|
||||
pub fn new(keypair: &ElGamalKeypair, transcript: &mut Transcript) -> Self {
|
||||
let proof = PubkeySigmaProof::new(keypair, transcript);
|
||||
Self {
|
||||
proof: proof.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
pubkey: &ElGamalPubkey,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
let proof: PubkeySigmaProof = self.proof.try_into()?;
|
||||
proof.verify(pubkey, transcript)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -109,7 +90,7 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pubkey_validity_correctness() {
|
||||
fn test_pubkey_validity_instruction_correctness() {
|
||||
let keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
let pubkey_validity_data = PubkeyValidityData::new(&keypair).unwrap();
|
||||
|
|
|
@ -11,7 +11,8 @@ use {
|
|||
instruction::{combine_lo_hi_ciphertexts, split_u64, Role},
|
||||
range_proof::RangeProof,
|
||||
sigma_proofs::{
|
||||
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
|
||||
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
|
||||
validity_proof::AggregatedValidityProof,
|
||||
},
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
|
@ -42,6 +43,10 @@ lazy_static::lazy_static! {
|
|||
TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1);
|
||||
}
|
||||
|
||||
/// The instruction data that is needed for the `ProofInstruction::VerifyTransfer` instruction.
|
||||
///
|
||||
/// It includes the cryptographic proof as well as the context data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct TransferData {
|
||||
|
@ -52,6 +57,7 @@ pub struct TransferData {
|
|||
pub proof: TransferProof,
|
||||
}
|
||||
|
||||
/// The context data needed to verify a transfer proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct TransferProofContext {
|
||||
|
@ -252,7 +258,7 @@ pub struct TransferProof {
|
|||
pub new_source_commitment: pod::PedersenCommitment,
|
||||
|
||||
/// Associated equality proof
|
||||
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
|
||||
|
||||
/// Associated ciphertext validity proof
|
||||
pub validity_proof: pod::AggregatedValidityProof,
|
||||
|
@ -307,11 +313,11 @@ impl TransferProof {
|
|||
transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment);
|
||||
|
||||
// generate equality_proof
|
||||
let equality_proof = CtxtCommEqualityProof::new(
|
||||
let equality_proof = CiphertextCommitmentEqualityProof::new(
|
||||
source_keypair,
|
||||
new_source_ciphertext,
|
||||
source_new_balance,
|
||||
&source_opening,
|
||||
source_new_balance,
|
||||
transcript,
|
||||
);
|
||||
|
||||
|
@ -377,13 +383,11 @@ impl TransferProof {
|
|||
transcript.append_commitment(b"commitment-new-source", &self.new_source_commitment);
|
||||
|
||||
let commitment: PedersenCommitment = self.new_source_commitment.try_into()?;
|
||||
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
|
||||
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
|
||||
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
||||
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||
|
||||
// verify equality proof
|
||||
//
|
||||
// TODO: we can also consider verifying equality and range proof in a batch
|
||||
equality_proof.verify(
|
||||
&transfer_pubkeys.source_pubkey,
|
||||
ciphertext_new_spendable,
|
||||
|
|
|
@ -14,7 +14,7 @@ use {
|
|||
},
|
||||
range_proof::RangeProof,
|
||||
sigma_proofs::{
|
||||
equality_proof::CtxtCommEqualityProof, fee_proof::FeeSigmaProof,
|
||||
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, fee_proof::FeeSigmaProof,
|
||||
validity_proof::AggregatedValidityProof,
|
||||
},
|
||||
transcript::TranscriptProtocol,
|
||||
|
@ -60,7 +60,10 @@ lazy_static::lazy_static! {
|
|||
pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS);
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, Pod, Zeroable)]
|
||||
/// The instruction data that is needed for the `ProofInstruction::TransferWithFee` instruction.
|
||||
///
|
||||
/// It includes the cryptographic proof as well as the cotnext data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct TransferWithFeeData {
|
||||
|
@ -71,6 +74,7 @@ pub struct TransferWithFeeData {
|
|||
pub proof: TransferWithFeeProof,
|
||||
}
|
||||
|
||||
/// The context data needed to verify a transfer-with-fee proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct TransferWithFeeProofContext {
|
||||
|
@ -386,7 +390,7 @@ impl ZkProofData<TransferWithFeeProofContext> for TransferWithFeeData {
|
|||
pub struct TransferWithFeeProof {
|
||||
pub new_source_commitment: pod::PedersenCommitment,
|
||||
pub claimed_commitment: pod::PedersenCommitment,
|
||||
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
|
||||
pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof,
|
||||
pub fee_sigma_proof: pod::FeeSigmaProof,
|
||||
pub fee_ciphertext_validity_proof: pod::AggregatedValidityProof,
|
||||
|
@ -472,11 +476,11 @@ impl TransferWithFeeProof {
|
|||
transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment);
|
||||
|
||||
// generate equality_proof
|
||||
let equality_proof = CtxtCommEqualityProof::new(
|
||||
let equality_proof = CiphertextCommitmentEqualityProof::new(
|
||||
source_keypair,
|
||||
new_source_ciphertext,
|
||||
source_new_balance,
|
||||
&opening_source,
|
||||
source_new_balance,
|
||||
transcript,
|
||||
);
|
||||
|
||||
|
@ -602,7 +606,7 @@ impl TransferWithFeeProof {
|
|||
let new_source_commitment: PedersenCommitment = self.new_source_commitment.try_into()?;
|
||||
let claimed_commitment: PedersenCommitment = self.claimed_commitment.try_into()?;
|
||||
|
||||
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
|
||||
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
|
||||
let ciphertext_amount_validity_proof: AggregatedValidityProof =
|
||||
self.ciphertext_amount_validity_proof.try_into()?;
|
||||
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;
|
||||
|
|
|
@ -7,7 +7,7 @@ use {
|
|||
},
|
||||
errors::ProofError,
|
||||
range_proof::RangeProof,
|
||||
sigma_proofs::equality_proof::CtxtCommEqualityProof,
|
||||
sigma_proofs::ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
|
@ -24,13 +24,10 @@ use {
|
|||
#[cfg(not(target_os = "solana"))]
|
||||
const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
|
||||
|
||||
/// This struct includes the cryptographic proof *and* the account data information needed to verify
|
||||
/// the proof
|
||||
///
|
||||
/// - The pre-instruction should call WithdrawData::verify_proof(&self)
|
||||
/// - The actual program should check that `current_ct` is consistent with what is
|
||||
/// currently stored in the confidential token account TODO: update this statement
|
||||
/// The instruction data that is needed for the `ProofInstruction::VerifyWithdraw` instruction.
|
||||
///
|
||||
/// It includes the cryptographic proof as well as the context data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct WithdrawData {
|
||||
|
@ -41,6 +38,7 @@ pub struct WithdrawData {
|
|||
pub proof: WithdrawProof, // 736 bytes
|
||||
}
|
||||
|
||||
/// The context data needed to verify a withdraw proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct WithdrawProofContext {
|
||||
|
@ -105,8 +103,9 @@ impl ZkProofData<WithdrawProofContext> for WithdrawData {
|
|||
}
|
||||
}
|
||||
|
||||
/// This struct represents the cryptographic proof component that certifies the account's solvency
|
||||
/// for withdrawal
|
||||
/// The withdraw proof.
|
||||
///
|
||||
/// It contains a ciphertext-commitment equality proof and a 64-bit range proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -115,7 +114,7 @@ pub struct WithdrawProof {
|
|||
pub commitment: pod::PedersenCommitment,
|
||||
|
||||
/// Associated equality proof
|
||||
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
|
||||
|
||||
/// Associated range proof
|
||||
pub range_proof: pod::RangeProof64, // 672 bytes
|
||||
|
@ -149,11 +148,11 @@ impl WithdrawProof {
|
|||
transcript.append_commitment(b"commitment", &pod_commitment);
|
||||
|
||||
// generate equality_proof
|
||||
let equality_proof = CtxtCommEqualityProof::new(
|
||||
let equality_proof = CiphertextCommitmentEqualityProof::new(
|
||||
keypair,
|
||||
final_ciphertext,
|
||||
final_balance,
|
||||
&opening,
|
||||
final_balance,
|
||||
transcript,
|
||||
);
|
||||
|
||||
|
@ -176,17 +175,13 @@ impl WithdrawProof {
|
|||
transcript.append_commitment(b"commitment", &self.commitment);
|
||||
|
||||
let commitment: PedersenCommitment = self.commitment.try_into()?;
|
||||
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
|
||||
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
|
||||
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||
|
||||
// verify equality proof
|
||||
//
|
||||
// TODO: we can also consider verifying equality and range proof in a batch
|
||||
equality_proof.verify(pubkey, final_ciphertext, &commitment, transcript)?;
|
||||
|
||||
// verify range proof
|
||||
//
|
||||
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
|
||||
range_proof.verify(
|
||||
vec![&commitment],
|
||||
vec![WITHDRAW_AMOUNT_BIT_LENGTH],
|
||||
|
|
|
@ -1,260 +0,0 @@
|
|||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::{
|
||||
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
||||
pedersen::PedersenOpening,
|
||||
},
|
||||
errors::ProofError,
|
||||
sigma_proofs::equality_proof::CtxtCtxtEqualityProof,
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
std::convert::TryInto,
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
instruction::{ProofType, ZkProofData},
|
||||
zk_token_elgamal::pod,
|
||||
},
|
||||
bytemuck::{Pod, Zeroable},
|
||||
};
|
||||
|
||||
/// This struct includes the cryptographic proof *and* the account data information needed to verify
|
||||
/// the proof
|
||||
///
|
||||
/// - The pre-instruction should call WithdrawWithheldTokensData::verify_proof(&self)
|
||||
/// - The actual program should check that the ciphertext in this struct is consistent with what is
|
||||
/// currently stored in the confidential token account
|
||||
///
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct WithdrawWithheldTokensData {
|
||||
pub context: WithdrawWithheldTokensProofContext,
|
||||
|
||||
pub proof: WithdrawWithheldTokensProof,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct WithdrawWithheldTokensProofContext {
|
||||
pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
|
||||
pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl WithdrawWithheldTokensData {
|
||||
pub fn new(
|
||||
withdraw_withheld_authority_keypair: &ElGamalKeypair,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
|
||||
amount: u64,
|
||||
) -> Result<Self, ProofError> {
|
||||
// encrypt withdraw amount under destination public key
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_pubkey.encrypt_with(amount, &destination_opening);
|
||||
|
||||
let pod_withdraw_withheld_authority_pubkey =
|
||||
pod::ElGamalPubkey(withdraw_withheld_authority_keypair.public.to_bytes());
|
||||
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
|
||||
let pod_withdraw_withheld_authority_ciphertext =
|
||||
pod::ElGamalCiphertext(withdraw_withheld_authority_ciphertext.to_bytes());
|
||||
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
|
||||
|
||||
let context = WithdrawWithheldTokensProofContext {
|
||||
withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey,
|
||||
destination_pubkey: pod_destination_pubkey,
|
||||
withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext,
|
||||
destination_ciphertext: pod_destination_ciphertext,
|
||||
};
|
||||
|
||||
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
|
||||
&pod_withdraw_withheld_authority_pubkey,
|
||||
&pod_destination_pubkey,
|
||||
&pod_withdraw_withheld_authority_ciphertext,
|
||||
&pod_destination_ciphertext,
|
||||
);
|
||||
|
||||
let proof = WithdrawWithheldTokensProof::new(
|
||||
withdraw_withheld_authority_keypair,
|
||||
destination_pubkey,
|
||||
withdraw_withheld_authority_ciphertext,
|
||||
amount,
|
||||
&destination_opening,
|
||||
&mut transcript,
|
||||
);
|
||||
|
||||
Ok(Self { context, proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZkProofData<WithdrawWithheldTokensProofContext> for WithdrawWithheldTokensData {
|
||||
const PROOF_TYPE: ProofType = ProofType::WithdrawWithheldTokens;
|
||||
|
||||
fn context_data(&self) -> &WithdrawWithheldTokensProofContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
|
||||
&self.context.withdraw_withheld_authority_pubkey,
|
||||
&self.context.destination_pubkey,
|
||||
&self.context.withdraw_withheld_authority_ciphertext,
|
||||
&self.context.destination_ciphertext,
|
||||
);
|
||||
|
||||
let withdraw_withheld_authority_pubkey =
|
||||
self.context.withdraw_withheld_authority_pubkey.try_into()?;
|
||||
let destination_pubkey = self.context.destination_pubkey.try_into()?;
|
||||
let withdraw_withheld_authority_ciphertext = self
|
||||
.context
|
||||
.withdraw_withheld_authority_ciphertext
|
||||
.try_into()?;
|
||||
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
|
||||
|
||||
self.proof.verify(
|
||||
&withdraw_withheld_authority_pubkey,
|
||||
&destination_pubkey,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut transcript,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct represents the cryptographic proof component that certifies the account's solvency
|
||||
/// for withdrawal
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct WithdrawWithheldTokensProof {
|
||||
pub proof: pod::CtxtCtxtEqualityProof,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl WithdrawWithheldTokensProof {
|
||||
fn transcript_new(
|
||||
withdraw_withheld_authority_pubkey: &pod::ElGamalPubkey,
|
||||
destination_pubkey: &pod::ElGamalPubkey,
|
||||
withdraw_withheld_authority_ciphertext: &pod::ElGamalCiphertext,
|
||||
destination_ciphertext: &pod::ElGamalCiphertext,
|
||||
) -> Transcript {
|
||||
let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof");
|
||||
|
||||
transcript.append_pubkey(
|
||||
b"withdraw-withheld-authority-pubkey",
|
||||
withdraw_withheld_authority_pubkey,
|
||||
);
|
||||
transcript.append_pubkey(b"dest-pubkey", destination_pubkey);
|
||||
|
||||
transcript.append_ciphertext(
|
||||
b"ciphertext-withdraw-withheld-authority",
|
||||
withdraw_withheld_authority_ciphertext,
|
||||
);
|
||||
transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);
|
||||
|
||||
transcript
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
withdraw_withheld_authority_keypair: &ElGamalKeypair,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
|
||||
amount: u64,
|
||||
destination_opening: &PedersenOpening,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
let equality_proof = CtxtCtxtEqualityProof::new(
|
||||
withdraw_withheld_authority_keypair,
|
||||
destination_pubkey,
|
||||
withdraw_withheld_authority_ciphertext,
|
||||
amount,
|
||||
destination_opening,
|
||||
transcript,
|
||||
);
|
||||
|
||||
Self {
|
||||
proof: equality_proof.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
destination_ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
let proof: CtxtCtxtEqualityProof = self.proof.try_into()?;
|
||||
proof.verify(
|
||||
source_pubkey,
|
||||
destination_pubkey,
|
||||
source_ciphertext,
|
||||
destination_ciphertext,
|
||||
transcript,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_withdraw_withheld() {
|
||||
let withdraw_withheld_authority_keypair = ElGamalKeypair::new_rand();
|
||||
let dest_keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
let amount: u64 = 0;
|
||||
let withdraw_withheld_authority_ciphertext =
|
||||
withdraw_withheld_authority_keypair.public.encrypt(amount);
|
||||
|
||||
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
|
||||
&withdraw_withheld_authority_keypair,
|
||||
&dest_keypair.public,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
|
||||
|
||||
let amount: u64 = 55;
|
||||
let withdraw_withheld_authority_ciphertext =
|
||||
withdraw_withheld_authority_keypair.public.encrypt(amount);
|
||||
|
||||
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
|
||||
&withdraw_withheld_authority_keypair,
|
||||
&dest_keypair.public,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
|
||||
|
||||
let amount = u64::max_value();
|
||||
let withdraw_withheld_authority_ciphertext =
|
||||
withdraw_withheld_authority_keypair.public.encrypt(amount);
|
||||
|
||||
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
|
||||
&withdraw_withheld_authority_keypair,
|
||||
&dest_keypair.public,
|
||||
&withdraw_withheld_authority_ciphertext,
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
//! The zero-balance proof instruction.
|
||||
//!
|
||||
//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof
|
||||
//! certifies that a given ciphertext encrypts the message 0 in the field (`Scalar::zero()`). To
|
||||
//! generate the proof, a prover must provide the decryption key for the ciphertext.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair},
|
||||
errors::ProofError,
|
||||
sigma_proofs::zero_balance_proof::ZeroBalanceProof,
|
||||
transcript::TranscriptProtocol,
|
||||
},
|
||||
merlin::Transcript,
|
||||
std::convert::TryInto,
|
||||
};
|
||||
use {
|
||||
crate::{
|
||||
instruction::{ProofType, ZkProofData},
|
||||
zk_token_elgamal::pod,
|
||||
},
|
||||
bytemuck::{Pod, Zeroable},
|
||||
};
|
||||
|
||||
/// The instruction data that is needed for the `ProofInstruction::ZeroBalance` instruction.
|
||||
///
|
||||
/// It includes the cryptographic proof as well as the context data information needed to verify
|
||||
/// the proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct ZeroBalanceProofData {
|
||||
/// The context data for the zero-balance proof
|
||||
pub context: ZeroBalanceProofContext, // 96 bytes
|
||||
|
||||
/// Proof that the source account available balance is zero
|
||||
pub proof: pod::ZeroBalanceProof, // 96 bytes
|
||||
}
|
||||
|
||||
/// The context data needed to verify a zero-balance proof.
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct ZeroBalanceProofContext {
|
||||
/// The source account ElGamal pubkey
|
||||
pub pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||
|
||||
/// The source account available balance in encrypted form
|
||||
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl ZeroBalanceProofData {
|
||||
pub fn new(
|
||||
keypair: &ElGamalKeypair,
|
||||
ciphertext: &ElGamalCiphertext,
|
||||
) -> Result<Self, ProofError> {
|
||||
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
|
||||
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
|
||||
|
||||
let context = ZeroBalanceProofContext {
|
||||
pubkey: pod_pubkey,
|
||||
ciphertext: pod_ciphertext,
|
||||
};
|
||||
|
||||
let mut transcript = ZeroBalanceProof::transcript_new(&pod_pubkey, &pod_ciphertext);
|
||||
let proof = ZeroBalanceProof::new(keypair, ciphertext, &mut transcript).into();
|
||||
|
||||
Ok(ZeroBalanceProofData { context, proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZkProofData<ZeroBalanceProofContext> for ZeroBalanceProofData {
|
||||
const PROOF_TYPE: ProofType = ProofType::ZeroBalance;
|
||||
|
||||
fn context_data(&self) -> &ZeroBalanceProofContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript =
|
||||
ZeroBalanceProof::transcript_new(&self.context.pubkey, &self.context.ciphertext);
|
||||
|
||||
let pubkey = self.context.pubkey.try_into()?;
|
||||
let ciphertext = self.context.ciphertext.try_into()?;
|
||||
let proof: ZeroBalanceProof = self.proof.try_into()?;
|
||||
proof
|
||||
.verify(&pubkey, &ciphertext, &mut transcript)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl ZeroBalanceProof {
|
||||
fn transcript_new(
|
||||
pubkey: &pod::ElGamalPubkey,
|
||||
ciphertext: &pod::ElGamalCiphertext,
|
||||
) -> Transcript {
|
||||
let mut transcript = Transcript::new(b"ZeroBalanceProof");
|
||||
|
||||
transcript.append_pubkey(b"pubkey", pubkey);
|
||||
transcript.append_ciphertext(b"ciphertext", ciphertext);
|
||||
|
||||
transcript
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_zero_balance_proof_instruction_correctness() {
|
||||
let keypair = ElGamalKeypair::new_rand();
|
||||
|
||||
// general case: encryption of 0
|
||||
let ciphertext = keypair.public.encrypt(0_u64);
|
||||
let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
|
||||
assert!(zero_balance_proof_data.verify_proof().is_ok());
|
||||
|
||||
// general case: encryption of > 0
|
||||
let ciphertext = keypair.public.encrypt(1_u64);
|
||||
let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
|
||||
assert!(zero_balance_proof_data.verify_proof().is_err());
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
//! The equality sigma proof system.
|
||||
//! The ciphertext-commitment equality sigma proof system.
|
||||
//!
|
||||
//! An equality proof is defined with respect to two cryptographic objects: a twisted ElGamal
|
||||
//! ciphertext and a Pedersen commitment. The proof certifies that a given ciphertext and
|
||||
//! commitment pair encrypts/encodes the same message. To generate the proof, a prover must provide
|
||||
//! the decryption key for the ciphertext and the Pedersen opening for the commitment.
|
||||
//!
|
||||
//! TODO: verify with respect to ciphertext
|
||||
//! A ciphertext-commitment equality proof is defined with respect to a twisted ElGamal ciphertext
|
||||
//! and a Pedersen commitment. The proof certifies that a given ciphertext and a commitment pair
|
||||
//! encrypts/encodes the same message. To generate the proof, a prover must provide the decryption
|
||||
//! key for the first ciphertext and the Pedersen opening for the commitment.
|
||||
//!
|
||||
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
|
@ -39,7 +37,7 @@ use {
|
|||
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct CtxtCommEqualityProof {
|
||||
pub struct CiphertextCommitmentEqualityProof {
|
||||
Y_0: CompressedRistretto,
|
||||
Y_1: CompressedRistretto,
|
||||
Y_2: CompressedRistretto,
|
||||
|
@ -50,7 +48,7 @@ pub struct CtxtCommEqualityProof {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CtxtCommEqualityProof {
|
||||
impl CiphertextCommitmentEqualityProof {
|
||||
/// Equality proof constructor. The proof is with respect to a ciphertext and commitment.
|
||||
///
|
||||
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
|
||||
|
@ -70,8 +68,8 @@ impl CtxtCommEqualityProof {
|
|||
pub fn new(
|
||||
source_keypair: &ElGamalKeypair,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
amount: u64,
|
||||
opening: &PedersenOpening,
|
||||
amount: u64,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
transcript.equality_proof_domain_sep();
|
||||
|
@ -112,7 +110,7 @@ impl CtxtCommEqualityProof {
|
|||
y_x.zeroize();
|
||||
y_r.zeroize();
|
||||
|
||||
CtxtCommEqualityProof {
|
||||
CiphertextCommitmentEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
|
@ -235,7 +233,7 @@ impl CtxtCommEqualityProof {
|
|||
let z_r =
|
||||
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
Ok(CtxtCommEqualityProof {
|
||||
Ok(CiphertextCommitmentEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
|
@ -246,246 +244,6 @@ impl CtxtCommEqualityProof {
|
|||
}
|
||||
}
|
||||
|
||||
/// Equality proof.
|
||||
///
|
||||
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct CtxtCtxtEqualityProof {
|
||||
Y_0: CompressedRistretto,
|
||||
Y_1: CompressedRistretto,
|
||||
Y_2: CompressedRistretto,
|
||||
Y_3: CompressedRistretto,
|
||||
z_s: Scalar,
|
||||
z_x: Scalar,
|
||||
z_r: Scalar,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CtxtCtxtEqualityProof {
|
||||
/// Equality proof constructor. The proof is with respect to two ciphertexts.
|
||||
///
|
||||
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
|
||||
/// For security, the caller (the main protocol) should hash these public components prior to
|
||||
/// invoking this constructor.
|
||||
///
|
||||
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
|
||||
///
|
||||
/// Note that the proof constructor does not take the actual Pedersen commitment as input; it
|
||||
/// takes the associated Pedersen opening instead.
|
||||
///
|
||||
/// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved
|
||||
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext
|
||||
/// * `source_ciphertext` - The first ElGamal ciphertext
|
||||
/// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment
|
||||
/// * `destination_opening` - The opening associated with the second ElGamal ciphertext
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn new(
|
||||
source_keypair: &ElGamalKeypair,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
amount: u64,
|
||||
destination_opening: &PedersenOpening,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
transcript.equality_proof_domain_sep();
|
||||
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let P_source = source_keypair.public.get_point();
|
||||
let D_source = source_ciphertext.handle.get_point();
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
|
||||
let s = source_keypair.secret.get_scalar();
|
||||
let x = Scalar::from(amount);
|
||||
let r = destination_opening.get_scalar();
|
||||
|
||||
// generate random masking factors that also serves as nonces
|
||||
let mut y_s = Scalar::random(&mut OsRng);
|
||||
let mut y_x = Scalar::random(&mut OsRng);
|
||||
let mut y_r = Scalar::random(&mut OsRng);
|
||||
|
||||
let Y_0 = (&y_s * P_source).compress();
|
||||
let Y_1 =
|
||||
RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress();
|
||||
let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress();
|
||||
let Y_3 = (&y_r * P_destination).compress();
|
||||
|
||||
// record masking factors in the transcript
|
||||
transcript.append_point(b"Y_0", &Y_0);
|
||||
transcript.append_point(b"Y_1", &Y_1);
|
||||
transcript.append_point(b"Y_2", &Y_2);
|
||||
transcript.append_point(b"Y_3", &Y_3);
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
transcript.challenge_scalar(b"w");
|
||||
|
||||
// compute the masked values
|
||||
let z_s = &(&c * s) + &y_s;
|
||||
let z_x = &(&c * &x) + &y_x;
|
||||
let z_r = &(&c * r) + &y_r;
|
||||
|
||||
// zeroize random scalars
|
||||
y_s.zeroize();
|
||||
y_x.zeroize();
|
||||
y_r.zeroize();
|
||||
|
||||
CtxtCtxtEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
Y_3,
|
||||
z_s,
|
||||
z_x,
|
||||
z_r,
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality proof verifier. The proof is with respect to two ciphertexts.
|
||||
///
|
||||
/// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved
|
||||
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved
|
||||
/// * `source_ciphertext` - The first ElGamal ciphertext to be proved
|
||||
/// * `destination_ciphertext` - The second ElGamal ciphertext to be proved
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn verify(
|
||||
self,
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
destination_ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), EqualityProofError> {
|
||||
transcript.equality_proof_domain_sep();
|
||||
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let P_source = source_pubkey.get_point();
|
||||
let C_source = source_ciphertext.commitment.get_point();
|
||||
let D_source = source_ciphertext.handle.get_point();
|
||||
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
let C_destination = destination_ciphertext.commitment.get_point();
|
||||
let D_destination = destination_ciphertext.handle.get_point();
|
||||
|
||||
// include Y_0, Y_1, Y_2 to transcript and extract challenges
|
||||
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
|
||||
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
|
||||
transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
|
||||
transcript.validate_and_append_point(b"Y_3", &self.Y_3)?;
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
let w = transcript.challenge_scalar(b"w"); // w used for batch verification
|
||||
let ww = &w * &w;
|
||||
let www = &w * &ww;
|
||||
|
||||
let w_negated = -&w;
|
||||
let ww_negated = -&ww;
|
||||
let www_negated = -&www;
|
||||
|
||||
// check that the required algebraic condition holds
|
||||
let Y_0 = self
|
||||
.Y_0
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_1 = self
|
||||
.Y_1
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_2 = self
|
||||
.Y_2
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_3 = self
|
||||
.Y_3
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
let check = RistrettoPoint::vartime_multiscalar_mul(
|
||||
vec![
|
||||
&self.z_s, // z_s
|
||||
&(-&c), // -c
|
||||
&(-&Scalar::one()), // -identity
|
||||
&(&w * &self.z_x), // w * z_x
|
||||
&(&w * &self.z_s), // w * z_s
|
||||
&(&w_negated * &c), // -w * c
|
||||
&w_negated, // -w
|
||||
&(&ww * &self.z_x), // ww * z_x
|
||||
&(&ww * &self.z_r), // ww * z_r
|
||||
&(&ww_negated * &c), // -ww * c
|
||||
&ww_negated, // -ww
|
||||
&(&www * &self.z_r), // z_r
|
||||
&(&www_negated * &c), // -www * c
|
||||
&www_negated,
|
||||
],
|
||||
vec![
|
||||
P_source, // P_source
|
||||
&(*H), // H
|
||||
&Y_0, // Y_0
|
||||
&(*G), // G
|
||||
D_source, // D_source
|
||||
C_source, // C_source
|
||||
&Y_1, // Y_1
|
||||
&(*G), // G
|
||||
&(*H), // H
|
||||
C_destination, // C_destination
|
||||
&Y_2, // Y_2
|
||||
P_destination, // P_destination
|
||||
D_destination, // D_destination
|
||||
&Y_3, // Y_3
|
||||
],
|
||||
);
|
||||
|
||||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofVerificationError::AlgebraicRelation.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 224] {
|
||||
let mut buf = [0_u8; 224];
|
||||
buf[..32].copy_from_slice(self.Y_0.as_bytes());
|
||||
buf[32..64].copy_from_slice(self.Y_1.as_bytes());
|
||||
buf[64..96].copy_from_slice(self.Y_2.as_bytes());
|
||||
buf[96..128].copy_from_slice(self.Y_3.as_bytes());
|
||||
buf[128..160].copy_from_slice(self.z_s.as_bytes());
|
||||
buf[160..192].copy_from_slice(self.z_x.as_bytes());
|
||||
buf[192..224].copy_from_slice(self.z_r.as_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
|
||||
if bytes.len() != 224 {
|
||||
return Err(ProofVerificationError::Deserialization.into());
|
||||
}
|
||||
|
||||
let bytes = array_ref![bytes, 0, 224];
|
||||
let (Y_0, Y_1, Y_2, Y_3, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32, 32];
|
||||
|
||||
let Y_0 = CompressedRistretto::from_slice(Y_0);
|
||||
let Y_1 = CompressedRistretto::from_slice(Y_1);
|
||||
let Y_2 = CompressedRistretto::from_slice(Y_2);
|
||||
let Y_3 = CompressedRistretto::from_slice(Y_3);
|
||||
|
||||
let z_s =
|
||||
Scalar::from_canonical_bytes(*z_s).ok_or(ProofVerificationError::Deserialization)?;
|
||||
let z_x =
|
||||
Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?;
|
||||
let z_r =
|
||||
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
Ok(CtxtCtxtEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
Y_3,
|
||||
z_s,
|
||||
z_x,
|
||||
z_r,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {
|
||||
|
@ -505,11 +263,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&source_keypair,
|
||||
&source_ciphertext,
|
||||
message,
|
||||
&destination_opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -533,11 +291,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&source_keypair,
|
||||
&source_ciphertext,
|
||||
message,
|
||||
&destination_opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -566,11 +324,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&elgamal_keypair,
|
||||
&ciphertext,
|
||||
message,
|
||||
&opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -595,11 +353,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&elgamal_keypair,
|
||||
&ciphertext,
|
||||
message,
|
||||
&opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -624,11 +382,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&elgamal_keypair,
|
||||
&ciphertext,
|
||||
message,
|
||||
&opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -652,11 +410,11 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCommEqualityProof::new(
|
||||
let proof = CiphertextCommitmentEqualityProof::new(
|
||||
&elgamal_keypair,
|
||||
&ciphertext,
|
||||
message,
|
||||
&opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
|
@ -669,74 +427,4 @@ mod test {
|
|||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ciphertext_ciphertext_equality_proof_correctness() {
|
||||
// success case
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let message: u64 = 55;
|
||||
|
||||
let source_ciphertext = source_keypair.public.encrypt(message);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(message, &destination_opening);
|
||||
|
||||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCtxtEqualityProof::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
message,
|
||||
&destination_opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&source_keypair.public,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut verifier_transcript
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// fail case: encrypted and committed messages are different
|
||||
let source_message: u64 = 55;
|
||||
let destination_message: u64 = 77;
|
||||
|
||||
let source_ciphertext = source_keypair.public.encrypt(source_message);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(destination_message, &destination_opening);
|
||||
|
||||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CtxtCtxtEqualityProof::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
message,
|
||||
&destination_opening,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&source_keypair.public,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut verifier_transcript
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
//! The ciphertext-ciphertext equality sigma proof system.
|
||||
//!
|
||||
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::{
|
||||
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
||||
pedersen::{PedersenOpening, G, H},
|
||||
},
|
||||
errors::ProofVerificationError,
|
||||
},
|
||||
curve25519_dalek::traits::MultiscalarMul,
|
||||
rand::rngs::OsRng,
|
||||
zeroize::Zeroize,
|
||||
};
|
||||
use {
|
||||
crate::{sigma_proofs::errors::EqualityProofError, transcript::TranscriptProtocol},
|
||||
arrayref::{array_ref, array_refs},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
traits::{IsIdentity, VartimeMultiscalarMul},
|
||||
},
|
||||
merlin::Transcript,
|
||||
};
|
||||
|
||||
/// The ciphertext-ciphertext equality proof.
|
||||
///
|
||||
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct CiphertextCiphertextEqualityProof {
|
||||
Y_0: CompressedRistretto,
|
||||
Y_1: CompressedRistretto,
|
||||
Y_2: CompressedRistretto,
|
||||
Y_3: CompressedRistretto,
|
||||
z_s: Scalar,
|
||||
z_x: Scalar,
|
||||
z_r: Scalar,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl CiphertextCiphertextEqualityProof {
|
||||
/// The ciphertext-ciphertext equality proof constructor.
|
||||
///
|
||||
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
|
||||
/// For security, the caller (the main protocol) should hash these public components prior to
|
||||
/// invoking this constructor.
|
||||
///
|
||||
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
|
||||
///
|
||||
/// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved
|
||||
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext
|
||||
/// * `source_ciphertext` - The first ElGamal ciphertext for which the prover knows a
|
||||
/// decryption key for
|
||||
/// * `destination_opening` - The opening (randomness) associated with the second ElGamal ciphertext
|
||||
/// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn new(
|
||||
source_keypair: &ElGamalKeypair,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
destination_opening: &PedersenOpening,
|
||||
amount: u64,
|
||||
transcript: &mut Transcript,
|
||||
) -> Self {
|
||||
transcript.equality_proof_domain_sep();
|
||||
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let P_source = source_keypair.public.get_point();
|
||||
let D_source = source_ciphertext.handle.get_point();
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
|
||||
let s = source_keypair.secret.get_scalar();
|
||||
let x = Scalar::from(amount);
|
||||
let r = destination_opening.get_scalar();
|
||||
|
||||
// generate random masking factors that also serves as nonces
|
||||
let mut y_s = Scalar::random(&mut OsRng);
|
||||
let mut y_x = Scalar::random(&mut OsRng);
|
||||
let mut y_r = Scalar::random(&mut OsRng);
|
||||
|
||||
let Y_0 = (&y_s * P_source).compress();
|
||||
let Y_1 =
|
||||
RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress();
|
||||
let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress();
|
||||
let Y_3 = (&y_r * P_destination).compress();
|
||||
|
||||
// record masking factors in the transcript
|
||||
transcript.append_point(b"Y_0", &Y_0);
|
||||
transcript.append_point(b"Y_1", &Y_1);
|
||||
transcript.append_point(b"Y_2", &Y_2);
|
||||
transcript.append_point(b"Y_3", &Y_3);
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
transcript.challenge_scalar(b"w");
|
||||
|
||||
// compute the masked values
|
||||
let z_s = &(&c * s) + &y_s;
|
||||
let z_x = &(&c * &x) + &y_x;
|
||||
let z_r = &(&c * r) + &y_r;
|
||||
|
||||
// zeroize random scalars
|
||||
y_s.zeroize();
|
||||
y_x.zeroize();
|
||||
y_r.zeroize();
|
||||
|
||||
CiphertextCiphertextEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
Y_3,
|
||||
z_s,
|
||||
z_x,
|
||||
z_r,
|
||||
}
|
||||
}
|
||||
|
||||
/// The ciphertext-ciphertext equality proof verifier. The proof is with respect to two
|
||||
/// ciphertexts.
|
||||
///
|
||||
/// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved
|
||||
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved
|
||||
/// * `source_ciphertext` - The first ElGamal ciphertext to be proved
|
||||
/// * `destination_ciphertext` - The second ElGamal ciphertext to be proved
|
||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||
pub fn verify(
|
||||
self,
|
||||
source_pubkey: &ElGamalPubkey,
|
||||
destination_pubkey: &ElGamalPubkey,
|
||||
source_ciphertext: &ElGamalCiphertext,
|
||||
destination_ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), EqualityProofError> {
|
||||
transcript.equality_proof_domain_sep();
|
||||
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let P_source = source_pubkey.get_point();
|
||||
let C_source = source_ciphertext.commitment.get_point();
|
||||
let D_source = source_ciphertext.handle.get_point();
|
||||
|
||||
let P_destination = destination_pubkey.get_point();
|
||||
let C_destination = destination_ciphertext.commitment.get_point();
|
||||
let D_destination = destination_ciphertext.handle.get_point();
|
||||
|
||||
// include Y_0, Y_1, Y_2 to transcript and extract challenges
|
||||
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
|
||||
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
|
||||
transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
|
||||
transcript.validate_and_append_point(b"Y_3", &self.Y_3)?;
|
||||
|
||||
let c = transcript.challenge_scalar(b"c");
|
||||
let w = transcript.challenge_scalar(b"w"); // w used for batch verification
|
||||
let ww = &w * &w;
|
||||
let www = &w * &ww;
|
||||
|
||||
let w_negated = -&w;
|
||||
let ww_negated = -&ww;
|
||||
let www_negated = -&www;
|
||||
|
||||
// check that the required algebraic condition holds
|
||||
let Y_0 = self
|
||||
.Y_0
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_1 = self
|
||||
.Y_1
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_2 = self
|
||||
.Y_2
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
let Y_3 = self
|
||||
.Y_3
|
||||
.decompress()
|
||||
.ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
let check = RistrettoPoint::vartime_multiscalar_mul(
|
||||
vec![
|
||||
&self.z_s, // z_s
|
||||
&(-&c), // -c
|
||||
&(-&Scalar::one()), // -identity
|
||||
&(&w * &self.z_x), // w * z_x
|
||||
&(&w * &self.z_s), // w * z_s
|
||||
&(&w_negated * &c), // -w * c
|
||||
&w_negated, // -w
|
||||
&(&ww * &self.z_x), // ww * z_x
|
||||
&(&ww * &self.z_r), // ww * z_r
|
||||
&(&ww_negated * &c), // -ww * c
|
||||
&ww_negated, // -ww
|
||||
&(&www * &self.z_r), // z_r
|
||||
&(&www_negated * &c), // -www * c
|
||||
&www_negated,
|
||||
],
|
||||
vec![
|
||||
P_source, // P_source
|
||||
&(*H), // H
|
||||
&Y_0, // Y_0
|
||||
&(*G), // G
|
||||
D_source, // D_source
|
||||
C_source, // C_source
|
||||
&Y_1, // Y_1
|
||||
&(*G), // G
|
||||
&(*H), // H
|
||||
C_destination, // C_destination
|
||||
&Y_2, // Y_2
|
||||
P_destination, // P_destination
|
||||
D_destination, // D_destination
|
||||
&Y_3, // Y_3
|
||||
],
|
||||
);
|
||||
|
||||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofVerificationError::AlgebraicRelation.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 224] {
|
||||
let mut buf = [0_u8; 224];
|
||||
buf[..32].copy_from_slice(self.Y_0.as_bytes());
|
||||
buf[32..64].copy_from_slice(self.Y_1.as_bytes());
|
||||
buf[64..96].copy_from_slice(self.Y_2.as_bytes());
|
||||
buf[96..128].copy_from_slice(self.Y_3.as_bytes());
|
||||
buf[128..160].copy_from_slice(self.z_s.as_bytes());
|
||||
buf[160..192].copy_from_slice(self.z_x.as_bytes());
|
||||
buf[192..224].copy_from_slice(self.z_r.as_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
|
||||
if bytes.len() != 224 {
|
||||
return Err(ProofVerificationError::Deserialization.into());
|
||||
}
|
||||
|
||||
let bytes = array_ref![bytes, 0, 224];
|
||||
let (Y_0, Y_1, Y_2, Y_3, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32, 32];
|
||||
|
||||
let Y_0 = CompressedRistretto::from_slice(Y_0);
|
||||
let Y_1 = CompressedRistretto::from_slice(Y_1);
|
||||
let Y_2 = CompressedRistretto::from_slice(Y_2);
|
||||
let Y_3 = CompressedRistretto::from_slice(Y_3);
|
||||
|
||||
let z_s =
|
||||
Scalar::from_canonical_bytes(*z_s).ok_or(ProofVerificationError::Deserialization)?;
|
||||
let z_x =
|
||||
Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?;
|
||||
let z_r =
|
||||
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
Ok(CiphertextCiphertextEqualityProof {
|
||||
Y_0,
|
||||
Y_1,
|
||||
Y_2,
|
||||
Y_3,
|
||||
z_s,
|
||||
z_x,
|
||||
z_r,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ciphertext_ciphertext_equality_proof_correctness() {
|
||||
// success case
|
||||
let source_keypair = ElGamalKeypair::new_rand();
|
||||
let destination_keypair = ElGamalKeypair::new_rand();
|
||||
let message: u64 = 55;
|
||||
|
||||
let source_ciphertext = source_keypair.public.encrypt(message);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(message, &destination_opening);
|
||||
|
||||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CiphertextCiphertextEqualityProof::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&source_keypair.public,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut verifier_transcript
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// fail case: encrypted and committed messages are different
|
||||
let source_message: u64 = 55;
|
||||
let destination_message: u64 = 77;
|
||||
|
||||
let source_ciphertext = source_keypair.public.encrypt(source_message);
|
||||
|
||||
let destination_opening = PedersenOpening::new_rand();
|
||||
let destination_ciphertext = destination_keypair
|
||||
.public
|
||||
.encrypt_with(destination_message, &destination_opening);
|
||||
|
||||
let mut prover_transcript = Transcript::new(b"Test");
|
||||
let mut verifier_transcript = Transcript::new(b"Test");
|
||||
|
||||
let proof = CiphertextCiphertextEqualityProof::new(
|
||||
&source_keypair,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_opening,
|
||||
message,
|
||||
&mut prover_transcript,
|
||||
);
|
||||
|
||||
assert!(proof
|
||||
.verify(
|
||||
&source_keypair.public,
|
||||
&destination_keypair.public,
|
||||
&source_ciphertext,
|
||||
&destination_ciphertext,
|
||||
&mut verifier_transcript
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
|
@ -15,7 +15,8 @@
|
|||
//! We refer to the zk-token paper for the formal details and security proofs of these argument
|
||||
//! systems.
|
||||
|
||||
pub mod equality_proof;
|
||||
pub mod ctxt_comm_equality_proof;
|
||||
pub mod ctxt_ctxt_equality_proof;
|
||||
pub mod errors;
|
||||
pub mod fee_proof;
|
||||
pub mod pubkey_proof;
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
//! The public-key (validity) proof system.
|
||||
//!
|
||||
//! A public-key proof is defined with respect to an ElGamal public key. The proof certifies that a
|
||||
//! given public key is a valid ElGamal public key (i.e. the prover knows a corresponding secret
|
||||
//! key). To generate the proof, a prover must prove the secret key for the public key.
|
||||
//!
|
||||
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
|
||||
|
@ -35,14 +31,14 @@ use {
|
|||
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct PubkeySigmaProof {
|
||||
pub struct PubkeyValidityProof {
|
||||
Y: CompressedRistretto,
|
||||
z: Scalar,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl PubkeySigmaProof {
|
||||
impl PubkeyValidityProof {
|
||||
/// Public-key proof constructor.
|
||||
///
|
||||
/// The function does *not* hash the public key and ciphertext into the transcript. For
|
||||
|
@ -136,7 +132,7 @@ impl PubkeySigmaProof {
|
|||
let Y = CompressedRistretto::from_slice(Y);
|
||||
let z = Scalar::from_canonical_bytes(*z).ok_or(ProofVerificationError::Deserialization)?;
|
||||
|
||||
Ok(PubkeySigmaProof { Y, z })
|
||||
Ok(PubkeyValidityProof { Y, z })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +151,7 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"test");
|
||||
let mut verifier_transcript = Transcript::new(b"test");
|
||||
|
||||
let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
|
||||
let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript);
|
||||
assert!(proof
|
||||
.verify(&keypair.public, &mut verifier_transcript)
|
||||
.is_ok());
|
||||
|
@ -166,7 +162,7 @@ mod test {
|
|||
let mut prover_transcript = Transcript::new(b"test");
|
||||
let mut verifier_transcript = Transcript::new(b"test");
|
||||
|
||||
let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
|
||||
let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript);
|
||||
assert!(proof
|
||||
.verify(&keypair.public, &mut verifier_transcript)
|
||||
.is_ok());
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
//! The zero-balance sigma proof system.
|
||||
//!
|
||||
//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof
|
||||
//! certifies that a given ciphertext encrypts the message 0 (`Scalar::zero()`). To generate the
|
||||
//! proof, a prover must provide the decryption key for the ciphertext.
|
||||
//!
|
||||
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
|
||||
//! zero-knowledge in the random oracle model.
|
||||
|
||||
|
|
|
@ -64,10 +64,11 @@ mod target_arch {
|
|||
},
|
||||
range_proof::{errors::RangeProofError, RangeProof},
|
||||
sigma_proofs::{
|
||||
equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof},
|
||||
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
|
||||
ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof,
|
||||
errors::*,
|
||||
fee_proof::FeeSigmaProof,
|
||||
pubkey_proof::PubkeySigmaProof,
|
||||
pubkey_proof::PubkeyValidityProof,
|
||||
validity_proof::{AggregatedValidityProof, ValidityProof},
|
||||
zero_balance_proof::ZeroBalanceProof,
|
||||
},
|
||||
|
@ -191,30 +192,30 @@ mod target_arch {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<CtxtCommEqualityProof> for pod::CtxtCommEqualityProof {
|
||||
fn from(proof: CtxtCommEqualityProof) -> Self {
|
||||
impl From<CiphertextCommitmentEqualityProof> for pod::CiphertextCommitmentEqualityProof {
|
||||
fn from(proof: CiphertextCommitmentEqualityProof) -> Self {
|
||||
Self(proof.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<pod::CtxtCommEqualityProof> for CtxtCommEqualityProof {
|
||||
impl TryFrom<pod::CiphertextCommitmentEqualityProof> for CiphertextCommitmentEqualityProof {
|
||||
type Error = EqualityProofError;
|
||||
|
||||
fn try_from(pod: pod::CtxtCommEqualityProof) -> Result<Self, Self::Error> {
|
||||
fn try_from(pod: pod::CiphertextCommitmentEqualityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CtxtCtxtEqualityProof> for pod::CtxtCtxtEqualityProof {
|
||||
fn from(proof: CtxtCtxtEqualityProof) -> Self {
|
||||
impl From<CiphertextCiphertextEqualityProof> for pod::CiphertextCiphertextEqualityProof {
|
||||
fn from(proof: CiphertextCiphertextEqualityProof) -> Self {
|
||||
Self(proof.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<pod::CtxtCtxtEqualityProof> for CtxtCtxtEqualityProof {
|
||||
impl TryFrom<pod::CiphertextCiphertextEqualityProof> for CiphertextCiphertextEqualityProof {
|
||||
type Error = EqualityProofError;
|
||||
|
||||
fn try_from(pod: pod::CtxtCtxtEqualityProof) -> Result<Self, Self::Error> {
|
||||
fn try_from(pod: pod::CiphertextCiphertextEqualityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
}
|
||||
}
|
||||
|
@ -275,16 +276,16 @@ mod target_arch {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<PubkeySigmaProof> for pod::PubkeySigmaProof {
|
||||
fn from(proof: PubkeySigmaProof) -> Self {
|
||||
impl From<PubkeyValidityProof> for pod::PubkeyValidityProof {
|
||||
fn from(proof: PubkeyValidityProof) -> Self {
|
||||
Self(proof.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<pod::PubkeySigmaProof> for PubkeySigmaProof {
|
||||
impl TryFrom<pod::PubkeyValidityProof> for PubkeyValidityProof {
|
||||
type Error = PubkeyValidityProofError;
|
||||
|
||||
fn try_from(pod: pod::PubkeySigmaProof) -> Result<Self, Self::Error> {
|
||||
fn try_from(pod: pod::PubkeyValidityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,25 +113,25 @@ impl fmt::Debug for DecryptHandle {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serialization of `CtxtCommEqualityProof`
|
||||
/// Serialization of `CiphertextCommitmentEqualityProof`
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct CtxtCommEqualityProof(pub [u8; 192]);
|
||||
pub struct CiphertextCommitmentEqualityProof(pub [u8; 192]);
|
||||
|
||||
// `CtxtCommEqualityProof` is a Pod and Zeroable.
|
||||
// `CiphertextCommitmentEqualityProof` is a Pod and Zeroable.
|
||||
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
|
||||
unsafe impl Zeroable for CtxtCommEqualityProof {}
|
||||
unsafe impl Pod for CtxtCommEqualityProof {}
|
||||
unsafe impl Zeroable for CiphertextCommitmentEqualityProof {}
|
||||
unsafe impl Pod for CiphertextCommitmentEqualityProof {}
|
||||
|
||||
/// Serialization of `CtxtCtxtEqualityProof`
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct CtxtCtxtEqualityProof(pub [u8; 224]);
|
||||
pub struct CiphertextCiphertextEqualityProof(pub [u8; 224]);
|
||||
|
||||
// `CtxtCtxtEqualityProof` is a Pod and Zeroable.
|
||||
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
|
||||
unsafe impl Zeroable for CtxtCtxtEqualityProof {}
|
||||
unsafe impl Pod for CtxtCtxtEqualityProof {}
|
||||
unsafe impl Zeroable for CiphertextCiphertextEqualityProof {}
|
||||
unsafe impl Pod for CiphertextCiphertextEqualityProof {}
|
||||
|
||||
/// Serialization of validity proofs
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -171,7 +171,7 @@ pub struct FeeSigmaProof(pub [u8; 256]);
|
|||
/// Serialization of public-key sigma proof
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct PubkeySigmaProof(pub [u8; 64]);
|
||||
pub struct PubkeyValidityProof(pub [u8; 64]);
|
||||
|
||||
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
|
@ -25,7 +25,7 @@ pub enum ProofInstruction {
|
|||
///
|
||||
CloseContextState,
|
||||
|
||||
/// Verify a close account zero-knowledge proof.
|
||||
/// Verify a zero-balance proof.
|
||||
///
|
||||
/// This instruction can be configured to optionally create a proof context state account.
|
||||
///
|
||||
|
@ -39,9 +39,9 @@ pub enum ProofInstruction {
|
|||
/// None
|
||||
///
|
||||
/// Data expected by this instruction:
|
||||
/// `CloseAccountData`
|
||||
/// `ZeroBalanceProofData`
|
||||
///
|
||||
VerifyCloseAccount,
|
||||
VerifyZeroBalance,
|
||||
|
||||
/// Verify a withdraw zero-knowledge proof.
|
||||
///
|
||||
|
@ -61,7 +61,7 @@ pub enum ProofInstruction {
|
|||
///
|
||||
VerifyWithdraw,
|
||||
|
||||
/// Verify a withdraw withheld tokens zero-knowledge proof.
|
||||
/// Verify a ciphertext-ciphertext equality proof.
|
||||
///
|
||||
/// This instruction can be configured to optionally create a proof context state account.
|
||||
///
|
||||
|
@ -75,9 +75,9 @@ pub enum ProofInstruction {
|
|||
/// None
|
||||
///
|
||||
/// Data expected by this instruction:
|
||||
/// `WithdrawWithheldTokensData`
|
||||
/// `CiphertextCiphertextEqualityProofData`
|
||||
///
|
||||
VerifyWithdrawWithheldTokens,
|
||||
VerifyCiphertextCiphertextEquality,
|
||||
|
||||
/// Verify a transfer zero-knowledge proof.
|
||||
///
|
||||
|
@ -161,12 +161,12 @@ pub fn close_context_state(
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a `VerifyCloseAccount` instruction.
|
||||
pub fn verify_close_account(
|
||||
/// Create a `VerifyZeroBalance` instruction.
|
||||
pub fn verify_zero_balance(
|
||||
context_state_info: Option<ContextStateInfo>,
|
||||
proof_data: &CloseAccountData,
|
||||
proof_data: &ZeroBalanceProofData,
|
||||
) -> Instruction {
|
||||
ProofInstruction::VerifyCloseAccount.encode_verify_proof(context_state_info, proof_data)
|
||||
ProofInstruction::VerifyZeroBalance.encode_verify_proof(context_state_info, proof_data)
|
||||
}
|
||||
|
||||
/// Create a `VerifyWithdraw` instruction.
|
||||
|
@ -177,12 +177,12 @@ pub fn verify_withdraw(
|
|||
ProofInstruction::VerifyWithdraw.encode_verify_proof(context_state_info, proof_data)
|
||||
}
|
||||
|
||||
/// Create a `VerifyWithdrawWithheldTokens` instruction.
|
||||
pub fn verify_withdraw_withheld_tokens(
|
||||
/// Create a `VerifyCiphertextCiphertextEquality` instruction.
|
||||
pub fn verify_ciphertext_ciphertext_equality(
|
||||
context_state_info: Option<ContextStateInfo>,
|
||||
proof_data: &WithdrawWithheldTokensData,
|
||||
proof_data: &CiphertextCiphertextEqualityProofData,
|
||||
) -> Instruction {
|
||||
ProofInstruction::VerifyWithdrawWithheldTokens
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality
|
||||
.encode_verify_proof(context_state_info, proof_data)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue