Line data Source code
1 : #include "fd_zksdk_private.h" 2 : #include "../../fd_borrowed_account.h" 3 : #include "../../fd_system_ids.h" 4 : #include "../../../log_collector/fd_log_collector.h" 5 : 6 : /* fd_zksdk_process_close_context_state is equivalent to process_close_proof_context() 7 : https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L127 */ 8 : int 9 0 : fd_zksdk_process_close_context_state( fd_exec_instr_ctx_t * ctx ) { 10 0 : #define ACC_IDX_PROOF (0UL) 11 0 : #define ACC_IDX_DEST (1UL) 12 0 : #define ACC_IDX_OWNER (2UL) 13 : 14 0 : fd_pubkey_t owner_pubkey[1]; 15 0 : fd_pubkey_t proof_pubkey[1]; 16 0 : fd_pubkey_t dest_pubkey[1]; 17 : 18 : /* Obtain the owner pubkey by borrowing the owner account in local scope 19 : https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs#L133-L141 */ 20 0 : do { 21 0 : fd_guarded_borrowed_account_t owner_acc = {0}; 22 0 : int instr_err_code = 0; 23 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, ACC_IDX_OWNER, &instr_err_code ) ) ) { 24 0 : if( FD_UNLIKELY( !!instr_err_code ) ) return instr_err_code; 25 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 26 0 : } 27 : 28 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_OWNER, &owner_acc ); 29 0 : *owner_pubkey = *owner_acc.acct->pubkey; 30 : /* implicit drop of borrowed owner_acc */ 31 0 : } while (0); 32 : 33 : /* Allocate space for borrowed accounts */ 34 0 : fd_guarded_borrowed_account_t proof_acc = {0}; 35 0 : fd_guarded_borrowed_account_t dest_acc = {0}; 36 : 37 : /* Obtain the proof account pubkey by borrowing the proof account. 38 : https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs#L143-L145 */ 39 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK(ctx, ACC_IDX_PROOF, &proof_acc ); 40 0 : *proof_pubkey = *proof_acc.acct->pubkey; 41 0 : fd_borrowed_account_drop( &proof_acc ); 42 : 43 : /* Obtain the dest account pubkey by borrowing the dest account. 44 : https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs#L146-L148*/ 45 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_DEST, &dest_acc ); 46 0 : *dest_pubkey = *dest_acc.acct->pubkey; 47 0 : fd_borrowed_account_drop( &dest_acc ); 48 : 49 0 : if( FD_UNLIKELY( fd_memeq( proof_pubkey, dest_pubkey, sizeof(fd_pubkey_t) ) ) ) { 50 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 51 0 : } 52 : 53 : /* Re-borrow the proof account 54 : https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs#L153-L154 */ 55 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK(ctx, ACC_IDX_PROOF, &proof_acc ); 56 : 57 : /* Check that the proof context account is owned by the zk-elgamal-proof program 58 : https://github.com/anza-xyz/agave/blob/v3.1.0-beta.0/programs/zk-elgamal-proof/src/lib.rs#167-L171 */ 59 0 : if( FD_UNLIKELY( !fd_memeq( fd_borrowed_account_get_owner( &proof_acc ), &fd_solana_zk_elgamal_proof_program_id, sizeof(fd_pubkey_t) ) ) ) { 60 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; 61 0 : } 62 : 63 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L161-L162 64 : Note: data also contains context data, but we only need the initial 33 bytes. */ 65 0 : if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &proof_acc ) < sizeof(fd_zksdk_proof_ctx_state_meta_t) ) ) { 66 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 67 0 : } 68 0 : fd_zksdk_proof_ctx_state_meta_t const * proof_ctx_state_meta = fd_type_pun_const( fd_borrowed_account_get_data( &proof_acc ) ); 69 : 70 : /* Check that the proof context account is initialized (proof_type != 0) 71 : ProofType::Uninitialized = 0 72 : https://github.com/anza-xyz/agave/blob/v3.1.0-beta.0/programs/zk-elgamal-proof/src/lib.rs#L161-L165 */ 73 0 : if( FD_UNLIKELY( proof_ctx_state_meta->proof_type == 0 ) ) { 74 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT; 75 0 : } 76 : 77 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L155 */ 78 0 : fd_pubkey_t const * expected_owner_addr = &proof_ctx_state_meta->ctx_state_authority; 79 : 80 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L157-L159 */ 81 0 : if( FD_UNLIKELY( !fd_memeq( owner_pubkey, expected_owner_addr, sizeof(fd_pubkey_t) ) ) ) { 82 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; 83 0 : } 84 : 85 : /* Re-borrow the dest account 86 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/zk-elgamal-proof/src/lib.rs#L162-L163 */ 87 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_DEST, &dest_acc ); 88 : 89 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L163-L166 */ 90 0 : int err = 0; 91 0 : err = fd_borrowed_account_checked_add_lamports( &dest_acc, fd_borrowed_account_get_lamports( &proof_acc ) ); 92 0 : if( FD_UNLIKELY( err ) ) { 93 0 : return err; 94 0 : } 95 0 : err = fd_borrowed_account_set_lamports( &proof_acc, 0UL ); 96 0 : if( FD_UNLIKELY( err ) ) { 97 0 : return err; 98 0 : } 99 0 : err = fd_borrowed_account_set_data_length( &proof_acc, 0UL ); 100 0 : if( FD_UNLIKELY( err ) ) { 101 0 : return err; 102 0 : } 103 0 : err = fd_borrowed_account_set_owner( &proof_acc, &fd_solana_system_program_id ); 104 0 : if( FD_UNLIKELY( err ) ) { 105 0 : return err; 106 0 : } 107 : 108 0 : return FD_EXECUTOR_INSTR_SUCCESS; 109 0 : } 110 : 111 : /* fd_zksdk_process_verify_proof is equivalent to process_verify_proof() 112 : and calls specific functions inside instructions/ to verify each 113 : individual ZKP. 114 : https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L32 */ 115 : int 116 12 : fd_zksdk_process_verify_proof( fd_exec_instr_ctx_t * ctx ) { 117 12 : int err; 118 12 : uchar const * instr_data = ctx->instr->data; 119 12 : ulong instr_acc_cnt = ctx->instr->acct_cnt; 120 12 : uchar instr_id = instr_data[0]; /* instr_data_sz already checked by the caller */ 121 : 122 : /* ProofContextState "header" size, ie. 1 authority pubkey + 1 proof_type byte */ 123 12 : #define CTX_HEAD_SZ 33UL 124 : 125 : /* Aux memory buffer. 126 : When proof data is taken from ix data we can access it directly, 127 : but when it's taken from account data we need to copy it to release 128 : the borrow. The largest ZKP is for range_proof_u256. 129 : Moreover, when storing context to an account, we need to serialize 130 : the ProofContextState struct that has 33 bytes of header -- we include 131 : them here so we can do a single memcpy. */ 132 12 : #define MAX_SZ (sizeof(fd_zksdk_range_proof_u256_proof_t)+sizeof(fd_zksdk_batched_range_proof_context_t)) 133 12 : uchar buffer[ CTX_HEAD_SZ+MAX_SZ ]; 134 : 135 : /* Specific instruction function */ 136 12 : int (*fd_zksdk_instr_verify_proof)( void const *, void const * ) = NULL; 137 12 : switch( instr_id ) { 138 0 : case FD_ZKSDK_INSTR_VERIFY_ZERO_CIPHERTEXT: 139 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_zero_ciphertext; 140 0 : break; 141 0 : case FD_ZKSDK_INSTR_VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY: 142 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_ciphertext_ciphertext_equality; 143 0 : break; 144 0 : case FD_ZKSDK_INSTR_VERIFY_CIPHERTEXT_COMMITMENT_EQUALITY: 145 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_ciphertext_commitment_equality; 146 0 : break; 147 12 : case FD_ZKSDK_INSTR_VERIFY_PUBKEY_VALIDITY: 148 12 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_pubkey_validity; 149 12 : break; 150 0 : case FD_ZKSDK_INSTR_VERIFY_PERCENTAGE_WITH_CAP: 151 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_percentage_with_cap; 152 0 : break; 153 0 : case FD_ZKSDK_INSTR_VERIFY_BATCHED_RANGE_PROOF_U64: 154 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_batched_range_proof_u64; 155 0 : break; 156 0 : case FD_ZKSDK_INSTR_VERIFY_BATCHED_RANGE_PROOF_U128: 157 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_batched_range_proof_u128; 158 0 : break; 159 0 : case FD_ZKSDK_INSTR_VERIFY_BATCHED_RANGE_PROOF_U256: 160 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_batched_range_proof_u256; 161 0 : break; 162 0 : case FD_ZKSDK_INSTR_VERIFY_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY: 163 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_grouped_ciphertext_2_handles_validity; 164 0 : break; 165 0 : case FD_ZKSDK_INSTR_VERIFY_BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY: 166 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_batched_grouped_ciphertext_2_handles_validity; 167 0 : break; 168 0 : case FD_ZKSDK_INSTR_VERIFY_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY: 169 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_grouped_ciphertext_3_handles_validity; 170 0 : break; 171 0 : case FD_ZKSDK_INSTR_VERIFY_BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY: 172 0 : fd_zksdk_instr_verify_proof = &fd_zksdk_instr_verify_proof_batched_grouped_ciphertext_3_handles_validity; 173 0 : break; 174 0 : default: 175 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 176 12 : } 177 : 178 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L42 */ 179 12 : ushort accessed_accounts = 0U; 180 12 : uchar const * context = NULL; 181 : /* Note: instr_id is guaranteed to be valid, to access values in the arrays. */ 182 12 : ulong context_sz = fd_zksdk_context_sz[instr_id]; 183 12 : ulong proof_data_sz = context_sz + fd_zksdk_proof_sz[instr_id]; 184 : 185 : /* if instruction data is exactly 5 bytes, then read proof from an account 186 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/zk-elgamal-proof/src/lib.rs#L46 */ 187 12 : if( ctx->instr->data_sz == 5UL ) { 188 : /* Case 1. Proof data from account data. */ 189 : 190 : /* Borrow the proof data account. 191 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/zk-elgamal-proof/src/lib.rs#L47-L48 */ 192 0 : fd_guarded_borrowed_account_t proof_data_acc = {0}; 193 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, 0UL, &proof_data_acc ); 194 : 195 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L48 */ 196 0 : accessed_accounts = 1U; 197 : 198 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L50-L61 199 : Note: it doesn't look like the ref code can throw any error. */ 200 0 : uint proof_data_offset = fd_uint_load_4_fast(&instr_data[1]); 201 : 202 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L62-L65 */ 203 0 : if( proof_data_offset+proof_data_sz > fd_borrowed_account_get_data_len( &proof_data_acc ) ) { 204 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 205 0 : } 206 0 : uchar const * proof_acc_data = fd_borrowed_account_get_data( &proof_data_acc ); 207 0 : context = fd_memcpy( buffer+CTX_HEAD_SZ, &proof_acc_data[proof_data_offset], proof_data_sz ); 208 12 : } else { 209 : /* Case 2. Proof data from ix data. */ 210 : 211 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L78-L82 212 : Note: instr_id is guaranteed to be valid, to access values in the arrays. */ 213 12 : if (ctx->instr->data_sz != 1 + proof_data_sz) { 214 3 : fd_log_collector_msg_literal( ctx, "invalid proof data" ); 215 3 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 216 3 : } 217 9 : context = instr_data + 1; 218 9 : } 219 : 220 : /* Verify individual ZKP 221 : https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L83-L86 */ 222 9 : void const * proof = context + fd_zksdk_context_sz[instr_id]; 223 9 : err = (*fd_zksdk_instr_verify_proof)( context, proof ); 224 9 : if( FD_UNLIKELY( err ) ) { 225 : //TODO: full log, including err 226 3 : fd_log_collector_msg_literal( ctx, "proof_verification failed" ); 227 3 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 228 3 : } 229 : 230 : /* Create context state if we have both proof_context and authority accounts. 231 : https://github.com/anza-xyz/agave/blob/v3.1.0-beta.0/programs/zk-elgamal-proof/src/lib.rs#L102-L106 */ 232 6 : if( instr_acc_cnt >= accessed_accounts + 2UL ) { 233 : /* Obtain the context_state_authority by borrowing the account temporarily in a local scope. 234 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/zk-elgamal-proof/src/lib.rs#L94-L99 235 : https://github.com/anza-xyz/agave/blob/v3.1.0-beta.0/programs/zk-elgamal-proof/src/lib.rs#L107-L110 */ 236 0 : fd_pubkey_t context_state_authority[1]; 237 0 : do { 238 0 : fd_guarded_borrowed_account_t _acc = {0}; 239 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, (ushort)(accessed_accounts+1), &_acc ); 240 0 : *context_state_authority = *_acc.acct->pubkey; 241 0 : } while(0); 242 : 243 : /* Borrow the proof context account 244 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/zk-elgamal-proof/src/lib.rs#L101-L102 */ 245 0 : fd_guarded_borrowed_account_t proof_context_acc = {0}; 246 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, accessed_accounts, &proof_context_acc ); 247 : 248 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L103-L105 */ 249 0 : if( FD_UNLIKELY( !fd_memeq( fd_borrowed_account_get_owner( &proof_context_acc ), &fd_solana_zk_elgamal_proof_program_id, sizeof(fd_pubkey_t) ) ) ) { 250 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; 251 0 : } 252 : 253 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L107-L112 */ 254 0 : if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &proof_context_acc ) >= CTX_HEAD_SZ && fd_borrowed_account_get_data( &proof_context_acc )[32] != 0 ) ) { 255 0 : return FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED; 256 0 : } 257 : 258 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L114-L115 259 : Note: nothing to do. */ 260 : 261 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L117-L119 */ 262 0 : ulong context_data_sx = CTX_HEAD_SZ + context_sz; 263 0 : if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &proof_context_acc ) != context_data_sx ) ) { 264 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 265 0 : } 266 : 267 : /* Check writability for any account that passes validation. 268 : Even with just 1 account, if it passes owner and data checks, it must be writable. 269 : https://github.com/anza-xyz/agave/blob/v3.1.0-beta.0/programs/zk-elgamal-proof/src/lib.rs#L112-L113 */ 270 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( ctx->instr, accessed_accounts ) ) ) { 271 0 : return FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED; 272 0 : } 273 : 274 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/zk-elgamal-proof/src/lib.rs#L121 */ 275 0 : fd_memcpy( buffer, context_state_authority, sizeof(fd_pubkey_t) ); // buffer[0..31] 276 0 : buffer[ 32 ] = instr_id; // buffer[32] 277 0 : if( ctx->instr->data_sz != 5UL ) { // buffer[33..] 278 0 : fd_memcpy( buffer+CTX_HEAD_SZ, context, context_sz ); 279 0 : } 280 0 : err = fd_borrowed_account_set_data_from_slice( &proof_context_acc, buffer, context_data_sx ); 281 0 : if( FD_UNLIKELY( err ) ) { 282 0 : return err; 283 0 : } 284 0 : } 285 : 286 6 : return FD_EXECUTOR_INSTR_SUCCESS; 287 6 : }