[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:
parent
f736fcee2b
commit
ef7ca5ee8b
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue