Line data Source code
1 : #include "fd_precompiles.h"
2 : #include "../fd_executor_err.h"
3 : #include "../../../ballet/keccak256/fd_keccak256.h"
4 : #include "../../../ballet/ed25519/fd_ed25519.h"
5 : #include "../../../ballet/secp256k1/fd_secp256k1.h"
6 : #include "../../../ballet/secp256r1/fd_secp256r1.h"
7 :
8 : /* Docs:
9 : https://docs.solana.com/developing/runtime-facilities/programs#ed25519-program
10 : https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program */
11 :
12 : /* There are 3 precompiles and 2 ways to serialize data.
13 : The most recent one seems are ed25519 and secp256r1 with 2 bytes per instruction,
14 : that works better with JS sdk even though it consumes a few bytes. */
15 : struct __attribute__((packed)) fd_precompile_sig_offsets {
16 : ushort sig_offset;
17 : ushort sig_instr_idx;
18 : ushort pubkey_offset;
19 : ushort pubkey_instr_idx;
20 : ushort msg_offset;
21 : ushort msg_data_sz;
22 : ushort msg_instr_idx;
23 : };
24 : typedef struct fd_precompile_sig_offsets fd_ed25519_signature_offsets_t;
25 : typedef struct fd_precompile_sig_offsets fd_secp256r1_signature_offsets_t;
26 :
27 : struct __attribute__((packed)) fd_precompile_one_byte_idx_sig_offsets {
28 : ushort sig_offset;
29 : uchar sig_instr_idx;
30 : ushort pubkey_offset;
31 : uchar pubkey_instr_idx;
32 : ushort msg_offset;
33 : ushort msg_data_sz;
34 : uchar msg_instr_idx;
35 : };
36 : typedef struct fd_precompile_one_byte_idx_sig_offsets fd_secp256k1_signature_offsets_t;
37 :
38 : /*
39 : Common
40 : */
41 :
42 0 : #define SIGNATURE_SERIALIZED_SIZE (64UL)
43 0 : #define SIGNATURE_OFFSETS_SERIALIZED_SIZE (14UL)
44 0 : #define SIGNATURE_OFFSETS_START (2UL)
45 : #define DATA_START (SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START)
46 :
47 : /*
48 : Custom
49 : */
50 :
51 0 : #define ED25519_PUBKEY_SERIALIZED_SIZE (32UL)
52 :
53 0 : #define SECP256R1_PUBKEY_SERIALIZED_SIZE (33UL)
54 :
55 0 : #define SECP256K1_PUBKEY_SERIALIZED_SIZE (20UL)
56 0 : #define SECP256K1_SIGNATURE_OFFSETS_SERIALIZED_SIZE (11UL)
57 0 : #define SECP256K1_SIGNATURE_OFFSETS_START (1UL)
58 : #define SECP256K1_DATA_START (SECP256K1_SIGNATURE_OFFSETS_SERIALIZED_SIZE + SECP256K1_SIGNATURE_OFFSETS_START)
59 :
60 : FD_STATIC_ASSERT( sizeof( fd_ed25519_signature_offsets_t )==SIGNATURE_OFFSETS_SERIALIZED_SIZE, fd_ballet );
61 : FD_STATIC_ASSERT( sizeof( fd_secp256k1_signature_offsets_t )==SECP256K1_SIGNATURE_OFFSETS_SERIALIZED_SIZE, fd_ballet );
62 :
63 : /*
64 : Common code
65 : */
66 :
67 : /* fd_precompile_get_instr_data fetches data across instructions.
68 : In Agave, the 2 precompiles have slightly different behavior:
69 : 1. Ed25519 has 16-bit instr index vs Secp256k1 has 8-bit
70 : 2. Ed25519 accepts instr index==0xFFFF as a special value to indicate
71 : the current instruction, Secp256k1 doesn't have this feature
72 : 3. Ed25519 always return InvalidDataOffsets, while Secp256k1 can
73 : return InvalidDataOffsets or InvalidSignature
74 : All these differences are completely useless, so we unify the logic.
75 : We handle the special case of index==0xFFFF as in Ed25519.
76 : We handle errors as in Secp256k1. */
77 : static inline int
78 : fd_precompile_get_instr_data( fd_exec_instr_ctx_t * ctx,
79 : ushort index,
80 : ushort offset,
81 : ushort sz,
82 0 : uchar const ** res ) {
83 0 : uchar const * data;
84 0 : ulong data_sz;
85 : /* The special value index==USHORT_MAX means current instruction.
86 : This feature has been introduced for ed25519, but not for secp256k1 where
87 : index is 1-byte only.
88 : So, fortunately, we can use the same function.
89 : https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L161-L163
90 : https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L1018 */
91 0 : if( index==USHORT_MAX ) {
92 :
93 : /* Use current instruction data */
94 0 : data = ctx->instr->data;
95 0 : data_sz = ctx->instr->data_sz;
96 :
97 0 : } else {
98 :
99 0 : if( FD_UNLIKELY( index>=ctx->txn_ctx->instr_info_cnt ) )
100 0 : return FD_EXECUTOR_PRECOMPILE_ERR_DATA_OFFSET;
101 :
102 0 : fd_instr_info_t const * instr = &ctx->txn_ctx->instr_infos[ index ];
103 0 : data = instr->data;
104 0 : data_sz = instr->data_sz;
105 :
106 0 : }
107 :
108 0 : if( FD_UNLIKELY( (ulong)offset+(ulong)sz > data_sz ) ) /* (offset+sz) in [0,2^17) */
109 0 : return FD_EXECUTOR_PRECOMPILE_ERR_SIGNATURE;
110 :
111 0 : *res = data + offset;
112 0 : return 0;
113 0 : }
114 :
115 : /*
116 : Ed25519
117 : */
118 :
119 : int
120 0 : fd_precompile_ed25519_verify( fd_exec_instr_ctx_t * ctx ) {
121 :
122 0 : uchar const * data = ctx->instr->data;
123 0 : ulong data_sz = ctx->instr->data_sz;
124 :
125 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L90-L96
126 : note: this part is really silly and in fact in leaves out the edge case [0, 0].
127 :
128 : Our implementation does the following:
129 : 1. assert that there's enough data to deser 1+ fd_ed25519_sig_offsets
130 : (in particular, data[0] is accessible)
131 : - in the unlikely case, check for the Agave edge case
132 : 2. if data[0]==0 return
133 : 3. compute and check expected size */
134 0 : if( FD_UNLIKELY( data_sz < DATA_START ) ) {
135 0 : if( FD_UNLIKELY( data_sz == 2 && data[0] == 0 ) ) {
136 0 : return FD_EXECUTOR_INSTR_SUCCESS;
137 0 : }
138 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
139 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
140 0 : }
141 :
142 0 : ulong sig_cnt = data[0];
143 0 : if( FD_UNLIKELY( sig_cnt==0 ) ) {
144 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
145 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
146 0 : }
147 :
148 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L97-L103 */
149 0 : ulong expected_data_size = sig_cnt * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
150 0 : if( FD_UNLIKELY( data_sz < expected_data_size ) ) {
151 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
152 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
153 0 : }
154 :
155 0 : ulong off = SIGNATURE_OFFSETS_START;
156 0 : for( ulong i = 0; i < sig_cnt; ++i ) {
157 0 : fd_ed25519_signature_offsets_t const * sigoffs = (const fd_ed25519_signature_offsets_t *) (data + off);
158 0 : off += SIGNATURE_OFFSETS_SERIALIZED_SIZE;
159 :
160 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L110-L112 */
161 : // ???
162 :
163 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L114-L121 */
164 0 : uchar const * sig = NULL;
165 0 : int err = fd_precompile_get_instr_data( ctx,
166 0 : sigoffs->sig_instr_idx,
167 0 : sigoffs->sig_offset,
168 0 : SIGNATURE_SERIALIZED_SIZE,
169 0 : &sig );
170 0 : if( FD_UNLIKELY( err ) ) {
171 0 : ctx->txn_ctx->custom_err = (uint)err;
172 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
173 0 : }
174 :
175 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L123-L124
176 : Note: we parse the signature as part of fd_ed25519_verify.
177 : Because of this, the return error code might be different from Agave in some edge cases. */
178 :
179 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L126-L133 */
180 0 : uchar const * pubkey = NULL;
181 0 : err = fd_precompile_get_instr_data( ctx,
182 0 : sigoffs->pubkey_instr_idx,
183 0 : sigoffs->pubkey_offset,
184 0 : ED25519_PUBKEY_SERIALIZED_SIZE,
185 0 : &pubkey );
186 0 : if( FD_UNLIKELY( err ) ) {
187 0 : ctx->txn_ctx->custom_err = (uint)err;
188 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
189 0 : }
190 :
191 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L135-L136
192 : Note: we parse the public key as part of fd_ed25519_verify.
193 : Because of this, the return error code might be different from Agave in some edge cases. */
194 :
195 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L138-L145 */
196 0 : uchar const * msg = NULL;
197 0 : ushort msg_sz = sigoffs->msg_data_sz;
198 0 : err = fd_precompile_get_instr_data( ctx,
199 0 : sigoffs->msg_instr_idx,
200 0 : sigoffs->msg_offset,
201 0 : msg_sz,
202 0 : &msg );
203 0 : if( FD_UNLIKELY( err ) ) {
204 0 : ctx->txn_ctx->custom_err = (uint)err;
205 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
206 0 : }
207 :
208 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/ed25519_instruction.rs#L147-L149 */
209 0 : fd_sha512_t sha[1];
210 0 : if( FD_UNLIKELY( fd_ed25519_verify( msg, msg_sz, sig, pubkey, sha )!=FD_ED25519_SUCCESS ) ) {
211 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_SIGNATURE;
212 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
213 0 : }
214 0 : }
215 :
216 0 : return FD_EXECUTOR_INSTR_SUCCESS;
217 0 : }
218 :
219 : /*
220 : Secp256K1
221 : */
222 :
223 : int
224 0 : fd_precompile_secp256k1_verify( fd_exec_instr_ctx_t * ctx ) {
225 :
226 0 : uchar const * data = ctx->instr->data;
227 0 : ulong data_sz = ctx->instr->data_sz;
228 :
229 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L934-L947
230 : see comment in ed25519, here the special case is [0] instead of [0, 0] */
231 0 : if( FD_UNLIKELY( data_sz < SECP256K1_DATA_START ) ) {
232 0 : if( FD_UNLIKELY( data_sz == 1 && data[0] == 0 ) ) {
233 0 : return FD_EXECUTOR_INSTR_SUCCESS;
234 0 : }
235 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
236 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
237 0 : }
238 :
239 : /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/sdk/src/secp256k1_instruction.rs#L938-L947 */
240 0 : ulong sig_cnt = data[0];
241 0 : if( FD_UNLIKELY( sig_cnt==0 && data_sz>1 ) ) {
242 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
243 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
244 0 : }
245 :
246 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L948-L953 */
247 0 : ulong expected_data_size = sig_cnt * SECP256K1_SIGNATURE_OFFSETS_SERIALIZED_SIZE + SECP256K1_SIGNATURE_OFFSETS_START;
248 0 : if( FD_UNLIKELY( data_sz < expected_data_size ) ) {
249 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
250 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
251 0 : }
252 :
253 0 : ulong off = SECP256K1_SIGNATURE_OFFSETS_START;
254 0 : for( ulong i = 0; i < sig_cnt; ++i ) {
255 0 : fd_secp256k1_signature_offsets_t const * sigoffs = (const fd_secp256k1_signature_offsets_t *) (data + off);
256 0 : off += SECP256K1_SIGNATURE_OFFSETS_SERIALIZED_SIZE;
257 :
258 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L960-L961 */
259 : // ???
260 :
261 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L963-L973
262 : Note: for whatever reason, Agave returns InvalidInstructionDataSize instead of InvalidDataOffsets.
263 : We just return the err as is. */
264 0 : uchar const * sig = NULL;
265 0 : int err = fd_precompile_get_instr_data( ctx,
266 0 : sigoffs->sig_instr_idx,
267 0 : sigoffs->sig_offset,
268 0 : SIGNATURE_SERIALIZED_SIZE + 1, /* extra byte is recovery id */
269 0 : &sig );
270 0 : if( FD_UNLIKELY( err ) ) {
271 0 : ctx->txn_ctx->custom_err = (uint)err;
272 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
273 0 : }
274 :
275 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L975-L981
276 : Note: we parse the signature and recovery id as part of fd_secp256k1_recover.
277 : Because of this, the return error code might be different from Agave in some edge cases. */
278 0 : int recovery_id = (int)sig[SIGNATURE_SERIALIZED_SIZE]; /* extra byte is recovery id */
279 :
280 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L983-L989 */
281 0 : uchar const * eth_address = NULL;
282 0 : err = fd_precompile_get_instr_data( ctx,
283 0 : sigoffs->pubkey_instr_idx,
284 0 : sigoffs->pubkey_offset,
285 0 : SECP256K1_PUBKEY_SERIALIZED_SIZE,
286 0 : ð_address );
287 0 : if( FD_UNLIKELY( err ) ) {
288 0 : ctx->txn_ctx->custom_err = (uint)err;
289 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
290 0 : }
291 :
292 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L991-L997 */
293 0 : uchar const * msg = NULL;
294 0 : ushort msg_sz = sigoffs->msg_data_sz;
295 0 : err = fd_precompile_get_instr_data( ctx,
296 0 : sigoffs->msg_instr_idx,
297 0 : sigoffs->msg_offset,
298 0 : msg_sz,
299 0 : &msg );
300 0 : if( FD_UNLIKELY( err ) ) {
301 0 : ctx->txn_ctx->custom_err = (uint)err;
302 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
303 0 : }
304 :
305 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L999-L1001 */
306 0 : uchar msg_hash[ FD_KECCAK256_HASH_SZ ];
307 0 : fd_keccak256_hash( msg, msg_sz, msg_hash );
308 :
309 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L1003-L1008 */
310 0 : uchar pubkey[64];
311 0 : if ( FD_UNLIKELY( fd_secp256k1_recover( pubkey, msg_hash, sig, recovery_id ) == NULL ) ) {
312 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_SIGNATURE;
313 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
314 0 : }
315 :
316 : /* https://github.com/anza-xyz/agave/blob/v1.18.12/sdk/src/secp256k1_instruction.rs#L1009-L1013 */
317 0 : uchar pubkey_hash[ FD_KECCAK256_HASH_SZ ];
318 0 : fd_keccak256_hash( pubkey, 64, pubkey_hash );
319 :
320 0 : if( FD_UNLIKELY( memcmp( eth_address, pubkey_hash+(FD_KECCAK256_HASH_SZ-SECP256K1_PUBKEY_SERIALIZED_SIZE), SECP256K1_PUBKEY_SERIALIZED_SIZE ) ) ) {
321 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_SIGNATURE;
322 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
323 0 : }
324 0 : }
325 :
326 0 : return FD_EXECUTOR_INSTR_SUCCESS;
327 0 : }
328 :
329 : /*
330 : Secp256r1
331 : */
332 :
333 : #ifdef FD_HAS_S2NBIGNUM
334 : int
335 0 : fd_precompile_secp256r1_verify( fd_exec_instr_ctx_t * ctx ) {
336 :
337 0 : uchar const * data = ctx->instr->data;
338 0 : ulong data_sz = ctx->instr->data_sz;
339 :
340 : /* ... */
341 0 : if( FD_UNLIKELY( data_sz < DATA_START ) ) {
342 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
343 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
344 0 : }
345 :
346 0 : ulong sig_cnt = data[0];
347 0 : if( FD_UNLIKELY( sig_cnt==0 ) ) {
348 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
349 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
350 0 : }
351 :
352 : /* ... */
353 0 : ulong expected_data_size = sig_cnt * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
354 0 : if( FD_UNLIKELY( data_sz < expected_data_size ) ) {
355 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_INSTR_DATA_SIZE;
356 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
357 0 : }
358 :
359 0 : ulong off = SIGNATURE_OFFSETS_START;
360 0 : for( ulong i = 0; i < sig_cnt; ++i ) {
361 0 : fd_secp256r1_signature_offsets_t const * sigoffs = (const fd_secp256r1_signature_offsets_t *) (data + off);
362 0 : off += SIGNATURE_OFFSETS_SERIALIZED_SIZE;
363 :
364 : /* ... */
365 0 : uchar const * sig = NULL;
366 0 : int err = fd_precompile_get_instr_data( ctx,
367 0 : sigoffs->sig_instr_idx,
368 0 : sigoffs->sig_offset,
369 0 : SIGNATURE_SERIALIZED_SIZE,
370 0 : &sig );
371 0 : if( FD_UNLIKELY( err ) ) {
372 0 : ctx->txn_ctx->custom_err = (uint)err;
373 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
374 0 : }
375 :
376 : /* ... */
377 0 : uchar const * pubkey = NULL;
378 0 : err = fd_precompile_get_instr_data( ctx,
379 0 : sigoffs->pubkey_instr_idx,
380 0 : sigoffs->pubkey_offset,
381 0 : SECP256R1_PUBKEY_SERIALIZED_SIZE,
382 0 : &pubkey );
383 0 : if( FD_UNLIKELY( err ) ) {
384 0 : ctx->txn_ctx->custom_err = (uint)err;
385 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
386 0 : }
387 :
388 : /* ... */
389 0 : uchar const * msg = NULL;
390 0 : ushort msg_sz = sigoffs->msg_data_sz;
391 0 : err = fd_precompile_get_instr_data( ctx,
392 0 : sigoffs->msg_instr_idx,
393 0 : sigoffs->msg_offset,
394 0 : msg_sz,
395 0 : &msg );
396 0 : if( FD_UNLIKELY( err ) ) {
397 0 : ctx->txn_ctx->custom_err = (uint)err;
398 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
399 0 : }
400 :
401 : /* ... */
402 0 : fd_sha256_t sha[1];
403 0 : if( FD_UNLIKELY( fd_secp256r1_verify( msg, msg_sz, sig, pubkey, sha )!=FD_SECP256R1_SUCCESS ) ) {
404 0 : ctx->txn_ctx->custom_err = FD_EXECUTOR_PRECOMPILE_ERR_SIGNATURE;
405 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
406 0 : }
407 0 : }
408 :
409 0 : return FD_EXECUTOR_INSTR_SUCCESS;
410 0 : }
411 : #else
412 : int
413 : fd_precompile_secp256r1_verify( FD_PARAM_UNUSED fd_exec_instr_ctx_t * ctx ) {
414 : return FD_EXECUTOR_INSTR_ERR_FATAL;
415 : }
416 : #endif
|