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