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: 71 84 84.5 %
Date: 2024-11-13 11:58:15 Functions: 3 3 100.0 %

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

Generated by: LCOV version 1.14