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 8601 : 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 8601 : 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 8601 : if( FD_UNLIKELY( seeds_cnt+( !!bump_seed )>FD_VM_PDA_SEEDS_MAX ) ) {
45 7875 : return FD_VM_SYSCALL_ERR_INVALID_PDA;
46 7875 : }
47 :
48 726 : fd_sha256_init( vm->sha );
49 3972 : for( ulong i=0UL; i<seeds_cnt; i++ ) {
50 3246 : ulong seed_sz = seed_szs[ i ];
51 :
52 : /* If the seed length is 0, then we don't need to append anything. solana_bpf_loader_program::syscalls::translate_slice
53 : returns an empty array in host space when given an empty array, which means this seed will have no affect on the PDA.
54 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L737-L742 */
55 3246 : if( FD_UNLIKELY( !seed_sz ) ) {
56 1536 : continue;
57 1536 : }
58 1710 : void const * seed_haddr = seed_haddrs[ i ];
59 1710 : fd_sha256_append( vm->sha, seed_haddr, seed_sz );
60 1710 : }
61 :
62 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/pubkey/src/lib.rs#L738-L747 */
63 726 : if( bump_seed ) {
64 498 : fd_sha256_append( vm->sha, bump_seed, 1UL );
65 498 : }
66 :
67 726 : if( FD_LIKELY( program_id )) {
68 726 : fd_sha256_append( vm->sha, program_id, FD_PUBKEY_FOOTPRINT );
69 726 : } else {
70 0 : FD_LOG_ERR(( "No program id passed in" ));
71 0 : }
72 :
73 726 : fd_sha256_append( vm->sha, "ProgramDerivedAddress", 21UL ); /* TODO: use marker constant */
74 :
75 726 : fd_sha256_fini( vm->sha, out );
76 :
77 : /* A PDA is valid if it is not a valid ed25519 curve point.
78 : In most cases the user will have derived the PDA off-chain, or the PDA is a known signer. */
79 726 : if( FD_UNLIKELY( fd_ed25519_point_validate( out->key ) ) ) {
80 351 : return FD_VM_SYSCALL_ERR_INVALID_PDA;
81 351 : }
82 :
83 375 : return FD_VM_SUCCESS;
84 726 : }
85 :
86 : /* fd_vm_translate_and_check_program_address_inputs is responsible for doing
87 : the preflight checks and translation of the seeds and program id.
88 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L719 */
89 :
90 : int
91 : fd_vm_translate_and_check_program_address_inputs( fd_vm_t * vm,
92 : ulong seeds_vaddr,
93 : ulong seeds_cnt,
94 : ulong program_id_vaddr,
95 : void const * * out_seed_haddrs,
96 : ulong * out_seed_szs,
97 618 : fd_pubkey_t const * * out_program_id ) {
98 :
99 1197 : fd_vm_vec_t const * untranslated_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, seeds_vaddr, FD_VM_ALIGN_RUST_SLICE_U8_REF,
100 1197 : fd_ulong_sat_mul( seeds_cnt, FD_VM_VEC_SIZE ) );
101 :
102 : /* This is a preflight check that is performed in Agave before deriving PDAs but after checking the seeds vaddr.
103 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L728-L730 */
104 585 : if( FD_UNLIKELY( seeds_cnt>FD_VM_PDA_SEEDS_MAX ) ) {
105 3 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
106 3 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
107 :
108 3 : }
109 3732 : for( ulong i=0UL; i<seeds_cnt; i++ ) {
110 3153 : ulong seed_sz = untranslated_seeds[i].len;
111 : /* Another preflight check
112 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L734-L736 */
113 3153 : if( FD_UNLIKELY( seed_sz>FD_VM_PDA_SEED_MEM_MAX ) ) {
114 3 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
115 3 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
116 3 : }
117 6300 : void const * seed_haddr = FD_VM_MEM_SLICE_HADDR_LD( vm, untranslated_seeds[i].addr, FD_VM_ALIGN_RUST_U8, seed_sz );
118 0 : out_seed_haddrs[ i ] = seed_haddr;
119 6300 : out_seed_szs [ i ] = seed_sz;
120 6300 : }
121 :
122 : /* We only want to do this check if the user requires it. */
123 579 : if( out_program_id ) {
124 1350 : *out_program_id = FD_VM_MEM_HADDR_LD( vm, program_id_vaddr, FD_VM_ALIGN_RUST_PUBKEY, FD_PUBKEY_FOOTPRINT );
125 1350 : }
126 570 : return 0;
127 579 : }
128 :
129 : /* fd_vm_syscall_sol_create_program_address is the entrypoint for the sol_create_program_address syscall:
130 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L729
131 :
132 : The main semantic difference between Firedancer's implementation and Solana's is that Solana
133 : translates all the seed pointers before doing any computation, while Firedancer translates
134 : the seed pointers on-demand. This is to avoid an extra memory allocation.
135 :
136 : This syscall creates a valid program derived address without searching for a bump seed.
137 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
138 : checking if the resulting hash is a valid ed25519 curve point.
139 :
140 : There is roughly a 50% chance of this syscall failing, due to the hash not being
141 : a valid curve point, for any given collection of seeds.
142 :
143 : Parameters:
144 : - _vm: a pointer to the VM
145 : - seed_vaddr: the address of the first element of an iovec-like scatter of a seed byte array in VM address space
146 : - seed_cnt: the number of scatter elements
147 : - program_id_vaddr: the address of the program id pubkey in VM address space
148 : - 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
149 : - r5: unused
150 : - _ret: a pointer to the return value of the syscall
151 : */
152 : int
153 : fd_vm_syscall_sol_create_program_address( /**/ void * _vm,
154 : /**/ ulong seeds_vaddr,
155 : /**/ ulong seeds_cnt,
156 : /**/ ulong program_id_vaddr,
157 : /**/ ulong out_vaddr,
158 : FD_PARAM_UNUSED ulong r5,
159 138 : /**/ ulong * _ret ) {
160 138 : fd_vm_t * vm = (fd_vm_t *)_vm;
161 :
162 138 : uchar * bump_seed = NULL;
163 :
164 138 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
165 :
166 0 : void const * seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
167 138 : ulong seed_szs [ FD_VM_PDA_SEEDS_MAX ];
168 138 : fd_pubkey_t const * program_id;
169 :
170 138 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
171 138 : seeds_vaddr,
172 138 : seeds_cnt,
173 138 : program_id_vaddr,
174 138 : seed_haddrs,
175 138 : seed_szs,
176 138 : &program_id );
177 138 : if( FD_UNLIKELY( err ) ) {
178 33 : *_ret = 0UL;
179 33 : return err;
180 33 : }
181 :
182 105 : fd_pubkey_t derived[1];
183 105 : err = fd_vm_derive_pda( vm, program_id, seed_haddrs, seed_szs, seeds_cnt, bump_seed, derived );
184 : /* Agave does their translation before the calculation, so if the translation fails we should fail
185 : the syscall.
186 :
187 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L744-L750 */
188 105 : if ( FD_UNLIKELY( err != FD_VM_SUCCESS ) ) {
189 :
190 : /* Place 1 in r0 and successfully exit if we failed to derive a PDA
191 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L753 */
192 0 : if ( FD_LIKELY( err == FD_VM_SYSCALL_ERR_INVALID_PDA ) ) {
193 0 : *_ret = 1UL;
194 0 : return FD_VM_SUCCESS;
195 0 : }
196 :
197 0 : return err;
198 0 : }
199 :
200 105 : fd_pubkey_t * out_haddr = FD_VM_MEM_HADDR_ST( vm, out_vaddr, FD_VM_ALIGN_RUST_U8, FD_PUBKEY_FOOTPRINT );
201 105 : memcpy( out_haddr, derived->uc, FD_PUBKEY_FOOTPRINT );
202 :
203 : /* Success */
204 105 : *_ret = 0UL;
205 105 : return FD_VM_SUCCESS;
206 105 : }
207 :
208 : /* fd_vm_syscall_sol_try_find_program_address is the entrypoint for the sol_try_find_program_address syscall:
209 : https://github.com/anza-xyz/agave/blob/v2.1.1/programs/bpf_loader/src/syscalls/mod.rs#L791
210 :
211 : This syscall creates a valid program derived address, searching for a valid ed25519 curve point by
212 : iterating through 255 possible bump seeds.
213 :
214 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
215 : checking if the resulting hash is a valid ed25519 curve point.
216 : */
217 : int
218 : fd_vm_syscall_sol_try_find_program_address( void * _vm,
219 : ulong seeds_vaddr,
220 : ulong seeds_cnt,
221 : ulong program_id_vaddr,
222 : ulong out_vaddr,
223 : ulong out_bump_seed_vaddr,
224 357 : ulong * _ret ) {
225 357 : fd_vm_t * vm = (fd_vm_t *)_vm;
226 :
227 : /* Costs the same as a create_program_address call.. weird but that is the protocol. */
228 357 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
229 :
230 : /* Similar to create_program_address but appends a 1 byte nonce that
231 : decrements from 255 down to 1 until a valid PDA is found.
232 :
233 : TODO: Solana Labs recomputes the SHA hash for each iteration here. We
234 : can leverage SHA's streaming properties to precompute all but the last
235 : two blocks (1 data, 0 or 1 padding). PROBABLY NEED TO ADD CHECKPT / RESTORE
236 : CALLS TO SHA TO SUPPORT THIS)*/
237 :
238 0 : uchar bump_seed[1];
239 :
240 : /* First we need to do the preflight checks */
241 357 : void const * seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
242 357 : ulong seed_szs [ FD_VM_PDA_SEEDS_MAX ];
243 357 : fd_pubkey_t const * program_id;
244 :
245 357 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
246 357 : seeds_vaddr,
247 357 : seeds_cnt,
248 357 : program_id_vaddr,
249 357 : seed_haddrs,
250 357 : seed_szs,
251 357 : &program_id );
252 357 : if( FD_UNLIKELY( err ) ) {
253 15 : *_ret = 0UL;
254 15 : return err;
255 15 : }
256 :
257 8397 : for( ulong i=0UL; i<255UL; i++ ) {
258 8373 : bump_seed[0] = (uchar)(255UL - i);
259 :
260 8373 : fd_pubkey_t derived[1];
261 8373 : err = fd_vm_derive_pda( vm, program_id, seed_haddrs, seed_szs, seeds_cnt, bump_seed, derived );
262 8373 : if( FD_LIKELY( err==FD_VM_SUCCESS ) ) {
263 : /* Stop looking if we have found a valid PDA */
264 147 : err = 0;
265 147 : fd_pubkey_t * out_haddr = FD_VM_MEM_HADDR_ST_( vm, out_vaddr, FD_VM_ALIGN_RUST_U8, sizeof(fd_pubkey_t), &err );
266 147 : if( FD_UNLIKELY( 0 != err ) ) {
267 3 : *_ret = 0UL;
268 3 : return err;
269 3 : }
270 144 : uchar * out_bump_seed_haddr = FD_VM_MEM_HADDR_ST_( vm, out_bump_seed_vaddr, FD_VM_ALIGN_RUST_U8, 1UL, &err );
271 144 : if( FD_UNLIKELY( 0 != err ) ) {
272 0 : *_ret = 0UL;
273 0 : return err;
274 0 : }
275 :
276 : /* Do the overlap check, which is only included for this syscall */
277 144 : FD_VM_MEM_CHECK_NON_OVERLAPPING( vm, out_vaddr, 32UL, out_bump_seed_vaddr, 1UL );
278 :
279 90 : memcpy( out_haddr, derived, sizeof(fd_pubkey_t) );
280 90 : *out_bump_seed_haddr = (uchar)*bump_seed;
281 :
282 90 : *_ret = 0UL;
283 90 : return FD_VM_SUCCESS;
284 8226 : } else if( FD_UNLIKELY( err!=FD_VM_SYSCALL_ERR_INVALID_PDA ) ) {
285 0 : return err;
286 0 : }
287 :
288 16281 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
289 16281 : }
290 :
291 24 : *_ret = 1UL;
292 24 : return FD_VM_SUCCESS;
293 342 : }
|