[zk-token-sdk] add range-proof proof instruction (#31788)

* add `VerifyRangeProof` instruction

* update proof program processor for `VerifyRangeProof64`

* specify target arch for `RANGEPROOF64_BIT_LENGTH

* rename `transcript_new` to `new_transcript`

* add constructor for `VerifyRangeProof64`

* add remark in the instruction description that context state account must be pre-allocated

* add proof description in `zk_token_proof_instruction.rs`

* rename `VerifyRangeProof64` to `VerifyRangeProofU64`

* use `u64::BITS`

* Update zk-token-sdk/src/zk_token_proof_instruction.rs

Co-authored-by: Tyera <teulberg@gmail.com>

* Apply suggestions from code review

Co-authored-by: Tyera <teulberg@gmail.com>

* fix range proof test

---------

Co-authored-by: Tyera <teulberg@gmail.com>
This commit is contained in:
samkim-crypto 2023-05-26 15:42:01 +09:00 committed by GitHub
parent f736fcee2b
commit ef7ca5ee8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 2 deletions

View File

@ -9,7 +9,10 @@ use {
transaction::{Transaction, TransactionError},
},
solana_zk_token_sdk::{
encryption::{elgamal::ElGamalKeypair, pedersen::PedersenOpening},
encryption::{
elgamal::ElGamalKeypair,
pedersen::{Pedersen, PedersenOpening},
},
instruction::*,
zk_token_proof_instruction::*,
zk_token_proof_program,
@ -18,13 +21,14 @@ use {
std::mem::size_of,
};
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 7] = [
ProofInstruction::VerifyZeroBalance,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyCiphertextCiphertextEquality,
ProofInstruction::VerifyTransfer,
ProofInstruction::VerifyTransferWithFee,
ProofInstruction::VerifyPubkeyValidity,
ProofInstruction::VerifyRangeProofU64,
];
#[tokio::test]
@ -330,6 +334,39 @@ async fn test_pubkey_validity() {
.await;
}
#[tokio::test]
async fn test_range_proof_u64() {
let amount = 123_u64;
let (commitment, opening) = Pedersen::new(amount);
let success_proof_data = RangeProofU64Data::new(&commitment, amount, &opening).unwrap();
let incorrect_amount = 124_u64;
let fail_proof_data = RangeProofU64Data::new(&commitment, incorrect_amount, &opening).unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyRangeProofU64,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyRangeProofU64,
size_of::<ProofContextState<RangeProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyRangeProofU64,
size_of::<ProofContextState<RangeProofContext>>(),
&success_proof_data,
)
.await;
}
async fn test_verify_proof_without_context<T, U>(
proof_instruction: ProofInstruction,
success_proof_data: &T,

View File

@ -192,5 +192,14 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
ic_msg!(invoke_context, "VerifyPubkeyValidity");
process_verify_proof::<PubkeyValidityData, PubkeyValidityProofContext>(invoke_context)
}
ProofInstruction::VerifyRangeProofU64 => {
if native_programs_consume_cu {
invoke_context
.consume_checked(105_066)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
}
ic_msg!(invoke_context, "VerifyRangeProof");
process_verify_proof::<RangeProofU64Data, RangeProofContext>(invoke_context)
}
}
});

View File

@ -1,5 +1,6 @@
pub mod ctxt_ctxt_equality;
pub mod pubkey_validity;
pub mod range_proof;
pub mod transfer;
pub mod transfer_with_fee;
pub mod withdraw;
@ -23,6 +24,7 @@ pub use {
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
},
pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext},
range_proof::{RangeProofContext, RangeProofU64Data},
transfer::{TransferData, TransferProofContext},
transfer_with_fee::{FeeParameters, TransferWithFeeData, TransferWithFeeProofContext},
withdraw::{WithdrawData, WithdrawProofContext},
@ -40,6 +42,7 @@ pub enum ProofType {
Transfer,
TransferWithFee,
PubkeyValidity,
RangeProofU64,
}
pub trait ZkProofData<T: Pod> {

View File

@ -0,0 +1,116 @@
//! The range proof instruction.
//!
//! A range proof certifies that a committed value in a Pedersen commitment is a number from a
//! certain range. Currently, only 64-bit range proof `VerifyRangeProofU64` is supported in the
//! proof program. It certifies that a committed number is an unsigned 64-bit number.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::pedersen::{PedersenCommitment, PedersenOpening},
errors::ProofError,
range_proof::RangeProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// The context data needed to verify a range-proof for a committed value in a Pedersen commitment.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct RangeProofContext {
pub commitment: pod::PedersenCommitment, // 32 bytes
}
/// The instruction data that is needed for the `ProofInstruction::VerifyRangeProofU64` 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 RangeProofU64Data {
/// The context data for a range proof
pub context: RangeProofContext,
/// The proof that a committed value in a Pedersen commitment is a 64-bit value
pub proof: pod::RangeProof64,
}
#[cfg(not(target_os = "solana"))]
impl RangeProofU64Data {
pub fn new(
commitment: &PedersenCommitment,
amount: u64,
opening: &PedersenOpening,
) -> Result<Self, ProofError> {
let pod_commitment = pod::PedersenCommitment(commitment.to_bytes());
let context = RangeProofContext {
commitment: pod_commitment,
};
let mut transcript = context.new_transcript();
// `u64::BITS` is 64, which fits in a single byte and should not overflow to `usize` for an
// overwhelming number of platforms. However, to be extra cautious, use `try_from` and
// `unwrap` here. A simple case `u64::BITS as usize` can silently overflow.
let bit_size = usize::try_from(u64::BITS).unwrap();
let proof = RangeProof::new(vec![amount], vec![bit_size], vec![opening], &mut transcript)
.try_into()?;
Ok(Self { context, proof })
}
}
impl ZkProofData<RangeProofContext> for RangeProofU64Data {
const PROOF_TYPE: ProofType = ProofType::RangeProofU64;
fn context_data(&self) -> &RangeProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = self.context_data().new_transcript();
let commitment = self.context.commitment.try_into()?;
let proof: RangeProof = self.proof.try_into()?;
let bit_size = usize::try_from(u64::BITS).unwrap();
proof
.verify(vec![&commitment], vec![bit_size], &mut transcript)
.map_err(|e| e.into())
}
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl RangeProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"RangeProof");
transcript.append_commitment(b"commitment", &self.commitment);
transcript
}
}
#[cfg(test)]
mod test {
use {super::*, crate::encryption::pedersen::Pedersen};
#[test]
fn test_range_proof_64_instruction_correctness() {
let amount = std::u64::MAX;
let (commitment, opening) = Pedersen::new(amount);
let proof_data = RangeProofU64Data::new(&commitment, amount, &opening).unwrap();
assert!(proof_data.verify_proof().is_ok());
}
}

View File

@ -132,6 +132,30 @@ pub enum ProofInstruction {
/// `PubkeyValidityData`
///
VerifyPubkeyValidity,
/// Verify a 64-bit range proof.
///
/// A range proof is defined with respect to a Pedersen commitment. The 64-bit range proof
/// certifies that a Pedersen commitment holds an unsigned 64-bit number.
///
/// This instruction can be configured to optionally initialize a proof context state account.
/// If creating a context state account, an account must be pre-allocated to the exact size of
/// `ProofContextState<RangeProofContext>` and assigned to the ZkToken proof program prior to
/// the execution of this instruction.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
/// `RangeProofU64Data`
///
VerifyRangeProofU64,
}
/// Pubkeys associated with a context state account to be used as parameters to functions.
@ -210,6 +234,14 @@ pub fn verify_pubkey_validity(
ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyRangeProofU64` instruction.
pub fn verify_range_proof_u64(
context_state_info: Option<ContextStateInfo>,
proof_data: &RangeProofU64Data,
) -> Instruction {
ProofInstruction::VerifyRangeProofU64.encode_verify_proof(context_state_info, proof_data)
}
impl ProofInstruction {
pub fn encode_verify_proof<T, U>(
&self,