cleaning up transfer proof

This commit is contained in:
Sam Kim 2021-12-09 10:56:33 -05:00 committed by Michael Vines
parent 30e12aef9a
commit ccdbe65c87
3 changed files with 97 additions and 199 deletions

View File

@ -6,7 +6,7 @@ mod withdraw;
use crate::errors::ProofError; use crate::errors::ProofError;
pub use { pub use {
close_account::CloseAccountData, close_account::CloseAccountData,
transfer::{TransferCommitments, TransferData, TransferPubKeys}, transfer::{TransferCommitments, TransferData, TransferPubkeys},
withdraw::WithdrawData, withdraw::WithdrawData,
}; };

View File

@ -7,11 +7,12 @@ use {
crate::{ crate::{
encryption::{ encryption::{
discrete_log::*, discrete_log::*,
elgamal::{ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
pedersen::{ pedersen::{
Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening, Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening,
}, },
}, },
equality_proof::EqualityProof,
errors::ProofError, errors::ProofError,
instruction::{Role, Verifiable}, instruction::{Role, Verifiable},
range_proof::RangeProof, range_proof::RangeProof,
@ -40,7 +41,7 @@ pub struct TransferData {
pub decrypt_handles_hi: TransferDecryptHandles, pub decrypt_handles_hi: TransferDecryptHandles,
/// The public encryption keys associated with the transfer: source, dest, and auditor /// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_public_keys: TransferPubKeys, // 96 bytes pub transfer_public_keys: TransferPubkeys, // 96 bytes
/// The final spendable ciphertext after the transfer /// The final spendable ciphertext after the transfer
pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes
@ -56,8 +57,7 @@ impl TransferData {
transfer_amount: u64, transfer_amount: u64,
spendable_balance: u64, spendable_balance: u64,
spendable_ct: ElGamalCiphertext, spendable_ct: ElGamalCiphertext,
source_pk: ElGamalPubkey, source_keypair: &ElGamalKeypair,
source_sk: &ElGamalSecretKey,
dest_pk: ElGamalPubkey, dest_pk: ElGamalPubkey,
auditor_pk: ElGamalPubkey, auditor_pk: ElGamalPubkey,
) -> Self { ) -> Self {
@ -70,11 +70,11 @@ impl TransferData {
let (comm_lo, open_lo) = Pedersen::new(amount_lo); let (comm_lo, open_lo) = Pedersen::new(amount_lo);
let (comm_hi, open_hi) = Pedersen::new(amount_hi); let (comm_hi, open_hi) = Pedersen::new(amount_hi);
let handle_source_lo = source_pk.decrypt_handle(&open_lo); let handle_source_lo = source_keypair.public.decrypt_handle(&open_lo);
let handle_dest_lo = dest_pk.decrypt_handle(&open_lo); let handle_dest_lo = dest_pk.decrypt_handle(&open_lo);
let handle_auditor_lo = auditor_pk.decrypt_handle(&open_lo); let handle_auditor_lo = auditor_pk.decrypt_handle(&open_lo);
let handle_source_hi = source_pk.decrypt_handle(&open_hi); let handle_source_hi = source_keypair.public.decrypt_handle(&open_hi);
let handle_dest_hi = dest_pk.decrypt_handle(&open_hi); let handle_dest_hi = dest_pk.decrypt_handle(&open_hi);
let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi); let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi);
@ -98,8 +98,8 @@ impl TransferData {
}; };
// grouping of the public keys for the transfer // grouping of the public keys for the transfer
let transfer_public_keys = TransferPubKeys { let transfer_public_keys = TransferPubkeys {
source_pk: source_pk.into(), source_pk: source_keypair.public.into(),
dest_pk: dest_pk.into(), dest_pk: dest_pk.into(),
auditor_pk: auditor_pk.into(), auditor_pk: auditor_pk.into(),
}; };
@ -120,8 +120,7 @@ impl TransferData {
// range_proof and validity_proof should be generated together // range_proof and validity_proof should be generated together
let proof = TransferProof::new( let proof = TransferProof::new(
source_sk, &source_keypair,
&source_pk,
&dest_pk, &dest_pk,
&auditor_pk, &auditor_pk,
(amount_lo as u64, amount_hi as u64), (amount_lo as u64, amount_hi as u64),
@ -207,221 +206,142 @@ impl Verifiable for TransferData {
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct TransferProof { pub struct TransferProof {
// Proof component for the spendable ciphertext components: R /// New Pedersen commitment for the remaining balance in source
pub R: pod::CompressedRistretto, // 32 bytes pub source_commitment: pod::PedersenCommitment,
// Proof component for the spendable ciphertext components: z
pub z: pod::Scalar, // 32 bytes /// Associated equality proof
// Proof component for the transaction amount components: T_src pub equality_proof: pod::EqualityProof,
pub T_joint: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_1 // Associated range proof
pub T_1: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_2
pub T_2: pod::CompressedRistretto, // 32 bytes
// Range proof component
pub range_proof: pod::RangeProof128, pub range_proof: pod::RangeProof128,
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))] #[cfg(not(target_arch = "bpf"))]
impl TransferProof { impl TransferProof {
fn transcript_new() -> Transcript {
Transcript::new(b"TransferProof")
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)] #[allow(clippy::many_single_char_names)]
pub fn new( pub fn new(
source_sk: &ElGamalSecretKey, source_keypair: &ElGamalKeypair,
source_pk: &ElGamalPubkey,
dest_pk: &ElGamalPubkey, dest_pk: &ElGamalPubkey,
auditor_pk: &ElGamalPubkey, auditor_pk: &ElGamalPubkey,
transfer_amt: (u64, u64), transfer_amt: (u64, u64),
lo_open: &PedersenOpening, lo_open: &PedersenOpening,
hi_open: &PedersenOpening, hi_open: &PedersenOpening,
new_spendable_balance: u64, source_new_balance: u64,
new_spendable_ct: &ElGamalCiphertext, source_new_balance_ct: &ElGamalCiphertext,
) -> Self { ) -> Self {
// TODO: should also commit to pubkeys and commitments later let mut transcript = Self::transcript_new();
let mut transcript = merlin::Transcript::new(b"TransferProof");
let H = PedersenBase::default().H; // add a domain separator to record the start of the protocol
let D = new_spendable_ct.decrypt_handle.get_point(); transcript.transfer_proof_domain_sep();
let s = source_sk.get_scalar();
// Generate proof for the new spendable ciphertext // generate a Pedersen commitment for the remaining balance in source
let r_new = Scalar::random(&mut OsRng); let (source_commitment, source_open) = Pedersen::new(source_new_balance);
let y = Scalar::random(&mut OsRng);
let R = RistrettoPoint::multiscalar_mul(vec![y, r_new], vec![D, H]).compress();
transcript.append_point(b"R", &R); // extract the relevant scalar and Ristretto points from the inputs
let c = transcript.challenge_scalar(b"c"); let P_EG = source_keypair.public.get_point();
let C_EG = source_new_balance_ct.message_comm.get_point();
let D_EG = source_new_balance_ct.decrypt_handle.get_point();
let C_Ped = source_commitment.get_point();
let z = s + c * y; // append all current state to the transcript
let new_spendable_open = PedersenOpening(c * r_new); transcript.append_point(b"P_EG", &P_EG.compress());
transcript.append_point(b"C_EG", &C_EG.compress());
transcript.append_point(b"D_EG", &D_EG.compress());
transcript.append_point(b"C_Ped", &C_Ped.compress());
// Generate proof for the transfer amounts // let c = transcript.challenge_scalar(b"c");
let t_1_blinding = PedersenOpening::random(&mut OsRng); // println!("{:?}", c);
let t_2_blinding = PedersenOpening::random(&mut OsRng);
// generate the range proof // generate equality_proof
let range_proof = RangeProof::create_with( let equality_proof = EqualityProof::new(
vec![new_spendable_balance, transfer_amt.0, transfer_amt.1], source_keypair,
vec![64, 32, 32], source_new_balance_ct,
vec![&new_spendable_open, lo_open, hi_open], source_new_balance,
&t_1_blinding, &source_open,
&t_2_blinding,
&mut transcript, &mut transcript,
); );
let u = transcript.challenge_scalar(b"u"); // TODO: Add ct validity proof
let P_joint = RistrettoPoint::multiscalar_mul( // generate the range proof
vec![Scalar::one(), u, u * u], let range_proof = RangeProof::create(
vec![ vec![source_new_balance, transfer_amt.0, transfer_amt.1],
source_pk.get_point(), vec![64, 32, 32],
dest_pk.get_point(), vec![&source_open, lo_open, hi_open],
auditor_pk.get_point(), &mut transcript,
],
); );
let T_joint = (new_spendable_open.get_scalar() * P_joint).compress();
let T_1 = (t_1_blinding.get_scalar() * P_joint).compress();
let T_2 = (t_2_blinding.get_scalar() * P_joint).compress();
transcript.append_point(b"T_1", &T_1);
transcript.append_point(b"T_2", &T_2);
Self { Self {
R: R.into(), source_commitment: source_commitment.into(),
z: z.into(), equality_proof: equality_proof.try_into().expect("equality proof"),
T_joint: T_joint.into(), range_proof: range_proof.try_into().expect("range proof"),
T_1: T_1.into(),
T_2: T_2.into(),
range_proof: range_proof.try_into().expect("invalid range proof"),
} }
} }
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl TransferProof {
pub fn verify( pub fn verify(
self, self,
amount_comms: &TransferCommitments, amount_comms: &TransferCommitments,
decryption_handles_lo: &TransferDecryptHandles, decryption_handles_lo: &TransferDecryptHandles,
decryption_handles_hi: &TransferDecryptHandles, decryption_handles_hi: &TransferDecryptHandles,
new_spendable_ct: &pod::ElGamalCiphertext, new_spendable_ct: &pod::ElGamalCiphertext,
transfer_public_keys: &TransferPubKeys, transfer_public_keys: &TransferPubkeys,
) -> Result<(), ProofError> { ) -> Result<(), ProofError> {
let mut transcript = Transcript::new(b"TransferProof"); let mut transcript = Self::transcript_new();
let commitment: PedersenCommitment = self.source_commitment.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?; let range_proof: RangeProof = self.range_proof.try_into()?;
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?; // add a domain separator to record the start of the protocol
let dest_pk: ElGamalPubkey = transfer_public_keys.dest_pk.try_into()?; transcript.transfer_proof_domain_sep();
let auditor_pk: ElGamalPubkey = transfer_public_keys.auditor_pk.try_into()?;
// derive Pedersen commitment for range proof verification // extract the relevant scalar and Ristretto points from the inputs
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?;
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?; let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
let C = new_spendable_ct.message_comm.get_point(); let P_EG = source_pk.get_point();
let D = new_spendable_ct.decrypt_handle.get_point(); let C_EG = new_spendable_ct.message_comm.get_point();
let D_EG = new_spendable_ct.decrypt_handle.get_point();
let C_Ped = commitment.get_point();
let R = self.R.into(); // append all current state to the transcript
let z: Scalar = self.z.into(); transcript.append_point(b"P_EG", &P_EG.compress());
transcript.append_point(b"C_EG", &C_EG.compress());
transcript.append_point(b"D_EG", &D_EG.compress());
transcript.append_point(b"C_Ped", &C_Ped.compress());
transcript.validate_and_append_point(b"R", &R)?; // verify equality proof
let c = transcript.challenge_scalar(b"c"); //
// TODO: we can also consider verifying equality and range proof in a batch
equality_proof.verify(&source_pk, &new_spendable_ct, &commitment, &mut transcript)?;
let R = R.decompress().ok_or(ProofError::VerificationError)?; // TODO: validity proof
let spendable_comm_verification =
RistrettoPoint::multiscalar_mul(vec![Scalar::one(), -z, c], vec![C, D, R]).compress();
// verify range proof // verify range proof
let range_proof_verification = range_proof.verify_challenges( range_proof.verify(
vec![ vec![
&spendable_comm_verification, &self.source_commitment.into(),
&amount_comms.lo.into(), &amount_comms.lo.into(),
&amount_comms.hi.into(), &amount_comms.hi.into(),
], ],
vec![64_usize, 32_usize, 32_usize], vec![64_usize, 32_usize, 32_usize],
&mut transcript, &mut transcript,
); )?;
if range_proof_verification.is_err() { Ok(())
return Err(ProofError::VerificationError);
}
let (z, x) = range_proof_verification.unwrap();
// check well-formedness of decryption handles
// derive joint public key
let u = transcript.challenge_scalar(b"u");
let P_joint = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
source_pk.get_point(),
dest_pk.get_point(),
auditor_pk.get_point(),
],
);
let t_x_blinding: Scalar = range_proof.t_x_blinding;
let T_1: CompressedRistretto = self.T_1.into();
let T_2: CompressedRistretto = self.T_2.into();
let handle_source_lo: PedersenDecryptHandle = decryption_handles_lo.source.try_into()?;
let handle_dest_lo: PedersenDecryptHandle = decryption_handles_lo.dest.try_into()?;
let handle_auditor_lo: PedersenDecryptHandle = decryption_handles_lo.auditor.try_into()?;
let D_joint: CompressedRistretto = self.T_joint.into();
let D_joint = D_joint.decompress().ok_or(ProofError::VerificationError)?;
let D_joint_lo = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
handle_source_lo.get_point(),
handle_dest_lo.get_point(),
handle_auditor_lo.get_point(),
],
);
let handle_source_hi: PedersenDecryptHandle = decryption_handles_hi.source.try_into()?;
let handle_dest_hi: PedersenDecryptHandle = decryption_handles_hi.dest.try_into()?;
let handle_auditor_hi: PedersenDecryptHandle = decryption_handles_hi.auditor.try_into()?;
let D_joint_hi = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
handle_source_hi.get_point(),
handle_dest_hi.get_point(),
handle_auditor_hi.get_point(),
],
);
// TODO: combine Pedersen commitment verification above to here for efficiency
// TODO: might need to add an additional proof-of-knowledge here (additional 64 byte)
let mega_check = RistrettoPoint::optional_multiscalar_mul(
vec![-t_x_blinding, x, x * x, z * z, z * z * z, z * z * z * z],
vec![
Some(P_joint),
T_1.decompress(),
T_2.decompress(),
Some(D_joint),
Some(D_joint_lo),
Some(D_joint_hi),
],
)
.ok_or(ProofError::VerificationError)?;
if mega_check.is_identity() {
Ok(())
} else {
Err(ProofError::VerificationError)
}
} }
} }
/// The ElGamal public keys needed for a transfer /// The ElGamal public keys needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct TransferPubKeys { pub struct TransferPubkeys {
pub source_pk: pod::ElGamalPubkey, // 32 bytes pub source_pk: pod::ElGamalPubkey, // 32 bytes
pub dest_pk: pod::ElGamalPubkey, // 32 bytes pub dest_pk: pod::ElGamalPubkey, // 32 bytes
pub auditor_pk: pod::ElGamalPubkey, // 32 bytes pub auditor_pk: pod::ElGamalPubkey, // 32 bytes
@ -485,16 +405,13 @@ mod test {
#[test] #[test]
fn test_transfer_correctness() { fn test_transfer_correctness() {
// ElGamalKeypair keys for source, destination, and auditor accounts // ElGamalKeypair keys for source, destination, and auditor accounts
let ElGamalKeypair { let source_keypair = ElGamalKeypair::default();
public: source_pk,
secret: source_sk,
} = ElGamalKeypair::default();
let dest_pk = ElGamalKeypair::default().public; let dest_pk = ElGamalKeypair::default().public;
let auditor_pk = ElGamalKeypair::default().public; let auditor_pk = ElGamalKeypair::default().public;
// create source account spendable ciphertext // create source account spendable ciphertext
let spendable_balance: u64 = 77; let spendable_balance: u64 = 77;
let spendable_ct = source_pk.encrypt(spendable_balance); let spendable_ct = source_keypair.public.encrypt(spendable_balance);
// transfer amount // transfer amount
let transfer_amount: u64 = 55; let transfer_amount: u64 = 55;
@ -504,8 +421,7 @@ mod test {
transfer_amount, transfer_amount,
spendable_balance, spendable_balance,
spendable_ct, spendable_ct,
source_pk, &source_keypair,
&source_sk,
dest_pk, dest_pk,
auditor_pk, auditor_pk,
); );
@ -516,10 +432,7 @@ mod test {
#[test] #[test]
fn test_source_dest_ciphertext() { fn test_source_dest_ciphertext() {
// ElGamalKeypair keys for source, destination, and auditor accounts // ElGamalKeypair keys for source, destination, and auditor accounts
let ElGamalKeypair { let source_keypair = ElGamalKeypair::default();
public: source_pk,
secret: source_sk,
} = ElGamalKeypair::default();
let ElGamalKeypair { let ElGamalKeypair {
public: dest_pk, public: dest_pk,
@ -533,7 +446,7 @@ mod test {
// create source account spendable ciphertext // create source account spendable ciphertext
let spendable_balance: u64 = 77; let spendable_balance: u64 = 77;
let spendable_ct = source_pk.encrypt(spendable_balance); let spendable_ct = source_keypair.public.encrypt(spendable_balance);
// transfer amount // transfer amount
let transfer_amount: u64 = 55; let transfer_amount: u64 = 55;
@ -543,15 +456,14 @@ mod test {
transfer_amount, transfer_amount,
spendable_balance, spendable_balance,
spendable_ct, spendable_ct,
source_pk, &source_keypair,
&source_sk,
dest_pk, dest_pk,
auditor_pk, auditor_pk,
); );
assert_eq!( assert_eq!(
transfer_data transfer_data
.decrypt_amount(Role::Source, &source_sk) .decrypt_amount(Role::Source, &source_keypair.secret)
.unwrap(), .unwrap(),
55_u64, 55_u64,
); );

View File

@ -6,11 +6,11 @@ use {
pub trait TranscriptProtocol { pub trait TranscriptProtocol {
/// Append a domain separator for an `n`-bit rangeproof for ElGamalKeypair /// Append a domain separator for an `n`-bit rangeproof for ElGamalKeypair
/// ciphertext using a decryption key /// ciphertext using a decryption key // TODO: remove?
fn rangeproof_from_key_domain_sep(&mut self, n: u64); fn rangeproof_from_key_domain_sep(&mut self, n: u64);
/// Append a domain separator for an `n`-bit rangeproof for ElGamalKeypair /// Append a domain separator for an `n`-bit rangeproof for ElGamalKeypair
/// ciphertext using an opening /// ciphertext using an opening // TODO: remove?
fn rangeproof_from_opening_domain_sep(&mut self, n: u64); fn rangeproof_from_opening_domain_sep(&mut self, n: u64);
/// Append a domain separator for a length-`n` inner product proof. /// Append a domain separator for a length-`n` inner product proof.
@ -19,17 +19,11 @@ pub trait TranscriptProtocol {
/// Append a domain separator for close account proof. /// Append a domain separator for close account proof.
fn close_account_proof_domain_sep(&mut self); fn close_account_proof_domain_sep(&mut self);
/// Append a domain separator for update account public key proof.
fn update_account_public_key_proof_domain_sep(&mut self);
/// Append a domain separator for withdraw proof. /// Append a domain separator for withdraw proof.
fn withdraw_proof_domain_sep(&mut self); fn withdraw_proof_domain_sep(&mut self);
/// Append a domain separator for transfer with range proof. /// Append a domain separator for transfer proof.
fn transfer_range_proof_sep(&mut self); fn transfer_proof_domain_sep(&mut self);
/// Append a domain separator for transfer with validity proof.
fn transfer_validity_proof_sep(&mut self);
/// Append a `scalar` with the given `label`. /// Append a `scalar` with the given `label`.
fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar); fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar);
@ -69,20 +63,12 @@ impl TranscriptProtocol for Transcript {
self.append_message(b"dom_sep", b"CloseAccountProof"); self.append_message(b"dom_sep", b"CloseAccountProof");
} }
fn update_account_public_key_proof_domain_sep(&mut self) {
self.append_message(b"dom_sep", b"UpdateAccountPublicKeyProof");
}
fn withdraw_proof_domain_sep(&mut self) { fn withdraw_proof_domain_sep(&mut self) {
self.append_message(b"dom_sep", b"WithdrawProof"); self.append_message(b"dom_sep", b"WithdrawProof");
} }
fn transfer_range_proof_sep(&mut self) { fn transfer_proof_domain_sep(&mut self) {
self.append_message(b"dom_sep", b"TransferRangeProof"); self.append_message(b"dom_sep", b"TransferProof");
}
fn transfer_validity_proof_sep(&mut self) {
self.append_message(b"dom_sep", b"TransferValidityProof");
} }
fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar) { fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar) {