LCOV - code coverage report
Current view: top level - flamenco/vm/syscall - fd_vm_syscall_pda.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 114 133 85.7 %
Date: 2025-01-08 12:08:44 Functions: 4 4 100.0 %

          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 : }

Generated by: LCOV version 1.14