Line data Source code
1 : #include "fd_vm_syscall.h"
2 :
3 : #include "../../../ballet/ed25519/fd_curve25519.h"
4 :
5 : /* fd_compute_pda derives a PDA given:
6 : - the vm
7 : - the program id, which should be provided through either program_id or program_id_vaddr
8 : - This allows the user to pass in a program ID in either host address space or virtual address space.
9 : - If both are passed in, the host address space pubkey will be used.
10 : - the program_id pubkey in virtual address space. if the host address space pubkey is not given then the virtual address will be translated.
11 : - the seeds array vaddr
12 : - the seeds array count
13 : - an optional bump seed
14 : - out, the address in host address space where the PDA will be written to
15 :
16 : If the derived PDA was not a valid ed25519 point, then this function will return FD_VM_SYSCALL_ERR_INVALID_PDA.
17 :
18 : The derivation can also fail because of an out-of-bounds memory access, or an invalid seed list.
19 : */
20 : int
21 : fd_vm_derive_pda( fd_vm_t * vm,
22 : fd_pubkey_t const * program_id,
23 : void const * * seed_haddrs,
24 : ulong * seed_szs,
25 : ulong seeds_cnt,
26 : uchar * bump_seed,
27 1998 : fd_pubkey_t * out ) {
28 :
29 : /* This is a preflight check that is performed in Agave before deriving PDAs but after checking the seeds vaddr.
30 : Weirdly they do two checks for seeds cnt - one before PDA derivation, and one during. The first check will
31 : fail the preflight checks, and the second should just continue execution. We can't put this check one level up
32 : because it's only done after haddr conversion / alignment / size checks, which is done by the above line. We
33 : also can't rely on just the second check because we need execution to halt.
34 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L728-L730 */
35 1998 : if( FD_UNLIKELY( seeds_cnt>FD_VM_PDA_SEEDS_MAX ) ) {
36 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
37 0 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
38 0 : }
39 :
40 : /* This check does NOT halt execution within `fd_vm_syscall_sol_try_find_program_address`. This means
41 : that if the user provides 16 seeds (excluding the bump) in the `try_find_program_address` syscall,
42 : this same check below will be hit 255 times and deduct that many CUs. Very strange...
43 : https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/pubkey/src/lib.rs#L725-L727 */
44 1998 : if( FD_UNLIKELY( seeds_cnt+( !!bump_seed )>FD_VM_PDA_SEEDS_MAX ) ) {
45 1000 : return FD_VM_SYSCALL_ERR_INVALID_PDA;
46 1000 : }
47 :
48 5573 : for( ulong i=0UL; i<seeds_cnt; i++ ) {
49 : /* This is an unconditional check in Agave:
50 : https://github.com/anza-xyz/agave/blob/v2.1.6/sdk/pubkey/src/lib.rs#L729-L731
51 : */
52 4576 : if( FD_UNLIKELY( seed_szs[ i ]>FD_VM_PDA_SEED_MEM_MAX ) ) {
53 1 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
54 1 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
55 1 : }
56 4576 : }
57 :
58 997 : fd_sha256_init( vm->sha );
59 5569 : for( ulong i=0UL; i<seeds_cnt; i++ ) {
60 4572 : ulong seed_sz = seed_szs[ i ];
61 :
62 : /* If the seed length is 0, then we don't need to append anything. solana_bpf_loader_program::syscalls::translate_slice
63 : returns an empty array in host space when given an empty array, which means this seed will have no affect on the PDA.
64 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L737-L742 */
65 4572 : if( FD_UNLIKELY( !seed_sz ) ) {
66 137 : continue;
67 137 : }
68 4435 : void const * seed_haddr = seed_haddrs[ i ];
69 4435 : fd_sha256_append( vm->sha, seed_haddr, seed_sz );
70 4435 : }
71 :
72 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/pubkey/src/lib.rs#L738-L747 */
73 997 : if( bump_seed ) {
74 561 : fd_sha256_append( vm->sha, bump_seed, 1UL );
75 561 : }
76 :
77 997 : if( FD_LIKELY( program_id )) {
78 997 : fd_sha256_append( vm->sha, program_id, FD_PUBKEY_FOOTPRINT );
79 997 : } else {
80 0 : FD_LOG_ERR(( "No program id passed in" ));
81 0 : }
82 :
83 997 : fd_sha256_append( vm->sha, "ProgramDerivedAddress", 21UL ); /* TODO: use marker constant */
84 :
85 997 : fd_sha256_fini( vm->sha, out );
86 :
87 : /* A PDA is valid if it is not a valid ed25519 curve point.
88 : In most cases the user will have derived the PDA off-chain, or the PDA is a known signer. */
89 997 : if( FD_UNLIKELY( fd_ed25519_point_validate( out->key ) ) ) {
90 409 : return FD_VM_SYSCALL_ERR_INVALID_PDA;
91 409 : }
92 :
93 588 : return FD_VM_SUCCESS;
94 997 : }
95 :
96 : /* fd_vm_translate_and_check_program_address_inputs is responsible for doing
97 : the preflight checks and translation of the seeds and program id.
98 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L719 */
99 :
100 : int
101 : fd_vm_translate_and_check_program_address_inputs( fd_vm_t * vm,
102 : ulong seeds_vaddr,
103 : ulong seeds_cnt,
104 : ulong program_id_vaddr,
105 : void const * * out_seed_haddrs,
106 : ulong * out_seed_szs,
107 : fd_pubkey_t const * * out_program_id,
108 750 : uchar is_syscall ) {
109 :
110 1486 : fd_vm_vec_t const * untranslated_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, seeds_vaddr, FD_VM_ALIGN_RUST_SLICE_U8_REF,
111 1486 : fd_ulong_sat_mul( seeds_cnt, FD_VM_VEC_SIZE ) );
112 :
113 : /* This is a preflight check that is performed in Agave before deriving PDAs but after checking the seeds vaddr.
114 : When called to help CPI signer translation, this logs an
115 : instruction error:
116 : https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/syscalls/cpi.rs#L538-L540
117 : However, when called from a syscall, this logs a syscall error:
118 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L728-L730 */
119 738 : if( FD_UNLIKELY( seeds_cnt>FD_VM_PDA_SEEDS_MAX ) ) {
120 2 : if( is_syscall ) {
121 1 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
122 1 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
123 1 : } else {
124 1 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED );
125 1 : return FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED;
126 1 : }
127 2 : }
128 3982 : for( ulong i=0UL; i<seeds_cnt; i++ ) {
129 3251 : ulong seed_sz = untranslated_seeds[i].len;
130 : /* Another preflight check
131 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L734-L736
132 : When this function is called from syscalls, we would like to
133 : abort when exceeding SEED_MEM_MAX.
134 : However, when we reuse this function from CPI for signer
135 : translation, this check doesn't exist. Sigh.
136 : Instead, the check is delayed until deriving PDA.
137 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L543
138 : */
139 3251 : if( FD_UNLIKELY( seed_sz>FD_VM_PDA_SEED_MEM_MAX && is_syscall ) ) {
140 3 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
141 3 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
142 3 : }
143 6494 : void const * seed_haddr = FD_VM_MEM_SLICE_HADDR_LD( vm, untranslated_seeds[i].addr, FD_VM_ALIGN_RUST_U8, seed_sz );
144 0 : out_seed_haddrs[ i ] = seed_haddr;
145 6494 : out_seed_szs [ i ] = seed_sz;
146 6494 : }
147 :
148 : /* We only want to do this check if the user requires it. */
149 731 : if( out_program_id ) {
150 1309 : *out_program_id = FD_VM_MEM_HADDR_LD( vm, program_id_vaddr, FD_VM_ALIGN_RUST_PUBKEY, FD_PUBKEY_FOOTPRINT );
151 1309 : }
152 730 : return FD_VM_SUCCESS;
153 731 : }
154 :
155 : /* fd_vm_syscall_sol_create_program_address is the entrypoint for the sol_create_program_address syscall:
156 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L729
157 :
158 : The main semantic difference between Firedancer's implementation and Solana's is that Solana
159 : translates all the seed pointers before doing any computation, while Firedancer translates
160 : the seed pointers on-demand. This is to avoid an extra memory allocation.
161 :
162 : This syscall creates a valid program derived address without searching for a bump seed.
163 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
164 : checking if the resulting hash is a valid ed25519 curve point.
165 :
166 : There is roughly a 50% chance of this syscall failing, due to the hash not being
167 : a valid curve point, for any given collection of seeds.
168 :
169 : Parameters:
170 : - _vm: a pointer to the VM
171 : - seed_vaddr: the address of the first element of an iovec-like scatter of a seed byte array in VM address space
172 : - seed_cnt: the number of scatter elements
173 : - program_id_vaddr: the address of the program id pubkey in VM address space
174 : - out_vaddr: the address of the memory location where the resulting derived PDA will be written to, in VM address space, if the syscall is successful
175 : - r5: unused
176 : */
177 : int
178 : fd_vm_syscall_sol_create_program_address( /**/ void * _vm,
179 : /**/ ulong seeds_vaddr,
180 : /**/ ulong seeds_cnt,
181 : /**/ ulong program_id_vaddr,
182 : /**/ ulong out_vaddr,
183 149 : FD_PARAM_UNUSED ulong r5 ) {
184 149 : fd_vm_t * vm = (fd_vm_t *)_vm;
185 :
186 :
187 149 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
188 :
189 0 : void const * seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
190 149 : ulong seed_szs [ FD_VM_PDA_SEEDS_MAX ];
191 149 : fd_pubkey_t const * program_id;
192 :
193 149 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
194 149 : seeds_vaddr,
195 149 : seeds_cnt,
196 149 : program_id_vaddr,
197 149 : seed_haddrs,
198 149 : seed_szs,
199 149 : &program_id,
200 149 : 1U );
201 149 : if( FD_UNLIKELY( err ) ) {
202 6 : return err;
203 6 : }
204 :
205 143 : fd_pubkey_t derived[1];
206 143 : uchar * bump_seed = NULL;
207 143 : err = fd_vm_derive_pda( vm, program_id, seed_haddrs, seed_szs, seeds_cnt, bump_seed, derived );
208 : /* Agave does their translation before the calculation, so if the translation fails we should fail
209 : the syscall.
210 :
211 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L744-L750 */
212 143 : if ( FD_UNLIKELY( err != FD_VM_SUCCESS ) ) {
213 :
214 : /* Place 1 in r0 and successfully exit if we failed to derive a PDA
215 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L753 */
216 61 : if ( FD_LIKELY( err == FD_VM_SYSCALL_ERR_INVALID_PDA ) ) {
217 61 : vm->reg[0] = 1UL;
218 61 : return FD_VM_SUCCESS;
219 61 : }
220 :
221 0 : return err;
222 61 : }
223 :
224 82 : uchar * address = FD_VM_HADDR_QUERY_U8_SLICE( vm, out_vaddr, FD_PUBKEY_FOOTPRINT );
225 82 : memcpy( address, derived->uc, FD_PUBKEY_FOOTPRINT );
226 :
227 : /* Success */
228 82 : vm->reg[0] = 0UL;
229 82 : return FD_VM_SUCCESS;
230 82 : }
231 :
232 : /* fd_vm_syscall_sol_try_find_program_address is the entrypoint for the sol_try_find_program_address syscall:
233 : https://github.com/anza-xyz/agave/blob/v2.1.1/programs/bpf_loader/src/syscalls/mod.rs#L791
234 :
235 : This syscall creates a valid program derived address, searching for a valid ed25519 curve point by
236 : iterating through 255 possible bump seeds.
237 :
238 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
239 : checking if the resulting hash is a valid ed25519 curve point.
240 : */
241 : int
242 : fd_vm_syscall_sol_try_find_program_address( void * _vm,
243 : ulong seeds_vaddr,
244 : ulong seeds_cnt,
245 : ulong program_id_vaddr,
246 : ulong out_vaddr,
247 298 : ulong out_bump_seed_vaddr ) {
248 298 : fd_vm_t * vm = (fd_vm_t *)_vm;
249 :
250 : /* Costs the same as a create_program_address call.. weird but that is the protocol. */
251 298 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
252 :
253 : /* Similar to create_program_address but appends a 1 byte nonce that
254 : decrements from 255 down to 1 until a valid PDA is found.
255 :
256 : TODO: Solana Labs recomputes the SHA hash for each iteration here. We
257 : can leverage SHA's streaming properties to precompute all but the last
258 : two blocks (1 data, 0 or 1 padding). PROBABLY NEED TO ADD CHECKPT / RESTORE
259 : CALLS TO SHA TO SUPPORT THIS)*/
260 :
261 0 : uchar bump_seed[1];
262 :
263 : /* First we need to do the preflight checks */
264 298 : void const * seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
265 298 : ulong seed_szs [ FD_VM_PDA_SEEDS_MAX ];
266 298 : fd_pubkey_t const * program_id;
267 :
268 298 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
269 298 : seeds_vaddr,
270 298 : seeds_cnt,
271 298 : program_id_vaddr,
272 298 : seed_haddrs,
273 298 : seed_szs,
274 298 : &program_id,
275 298 : 1U );
276 298 : if( FD_UNLIKELY( err ) ) {
277 5 : return err;
278 5 : }
279 :
280 1564 : for( ulong i=0UL; i<255UL; i++ ) {
281 1561 : bump_seed[0] = (uchar)(255UL - i);
282 :
283 1561 : fd_pubkey_t derived[1];
284 1561 : err = fd_vm_derive_pda( vm, program_id, seed_haddrs, seed_szs, seeds_cnt, bump_seed, derived );
285 :
286 : /* Stop looking if we have found a valid PDA */
287 1561 : if( FD_LIKELY( err==FD_VM_SUCCESS ) ) {
288 :
289 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/programs/bpf_loader/src/syscalls/mod.rs#L919-L924 */
290 287 : fd_vm_haddr_query_t bump_seed_ref_query = {
291 287 : .vaddr = out_bump_seed_vaddr,
292 287 : .align = FD_VM_ALIGN_RUST_U8,
293 287 : .sz = 1UL,
294 287 : .is_slice = 0,
295 287 : };
296 :
297 287 : fd_vm_haddr_query_t address_query = {
298 287 : .vaddr = out_vaddr,
299 287 : .align = FD_VM_ALIGN_RUST_U8,
300 287 : .sz = FD_PUBKEY_FOOTPRINT,
301 287 : .is_slice = 1,
302 287 : };
303 :
304 287 : fd_vm_haddr_query_t * queries[] = { &bump_seed_ref_query, &address_query };
305 287 : FD_VM_TRANSLATE_MUT( vm, queries );
306 :
307 283 : memcpy( address_query.haddr, derived->uc, sizeof(fd_pubkey_t) );
308 283 : memcpy( bump_seed_ref_query.haddr, bump_seed, sizeof(uchar) );
309 :
310 283 : vm->reg[0] = 0UL;
311 283 : return FD_VM_SUCCESS;
312 1274 : } else if( FD_UNLIKELY( err!=FD_VM_SYSCALL_ERR_INVALID_PDA ) ) {
313 0 : return err;
314 0 : }
315 :
316 2545 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
317 2545 : }
318 :
319 3 : vm->reg[0] = 1UL;
320 3 : return FD_VM_SUCCESS;
321 293 : }
|