Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit eef0666

Browse files
committed
refactor proof extraction logic into a spl-token-confidential-transfer-proof-extraction
1 parent 82ad10a commit eef0666

File tree

3 files changed

+181
-181
lines changed

3 files changed

+181
-181
lines changed

token/confidential-transfer/proof-extraction/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ edition = "2021"
1010
[dependencies]
1111
bytemuck = "1.19.0"
1212
solana-curve25519 = "2.0.3"
13+
solana-program = "2.0.3"
1314
solana-zk-sdk = "2.0.3"
15+
spl-pod = "0.4.0"
1416
thiserror = "1.0.64"
1517

1618
[lib]

token/confidential-transfer/proof-extraction/src/lib.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,182 @@ pub mod mint;
55
pub mod transfer;
66
pub mod transfer_with_fee;
77
pub mod withdraw;
8+
9+
use {
10+
bytemuck::Pod,
11+
solana_program::{
12+
account_info::{next_account_info, AccountInfo},
13+
entrypoint::ProgramResult,
14+
instruction::Instruction,
15+
msg,
16+
program_error::ProgramError,
17+
pubkey::Pubkey,
18+
sysvar::instructions::get_instruction_relative,
19+
},
20+
solana_zk_sdk::zk_elgamal_proof_program::{
21+
self,
22+
instruction::ProofInstruction,
23+
proof_data::{ProofType, ZkProofData},
24+
state::ProofContextState,
25+
},
26+
spl_pod::bytemuck::pod_from_bytes,
27+
std::{num::NonZeroI8, slice::Iter},
28+
};
29+
30+
/// Checks that the supplied program ID is correct for the ZK ElGamal proof
31+
/// program
32+
pub fn check_zk_elgamal_proof_program_account(
33+
zk_elgamal_proof_program_id: &Pubkey,
34+
) -> ProgramResult {
35+
if zk_elgamal_proof_program_id != &solana_zk_sdk::zk_elgamal_proof_program::id() {
36+
return Err(ProgramError::IncorrectProgramId);
37+
}
38+
Ok(())
39+
}
40+
41+
/// If a proof is to be read from a record account, the proof instruction data
42+
/// must be 5 bytes: 1 byte for the proof type and 4 bytes for the u32 offset
43+
const INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT: usize = 5;
44+
45+
/// Decodes the proof context data associated with a zero-knowledge proof
46+
/// instruction.
47+
pub fn decode_proof_instruction_context<T: Pod + ZkProofData<U>, U: Pod>(
48+
account_info_iter: &mut Iter<'_, AccountInfo<'_>>,
49+
expected: ProofInstruction,
50+
instruction: &Instruction,
51+
) -> Result<U, ProgramError> {
52+
if instruction.program_id != zk_elgamal_proof_program::id()
53+
|| ProofInstruction::instruction_type(&instruction.data) != Some(expected)
54+
{
55+
msg!("Unexpected proof instruction");
56+
return Err(ProgramError::InvalidInstructionData);
57+
}
58+
59+
// If the instruction data size is exactly 5 bytes, then interpret it as an
60+
// offset byte for a record account. This behavior is identical to that of
61+
// the ZK ElGamal proof program.
62+
if instruction.data.len() == INSTRUCTION_DATA_LENGTH_WITH_RECORD_ACCOUNT {
63+
let record_account = next_account_info(account_info_iter)?;
64+
65+
// first byte is the proof type
66+
let start_offset = u32::from_le_bytes(instruction.data[1..].try_into().unwrap()) as usize;
67+
let end_offset = start_offset
68+
.checked_add(std::mem::size_of::<T>())
69+
.ok_or(ProgramError::InvalidAccountData)?;
70+
71+
let record_account_data = record_account.data.borrow();
72+
let raw_proof_data = record_account_data
73+
.get(start_offset..end_offset)
74+
.ok_or(ProgramError::AccountDataTooSmall)?;
75+
76+
bytemuck::try_from_bytes::<T>(raw_proof_data)
77+
.map(|proof_data| *ZkProofData::context_data(proof_data))
78+
.map_err(|_| ProgramError::InvalidAccountData)
79+
} else {
80+
ProofInstruction::proof_data::<T, U>(&instruction.data)
81+
.map(|proof_data| *ZkProofData::context_data(proof_data))
82+
.ok_or(ProgramError::InvalidInstructionData)
83+
}
84+
}
85+
86+
/// A proof location type meant to be used for arguments to instruction
87+
/// constructors.
88+
#[derive(Clone, Copy)]
89+
pub enum ProofLocation<'a, T> {
90+
/// The proof is included in the same transaction of a corresponding
91+
/// token-2022 instruction.
92+
InstructionOffset(NonZeroI8, ProofData<'a, T>),
93+
/// The proof is pre-verified into a context state account.
94+
ContextStateAccount(&'a Pubkey),
95+
}
96+
97+
impl<'a, T> ProofLocation<'a, T> {
98+
/// Returns true if the proof location is an instruction offset
99+
pub fn is_instruction_offset(&self) -> bool {
100+
match self {
101+
Self::InstructionOffset(_, _) => true,
102+
Self::ContextStateAccount(_) => false,
103+
}
104+
}
105+
}
106+
107+
/// A proof data type to distinguish between proof data included as part of
108+
/// zk-token proof instruction data and proof data stored in a record account.
109+
#[derive(Clone, Copy)]
110+
pub enum ProofData<'a, T> {
111+
/// The proof data
112+
InstructionData(&'a T),
113+
/// The address of a record account containing the proof data and its byte
114+
/// offset
115+
RecordAccount(&'a Pubkey, u32),
116+
}
117+
118+
/// Verify zero-knowledge proof and return the corresponding proof context.
119+
pub fn verify_and_extract_context<'a, T: Pod + ZkProofData<U>, U: Pod>(
120+
account_info_iter: &mut Iter<'_, AccountInfo<'a>>,
121+
proof_instruction_offset: i64,
122+
sysvar_account_info: Option<&'_ AccountInfo<'a>>,
123+
) -> Result<U, ProgramError> {
124+
if proof_instruction_offset == 0 {
125+
// interpret `account_info` as a context state account
126+
let context_state_account_info = next_account_info(account_info_iter)?;
127+
check_zk_elgamal_proof_program_account(context_state_account_info.owner)?;
128+
let context_state_account_data = context_state_account_info.data.borrow();
129+
let context_state = pod_from_bytes::<ProofContextState<U>>(&context_state_account_data)?;
130+
131+
if context_state.proof_type != T::PROOF_TYPE.into() {
132+
return Err(ProgramError::InvalidInstructionData);
133+
}
134+
135+
Ok(context_state.proof_context)
136+
} else {
137+
// if sysvar account is not provided, then get the sysvar account
138+
let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info {
139+
sysvar_account_info
140+
} else {
141+
next_account_info(account_info_iter)?
142+
};
143+
let zkp_instruction =
144+
get_instruction_relative(proof_instruction_offset, sysvar_account_info)?;
145+
let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?;
146+
Ok(decode_proof_instruction_context::<T, U>(
147+
account_info_iter,
148+
expected_proof_type,
149+
&zkp_instruction,
150+
)?)
151+
}
152+
}
153+
154+
/// Converts a zk proof type to a corresponding ZK ElGamal proof program
155+
/// instruction that verifies the proof.
156+
pub fn zk_proof_type_to_instruction(
157+
proof_type: ProofType,
158+
) -> Result<ProofInstruction, ProgramError> {
159+
match proof_type {
160+
ProofType::ZeroCiphertext => Ok(ProofInstruction::VerifyZeroCiphertext),
161+
ProofType::CiphertextCiphertextEquality => {
162+
Ok(ProofInstruction::VerifyCiphertextCiphertextEquality)
163+
}
164+
ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity),
165+
ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64),
166+
ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128),
167+
ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256),
168+
ProofType::CiphertextCommitmentEquality => {
169+
Ok(ProofInstruction::VerifyCiphertextCommitmentEquality)
170+
}
171+
ProofType::GroupedCiphertext2HandlesValidity => {
172+
Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity)
173+
}
174+
ProofType::BatchedGroupedCiphertext2HandlesValidity => {
175+
Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity)
176+
}
177+
ProofType::PercentageWithCap => Ok(ProofInstruction::VerifyPercentageWithCap),
178+
ProofType::GroupedCiphertext3HandlesValidity => {
179+
Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity)
180+
}
181+
ProofType::BatchedGroupedCiphertext3HandlesValidity => {
182+
Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity)
183+
}
184+
ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData),
185+
}
186+
}

token/program-2022/src/proof.rs

Lines changed: 0 additions & 181 deletions
This file was deleted.

0 commit comments

Comments
 (0)