Add ecrecover syscall (#17720)

Co-authored-by: Anton Lisanin <lisanin.anton@gmail.com>
This commit is contained in:
s-medvedev 2021-07-07 23:15:14 +03:00 committed by GitHub
parent 92c5cdab62
commit 1f288ce527
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 381 additions and 3 deletions

2
Cargo.lock generated
View File

@ -4344,6 +4344,7 @@ version = "1.8.0"
dependencies = [
"bincode",
"byteorder",
"libsecp256k1",
"log 0.4.14",
"num-derive",
"num-traits",
@ -5258,6 +5259,7 @@ dependencies = [
"hex",
"itertools 0.10.1",
"lazy_static",
"libsecp256k1",
"log 0.4.14",
"num-derive",
"num-traits",

View File

@ -2752,6 +2752,7 @@ version = "1.8.0"
dependencies = [
"bincode",
"byteorder 1.4.3",
"libsecp256k1",
"log",
"num-derive",
"num-traits",
@ -3018,6 +3019,13 @@ dependencies = [
"solana-program 1.8.0",
]
[[package]]
name = "solana-bpf-rust-secp256k1-recover"
version = "1.8.0"
dependencies = [
"solana-program 1.8.0",
]
[[package]]
name = "solana-bpf-rust-sha"
version = "1.8.0"
@ -3370,6 +3378,7 @@ dependencies = [
"hex",
"itertools 0.10.1",
"lazy_static",
"libsecp256k1",
"log",
"num-derive",
"num-traits",

View File

@ -73,6 +73,7 @@ members = [
"rust/ro_modify",
"rust/ro_account_modify",
"rust/sanity",
"rust/secp256k1_recover",
"rust/sha",
"rust/spoof1",
"rust/spoof1_system",

View File

@ -86,6 +86,7 @@ fn main() {
"ro_modify",
"ro_account_modify",
"sanity",
"secp256k1_recover",
"sha",
"spoof1",
"spoof1_system",

View File

@ -0,0 +1,38 @@
/**
* @brief Secp256k1Recover Syscall test
*/
#include <solana_sdk.h>
extern uint64_t entrypoint(const uint8_t *input) {
uint8_t result[SECP256K1_RECOVER_RESULT_LENGTH];
uint8_t expected[] = { 0x42, 0xcd, 0x27, 0xe4, 0x0f, 0xdf, 0x7c, 0x97,
0x0a, 0xa2, 0xca, 0x0b, 0x88, 0x5b, 0x96, 0x0f,
0x8b, 0x62, 0x8a, 0x41, 0xa1, 0x81, 0xe7, 0xe6,
0x8e, 0x03, 0xea, 0x0b, 0x84, 0x20, 0x58, 0x9b,
0x32, 0x06, 0xbd, 0x66, 0x2f, 0x75, 0x65, 0xd6,
0x9d, 0xbd, 0x1d, 0x34, 0x29, 0x6a, 0xd9, 0x35,
0x38, 0xed, 0x86, 0x9e, 0x99, 0x20, 0x43, 0xc3,
0xeb, 0xad, 0x65, 0x50, 0xa0, 0x11, 0x6e, 0x5d};
uint8_t hash[] = { 0xde, 0xa5, 0x66, 0xb6, 0x94, 0x3b, 0xe0, 0xe9,
0x62, 0x53, 0xc2, 0x21, 0x5b, 0x1b, 0xac, 0x69,
0xe7, 0xa8, 0x1e, 0xdb, 0x41, 0xc5, 0x02, 0x8b,
0x4f, 0x5c, 0x45, 0xc5, 0x3b, 0x49, 0x54, 0xd0};
uint8_t signature[] = { 0x97, 0xa4, 0xee, 0x31, 0xfe, 0x82, 0x65, 0x72,
0x9f, 0x4a, 0xa6, 0x7d, 0x24, 0xd4, 0xa7, 0x27,
0xf8, 0xc3, 0x15, 0xa4, 0xc8, 0xf9, 0x80, 0xeb,
0x4c, 0x4d, 0x4a, 0xfa, 0x6e, 0xc9, 0x42, 0x41,
0x5d, 0x10, 0xd9, 0xc2, 0x8a, 0x90, 0xe9, 0x92,
0x9c, 0x52, 0x4b, 0x2c, 0xfb, 0x65, 0xdf, 0xbc,
0xf6, 0x8c, 0xfd, 0x68, 0xdb, 0x17, 0xf9, 0x5d,
0x23, 0x5f, 0x96, 0xd8, 0xf0, 0x72, 0x01, 0x2d};
uint64_t recovery_id = 1;
uint64_t result_code = sol_secp256k1_recover(hash, recovery_id, signature, result);
sol_assert(0 == result_code);
sol_assert(0 == sol_memcmp(result, expected, SHA256_RESULT_LENGTH));
return SUCCESS;
}

View File

@ -0,0 +1,19 @@
[package]
name = "solana-bpf-rust-secp256k1-recover"
version = "1.8.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-secp256k1-recover"
edition = "2018"
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.8.0" }
[lib]
crate-type = ["cdylib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,44 @@
//! @brief Secp256k1Recover Syscall test
extern crate solana_program;
use solana_program::{custom_panic_default, msg};
fn test_secp256k1_recover() {
use solana_program::secp256k1_recover::secp256k1_recover;
let expected: [u8; 64] = [
0x42, 0xcd, 0x27, 0xe4, 0x0f, 0xdf, 0x7c, 0x97, 0x0a, 0xa2, 0xca, 0x0b, 0x88, 0x5b, 0x96,
0x0f, 0x8b, 0x62, 0x8a, 0x41, 0xa1, 0x81, 0xe7, 0xe6, 0x8e, 0x03, 0xea, 0x0b, 0x84, 0x20,
0x58, 0x9b, 0x32, 0x06, 0xbd, 0x66, 0x2f, 0x75, 0x65, 0xd6, 0x9d, 0xbd, 0x1d, 0x34, 0x29,
0x6a, 0xd9, 0x35, 0x38, 0xed, 0x86, 0x9e, 0x99, 0x20, 0x43, 0xc3, 0xeb, 0xad, 0x65, 0x50,
0xa0, 0x11, 0x6e, 0x5d,
];
let hash: [u8; 32] = [
0xde, 0xa5, 0x66, 0xb6, 0x94, 0x3b, 0xe0, 0xe9, 0x62, 0x53, 0xc2, 0x21, 0x5b, 0x1b, 0xac,
0x69, 0xe7, 0xa8, 0x1e, 0xdb, 0x41, 0xc5, 0x02, 0x8b, 0x4f, 0x5c, 0x45, 0xc5, 0x3b, 0x49,
0x54, 0xd0,
];
let recovery_id: u8 = 1;
let signature: [u8; 64] = [
0x97, 0xa4, 0xee, 0x31, 0xfe, 0x82, 0x65, 0x72, 0x9f, 0x4a, 0xa6, 0x7d, 0x24, 0xd4, 0xa7,
0x27, 0xf8, 0xc3, 0x15, 0xa4, 0xc8, 0xf9, 0x80, 0xeb, 0x4c, 0x4d, 0x4a, 0xfa, 0x6e, 0xc9,
0x42, 0x41, 0x5d, 0x10, 0xd9, 0xc2, 0x8a, 0x90, 0xe9, 0x92, 0x9c, 0x52, 0x4b, 0x2c, 0xfb,
0x65, 0xdf, 0xbc, 0xf6, 0x8c, 0xfd, 0x68, 0xdb, 0x17, 0xf9, 0x5d, 0x23, 0x5f, 0x96, 0xd8,
0xf0, 0x72, 0x01, 0x2d,
];
let public_key = secp256k1_recover(&hash[..], recovery_id, &signature[..]).unwrap();
assert_eq!(public_key.to_bytes(), expected);
}
#[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
msg!("secp256k1_recover");
test_secp256k1_recover();
0
}
custom_panic_default!();

View File

@ -432,6 +432,7 @@ fn test_program_bpf_sanity() {
("relative_call", true),
("sanity", true),
("sanity++", true),
("secp256k1_recover", true),
("sha", true),
("struct_pass", true),
("struct_ret", true),
@ -453,6 +454,7 @@ fn test_program_bpf_sanity() {
("solana_bpf_rust_param_passing", true),
("solana_bpf_rust_rand", true),
("solana_bpf_rust_sanity", true),
("solana_bpf_rust_secp256k1_recover", true),
("solana_bpf_rust_sha", true),
]);
}
@ -1283,6 +1285,7 @@ fn assert_instruction_count() {
("relative_call", 10),
("sanity", 169),
("sanity++", 168),
("secp256k1_recover", 357),
("sha", 1040),
("struct_pass", 8),
("struct_ret", 22),
@ -1303,8 +1306,9 @@ fn assert_instruction_count() {
("solana_bpf_rust_noop", 478),
("solana_bpf_rust_param_passing", 46),
("solana_bpf_rust_rand", 481),
("solana_bpf_rust_sanity", 873),
("solana_bpf_rust_sha", 32301),
("solana_bpf_rust_sanity", 900),
("solana_bpf_rust_secp256k1_recover", 301),
("solana_bpf_rust_sha", 32333),
]);
}

View File

@ -19,6 +19,7 @@ log = "0.4.14"
num-derive = "0.3"
num-traits = "0.2"
rand_core = "0.6.3"
libsecp256k1 = "0.5.0"
sha3 = "0.9.1"
solana-measure = { path = "../../measure", version = "=1.8.0" }
solana-runtime = { path = "../../runtime", version = "=1.8.0" }

View File

@ -20,7 +20,8 @@ use solana_sdk::{
epoch_schedule::EpochSchedule,
feature_set::{
blake3_syscall_enabled, cpi_data_cost, enforce_aligned_host_addrs,
keccak256_syscall_enabled, memory_ops_syscalls, sysvar_via_syscall, update_data_on_realloc,
keccak256_syscall_enabled, memory_ops_syscalls, secp256k1_recover_syscall_enabled,
sysvar_via_syscall, update_data_on_realloc,
},
hash::{Hasher, HASH_BYTES},
ic_msg,
@ -31,6 +32,9 @@ use solana_sdk::{
process_instruction::{self, stable_log, ComputeMeter, InvokeContext, Logger},
pubkey::{Pubkey, PubkeyError, MAX_SEEDS},
rent::Rent,
secp256k1_recover::{
Secp256k1RecoverError, SECP256K1_PUBLIC_KEY_LENGTH, SECP256K1_SIGNATURE_LENGTH,
},
sysvar::{self, fees::Fees, Sysvar, SysvarId},
};
use std::{
@ -133,6 +137,11 @@ pub fn register_syscalls(
syscall_registry.register_syscall_by_name(b"sol_keccak256", SyscallKeccak256::call)?;
}
if invoke_context.is_feature_active(&secp256k1_recover_syscall_enabled::id()) {
syscall_registry
.register_syscall_by_name(b"sol_secp256k1_recover", SyscallSecp256k1Recover::call)?;
}
if invoke_context.is_feature_active(&blake3_syscall_enabled::id()) {
syscall_registry.register_syscall_by_name(b"sol_blake3", SyscallBlake3::call)?;
}
@ -330,6 +339,16 @@ pub fn bind_syscall_context_objects<'a>(
}),
);
bind_feature_gated_syscall_context_object!(
vm,
invoke_context.is_feature_active(&secp256k1_recover_syscall_enabled::id()),
Box::new(SyscallSecp256k1Recover {
cost: bpf_compute_budget.secp256k1_recover_cost,
compute_meter: invoke_context.get_compute_meter(),
loader_id,
}),
);
let is_sysvar_via_syscall_active = invoke_context.is_feature_active(&sysvar_via_syscall::id());
let invoke_context = Rc::new(RefCell::new(invoke_context));
@ -1342,6 +1361,92 @@ impl<'a> SyscallObject<BpfError> for SyscallMemset<'a> {
}
}
/// secp256k1_recover
pub struct SyscallSecp256k1Recover<'a> {
cost: u64,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallSecp256k1Recover<'a> {
fn call(
&mut self,
hash_addr: u64,
recovery_id_val: u64,
signature_addr: u64,
result_addr: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
question_mark!(self.compute_meter.consume(self.cost), result);
let hash = question_mark!(
translate_slice::<u8>(
memory_mapping,
hash_addr,
keccak::HASH_BYTES as u64,
self.loader_id,
true,
),
result
);
let signature = question_mark!(
translate_slice::<u8>(
memory_mapping,
signature_addr,
SECP256K1_SIGNATURE_LENGTH as u64,
self.loader_id,
true,
),
result
);
let secp256k1_recover_result = question_mark!(
translate_slice_mut::<u8>(
memory_mapping,
result_addr,
SECP256K1_PUBLIC_KEY_LENGTH as u64,
self.loader_id,
true,
),
result
);
let message = match libsecp256k1::Message::parse_slice(hash) {
Ok(msg) => msg,
Err(_) => {
*result = Ok(Secp256k1RecoverError::InvalidHash.into());
return;
}
};
let recovery_id = match libsecp256k1::RecoveryId::parse(recovery_id_val as u8) {
Ok(id) => id,
Err(_) => {
*result = Ok(Secp256k1RecoverError::InvalidRecoveryId.into());
return;
}
};
let signature = match libsecp256k1::Signature::parse_standard_slice(signature) {
Ok(sig) => sig,
Err(_) => {
*result = Ok(Secp256k1RecoverError::InvalidSignature.into());
return;
}
};
let public_key = match libsecp256k1::recover(&message, &signature, &recovery_id) {
Ok(key) => key.serialize(),
Err(_) => {
*result = Ok(Secp256k1RecoverError::InvalidSignature.into());
return;
}
};
secp256k1_recover_result.copy_from_slice(&public_key[1..65]);
*result = Ok(SUCCESS);
}
}
// Blake3
pub struct SyscallBlake3<'a> {
base_cost: u64,

View File

@ -457,6 +457,37 @@ uint64_t sol_keccak256(
uint8_t *result
);
/** Length of a secp256k1 recover input hash */
#define SECP256K1_RECOVER_HASH_LENGTH 32
/** Length of a secp256k1 input signature */
#define SECP256K1_RECOVER_SIGNATURE_LENGTH 64
/** Length of a secp256k1 recover result */
#define SECP256K1_RECOVER_RESULT_LENGTH 64
/** The hash provided to a sol_secp256k1_recover is invalid */
#define SECP256K1_RECOVER_ERROR_INVALID_HASH 1
/** The recovery_id provided to a sol_secp256k1_recover is invalid */
#define SECP256K1_RECOVER_ERROR_INVALID_RECOVERY_ID 2
/** The signature provided to a sol_secp256k1_recover is invalid */
#define SECP256K1_RECOVER_ERROR_INVALID_SIGNATURE 3
/**
* Recover public key from a signed message.
*
* @param hash Hashed message
* @param recovery_id Tag used for public key recovery from signatures. Can be 0 or 1
* @param signature An ECDSA signature
* @param result 64 byte array to hold the result. A recovered public key
* @return 0 if executed successfully
*/
uint64_t sol_secp256k1_recover(
const uint8_t *hash,
uint64_t recovery_id,
const uint8_t *signature,
uint8_t *result
);
/**
* Length of a Blake3 hash result
*/

View File

@ -36,6 +36,7 @@ thiserror = "1.0"
[target.'cfg(not(target_arch = "bpf"))'.dependencies]
curve25519-dalek = "3.0.0"
libsecp256k1 = "0.5.0"
rand = "0.7.0"
solana-logger = { path = "../../logger", version = "=1.8.0" }

View File

@ -39,6 +39,7 @@ pub mod pubkey;
pub mod rent;
pub mod sanitize;
pub mod secp256k1_program;
pub mod secp256k1_recover;
pub mod serialize_utils;
pub mod short_vec;
pub mod slot_hashes;

View File

@ -0,0 +1,113 @@
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use core::convert::TryFrom;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum Secp256k1RecoverError {
#[error("The hash provided to a secp256k1_recover is invalid")]
InvalidHash,
#[error("The recovery_id provided to a secp256k1_recover is invalid")]
InvalidRecoveryId,
#[error("The signature provided to a secp256k1_recover is invalid")]
InvalidSignature,
}
impl From<u64> for Secp256k1RecoverError {
fn from(v: u64) -> Secp256k1RecoverError {
match v {
1 => Secp256k1RecoverError::InvalidHash,
2 => Secp256k1RecoverError::InvalidRecoveryId,
3 => Secp256k1RecoverError::InvalidSignature,
_ => panic!("Unsupported Secp256k1RecoverError"),
}
}
}
impl From<Secp256k1RecoverError> for u64 {
fn from(v: Secp256k1RecoverError) -> u64 {
match v {
Secp256k1RecoverError::InvalidHash => 1,
Secp256k1RecoverError::InvalidRecoveryId => 2,
Secp256k1RecoverError::InvalidSignature => 3,
}
}
}
pub const SECP256K1_SIGNATURE_LENGTH: usize = 64;
pub const SECP256K1_PUBLIC_KEY_LENGTH: usize = 64;
#[repr(transparent)]
#[derive(
BorshSerialize,
BorshDeserialize,
BorshSchema,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
AbiExample,
)]
pub struct Secp256k1Pubkey(pub [u8; SECP256K1_PUBLIC_KEY_LENGTH]);
impl Secp256k1Pubkey {
pub fn new(pubkey_vec: &[u8]) -> Self {
Self(
<[u8; SECP256K1_PUBLIC_KEY_LENGTH]>::try_from(<&[u8]>::clone(&pubkey_vec))
.expect("Slice must be the same length as a Pubkey"),
)
}
pub fn to_bytes(self) -> [u8; 64] {
self.0
}
}
pub fn secp256k1_recover(
hash: &[u8],
recovery_id: u8,
signature: &[u8],
) -> Result<Secp256k1Pubkey, Secp256k1RecoverError> {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_secp256k1_recover(
hash: *const u8,
recovery_id: u64,
signature: *const u8,
result: *mut u8,
) -> u64;
}
let mut pubkey_buffer = [0u8; SECP256K1_PUBLIC_KEY_LENGTH];
let result = unsafe {
sol_secp256k1_recover(
hash.as_ptr(),
recovery_id as u64,
signature.as_ptr(),
pubkey_buffer.as_mut_ptr(),
)
};
match result {
0 => Ok(Secp256k1Pubkey::new(&pubkey_buffer)),
error => Err(Secp256k1RecoverError::from(error)),
}
}
#[cfg(not(target_arch = "bpf"))]
{
let message = libsecp256k1::Message::parse_slice(hash)
.map_err(|_| Secp256k1RecoverError::InvalidHash)?;
let recovery_id = libsecp256k1::RecoveryId::parse(recovery_id)
.map_err(|_| Secp256k1RecoverError::InvalidRecoveryId)?;
let signature = libsecp256k1::Signature::parse_standard_slice(signature)
.map_err(|_| Secp256k1RecoverError::InvalidSignature)?;
let secp256k1_key = libsecp256k1::recover(&message, &signature, &recovery_id)
.map_err(|_| Secp256k1RecoverError::InvalidSignature)?;
Ok(Secp256k1Pubkey::new(&secp256k1_key.serialize()[1..65]))
}
}

View File

@ -127,6 +127,10 @@ pub mod memory_ops_syscalls {
solana_sdk::declare_id!("ENQi37wsVhTvFz2gUiZAAbqFEWGN2jwFsqdEDTE8A4MU");
}
pub mod secp256k1_recover_syscall_enabled {
solana_sdk::declare_id!("6RvdSWHh8oh72Dp7wMTS2DBkf3fRPtChfNrAo3cZZoXJ");
}
pub mod add_missing_program_error_mappings {
solana_sdk::declare_id!("3QEUpjhgPEt92nz3Mqf6pABkHPGCQwSvKtyGMq4SuQyL");
}
@ -187,6 +191,7 @@ lazy_static! {
(keccak256_syscall_enabled::id(), "keccak256 syscall"),
(stake_program_v4::id(), "solana_stake_program v4"),
(memory_ops_syscalls::id(), "add syscalls for memory operations"),
(secp256k1_recover_syscall_enabled::id(), "secp256k1_recover syscall"),
(add_missing_program_error_mappings::id(), "add missing program error mappings"),
(system_transfer_zero_check::id(), "perform all checks for transfers of 0 lamports"),
(blake3_syscall_enabled::id(), "blake3 syscall"),

View File

@ -179,6 +179,8 @@ pub struct BpfComputeBudget {
pub cpi_bytes_per_unit: u64,
/// Base number of compute units consumed to get a sysvar
pub sysvar_base_cost: u64,
/// Number of compute units consumed to call secp256k1_recover
pub secp256k1_recover_cost: u64,
}
impl Default for BpfComputeBudget {
fn default() -> Self {
@ -202,6 +204,7 @@ impl BpfComputeBudget {
max_cpi_instruction_size: 1280, // IPv6 Min MTU size
cpi_bytes_per_unit: 250, // ~50MB at 200,000 units
sysvar_base_cost: 100,
secp256k1_recover_cost: 25_000,
}
}
}