@@ -5,3 +5,182 @@ pub mod mint;
55pub mod transfer;
66pub mod transfer_with_fee;
77pub 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+ }
0 commit comments