diff --git a/Cargo.lock b/Cargo.lock index d0bf159c25..889e5e15c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2963,6 +2963,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "light-poseidon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949bdd22e4ed93481d45e9a6badb34b99132bcad0c8a8d4f05c42f7dcc7b90bc" +dependencies = [ + "ark-bn254", + "ark-ff", + "thiserror", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -6478,6 +6489,7 @@ dependencies = [ "lazy_static", "libc", "libsecp256k1", + "light-poseidon", "log", "memoffset 0.9.0", "num-bigint 0.4.4", diff --git a/Cargo.toml b/Cargo.toml index ca197af02f..b83142e6e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -227,6 +227,7 @@ lazy_static = "1.4.0" libc = "0.2.147" libloading = "0.7.4" libsecp256k1 = "0.6.0" +light-poseidon = "0.1.1" log = "0.4.20" lru = "0.7.7" lz4 = "1.24.0" diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index ff217f0798..02983f9b9a 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -122,6 +122,14 @@ pub struct ComputeBudget { /// Maximum accounts data size, in bytes, that a transaction is allowed to load; The /// value is capped by MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES to prevent overuse of memory. pub loaded_accounts_data_size_limit: usize, + /// Coefficient `a` of the quadratic function which determines the number + /// of compute units consumed to call poseidon syscall for a given number + /// of inputs. + pub poseidon_cost_coefficient_a: u64, + /// Coefficient `c` of the quadratic function which determines the number + /// of compute units consumed to call poseidon syscall for a given number + /// of inputs. + pub poseidon_cost_coefficient_c: u64, } impl Default for ComputeBudget { @@ -171,6 +179,8 @@ impl ComputeBudget { alt_bn128_pairing_one_pair_cost_other: 12_121, big_modular_exponentiation_cost: 33, loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, + poseidon_cost_coefficient_a: 61, + poseidon_cost_coefficient_c: 542, } } @@ -308,6 +318,35 @@ impl ComputeBudget { prioritization_fee: prioritization_fee_details.get_fee(), } } + + /// Returns cost of the Poseidon hash function for the given number of + /// inputs is determined by the following quadratic function: + /// + /// 61*n^2 + 542 + /// + /// Which aproximates the results of benchmarks of light-posiedon + /// library[0]. These results assume 1 CU per 33 ns. Examples: + /// + /// * 1 input + /// * light-poseidon benchmark: `18,303 / 33 ≈ 555` + /// * function: `61*1^2 + 542 = 603` + /// * 2 inputs + /// * light-poseidon benchmark: `25,866 / 33 ≈ 784` + /// * function: `61*2^2 + 542 = 786` + /// * 3 inputs + /// * light-poseidon benchmark: `37,549 / 33 ≈ 1,138` + /// * function; `61*3^2 + 542 = 1091` + /// + /// [0] https://github.com/Lightprotocol/light-poseidon#performance + pub fn poseidon_cost(&self, nr_inputs: u64) -> Option { + let squared_inputs = nr_inputs.checked_pow(2)?; + let mul_result = self + .poseidon_cost_coefficient_a + .checked_mul(squared_inputs)?; + let final_result = mul_result.checked_add(self.poseidon_cost_coefficient_c)?; + + Some(final_result) + } } #[cfg(test)] diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index d8e3996129..6bd9b2778d 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -37,8 +37,8 @@ use { disable_cpi_setting_executable_and_rent_epoch, disable_deploy_of_alloc_free_syscall, disable_fees_sysvar, enable_alt_bn128_syscall, enable_big_mod_exp_syscall, enable_early_verification_of_account_modifications, enable_partitioned_epoch_reward, - error_on_syscall_bpf_function_hash_collisions, last_restart_slot_sysvar, - libsecp256k1_0_5_upgrade_enabled, reject_callx_r10, + enable_poseidon_syscall, error_on_syscall_bpf_function_hash_collisions, + last_restart_slot_sysvar, libsecp256k1_0_5_upgrade_enabled, reject_callx_r10, stop_sibling_instruction_search_at_parent, stop_truncating_strings_in_syscalls, switch_to_new_elf_parser, }, @@ -47,7 +47,7 @@ use { AccountMeta, InstructionError, ProcessedSiblingInstruction, TRANSACTION_LEVEL_STACK_HEIGHT, }, - keccak, native_loader, + keccak, native_loader, poseidon, precompiles::is_precompile, program::MAX_RETURN_DATA, program_stubs::is_nonoverlapping, @@ -125,6 +125,8 @@ pub enum SyscallError { InvalidAttribute, #[error("Invalid pointer")] InvalidPointer, + #[error("Arithmetic overflow")] + ArithmeticOverflow, } type Error = Box; @@ -160,6 +162,7 @@ pub fn create_program_runtime_environment_v1<'a>( let disable_deploy_of_alloc_free_syscall = reject_deployment_of_broken_elfs && feature_set.is_active(&disable_deploy_of_alloc_free_syscall::id()); let last_restart_slot_syscall_enabled = feature_set.is_active(&last_restart_slot_sysvar::id()); + let enable_poseidon_syscall = feature_set.is_active(&enable_poseidon_syscall::id()); // !!! ATTENTION !!! // When adding new features for RBPF here, // also add them to `Bank::apply_builtin_program_feature_transitions()`. @@ -327,6 +330,14 @@ pub fn create_program_runtime_environment_v1<'a>( SyscallBigModExp::call, )?; + // Poseidon + register_feature_gated_function!( + result, + enable_poseidon_syscall, + b"sol_poseidon", + SyscallPoseidon::call, + )?; + // Log data result.register_function(b"sol_log_data", SyscallLogData::call)?; @@ -1795,6 +1806,78 @@ declare_syscall!( } ); +declare_syscall!( + // Poseidon + SyscallPoseidon, + fn inner_call( + invoke_context: &mut InvokeContext, + parameters: u64, + endianness: u64, + vals_addr: u64, + vals_len: u64, + result_addr: u64, + memory_mapping: &mut MemoryMapping, + ) -> Result { + let parameters: poseidon::Parameters = parameters.try_into()?; + let endianness: poseidon::Endianness = endianness.try_into()?; + + if vals_len > 12 { + ic_msg!( + invoke_context, + "Poseidon hashing {} sequences is not supported", + vals_len, + ); + return Err(SyscallError::InvalidLength.into()); + } + + let budget = invoke_context.get_compute_budget(); + let Some(cost) = budget.poseidon_cost(vals_len) else { + ic_msg!( + invoke_context, + "Overflow while calculating the compute cost" + ); + return Err(SyscallError::ArithmeticOverflow.into()); + }; + consume_compute_meter(invoke_context, cost.to_owned())?; + + let hash_result = translate_slice_mut::( + memory_mapping, + result_addr, + poseidon::HASH_BYTES as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + let inputs = translate_slice::<&[u8]>( + memory_mapping, + vals_addr, + vals_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + let inputs = inputs + .iter() + .map(|input| { + translate_slice::( + memory_mapping, + input.as_ptr() as *const _ as u64, + input.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + ) + }) + .collect::, Error>>()?; + let hash = match poseidon::hashv(parameters, endianness, inputs.as_slice()) { + Ok(hash) => hash, + Err(e) => { + return Ok(e.into()); + } + }; + hash_result.copy_from_slice(&hash.to_bytes()); + + Ok(SUCCESS) + } +); + #[cfg(test)] #[allow(clippy::integer_arithmetic)] #[allow(clippy::indexing_slicing)] diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 4aa3d33d15..17c1c6f02b 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -2603,6 +2603,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "light-poseidon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949bdd22e4ed93481d45e9a6badb34b99132bcad0c8a8d4f05c42f7dcc7b90bc" +dependencies = [ + "ark-bn254", + "ark-ff", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.4.3" @@ -5248,6 +5259,7 @@ dependencies = [ "lazy_static", "libc", "libsecp256k1 0.6.0", + "light-poseidon", "log", "memoffset 0.9.0", "num-bigint 0.4.4", @@ -5848,6 +5860,14 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-sbf-rust-poseidon" +version = "1.17.0" +dependencies = [ + "array-bytes", + "solana-program", +] + [[package]] name = "solana-sbf-rust-rand" version = "1.17.0" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 8c1a784e60..d2f3022616 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -140,6 +140,7 @@ members = [ "rust/panic", "rust/param_passing", "rust/param_passing_dep", + "rust/poseidon", "rust/rand", "rust/realloc", "rust/realloc_invoke", diff --git a/programs/sbf/build.rs b/programs/sbf/build.rs index 409657f9e5..8bffd48c9f 100644 --- a/programs/sbf/build.rs +++ b/programs/sbf/build.rs @@ -92,6 +92,7 @@ fn main() { "noop", "panic", "param_passing", + "poseidon", "rand", "realloc", "realloc_invoke", diff --git a/programs/sbf/c/src/poseidon/poseidon.c b/programs/sbf/c/src/poseidon/poseidon.c new file mode 100644 index 0000000000..5d49f4b8eb --- /dev/null +++ b/programs/sbf/c/src/poseidon/poseidon.c @@ -0,0 +1,359 @@ +/** + * @brief Poseidon syscall test + */ +#include +#include +#include + +extern uint64_t entrypoint(const uint8_t *input) { + // Two inputs: ones and twos (big-endian). + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = {13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, + 3, 85, 242, 99, 25, 32, 123, 132, 254, 156, 162, + 206, 27, 38, 231, 53, 200, 41, 130, 25, 144}; + + uint8_t input1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + uint8_t input2[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input2, SOL_ARRAY_SIZE(input2)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // Two inputs: ones and twos (little-endian). + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = {144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, + 156, 254, 132, 123, 32, 25, 99, 242, 85, 3, 94, + 235, 125, 28, 140, 138, 143, 147, 225, 84, 13}; + + uint8_t input1[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + uint8_t input2[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input2, SOL_ARRAY_SIZE(input2)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_LITTLE_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + + uint8_t input1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + }; + + // 1 input. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, + 101, 77, 106, 60, 19, 14, 150, 164, 209, 22, 139, + 51, 132, 139, 137, 125, 197, 2, 130, 1, 51, + }; + + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 2 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, + 169, 243, 2, 63, 119, 18, 148, 167, 138, 203, 112, + 231, 63, 144, 175, 226, 124, 173, 64, 30, 129, + }; + + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 3 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, + 178, 20, 203, 62, 129, 188, 177, 182, 227, 9, 97, + 205, 35, 194, 2, 177, 134, 115, 191, 37, 67, + }; + + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 4 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, + 65, 74, 55, 104, 31, 120, 68, 45, 39, 216, 99, + 133, 153, 28, 23, 214, 252, 12, 75, 125, 113, + }; + + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 5 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, + 49, 34, 196, 125, 102, 168, 3, 199, 43, 65, 88, + 156, 177, 191, 134, 135, 65, 178, 6, 185, 187, + }; + + const SolBytes inputs[] = {{input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 6 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, + 229, 189, 191, 80, 179, 144, 53, 215, 114, 159, 19, + 91, 151, 9, 137, 15, 133, 197, 220, 94, 118, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 7 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, + 19, 157, 157, 169, 89, 190, 42, 49, 178, 199, 8, + 165, 248, 25, 84, 178, 101, 229, 58, 48, 184, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 8 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, + 51, 66, 81, 71, 9, 92, 79, 202, 187, 35, 61, + 35, 11, 109, 70, 162, 20, 217, 91, 40, 132, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 9 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, + 196, 46, 187, 68, 204, 110, 231, 5, 95, 97, 251, + 202, 94, 49, 59, 138, 95, 202, 131, 76, 71, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 10 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, + 79, 74, 112, 119, 193, 255, 146, 96, 228, 72, 133, + 196, 184, 29, 209, 49, 173, 58, 134, 205, 150, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 11 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, + 188, 235, 95, 58, 102, 220, 65, 66, 235, 112, 181, + 103, 101, 188, 53, 143, 27, 236, 64, 187, 155, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + // 12 inputs. + { + uint8_t result[POSEIDON_RESULT_LENGTH]; + uint8_t expected[] = { + 20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, + 221, 172, 101, 194, 229, 46, 133, 19, 192, 129, 193, + 205, 114, 201, 128, 6, 9, 142, 154, 143, 190, + }; + + const SolBytes inputs[] = { + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}, + {input1, SOL_ARRAY_SIZE(input1)}, {input1, SOL_ARRAY_SIZE(input1)}}; + + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + POSEIDON_ENDIANNESS_BIG_ENDIAN, + inputs, + SOL_ARRAY_SIZE(inputs), + result + ); + + sol_assert(0 == sol_memcmp(result, expected, POSEIDON_RESULT_LENGTH)); + } + return SUCCESS; +} diff --git a/programs/sbf/rust/poseidon/Cargo.toml b/programs/sbf/rust/poseidon/Cargo.toml new file mode 100644 index 0000000000..637b8a0e2b --- /dev/null +++ b/programs/sbf/rust/poseidon/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-sbf-rust-poseidon" +description = "Solana SBF test program written in Rust" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +array-bytes = { workspace = true } +solana-program = { workspace = true } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/sbf/rust/poseidon/src/lib.rs b/programs/sbf/rust/poseidon/src/lib.rs new file mode 100644 index 0000000000..34564e8946 --- /dev/null +++ b/programs/sbf/rust/poseidon/src/lib.rs @@ -0,0 +1,230 @@ +//! Example SBF program using Poseidon syscall + +use solana_program::{ + custom_heap_default, custom_panic_default, msg, + poseidon::{hashv, Endianness, Parameters, PoseidonSyscallError}, +}; + +fn test_poseidon_input_ones_twos() -> Result<(), PoseidonSyscallError> { + let input1 = [1u8; 32]; + let input2 = [2u8; 32]; + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input1, &input2], + )?; + + assert_eq!( + hash.to_bytes(), + [ + 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123, 132, + 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144 + ] + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::LittleEndian, + &[&input1, &input2], + )?; + + assert_eq!( + hash.to_bytes(), + [ + 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99, 242, + 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13 + ], + ); + + Ok(()) +} + +fn test_poseidon_input_one() -> Result<(), PoseidonSyscallError> { + let input = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, + ]; + + let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &[&input])?; + assert_eq!( + hash.to_bytes(), + [ + 41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, 101, 77, 106, 60, 19, 14, 150, 164, + 209, 22, 139, 51, 132, 139, 137, 125, 197, 2, 130, 1, 51, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, 169, 243, 2, 63, 119, 18, 148, 167, + 138, 203, 112, 231, 63, 144, 175, 226, 124, 173, 64, 30, 129, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, 178, 20, 203, 62, 129, 188, 177, 182, + 227, 9, 97, 205, 35, 194, 2, 177, 134, 115, 191, 37, 67, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input, &input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, 65, 74, 55, 104, 31, 120, 68, 45, 39, + 216, 99, 133, 153, 28, 23, 214, 252, 12, 75, 125, 113, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input, &input, &input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, 49, 34, 196, 125, 102, 168, 3, 199, + 43, 65, 88, 156, 177, 191, 134, 135, 65, 178, 6, 185, 187, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input, &input, &input, &input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, 229, 189, 191, 80, 179, 144, 53, + 215, 114, 159, 19, 91, 151, 9, 137, 15, 133, 197, 220, 94, 118, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input, &input, &input, &input, &input, &input, &input], + )?; + assert_eq!( + hash.to_bytes(), + [ + 34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, 19, 157, 157, 169, 89, 190, 42, 49, 178, + 199, 8, 165, 248, 25, 84, 178, 101, 229, 58, 48, 184, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[ + &input, &input, &input, &input, &input, &input, &input, &input, + ], + )?; + assert_eq!( + hash.to_bytes(), + [ + 23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, 51, 66, 81, 71, 9, 92, 79, 202, 187, + 35, 61, 35, 11, 109, 70, 162, 20, 217, 91, 40, 132, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[ + &input, &input, &input, &input, &input, &input, &input, &input, &input, + ], + )?; + assert_eq!( + hash.to_bytes(), + [ + 14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, 196, 46, 187, 68, 204, 110, 231, 5, + 95, 97, 251, 202, 94, 49, 59, 138, 95, 202, 131, 76, 71, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[ + &input, &input, &input, &input, &input, &input, &input, &input, &input, &input, + ], + )?; + assert_eq!( + hash.to_bytes(), + [ + 46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, 79, 74, 112, 119, 193, 255, 146, 96, + 228, 72, 133, 196, 184, 29, 209, 49, 173, 58, 134, 205, 150, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[ + &input, &input, &input, &input, &input, &input, &input, &input, &input, &input, &input, + ], + )?; + assert_eq!( + hash.to_bytes(), + [ + 0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, 188, 235, 95, 58, 102, 220, 65, 66, + 235, 112, 181, 103, 101, 188, 53, 143, 27, 236, 64, 187, 155, + ], + ); + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[ + &input, &input, &input, &input, &input, &input, &input, &input, &input, &input, &input, + &input, + ], + )?; + assert_eq!( + hash.to_bytes(), + [ + 20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, 221, 172, 101, 194, 229, 46, 133, + 19, 192, 129, 193, 205, 114, 201, 128, 6, 9, 142, 154, 143, 190, + ], + ); + + Ok(()) +} + +#[no_mangle] +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { + msg!("poseidon_hash"); + + if let Err(e) = test_poseidon_input_ones_twos() { + return e.into(); + } + if let Err(e) = test_poseidon_input_one() { + return e.into(); + } + + 0 +} + +custom_heap_default!(); +custom_panic_default!(); diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index ff6488adf4..6acb18e479 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -285,6 +285,7 @@ fn test_program_sbf_sanity() { ("noop", true), ("noop++", true), ("panic", false), + ("poseidon", true), ("relative_call", true), ("return_data", true), ("sanity", true), @@ -312,6 +313,7 @@ fn test_program_sbf_sanity() { ("solana_sbf_rust_noop", true), ("solana_sbf_rust_panic", false), ("solana_sbf_rust_param_passing", true), + ("solana_sbf_rust_poseidon", true), ("solana_sbf_rust_rand", true), ("solana_sbf_rust_sanity", true), ("solana_sbf_rust_secp256k1_recover", true), diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 2db0a97295..9d3583faa8 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -58,6 +58,7 @@ curve25519-dalek = { workspace = true, features = ["serde"] } itertools = { workspace = true } libc = { workspace = true, features = ["extra_traits"] } libsecp256k1 = { workspace = true } +light-poseidon = { workspace = true } num-bigint = { workspace = true } rand = { workspace = true } tiny-bip39 = { workspace = true } diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index ded97af633..21312fb7f3 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -506,6 +506,7 @@ pub mod log; pub mod message; pub mod native_token; pub mod nonce; +pub mod poseidon; pub mod program; pub mod program_error; pub mod program_memory; diff --git a/sdk/program/src/poseidon.rs b/sdk/program/src/poseidon.rs new file mode 100644 index 0000000000..c23cded6db --- /dev/null +++ b/sdk/program/src/poseidon.rs @@ -0,0 +1,444 @@ +//! Hashing with the [Poseidon] hash function. +//! +//! [Poseidon]: https://www.poseidon-hash.info/ + +use thiserror::Error; + +/// Length of Poseidon hash result. +pub const HASH_BYTES: usize = 32; + +#[derive(Error, Debug)] +pub enum PoseidonSyscallError { + #[error("Invalid parameters.")] + InvalidParameters, + #[error("Invalid endianness.")] + InvalidEndianness, + #[error("Invalid number of inputs. Maximum allowed is 12.")] + InvalidNumberOfInputs, + #[error("Input is an empty slice.")] + EmptyInput, + #[error( + "Invalid length of the input. The length matching the modulus of the prime field is 32." + )] + InvalidInputLength, + #[error("Input is larger than the modulus of the prime field.")] + InputLargerThanModulus, + #[error("Failed to convert a vector of bytes into an array.")] + VecToArray, + #[error("Failed to convert the number of inputs from u64 to u8.")] + U64Tou8, + #[error("Invalid width. Choose a width between 2 and 16 for 1 to 15 inputs.")] + InvalidWidthCircom, + #[error("Unexpected error")] + Unexpected, +} + +impl From for PoseidonSyscallError { + fn from(error: u64) -> Self { + match error { + 1 => PoseidonSyscallError::InvalidParameters, + 2 => PoseidonSyscallError::InvalidEndianness, + 3 => PoseidonSyscallError::InvalidNumberOfInputs, + 4 => PoseidonSyscallError::EmptyInput, + 5 => PoseidonSyscallError::InvalidInputLength, + 6 => PoseidonSyscallError::InputLargerThanModulus, + 7 => PoseidonSyscallError::VecToArray, + 8 => PoseidonSyscallError::U64Tou8, + 9 => PoseidonSyscallError::InvalidWidthCircom, + _ => PoseidonSyscallError::Unexpected, + } + } +} + +impl From for u64 { + fn from(error: PoseidonSyscallError) -> Self { + match error { + PoseidonSyscallError::InvalidParameters => 1, + PoseidonSyscallError::InvalidEndianness => 2, + PoseidonSyscallError::InvalidNumberOfInputs => 3, + PoseidonSyscallError::EmptyInput => 4, + PoseidonSyscallError::InvalidInputLength => 5, + PoseidonSyscallError::InputLargerThanModulus => 6, + PoseidonSyscallError::VecToArray => 7, + PoseidonSyscallError::U64Tou8 => 8, + PoseidonSyscallError::InvalidWidthCircom => 9, + PoseidonSyscallError::Unexpected => 10, + } + } +} + +/// Configuration parameters for the Poseidon hash function. +/// +/// The parameters of each configuration consist of: +/// +/// - **Elliptic curve type**: This defines the prime field in which the +/// cryptographic operations are conducted. +/// - **S-Box**: The substitution box used in the cryptographic rounds. +/// - **Full rounds**: The number of full transformation rounds in the hash +/// function. +/// - **Partial rounds**: The number of partial transformation rounds in the +/// hash function. +/// +/// Each configuration variant's name is composed of its elliptic curve type +/// followed by its S-Box specification. +#[repr(u64)] +pub enum Parameters { + /// Configuration using the Barreto–Naehrig curve with an embedding degree + /// of 12, defined over a 254-bit prime field. + /// + /// Configuration Details: + /// - **S-Box**: \( x^5 \) + /// - **Width**: \( 2 \leq t \leq 13 \) + /// - **Inputs**: \( 1 \leq n \leq 12 \) + /// - **Full rounds**: 8 + /// - **Partial rounds**: Depending on width: [56, 57, 56, 60, 60, 63, 64, + /// 63, 60, 66, 60, 65] + Bn254X5 = 0, +} + +impl TryFrom for Parameters { + type Error = PoseidonSyscallError; + + fn try_from(value: u64) -> Result { + match value { + x if x == Parameters::Bn254X5 as u64 => Ok(Parameters::Bn254X5), + _ => Err(PoseidonSyscallError::InvalidParameters), + } + } +} + +impl From for u64 { + fn from(value: Parameters) -> Self { + match value { + Parameters::Bn254X5 => 0, + } + } +} + +/// Endianness of inputs and result. +#[repr(u64)] +pub enum Endianness { + /// Big-endian inputs and result. + BigEndian = 0, + /// Little-endian inputs and result. + LittleEndian, +} + +impl TryFrom for Endianness { + type Error = PoseidonSyscallError; + + fn try_from(value: u64) -> Result { + match value { + x if x == Endianness::BigEndian as u64 => Ok(Endianness::BigEndian), + x if x == Endianness::LittleEndian as u64 => Ok(Endianness::LittleEndian), + _ => Err(PoseidonSyscallError::InvalidEndianness), + } + } +} + +impl From for u64 { + fn from(value: Endianness) -> Self { + match value { + Endianness::BigEndian => 0, + Endianness::LittleEndian => 1, + } + } +} + +/// Poseidon hash result. +#[repr(transparent)] +pub struct PoseidonHash(pub [u8; HASH_BYTES]); + +impl PoseidonHash { + pub fn new(hash_array: [u8; HASH_BYTES]) -> Self { + Self(hash_array) + } + + pub fn to_bytes(&self) -> [u8; HASH_BYTES] { + self.0 + } +} + +/// Return a Poseidon hash for the given data with the given elliptic curve and +/// endianness. +/// +/// # Examples +/// +/// ```rust +/// use solana_program::poseidon::{hashv, Endianness, Parameters}; +/// +/// # fn test() { +/// let input1 = [1u8; 32]; +/// let input2 = [2u8; 32]; +/// +/// let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &[&input1, &input2]).unwrap(); +/// assert_eq!( +/// hash.to_bytes(), +/// [ +/// 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123, +/// 132, 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144 +/// ] +/// ); +/// +/// let hash = hashv(Parameters::Bn254X5, Endianness::LittleEndian, &[&input1, &input2]).unwrap(); +/// assert_eq!( +/// hash.to_bytes(), +/// [ +/// 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99, +/// 242, 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13 +/// ] +/// ); +/// # } +/// ``` +#[allow(unused_variables)] +pub fn hashv( + // This parameter is not used currently, because we support only one curve + // (BN254). It should be used in case we add more curves in the future. + parameters: Parameters, + endianness: Endianness, + vals: &[&[u8]], +) -> Result { + // Perform the calculation inline, calling this from within a program is + // not supported. + #[cfg(not(target_os = "solana"))] + { + use { + ark_bn254::Fr, + light_poseidon::{Poseidon, PoseidonBytesHasher, PoseidonError}, + }; + + impl From for PoseidonSyscallError { + fn from(error: PoseidonError) -> Self { + match error { + PoseidonError::InvalidNumberOfInputs { + inputs: _, + max_limit: _, + width: _, + } => PoseidonSyscallError::InvalidNumberOfInputs, + PoseidonError::EmptyInput => PoseidonSyscallError::EmptyInput, + PoseidonError::InvalidInputLength { + len: _, + modulus_bytes_len: _, + } => PoseidonSyscallError::InvalidInputLength, + PoseidonError::InputLargerThanModulus => { + PoseidonSyscallError::InputLargerThanModulus + } + PoseidonError::VecToArray => PoseidonSyscallError::VecToArray, + PoseidonError::U64Tou8 => PoseidonSyscallError::U64Tou8, + PoseidonError::InvalidWidthCircom { + width: _, + max_limit: _, + } => PoseidonSyscallError::InvalidWidthCircom, + } + } + } + + let mut hasher = + Poseidon::::new_circom(vals.len()).map_err(PoseidonSyscallError::from)?; + let res = match endianness { + Endianness::BigEndian => hasher.hash_bytes_be(vals), + Endianness::LittleEndian => hasher.hash_bytes_le(vals), + } + .map_err(PoseidonSyscallError::from)?; + + Ok(PoseidonHash(res)) + } + // Call via a system call to perform the calculation. + #[cfg(target_os = "solana")] + { + let mut hash_result = [0; HASH_BYTES]; + let result = unsafe { + crate::syscalls::sol_poseidon( + parameters.into(), + endianness.into(), + vals as *const _ as *const u8, + vals.len() as u64, + &mut hash_result as *mut _ as *mut u8, + ) + }; + + match result { + 0 => Ok(PoseidonHash::new(hash_result)), + e => Err(PoseidonSyscallError::from(e)), + } + } +} + +/// Return a Poseidon hash for the given data with the given elliptic curve and +/// endianness. +/// +/// # Examples +/// +/// ```rust +/// use solana_program::poseidon::{hash, Endianness, Parameters}; +/// +/// # fn test() { +/// let input = [1u8; 32]; +/// +/// let result = hash(Parameters::Bn254X5, Endianness::BigEndian, &input).unwrap(); +/// assert_eq!( +/// result.to_bytes(), +/// [ +/// 5, 191, 172, 229, 129, 238, 97, 119, 204, 25, 198, 197, 99, 99, 166, 136, 130, 241, +/// 30, 132, 7, 172, 99, 157, 185, 145, 224, 210, 127, 27, 117, 230 +/// ], +/// ); +/// +/// let hash = hash(Parameters::Bn254X5, Endianness::LittleEndian, &input).unwrap(); +/// assert_eq!( +/// hash.to_bytes(), +/// [ +/// 230, 117, 27, 127, 210, 224, 145, 185, 157, 99, 172, 7, 132, 30, 241, 130, 136, +/// 166, 99, 99, 197, 198, 25, 204, 119, 97, 238, 129, 229, 172, 191, 5 +/// ], +/// ); +/// # } +/// ``` +pub fn hash( + parameters: Parameters, + endianness: Endianness, + val: &[u8], +) -> Result { + hashv(parameters, endianness, &[val]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_poseidon_input_ones_be() { + let input = [1u8; 32]; + + let hash = hash(Parameters::Bn254X5, Endianness::BigEndian, &input).unwrap(); + assert_eq!( + hash.to_bytes(), + [ + 5, 191, 172, 229, 129, 238, 97, 119, 204, 25, 198, 197, 99, 99, 166, 136, 130, 241, + 30, 132, 7, 172, 99, 157, 185, 145, 224, 210, 127, 27, 117, 230 + ] + ); + } + + #[test] + fn test_poseidon_input_ones_le() { + let input = [1u8; 32]; + + let hash = hash(Parameters::Bn254X5, Endianness::LittleEndian, &input).unwrap(); + assert_eq!( + hash.to_bytes(), + [ + 230, 117, 27, 127, 210, 224, 145, 185, 157, 99, 172, 7, 132, 30, 241, 130, 136, + 166, 99, 99, 197, 198, 25, 204, 119, 97, 238, 129, 229, 172, 191, 5 + ], + ); + } + + #[test] + fn test_poseidon_input_ones_twos_be() { + let input1 = [1u8; 32]; + let input2 = [2u8; 32]; + + let hash = hashv( + Parameters::Bn254X5, + Endianness::BigEndian, + &[&input1, &input2], + ) + .unwrap(); + assert_eq!( + hash.to_bytes(), + [ + 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123, + 132, 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144 + ] + ); + } + + #[test] + fn test_poseidon_input_ones_twos_le() { + let input1 = [1u8; 32]; + let input2 = [2u8; 32]; + + let hash = hashv( + Parameters::Bn254X5, + Endianness::LittleEndian, + &[&input1, &input2], + ) + .unwrap(); + assert_eq!( + hash.to_bytes(), + [ + 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99, + 242, 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13 + ] + ); + } + + #[test] + fn test_poseidon_input_one() { + let input = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, + ]; + + let expected_hashes = [ + [ + 41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, 101, 77, 106, 60, 19, 14, 150, + 164, 209, 22, 139, 51, 132, 139, 137, 125, 197, 2, 130, 1, 51, + ], + [ + 0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, 169, 243, 2, 63, 119, 18, 148, + 167, 138, 203, 112, 231, 63, 144, 175, 226, 124, 173, 64, 30, 129, + ], + [ + 2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, 178, 20, 203, 62, 129, 188, 177, + 182, 227, 9, 97, 205, 35, 194, 2, 177, 134, 115, 191, 37, 67, + ], + [ + 8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, 65, 74, 55, 104, 31, 120, 68, 45, + 39, 216, 99, 133, 153, 28, 23, 214, 252, 12, 75, 125, 113, + ], + [ + 16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, 49, 34, 196, 125, 102, 168, 3, + 199, 43, 65, 88, 156, 177, 191, 134, 135, 65, 178, 6, 185, 187, + ], + [ + 42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, 229, 189, 191, 80, 179, 144, 53, + 215, 114, 159, 19, 91, 151, 9, 137, 15, 133, 197, 220, 94, 118, + ], + [ + 34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, 19, 157, 157, 169, 89, 190, 42, 49, + 178, 199, 8, 165, 248, 25, 84, 178, 101, 229, 58, 48, 184, + ], + [ + 23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, 51, 66, 81, 71, 9, 92, 79, 202, + 187, 35, 61, 35, 11, 109, 70, 162, 20, 217, 91, 40, 132, + ], + [ + 14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, 196, 46, 187, 68, 204, 110, 231, + 5, 95, 97, 251, 202, 94, 49, 59, 138, 95, 202, 131, 76, 71, + ], + [ + 46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, 79, 74, 112, 119, 193, 255, 146, + 96, 228, 72, 133, 196, 184, 29, 209, 49, 173, 58, 134, 205, 150, + ], + [ + 0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, 188, 235, 95, 58, 102, 220, 65, + 66, 235, 112, 181, 103, 101, 188, 53, 143, 27, 236, 64, 187, 155, + ], + [ + 20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, 221, 172, 101, 194, 229, 46, + 133, 19, 192, 129, 193, 205, 114, 201, 128, 6, 9, 142, 154, 143, 190, + ], + ]; + + for (i, expected_hash) in expected_hashes.iter().enumerate() { + let inputs = vec![&input; i + 1] + .into_iter() + .map(|arr| &arr[..]) + .collect::>(); + let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &inputs).unwrap(); + assert_eq!(hash.to_bytes(), *expected_hash); + } + } +} diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index 21184f0650..dbf6d5e3a4 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -69,6 +69,7 @@ define_syscall!(fn sol_curve_pairing_map(curve_id: u64, point: *const u8, result define_syscall!(fn sol_alt_bn128_group_op(group_op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64); define_syscall!(fn sol_big_mod_exp(params: *const u8, result: *mut u8) -> u64); define_syscall!(fn sol_get_epoch_rewards_sysvar(addr: *mut u8) -> u64); +define_syscall!(fn sol_poseidon(parameters: u64, endianness: u64, vals: *const u8, val_len: u64, hash_result: *mut u8) -> u64); #[cfg(target_feature = "static-syscalls")] pub const fn sys_hash(name: &str) -> usize { diff --git a/sdk/sbf/c/inc/sol/inc/poseidon.inc b/sdk/sbf/c/inc/sol/inc/poseidon.inc new file mode 100644 index 0000000000..94f3d16aab --- /dev/null +++ b/sdk/sbf/c/inc/sol/inc/poseidon.inc @@ -0,0 +1,62 @@ +#pragma once +/** + * @brief Solana poseidon system call +**/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Length of a Poseidon hash result + */ +#define POSEIDON_RESULT_LENGTH 32 + +/** + * Configuration using the Barreto–Naehrig curve with an embedding degree of + * 12, defined over a 254-bit prime field. + * + * Configuration Details: + * - S-Box: x^5 + * - Width: 2 <= t <= 13 + * - Inputs: 1 <= n <= 12 + * - Full rounds: 8 + * - Partial rounds: Depending on width: [56, 57, 56, 60, 60, 63, 64, 63, + * 60, 66, 60, 65] + */ +#define POSEIDON_PARAMETERS_BN254_X5 0 + +/** + * Big-endian inputs and output + */ +#define POSEIDON_ENDIANNESS_BIG_ENDIAN 0 + +/** + * Little-endian inputs and output + */ +#define POSEIDON_ENDIANNESS_LITTLE_ENDIAN 1 + +/** + * Poseidon + * + * @param parameters Configuration parameters for the hash function + * @param endianness Endianness of inputs and result + * @param bytes Array of byte arrays + * @param bytes_len Number of byte arrays + * @param result 32 byte array to hold the result + */ +@SYSCALL uint64_t sol_poseidon( + const uint64_t parameters, + const uint64_t endianness, + const SolBytes *bytes, + const uint64_t bytes_len, + uint8_t *result +); + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/sbf/c/inc/sol/poseidon.h b/sdk/sbf/c/inc/sol/poseidon.h new file mode 100644 index 0000000000..b9e7a4395d --- /dev/null +++ b/sdk/sbf/c/inc/sol/poseidon.h @@ -0,0 +1,83 @@ +#pragma once +/** + * @brief Solana poseidon system call +**/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Length of a Poseidon hash result + */ +#define POSEIDON_RESULT_LENGTH 32 + +/** + * Configuration using the Barreto–Naehrig curve with an embedding degree of + * 12, defined over a 254-bit prime field. + * + * Configuration Details: + * - S-Box: x^5 + * - Width: 2 <= t <= 13 + * - Inputs: 1 <= n <= 12 + * - Full rounds: 8 + * - Partial rounds: Depending on width: [56, 57, 56, 60, 60, 63, 64, 63, + * 60, 66, 60, 65] + */ +#define POSEIDON_PARAMETERS_BN254_X5 0 + +/** + * Big-endian inputs and output + */ +#define POSEIDON_ENDIANNESS_BIG_ENDIAN 0 + +/** + * Little-endian inputs and output + */ +#define POSEIDON_ENDIANNESS_LITTLE_ENDIAN 1 + +/** + * Poseidon + * + * @param parameters Configuration parameters for the hash function + * @param endianness Endianness of inputs and result + * @param bytes Array of byte arrays + * @param bytes_len Number of byte arrays + * @param result 32 byte array to hold the result + */ +/* DO NOT MODIFY THIS GENERATED FILE. INSTEAD CHANGE sdk/sbf/c/inc/sol/inc/poseidon.inc AND RUN `cargo run --bin gen-headers` */ +#ifndef SOL_SBFV2 +uint64_t sol_poseidon( + const uint64_t parameters, + const uint64_t endianness, + const SolBytes *bytes, + const uint64_t bytes_len, + uint8_t *result +); +#else +typedef uint64_t(*sol_poseidon_pointer_type)( + const uint64_t parameters, + const uint64_t endianness, + const SolBytes *bytes, + const uint64_t bytes_len, + uint8_t *result +); +static uint64_t sol_poseidon( + const uint64_t parameters arg1, + const uint64_t endianness arg2, + const SolBytes *bytes arg3, + const uint64_t bytes_len arg4, + uint8_t *result + arg5) { + sol_poseidon_pointer_type sol_poseidon_pointer = (sol_poseidon_pointer_type) 3298065441; + return sol_poseidon_pointer(arg1, arg2, arg3, arg4, arg5); +} +#endif + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 23e9555c27..2ef997fee9 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -691,6 +691,10 @@ pub mod revise_turbine_epoch_stakes { solana_sdk::declare_id!("BTWmtJC8U5ZLMbBUUA1k6As62sYjPEjAiNAT55xYGdJU"); } +pub mod enable_poseidon_syscall { + solana_sdk::declare_id!("Pos1111111111111111111111111111111111111111"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -856,6 +860,7 @@ lazy_static! { (last_restart_slot_sysvar::id(), "enable new sysvar last_restart_slot"), (reduce_stake_warmup_cooldown::id(), "reduce stake warmup cooldown from 25% to 9%"), (revise_turbine_epoch_stakes::id(), "revise turbine epoch stakes"), + (enable_poseidon_syscall::id(), "Enable Poseidon syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index d0e5e8ec0c..98b7694a41 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -49,10 +49,11 @@ pub use solana_program::{ declare_sysvar_id, decode_error, ed25519_program, epoch_rewards, epoch_schedule, fee_calculator, impl_sysvar_get, incinerator, instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, loader_v4, loader_v4_instruction, message, - msg, native_token, nonce, program, program_error, program_memory, program_option, program_pack, - rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils, - short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, syscalls, - system_instruction, system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen, + msg, native_token, nonce, poseidon, program, program_error, program_memory, program_option, + program_pack, rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, + serialize_utils, short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, + syscalls, system_instruction, system_program, sysvar, unchecked_div_by_const, vote, + wasm_bindgen, }; pub mod account;