Line data Source code
1 : #include "fd_vm_syscall.h"
2 :
3 : #include "../../../ballet/ed25519/fd_curve25519.h"
4 :
5 : /* The maximum number of seeds a PDA can have
6 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/sdk/program/src/pubkey.rs#L21 */
7 : #define FD_VM_PDA_SEEDS_MAX (16UL)
8 : /* The maximum length of a PDA seed
9 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/sdk/program/src/pubkey.rs#L19 */
10 : #define FD_VM_PDA_SEED_MEM_MAX (32UL)
11 :
12 : /* fd_compute_pda derives a PDA given:
13 : - the vm
14 : - the program id, which should be provided through either program_id or program_id_vaddr
15 : - This allows the user to pass in a program ID in either host address space or virtual address space.
16 : - If both are passed in, the host address space pubkey will be used.
17 : - the program_id pubkey in virtual address space. if the host address space pubkey is not given then the virtual address will be translated.
18 : - the seeds array vaddr
19 : - the seeds array count
20 : - an optional bump seed
21 : - out, the address in host address space where the PDA will be written to
22 :
23 : If the derived PDA was not a valid ed25519 point, then this function will return FD_VM_ERR_INVALID_PDA.
24 :
25 : The derivation can also fail because of an out-of-bounds memory access, or an invalid seed list.
26 : */
27 : int
28 : fd_vm_derive_pda( fd_vm_t * vm,
29 : fd_pubkey_t const * program_id,
30 : ulong program_id_vaddr,
31 : ulong seeds_vaddr,
32 : ulong seeds_cnt,
33 : uchar * bump_seed,
34 11148 : fd_pubkey_t * out ) {
35 :
36 22257 : fd_vm_vec_t const * seeds_haddr = FD_VM_MEM_SLICE_HADDR_LD( vm, seeds_vaddr, FD_VM_ALIGN_RUST_SLICE_U8_REF,
37 22257 : fd_ulong_sat_mul( seeds_cnt, FD_VM_VEC_SIZE ) );
38 :
39 : /* This is a preflight check that is performed in Agave before deriving PDAs but after checking the seeds vaddr.
40 : Weirdly they do two checks for seeds cnt - one before PDA derivation, and one during. The first check will
41 : fail the preflight checks, and the second should just continue execution. We can't put this check one level up
42 : because it's only done after haddr conversion / alignment / size checks, which is done by the above line. We
43 : also can't rely on just the second check because we need execution to halt.
44 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L728-L730 */
45 11115 : if( FD_UNLIKELY( seeds_cnt>FD_VM_PDA_SEEDS_MAX ) ) {
46 3 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_ERR_SYSCALL_BAD_SEEDS );
47 3 : return FD_VM_ERR_INVAL;
48 3 : }
49 :
50 : /* This check does NOT halt execution within `fd_vm_syscall_sol_try_find_program_address`. This means
51 : that if the user provides 16 seeds (excluding the bump) in the `try_find_program_address` syscall,
52 : this same check below will be hit 255 times and deduct that many CUs. Very strange...
53 : https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/pubkey/src/lib.rs#L725-L727 */
54 11112 : if( FD_UNLIKELY( seeds_cnt+( !!bump_seed )>FD_VM_PDA_SEEDS_MAX ) ) {
55 10710 : return FD_VM_ERR_INVALID_PDA;
56 10710 : }
57 :
58 402 : fd_sha256_init( vm->sha );
59 1593 : for ( ulong i=0UL; i<seeds_cnt; i++ ) {
60 1191 : ulong seed_sz = seeds_haddr[i].len;
61 :
62 : /* Another preflight check
63 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L734-L736 */
64 1191 : if( FD_UNLIKELY( seed_sz>FD_VM_PDA_SEED_MEM_MAX ) ) {
65 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_ERR_SYSCALL_BAD_SEEDS );
66 0 : return FD_VM_ERR_INVAL;
67 0 : }
68 :
69 : /* If the seed length is 0, then we don't need to append anything. solana_bpf_loader_program::syscalls::translate_slice
70 : returns an empty array in host space when given an empty array, which means this seed will have no affect on the PDA.
71 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mod.rs#L737-L742 */
72 1191 : if ( FD_UNLIKELY( seed_sz==0 ) ) {
73 0 : continue;
74 0 : }
75 2382 : void const * seed_haddr = FD_VM_MEM_SLICE_HADDR_LD( vm, seeds_haddr[i].addr, FD_VM_ALIGN_RUST_U8, seed_sz );
76 0 : fd_sha256_append( vm->sha, seed_haddr, seed_sz );
77 2382 : }
78 :
79 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/pubkey/src/lib.rs#L738-L747 */
80 402 : if( bump_seed ) {
81 174 : fd_sha256_append( vm->sha, bump_seed, 1UL );
82 174 : }
83 :
84 402 : if ( program_id != NULL ) {
85 123 : fd_sha256_append( vm->sha, program_id, FD_PUBKEY_FOOTPRINT );
86 279 : } else {
87 837 : fd_pubkey_t const * program_id_translated = FD_VM_MEM_HADDR_LD( vm, program_id_vaddr, FD_VM_ALIGN_RUST_PUBKEY, FD_PUBKEY_FOOTPRINT );
88 279 : fd_sha256_append( vm->sha, program_id_translated, FD_PUBKEY_FOOTPRINT );
89 837 : }
90 :
91 402 : fd_sha256_append( vm->sha, "ProgramDerivedAddress", 21UL ); /* TODO: use marker constant */
92 :
93 402 : fd_sha256_fini( vm->sha, out );
94 :
95 : /* A PDA is valid if it is not a valid ed25519 curve point.
96 : In most cases the user will have derived the PDA off-chain, or the PDA is a known signer. */
97 402 : if( FD_UNLIKELY( fd_ed25519_point_validate( out->key ) ) ) {
98 84 : return FD_VM_ERR_INVALID_PDA;
99 84 : }
100 :
101 318 : return FD_VM_SUCCESS;
102 402 : }
103 :
104 : /* fd_vm_syscall_sol_create_program_address is the entrypoint for the sol_create_program_address syscall:
105 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L729
106 :
107 : The main semantic difference between Firedancer's implementation and Solana's is that Solana
108 : translates all the seed pointers before doing any computation, while Firedancer translates
109 : the seed pointers on-demand. This is to avoid an extra memory allocation.
110 :
111 : This syscall creates a valid program derived address without searching for a bump seed.
112 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
113 : checking if the resulting hash is a valid ed25519 curve point.
114 :
115 : There is roughly a 50% chance of this syscall failing, due to the hash not being
116 : a valid curve point, for any given collection of seeds.
117 :
118 : Parameters:
119 : - _vm: a pointer to the VM
120 : - seed_vaddr: the address of the first element of an iovec-like scatter of a seed byte array in VM address space
121 : - seed_cnt: the number of scatter elements
122 : - program_id_vaddr: the address of the program id pubkey in VM address space
123 : - 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
124 : - r5: unused
125 : - _ret: a pointer to the return value of the syscall
126 : */
127 : int
128 : fd_vm_syscall_sol_create_program_address( /**/ void * _vm,
129 : /**/ ulong seeds_vaddr,
130 : /**/ ulong seeds_cnt,
131 : /**/ ulong program_id_vaddr,
132 : /**/ ulong out_vaddr,
133 : FD_PARAM_UNUSED ulong r5,
134 138 : /**/ ulong * _ret ) {
135 138 : fd_vm_t * vm = (fd_vm_t *)_vm;
136 :
137 138 : uchar * bump_seed = NULL;
138 :
139 138 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
140 :
141 0 : fd_pubkey_t derived[1];
142 138 : int err = fd_vm_derive_pda( vm, NULL, program_id_vaddr, seeds_vaddr, seeds_cnt, bump_seed, derived );
143 : /* Agave does their translation before the calculation, so if the translation fails we should fail
144 : the syscall.
145 :
146 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L744-L750 */
147 138 : if ( FD_UNLIKELY( err != FD_VM_SUCCESS ) ) {
148 :
149 : /* Place 1 in r0 and successfully exit if we failed to derive a PDA
150 : https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L753 */
151 33 : if ( FD_LIKELY( err == FD_VM_ERR_INVALID_PDA ) ) {
152 0 : *_ret = 1UL;
153 0 : return FD_VM_SUCCESS;
154 0 : }
155 :
156 33 : return err;
157 33 : }
158 :
159 105 : fd_pubkey_t * out_haddr = FD_VM_MEM_HADDR_ST( vm, out_vaddr, FD_VM_ALIGN_RUST_U8, FD_PUBKEY_FOOTPRINT );
160 105 : memcpy( out_haddr, derived->uc, FD_PUBKEY_FOOTPRINT );
161 :
162 : /* Success */
163 105 : *_ret = 0UL;
164 105 : return FD_VM_SUCCESS;
165 210 : }
166 :
167 : /* fd_vm_syscall_sol_try_find_program_address is the entrypoint for the sol_try_find_program_address syscall:
168 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/mod.rs#L727
169 :
170 : This syscall creates a valid program derived address, searching for a valid ed25519 curve point by
171 : iterating through 255 possible bump seeds.
172 :
173 : It does this by hashing all the seeds, the program id, and the PDA marker, and then
174 : checking if the resulting hash is a valid ed25519 curve point.
175 : */
176 : int
177 : fd_vm_syscall_sol_try_find_program_address( void * _vm,
178 : ulong seeds_vaddr,
179 : ulong seeds_cnt,
180 : ulong program_id_vaddr,
181 : ulong out_vaddr,
182 : ulong out_bump_seed_vaddr,
183 135 : ulong * _ret ) {
184 135 : fd_vm_t * vm = (fd_vm_t *)_vm;
185 :
186 : /* Costs the same as a create_program_address call.. weird but that is the protocol. */
187 135 : FD_VM_CU_UPDATE( vm, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
188 :
189 : /* Similar to create_program_address but appends a 1 byte nonce that
190 : decrements from 255 down to 1 until a valid PDA is found.
191 :
192 : TODO: Solana Labs recomputes the SHA hash for each iteration here. We
193 : can leverage SHA's streaming properties to precompute all but the last
194 : two blocks (1 data, 0 or 1 padding). PROBABLY NEED TO ADD CHECKPT / RESTORE
195 : CALLS TO SHA TO SUPPORT THIS)*/
196 :
197 0 : uchar bump_seed[1];
198 :
199 : /* Agave performs preflight checks on the seed lengths and translation before doing any
200 : derivation and deducting CUs, whereas we do it on the fly in a single iteration.
201 : To maintain CU conformance at a fuzzing level, we should perform the CU deduction only if
202 : our adjacent preflight checks do not fail. If they do at some point in the derivation,
203 : no extra CUs will be charged. */
204 135 : ulong owed_cus = 0UL;
205 10929 : for( ulong i=0UL; i<255UL; i++ ) {
206 10887 : bump_seed[0] = (uchar)(255UL - i);
207 :
208 10887 : fd_pubkey_t derived[1];
209 10887 : int err = fd_vm_derive_pda( vm, NULL, program_id_vaddr, seeds_vaddr, seeds_cnt, bump_seed, derived );
210 10887 : if( FD_LIKELY( err==FD_VM_SUCCESS ) ) {
211 : /* Stop looking if we have found a valid PDA */
212 270 : fd_pubkey_t * out_haddr = FD_VM_MEM_HADDR_ST( vm, out_vaddr, FD_VM_ALIGN_RUST_U8, sizeof(fd_pubkey_t) );
213 270 : uchar * out_bump_seed_haddr = FD_VM_MEM_HADDR_ST( vm, out_bump_seed_vaddr, FD_VM_ALIGN_RUST_U8, 1UL );
214 :
215 : /* Do the overlap check, which is only included for this syscall */
216 90 : FD_VM_MEM_CHECK_NON_OVERLAPPING( vm, out_vaddr, 32UL, out_bump_seed_vaddr, 1UL );
217 :
218 90 : memcpy( out_haddr, derived, sizeof(fd_pubkey_t) );
219 90 : *out_bump_seed_haddr = (uchar)*bump_seed;
220 :
221 90 : FD_VM_CU_UPDATE( vm, owed_cus );
222 :
223 0 : *_ret = 0UL;
224 90 : return FD_VM_SUCCESS;
225 10797 : } else if( FD_UNLIKELY( err!=FD_VM_ERR_INVALID_PDA ) ) {
226 3 : return err;
227 3 : }
228 :
229 10794 : owed_cus = fd_ulong_sat_add( owed_cus, FD_VM_CREATE_PROGRAM_ADDRESS_UNITS );
230 10794 : }
231 :
232 42 : FD_VM_CU_UPDATE( vm, owed_cus );
233 :
234 0 : *_ret = 1UL;
235 24 : return FD_VM_SUCCESS;
236 42 : }
|