[zk-token-sdk] Add fee sigma proof verification instruction (#32291)
* add fee sigma proof verification instruction * update proof program processor for fee sigma proof * update bench for fee sigma proof * cargo fmt * clippy * fix target arch * add comments to describe fee, delta, and claimed instruction data field
This commit is contained in:
parent
87c1b67d53
commit
4bc8f90182
|
@ -7270,6 +7270,7 @@ version = "1.17.0"
|
|||
dependencies = [
|
||||
"bytemuck",
|
||||
"criterion",
|
||||
"curve25519-dalek",
|
||||
"getrandom 0.1.16",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
|
@ -7283,6 +7284,7 @@ name = "solana-zk-token-proof-program-tests"
|
|||
version = "1.17.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"curve25519-dalek",
|
||||
"solana-program-runtime",
|
||||
"solana-program-test",
|
||||
"solana-sdk",
|
||||
|
|
|
@ -9,6 +9,7 @@ edition = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
bytemuck = { workspace = true, features = ["derive"] }
|
||||
curve25519-dalek = { workspace = true }
|
||||
solana-program-runtime = { workspace = true }
|
||||
solana-program-test = { workspace = true }
|
||||
solana-sdk = { workspace = true }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
bytemuck::Pod,
|
||||
curve25519_dalek::scalar::Scalar,
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
|
@ -22,7 +23,7 @@ use {
|
|||
std::mem::size_of,
|
||||
};
|
||||
|
||||
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 13] = [
|
||||
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 14] = [
|
||||
ProofInstruction::VerifyZeroBalance,
|
||||
ProofInstruction::VerifyWithdraw,
|
||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||
|
@ -36,6 +37,7 @@ const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 13] = [
|
|||
ProofInstruction::VerifyCiphertextCommitmentEquality,
|
||||
ProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
|
||||
ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
|
||||
ProofInstruction::VerifyFeeSigma,
|
||||
];
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -703,6 +705,75 @@ async fn test_batched_grouped_ciphertext_2_handles_validity() {
|
|||
.await;
|
||||
}
|
||||
|
||||
#[allow(clippy::op_ref)]
|
||||
#[tokio::test]
|
||||
async fn test_fee_sigma() {
|
||||
let transfer_amount: u64 = 1;
|
||||
let max_fee: u64 = 3;
|
||||
|
||||
let fee_rate: u16 = 400;
|
||||
let fee_amount: u64 = 1;
|
||||
let delta_fee: u64 = 9600;
|
||||
|
||||
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
||||
let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
|
||||
|
||||
let scalar_rate = Scalar::from(fee_rate);
|
||||
let delta_commitment =
|
||||
&fee_commitment * Scalar::from(10_000_u64) - &transfer_commitment * &scalar_rate;
|
||||
let delta_opening = &fee_opening * &Scalar::from(10_000_u64) - &transfer_opening * &scalar_rate;
|
||||
|
||||
let (claimed_commitment, claimed_opening) = Pedersen::new(delta_fee);
|
||||
|
||||
let success_proof_data = FeeSigmaProofData::new(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
&fee_opening,
|
||||
&delta_opening,
|
||||
&claimed_opening,
|
||||
fee_amount,
|
||||
delta_fee,
|
||||
max_fee,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let fail_proof_data = FeeSigmaProofData::new(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
&fee_opening,
|
||||
&delta_opening,
|
||||
&claimed_opening,
|
||||
fee_amount,
|
||||
0,
|
||||
max_fee,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_verify_proof_without_context(
|
||||
ProofInstruction::VerifyFeeSigma,
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_verify_proof_with_context(
|
||||
ProofInstruction::VerifyFeeSigma,
|
||||
size_of::<ProofContextState<FeeSigmaProofContext>>(),
|
||||
&success_proof_data,
|
||||
&fail_proof_data,
|
||||
)
|
||||
.await;
|
||||
|
||||
test_close_context_state(
|
||||
ProofInstruction::VerifyFeeSigma,
|
||||
size_of::<ProofContextState<FeeSigmaProofContext>>(),
|
||||
&success_proof_data,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn test_verify_proof_without_context<T, U>(
|
||||
proof_instruction: ProofInstruction,
|
||||
success_proof_data: &T,
|
||||
|
|
|
@ -19,6 +19,7 @@ solana-zk-token-sdk = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
curve25519-dalek = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "verify_proofs"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
criterion::{criterion_group, criterion_main, Criterion},
|
||||
curve25519_dalek::scalar::Scalar,
|
||||
solana_zk_token_sdk::{
|
||||
encryption::{
|
||||
elgamal::ElGamalKeypair,
|
||||
|
@ -10,8 +11,9 @@ use {
|
|||
transfer::FeeParameters, BatchedGroupedCiphertext2HandlesValidityProofData,
|
||||
BatchedRangeProofU128Data, BatchedRangeProofU256Data, BatchedRangeProofU64Data,
|
||||
CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData,
|
||||
GroupedCiphertext2HandlesValidityProofData, PubkeyValidityData, RangeProofU64Data,
|
||||
TransferData, TransferWithFeeData, WithdrawData, ZeroBalanceProofData, ZkProofData,
|
||||
FeeSigmaProofData, GroupedCiphertext2HandlesValidityProofData, PubkeyValidityData,
|
||||
RangeProofU64Data, TransferData, TransferWithFeeData, WithdrawData,
|
||||
ZeroBalanceProofData, ZkProofData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -188,6 +190,45 @@ fn bench_batched_grouped_ciphertext_validity(c: &mut Criterion) {
|
|||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::op_ref)]
|
||||
fn bench_fee_sigma(c: &mut Criterion) {
|
||||
let transfer_amount: u64 = 1;
|
||||
let max_fee: u64 = 3;
|
||||
|
||||
let fee_rate: u16 = 400;
|
||||
let fee_amount: u64 = 1;
|
||||
let delta_fee: u64 = 9600;
|
||||
|
||||
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
||||
let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
|
||||
|
||||
let scalar_rate = Scalar::from(fee_rate);
|
||||
let delta_commitment =
|
||||
&fee_commitment * Scalar::from(10_000_u64) - &transfer_commitment * &scalar_rate;
|
||||
let delta_opening = &fee_opening * &Scalar::from(10_000_u64) - &transfer_opening * &scalar_rate;
|
||||
|
||||
let (claimed_commitment, claimed_opening) = Pedersen::new(delta_fee);
|
||||
|
||||
let proof_data = FeeSigmaProofData::new(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
&fee_opening,
|
||||
&delta_opening,
|
||||
&claimed_opening,
|
||||
fee_amount,
|
||||
delta_fee,
|
||||
max_fee,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("fee_sigma", |b| {
|
||||
b.iter(|| {
|
||||
proof_data.verify_proof().unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_batched_range_proof_u64(c: &mut Criterion) {
|
||||
let amount_1 = 255_u64;
|
||||
let amount_2 = 77_u64;
|
||||
|
@ -414,5 +455,6 @@ criterion_group!(
|
|||
bench_batched_range_proof_u256,
|
||||
bench_transfer,
|
||||
bench_transfer_with_fee,
|
||||
bench_fee_sigma,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
|
|
@ -267,5 +267,12 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
|
|||
BatchedGroupedCiphertext2HandlesValidityProofContext,
|
||||
>(invoke_context)
|
||||
}
|
||||
ProofInstruction::VerifyFeeSigma => {
|
||||
invoke_context
|
||||
.consume_checked(6_547)
|
||||
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
|
||||
ic_msg!(invoke_context, "VerifyFeeSigma");
|
||||
process_verify_proof::<FeeSigmaProofData, FeeSigmaProofContext>(invoke_context)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
//! The fee sigma proof instruction.
|
||||
//!
|
||||
//! A fee sigma proof certifies that a Pedersen commitment to a transfer fee for SPL Token 2022 is
|
||||
//! well-formed.
|
||||
//!
|
||||
//! A formal documentation of how transfer fees and fee sigma proof are computed can be found in
|
||||
//! the [`ZK Token proof`] program documentation.
|
||||
//!
|
||||
//! [`ZK Token proof`]: https://edge.docs.solana.com/developing/runtime-facilities/zk-token-proof
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
use {
|
||||
crate::{
|
||||
encryption::pedersen::{PedersenCommitment, PedersenOpening},
|
||||
errors::ProofError,
|
||||
sigma_proofs::fee_proof::FeeSigmaProof,
|
||||
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::VerifyFeeSigma` 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 FeeSigmaProofData {
|
||||
pub context: FeeSigmaProofContext,
|
||||
|
||||
pub proof: pod::FeeSigmaProof,
|
||||
}
|
||||
|
||||
/// The context data needed to verify a pubkey validity proof.
|
||||
///
|
||||
/// We refer to [`ZK Token proof`] for the formal details on how the fee sigma proof is computed.
|
||||
///
|
||||
/// [`ZK Token proof`]: https://edge.docs.solana.com/developing/runtime-facilities/zk-token-proof
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct FeeSigmaProofContext {
|
||||
/// The Pedersen commitment to the transfer fee
|
||||
pub fee_commitment: pod::PedersenCommitment,
|
||||
|
||||
/// The Pedersen commitment to the real delta fee.
|
||||
pub delta_commitment: pod::PedersenCommitment,
|
||||
|
||||
/// The Pedersen commitment to the claimed delta fee.
|
||||
pub claimed_commitment: pod::PedersenCommitment,
|
||||
|
||||
/// The maximum cap for a transfer fee
|
||||
pub max_fee: pod::PodU64,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl FeeSigmaProofData {
|
||||
pub fn new(
|
||||
fee_commitment: &PedersenCommitment,
|
||||
delta_commitment: &PedersenCommitment,
|
||||
claimed_commitment: &PedersenCommitment,
|
||||
fee_opening: &PedersenOpening,
|
||||
delta_opening: &PedersenOpening,
|
||||
claimed_opening: &PedersenOpening,
|
||||
fee_amount: u64,
|
||||
delta_fee: u64,
|
||||
max_fee: u64,
|
||||
) -> Result<Self, ProofError> {
|
||||
let pod_fee_commitment = pod::PedersenCommitment(fee_commitment.to_bytes());
|
||||
let pod_delta_commitment = pod::PedersenCommitment(delta_commitment.to_bytes());
|
||||
let pod_claimed_commitment = pod::PedersenCommitment(claimed_commitment.to_bytes());
|
||||
let pod_max_fee = max_fee.into();
|
||||
|
||||
let context = FeeSigmaProofContext {
|
||||
fee_commitment: pod_fee_commitment,
|
||||
delta_commitment: pod_delta_commitment,
|
||||
claimed_commitment: pod_claimed_commitment,
|
||||
max_fee: pod_max_fee,
|
||||
};
|
||||
|
||||
let mut transcript = context.new_transcript();
|
||||
|
||||
let proof = FeeSigmaProof::new(
|
||||
(fee_amount, fee_commitment, fee_opening),
|
||||
(delta_fee, delta_commitment, delta_opening),
|
||||
(claimed_commitment, claimed_opening),
|
||||
max_fee,
|
||||
&mut transcript,
|
||||
)
|
||||
.into();
|
||||
|
||||
Ok(Self { context, proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl ZkProofData<FeeSigmaProofContext> for FeeSigmaProofData {
|
||||
const PROOF_TYPE: ProofType = ProofType::FeeSigma;
|
||||
|
||||
fn context_data(&self) -> &FeeSigmaProofContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
fn verify_proof(&self) -> Result<(), ProofError> {
|
||||
let mut transcript = self.context.new_transcript();
|
||||
|
||||
let fee_commitment = self.context.fee_commitment.try_into()?;
|
||||
let delta_commitment = self.context.delta_commitment.try_into()?;
|
||||
let claimed_commitment = self.context.claimed_commitment.try_into()?;
|
||||
let max_fee = self.context.max_fee.into();
|
||||
let proof: FeeSigmaProof = self.proof.try_into()?;
|
||||
|
||||
proof
|
||||
.verify(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
max_fee,
|
||||
&mut transcript,
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "solana"))]
|
||||
impl FeeSigmaProofContext {
|
||||
fn new_transcript(&self) -> Transcript {
|
||||
let mut transcript = Transcript::new(b"FeeSigmaProof");
|
||||
transcript.append_commitment(b"fee-commitment", &self.fee_commitment);
|
||||
transcript.append_commitment(b"delta-commitment", &self.fee_commitment);
|
||||
transcript.append_commitment(b"claimed-commitment", &self.fee_commitment);
|
||||
transcript.append_u64(b"max-fee", self.max_fee.into());
|
||||
transcript
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {super::*, crate::encryption::pedersen::Pedersen, curve25519_dalek::scalar::Scalar};
|
||||
|
||||
#[test]
|
||||
fn test_fee_sigma_instruction_correctness() {
|
||||
// transfer fee amount is below max fee
|
||||
let transfer_amount: u64 = 1;
|
||||
let max_fee: u64 = 3;
|
||||
|
||||
let fee_rate: u16 = 400;
|
||||
let fee_amount: u64 = 1;
|
||||
let delta_fee: u64 = 9600;
|
||||
|
||||
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
||||
let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
|
||||
|
||||
let scalar_rate = Scalar::from(fee_rate);
|
||||
let delta_commitment =
|
||||
&fee_commitment * Scalar::from(10_000_u64) - &transfer_commitment * &scalar_rate;
|
||||
let delta_opening =
|
||||
&fee_opening * &Scalar::from(10_000_u64) - &transfer_opening * &scalar_rate;
|
||||
|
||||
let (claimed_commitment, claimed_opening) = Pedersen::new(delta_fee);
|
||||
|
||||
let proof_data = FeeSigmaProofData::new(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
&fee_opening,
|
||||
&delta_opening,
|
||||
&claimed_opening,
|
||||
fee_amount,
|
||||
delta_fee,
|
||||
max_fee,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof_data.verify_proof().is_ok());
|
||||
|
||||
// transfer fee amount is equal to max fee
|
||||
let transfer_amount: u64 = 55;
|
||||
let max_fee: u64 = 3;
|
||||
|
||||
let fee_rate: u16 = 555;
|
||||
let fee_amount: u64 = 4;
|
||||
|
||||
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
||||
let (fee_commitment, fee_opening) = Pedersen::new(max_fee);
|
||||
|
||||
let scalar_rate = Scalar::from(fee_rate);
|
||||
let delta_commitment =
|
||||
&fee_commitment * &Scalar::from(10000_u64) - &transfer_commitment * &scalar_rate;
|
||||
let delta_opening =
|
||||
&fee_opening * &Scalar::from(10000_u64) - &transfer_opening * &scalar_rate;
|
||||
|
||||
let (claimed_commitment, claimed_opening) = Pedersen::new(0_u64);
|
||||
|
||||
let proof_data = FeeSigmaProofData::new(
|
||||
&fee_commitment,
|
||||
&delta_commitment,
|
||||
&claimed_commitment,
|
||||
&fee_opening,
|
||||
&delta_opening,
|
||||
&claimed_opening,
|
||||
fee_amount,
|
||||
delta_fee,
|
||||
max_fee,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(proof_data.verify_proof().is_ok());
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ pub mod batched_grouped_ciphertext_validity;
|
|||
pub mod batched_range_proof;
|
||||
pub mod ciphertext_ciphertext_equality;
|
||||
pub mod ciphertext_commitment_equality;
|
||||
pub mod fee_sigma;
|
||||
pub mod grouped_ciphertext_validity;
|
||||
pub mod pubkey_validity;
|
||||
pub mod range_proof;
|
||||
|
@ -33,6 +34,7 @@ pub use {
|
|||
ciphertext_commitment_equality::{
|
||||
CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData,
|
||||
},
|
||||
fee_sigma::{FeeSigmaProofContext, FeeSigmaProofData},
|
||||
grouped_ciphertext_validity::{
|
||||
GroupedCiphertext2HandlesValidityProofContext, GroupedCiphertext2HandlesValidityProofData,
|
||||
},
|
||||
|
@ -64,6 +66,7 @@ pub enum ProofType {
|
|||
CiphertextCommitmentEquality,
|
||||
GroupedCiphertext2HandlesValidity,
|
||||
BatchedGroupedCiphertext2HandlesValidity,
|
||||
FeeSigma,
|
||||
}
|
||||
|
||||
pub trait ZkProofData<T: Pod> {
|
||||
|
|
|
@ -305,6 +305,25 @@ pub enum ProofInstruction {
|
|||
/// `BatchedGroupedCiphertextValidityProofContext`
|
||||
///
|
||||
VerifyBatchedGroupedCiphertext2HandlesValidity,
|
||||
|
||||
/// Verify a fee sigma proof.
|
||||
///
|
||||
/// A fee sigma proof certifies that a Pedersen commitment that encodes a transfer fee for SPL
|
||||
/// Token 2022 is well-formed.
|
||||
///
|
||||
/// 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:
|
||||
/// `FeeSigmaProofData`
|
||||
///
|
||||
VerifyFeeSigma,
|
||||
}
|
||||
|
||||
/// Pubkeys associated with a context state account to be used as parameters to functions.
|
||||
|
|
Loading…
Reference in New Issue