This commit is contained in:
samkim-crypto 2021-10-06 10:54:03 -04:00 committed by Michael Vines
parent db69128825
commit 09b8baa4b1
1 changed files with 105 additions and 200 deletions

View File

@ -26,14 +26,26 @@ use {
std::convert::TryInto,
};
/// Just a grouping struct for the data required for the two transfer instructions. It is
/// convenient to generate the two components jointly as they share common components.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferData {
pub range_proof: TransferRangeProofData,
pub validity_proof: TransferValidityProofData,
ephemeral_state: TransferEphemeralState, // 128 bytes
/// The transfer amount encoded as Pedersen commitments
pub amount_comms: TransferCommitments,
/// The decryption handles that allow decryption of the lo-bits of the transfer amount
pub decrypt_handles_lo: TransferDecryptHandles,
/// The decryption handles that allow decryption of the hi-bits of the transfer amount
pub decrypt_handles_hi: TransferDecryptHandles,
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_public_keys: TransferPubKeys, // 96 bytes
/// The final spendable ciphertext after the transfer
pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes
/// Zero-knowledge proofs for Transfer
pub proof: TransferProof,
}
#[cfg(not(target_arch = "bpf"))]
@ -65,19 +77,19 @@ impl TransferData {
let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi);
// message encoding as Pedersen commitments, which will be included in range proof data
let amount_comms = TransferComms {
let amount_comms = TransferCommitments {
lo: comm_lo.into(),
hi: comm_hi.into(),
};
// decryption handles, which will be included in the validity proof data
let decryption_handles_lo = TransferHandles {
let decrypt_handles_lo = TransferDecryptHandles {
source: handle_source_lo.into(),
dest: handle_dest_lo.into(),
auditor: handle_auditor_lo.into(),
};
let decryption_handles_hi = TransferHandles {
let decrypt_handles_hi = TransferDecryptHandles {
source: handle_source_hi.into(),
dest: handle_dest_hi.into(),
auditor: handle_auditor_hi.into(),
@ -105,7 +117,7 @@ impl TransferData {
};
// range_proof and validity_proof should be generated together
let (transfer_proofs, ephemeral_state) = TransferProofs::new(
let proof = TransferProof::new(
source_sk,
&source_pk,
&dest_pk,
@ -117,44 +129,27 @@ impl TransferData {
&new_spendable_ct,
);
// generate data components
let range_proof = TransferRangeProofData {
Self {
amount_comms,
proof: transfer_proofs.range_proof,
};
let validity_proof = TransferValidityProofData {
decryption_handles_lo,
decryption_handles_hi,
transfer_public_keys,
decrypt_handles_lo,
decrypt_handles_hi,
new_spendable_ct: new_spendable_ct.into(),
proof: transfer_proofs.validity_proof,
};
TransferData {
range_proof,
validity_proof,
ephemeral_state,
transfer_public_keys,
proof,
}
}
/// Extracts the lo and hi source ciphertexts associated with a transfer data and returns the
/// *combined* ciphertext
pub fn source_ciphertext(&self) -> Result<ElGamalCiphertext, ProofError> {
let transfer_comms_lo: PedersenCommitment = self.range_proof.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.range_proof.amount_comms.hi.try_into()?;
let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?;
let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi);
let decryption_handle_lo: PedersenDecryptHandle = self
.validity_proof
.decryption_handles_lo
.source
.try_into()?;
let decryption_handle_hi: PedersenDecryptHandle = self
.validity_proof
.decryption_handles_hi
.source
.try_into()?;
let decryption_handle_lo: PedersenDecryptHandle =
self.decrypt_handles_lo.source.try_into()?;
let decryption_handle_hi: PedersenDecryptHandle =
self.decrypt_handles_hi.source.try_into()?;
let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi);
Ok((transfer_comm, decryption_handle).into())
@ -163,14 +158,14 @@ impl TransferData {
/// Extracts the lo and hi destination ciphertexts associated with a transfer data and returns
/// the *combined* ciphertext
pub fn dest_ciphertext(&self) -> Result<ElGamalCiphertext, ProofError> {
let transfer_comms_lo: PedersenCommitment = self.range_proof.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.range_proof.amount_comms.hi.try_into()?;
let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?;
let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi);
let decryption_handle_lo: PedersenDecryptHandle =
self.validity_proof.decryption_handles_lo.dest.try_into()?;
self.decrypt_handles_lo.dest.try_into()?;
let decryption_handle_hi: PedersenDecryptHandle =
self.validity_proof.decryption_handles_hi.dest.try_into()?;
self.decrypt_handles_hi.dest.try_into()?;
let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi);
Ok((transfer_comm, decryption_handle).into())
@ -180,101 +175,37 @@ impl TransferData {
#[cfg(not(target_arch = "bpf"))]
impl Verifiable for TransferData {
fn verify(&self) -> Result<(), ProofError> {
self.range_proof.verify(&self.ephemeral_state)?;
self.validity_proof.verify(&self.ephemeral_state)
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferRangeProofData {
/// The transfer amount encoded as Pedersen commitments
pub amount_comms: TransferComms, // 64 bytes
/// Proof that certifies:
/// 1. the source account has enough funds for the transfer (i.e. the final balance is a
/// 64-bit positive number)
/// 2. the transfer amount is a 64-bit positive number
pub proof: pod::RangeProof128, // 736 bytes
}
#[cfg(not(target_arch = "bpf"))]
impl TransferRangeProofData {
fn verify(&self, ephemeral_state: &TransferEphemeralState) -> Result<(), ProofError> {
let mut transcript = Transcript::new(b"TransferRangeProof");
// standard range proof verification
let proof: RangeProof = self.proof.try_into()?;
proof.verify_with(
vec![
&ephemeral_state.spendable_comm_verification.into(),
&self.amount_comms.lo.into(),
&self.amount_comms.hi.into(),
],
vec![64_usize, 32_usize, 32_usize],
Some(ephemeral_state.x.into()),
Some(ephemeral_state.z.into()),
&mut transcript,
)
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferValidityProofData {
/// The decryption handles that allow decryption of the lo-bits
pub decryption_handles_lo: TransferHandles, // 96 bytes
/// The decryption handles that allow decryption of the hi-bits
pub decryption_handles_hi: TransferHandles, // 96 bytes
/// The public encryption keys associated with the transfer: source, dest, and auditor
pub transfer_public_keys: TransferPubKeys, // 96 bytes
/// The final spendable ciphertext after the transfer
pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes
/// Proof that certifies that the decryption handles are generated correctly
pub proof: ValidityProof, // 160 bytes
}
/// The joint data that is shared between the two transfer instructions.
///
/// Identical ephemeral data should be included in the two transfer instructions and this should be
/// checked by the ZK Token program.
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(C)]
pub struct TransferEphemeralState {
pub spendable_comm_verification: pod::PedersenCommitment, // 32 bytes
pub x: pod::Scalar, // 32 bytes
pub z: pod::Scalar, // 32 bytes
pub t_x_blinding: pod::Scalar, // 32 bytes
}
#[cfg(not(target_arch = "bpf"))]
impl TransferValidityProofData {
fn verify(&self, ephemeral_state: &TransferEphemeralState) -> Result<(), ProofError> {
self.proof.verify(
&self.new_spendable_ct.try_into()?,
&self.decryption_handles_lo,
&self.decryption_handles_hi,
&self.amount_comms,
&self.decrypt_handles_lo,
&self.decrypt_handles_hi,
&self.new_spendable_ct,
&self.transfer_public_keys,
ephemeral_state,
)
}
}
/// Just a grouping struct for the two proofs that are needed for a transfer instruction. The two
/// proofs have to be generated together as they share joint data.
#[cfg(not(target_arch = "bpf"))]
pub struct TransferProofs {
#[allow(non_snake_case)]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferProof {
// Proof component for the spendable ciphertext components: R
pub R: pod::CompressedRistretto, // 32 bytes
// Proof component for the spendable ciphertext components: z
pub z: pod::Scalar, // 32 bytes
// Proof component for the transaction amount components: T_src
pub T_joint: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_1
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 validity_proof: ValidityProof,
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl TransferProofs {
impl TransferProof {
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
pub fn new(
@ -287,9 +218,9 @@ impl TransferProofs {
hi_open: &PedersenOpening,
new_spendable_balance: u64,
new_spendable_ct: &ElGamalCiphertext,
) -> (Self, TransferEphemeralState) {
) -> Self {
// TODO: should also commit to pubkeys and commitments later
let mut transcript_validity_proof = merlin::Transcript::new(b"TransferValidityProof");
let mut transcript = merlin::Transcript::new(b"TransferProof");
let H = PedersenBase::default().H;
let D = new_spendable_ct.decrypt_handle.get_point();
@ -300,20 +231,28 @@ impl TransferProofs {
let y = Scalar::random(&mut OsRng);
let R = RistrettoPoint::multiscalar_mul(vec![y, r_new], vec![D, H]).compress();
transcript_validity_proof.append_point(b"R", &R);
let c = transcript_validity_proof.challenge_scalar(b"c");
transcript.append_point(b"R", &R);
let c = transcript.challenge_scalar(b"c");
let z = s + c * y;
let new_spendable_open = PedersenOpening(c * r_new);
let spendable_comm_verification =
Pedersen::with(new_spendable_balance, &new_spendable_open);
// Generate proof for the transfer amounts
let t_1_blinding = PedersenOpening::random(&mut OsRng);
let t_2_blinding = PedersenOpening::random(&mut OsRng);
let u = transcript_validity_proof.challenge_scalar(b"u");
// generate the range proof
let range_proof = RangeProof::create_with(
vec![new_spendable_balance, transfer_amt.0, transfer_amt.1],
vec![64, 32, 32],
vec![&new_spendable_open, lo_open, hi_open],
&t_1_blinding,
&t_2_blinding,
&mut transcript,
);
let u = transcript.challenge_scalar(b"u");
let P_joint = RistrettoPoint::multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
@ -326,85 +265,41 @@ impl TransferProofs {
let T_1 = (t_1_blinding.get_scalar() * P_joint).compress();
let T_2 = (t_2_blinding.get_scalar() * P_joint).compress();
transcript_validity_proof.append_point(b"T_1", &T_1);
transcript_validity_proof.append_point(b"T_2", &T_2);
transcript.append_point(b"T_1", &T_1);
transcript.append_point(b"T_2", &T_2);
// define the validity proof
let validity_proof = ValidityProof {
Self {
R: R.into(),
z: z.into(),
T_joint: T_joint.into(),
T_1: T_1.into(),
T_2: T_2.into(),
};
// generate the range proof
let mut transcript_range_proof = Transcript::new(b"TransferRangeProof");
let (range_proof, x, z) = RangeProof::create_with(
vec![new_spendable_balance, transfer_amt.0, transfer_amt.1],
vec![64, 32, 32],
vec![&new_spendable_open, lo_open, hi_open],
&t_1_blinding,
&t_2_blinding,
&mut transcript_range_proof,
);
// define ephemeral state
let ephemeral_state = TransferEphemeralState {
spendable_comm_verification: spendable_comm_verification.into(),
x: x.into(),
z: z.into(),
t_x_blinding: range_proof.t_x_blinding.into(),
};
(
Self {
range_proof: range_proof.try_into().expect("valid range_proof"),
validity_proof,
},
ephemeral_state,
)
range_proof: range_proof.try_into().expect("invalid range proof"),
}
}
}
/// Proof components for transfer instructions.
///
/// These two components should be output by a RangeProof creation function.
#[allow(non_snake_case)]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ValidityProof {
// Proof component for the spendable ciphertext components: R
pub R: pod::CompressedRistretto, // 32 bytes
// Proof component for the spendable ciphertext components: z
pub z: pod::Scalar, // 32 bytes
// Proof component for the transaction amount components: T_src
pub T_joint: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_1
pub T_1: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_2
pub T_2: pod::CompressedRistretto, // 32 bytes
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl ValidityProof {
impl TransferProof {
pub fn verify(
self,
new_spendable_ct: &ElGamalCiphertext,
decryption_handles_lo: &TransferHandles,
decryption_handles_hi: &TransferHandles,
amount_comms: &TransferCommitments,
decryption_handles_lo: &TransferDecryptHandles,
decryption_handles_hi: &TransferDecryptHandles,
new_spendable_ct: &pod::ElGamalCiphertext,
transfer_public_keys: &TransferPubKeys,
ephemeral_state: &TransferEphemeralState,
) -> Result<(), ProofError> {
let mut transcript = Transcript::new(b"TransferValidityProof");
let mut transcript = Transcript::new(b"TransferProof");
let range_proof: RangeProof = self.range_proof.try_into()?;
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?;
let dest_pk: ElGamalPubkey = transfer_public_keys.dest_pk.try_into()?;
let auditor_pk: ElGamalPubkey = transfer_public_keys.auditor_pk.try_into()?;
// verify Pedersen commitment in the ephemeral state
let C_ephemeral: CompressedRistretto = ephemeral_state.spendable_comm_verification.into();
// derive Pedersen commitment for range proof verification
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
let C = new_spendable_ct.message_comm.get_point();
let D = new_spendable_ct.decrypt_handle.get_point();
@ -420,9 +315,23 @@ impl ValidityProof {
let spendable_comm_verification =
RistrettoPoint::multiscalar_mul(vec![Scalar::one(), -z, c], vec![C, D, R]).compress();
if C_ephemeral != spendable_comm_verification {
// verify range proof
let range_proof_verification = range_proof.verify_challenges(
vec![
&spendable_comm_verification,
&amount_comms.lo.into(),
&amount_comms.hi.into(),
],
vec![64_usize, 32_usize, 32_usize],
&mut transcript,
);
if range_proof_verification.is_err() {
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");
@ -435,14 +344,10 @@ impl ValidityProof {
],
);
// check well-formedness of decryption handles
let t_x_blinding: Scalar = ephemeral_state.t_x_blinding.into();
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 x = ephemeral_state.x.into();
let z: Scalar = ephemeral_state.z.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()?;
@ -507,7 +412,7 @@ pub struct TransferPubKeys {
/// The transfer amount commitments needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferComms {
pub struct TransferCommitments {
pub lo: pod::PedersenCommitment, // 32 bytes
pub hi: pod::PedersenCommitment, // 32 bytes
}
@ -515,7 +420,7 @@ pub struct TransferComms {
/// The decryption handles needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferHandles {
pub struct TransferDecryptHandles {
pub source: pod::PedersenDecryptHandle, // 32 bytes
pub dest: pod::PedersenDecryptHandle, // 32 bytes
pub auditor: pod::PedersenDecryptHandle, // 32 bytes