Add keccak-secp256k1 instruction (#11839)

* Implement keccak-secp256k1 instruction

Verifies eth addreses with ecrecover function

* Move secp256k1 test
This commit is contained in:
sakridge
2020-09-15 18:23:21 -07:00
committed by GitHub
parent 7237e7065f
commit 3930cb865a
25 changed files with 732 additions and 52 deletions

View File

@ -1,5 +1,6 @@
use crate::clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use crate::message::Message;
use crate::secp256k1_program;
use log::*;
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
@ -18,6 +19,11 @@ impl Default for FeeCalculator {
}
}
#[derive(Clone)]
pub struct FeeConfig {
pub is_secp256k1_enabled: bool,
}
impl FeeCalculator {
pub fn new(lamports_per_signature: u64) -> Self {
Self {
@ -25,8 +31,27 @@ impl FeeCalculator {
}
}
pub fn calculate_fee(&self, message: &Message) -> u64 {
self.lamports_per_signature * u64::from(message.header.num_required_signatures)
// extra_config: None == everything enabled
pub fn calculate_fee(&self, message: &Message, extra_config: Option<FeeConfig>) -> u64 {
let is_secp256k1_enabled = match extra_config {
Some(config) => config.is_secp256k1_enabled,
None => true,
};
let mut num_secp_signatures: u64 = 0;
if is_secp256k1_enabled {
for instruction in &message.instructions {
let program_index = instruction.program_id_index as usize;
// Transaction may not be sanitized here
if program_index < message.account_keys.len() {
let id = message.account_keys[program_index];
if secp256k1_program::check_id(&id) && !instruction.data.is_empty() {
num_secp_signatures += instruction.data[0] as u64;
}
}
}
}
self.lamports_per_signature
* (u64::from(message.header.num_required_signatures) + num_secp_signatures)
}
}
@ -182,25 +207,76 @@ mod tests {
#[test]
fn test_fee_calculator_calculate_fee() {
let fee_config = Some(FeeConfig {
is_secp256k1_enabled: true,
});
// Default: no fee.
let message = Message::default();
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
assert_eq!(
FeeCalculator::default().calculate_fee(&message, fee_config.clone()),
0
);
// No signature, no fee.
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
assert_eq!(FeeCalculator::new(1).calculate_fee(&message, fee_config), 0);
let fee_config = Some(FeeConfig {
is_secp256k1_enabled: false,
});
// One signature, a fee.
let pubkey0 = Pubkey::new(&[0; 32]);
let pubkey1 = Pubkey::new(&[1; 32]);
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let message = Message::new(&[ix0], Some(&pubkey0));
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
assert_eq!(FeeCalculator::new(2).calculate_fee(&message, fee_config), 2);
// Two signatures, double the fee.
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
let message = Message::new(&[ix0, ix1], Some(&pubkey0));
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
assert_eq!(FeeCalculator::new(2).calculate_fee(&message, None), 4);
}
#[test]
fn test_fee_calculator_calculate_fee_secp256k1() {
use crate::instruction::Instruction;
let pubkey0 = Pubkey::new(&[0; 32]);
let pubkey1 = Pubkey::new(&[1; 32]);
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let mut secp_instruction = Instruction {
program_id: crate::secp256k1_program::id(),
accounts: vec![],
data: vec![],
};
let mut secp_instruction2 = Instruction {
program_id: crate::secp256k1_program::id(),
accounts: vec![],
data: vec![1],
};
let message = Message::new(
&[
ix0.clone(),
secp_instruction.clone(),
secp_instruction2.clone(),
],
Some(&pubkey0),
);
let fee_config = Some(FeeConfig {
is_secp256k1_enabled: true,
});
assert_eq!(
FeeCalculator::new(1).calculate_fee(&message, fee_config.clone()),
2
);
secp_instruction.data = vec![0];
secp_instruction2.data = vec![10];
let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
assert_eq!(
FeeCalculator::new(1).calculate_fee(&message, fee_config),
11
);
}
#[test]

View File

@ -37,6 +37,7 @@ pub mod pubkey;
pub mod rent;
pub mod rpc_port;
pub mod sanitize;
pub mod secp256k1_program;
pub mod short_vec;
pub mod slot_hashes;
pub mod slot_history;
@ -89,6 +90,8 @@ pub mod genesis_config;
#[cfg(not(feature = "program"))]
pub mod hard_forks;
#[cfg(not(feature = "program"))]
pub mod secp256k1;
#[cfg(not(feature = "program"))]
pub mod shred_version;
#[cfg(not(feature = "program"))]
pub mod signature;

146
sdk/src/secp256k1.rs Normal file
View File

@ -0,0 +1,146 @@
use crate::clock::{Epoch, GENESIS_EPOCH};
use crate::fee_calculator::FeeConfig;
use crate::genesis_config::ClusterType;
use digest::Digest;
use serde_derive::{Deserialize, Serialize};
pub fn get_fee_config(cluster_type: ClusterType, epoch: Epoch) -> Option<FeeConfig> {
Some(FeeConfig {
is_secp256k1_enabled: is_enabled(cluster_type, epoch),
})
}
pub fn is_enabled_epoch(cluster_type: ClusterType) -> Epoch {
match cluster_type {
ClusterType::Development => GENESIS_EPOCH,
ClusterType::Testnet => u64::MAX,
ClusterType::MainnetBeta => u64::MAX,
ClusterType::Devnet => u64::MAX,
}
}
pub fn is_enabled(cluster_type: ClusterType, epoch: Epoch) -> bool {
epoch >= is_enabled_epoch(cluster_type)
}
#[derive(Debug)]
pub enum Secp256k1Error {
InvalidSignature,
InvalidRecoveryId,
InvalidDataOffsets,
InvalidInstructionDataSize,
}
pub const HASHED_PUBKEY_SERIALIZED_SIZE: usize = 20;
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 11;
pub fn construct_eth_pubkey(pubkey: &secp256k1::PublicKey) -> [u8; HASHED_PUBKEY_SERIALIZED_SIZE] {
let mut addr = [0u8; HASHED_PUBKEY_SERIALIZED_SIZE];
addr.copy_from_slice(&sha3::Keccak256::digest(&pubkey.serialize()[1..])[12..]);
assert_eq!(addr.len(), HASHED_PUBKEY_SERIALIZED_SIZE);
addr
}
#[derive(Default, Serialize, Deserialize, Debug)]
pub struct SecpSignatureOffsets {
pub signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
pub signature_instruction_index: u8,
pub eth_address_offset: u16, // offset to eth_address of 20 bytes
pub eth_address_instruction_index: u8,
pub message_data_offset: u16, // offset to start of message data
pub message_data_size: u16, // size of message data
pub message_instruction_index: u8,
}
fn get_data_slice<'a>(
instruction_datas: &'a [&[u8]],
instruction_index: u8,
offset_start: u16,
size: usize,
) -> Result<&'a [u8], Secp256k1Error> {
let signature_index = instruction_index as usize;
if signature_index > instruction_datas.len() {
return Err(Secp256k1Error::InvalidDataOffsets);
}
let signature_instruction = &instruction_datas[signature_index];
let start = offset_start as usize;
let end = start + size;
if end > signature_instruction.len() {
return Err(Secp256k1Error::InvalidSignature);
}
Ok(&instruction_datas[signature_index][start..end])
}
pub fn verify_eth_addresses(
data: &[u8],
instruction_datas: &[&[u8]],
) -> Result<(), Secp256k1Error> {
if data.is_empty() {
return Err(Secp256k1Error::InvalidInstructionDataSize);
}
let count = data[0] as usize;
let expected_data_size = 1 + count * SIGNATURE_OFFSETS_SERIALIZED_SIZE;
if data.len() < expected_data_size {
return Err(Secp256k1Error::InvalidInstructionDataSize);
}
for i in 0..count {
let start = 1 + i * SIGNATURE_OFFSETS_SERIALIZED_SIZE;
let end = start + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
let offsets: SecpSignatureOffsets = bincode::deserialize(&data[start..end])
.map_err(|_| Secp256k1Error::InvalidSignature)?;
// Parse out signature
let signature_index = offsets.signature_instruction_index as usize;
if signature_index > instruction_datas.len() {
return Err(Secp256k1Error::InvalidInstructionDataSize);
}
let signature_instruction = instruction_datas[signature_index];
let sig_start = offsets.signature_offset as usize;
let sig_end = sig_start + SIGNATURE_SERIALIZED_SIZE;
if sig_end >= signature_instruction.len() {
return Err(Secp256k1Error::InvalidSignature);
}
let signature =
secp256k1::Signature::parse_slice(&signature_instruction[sig_start..sig_end])
.map_err(|_| Secp256k1Error::InvalidSignature)?;
let recovery_id = secp256k1::RecoveryId::parse(signature_instruction[sig_end])
.map_err(|_| Secp256k1Error::InvalidRecoveryId)?;
// Parse out pubkey
let eth_address_slice = get_data_slice(
&instruction_datas,
offsets.eth_address_instruction_index,
offsets.eth_address_offset,
HASHED_PUBKEY_SERIALIZED_SIZE,
)?;
// Parse out message
let message_slice = get_data_slice(
&instruction_datas,
offsets.message_instruction_index,
offsets.message_data_offset,
offsets.message_data_size as usize,
)?;
let mut hasher = sha3::Keccak256::new();
hasher.update(message_slice);
let message_hash = hasher.finalize();
let pubkey = secp256k1::recover(
&secp256k1::Message::parse_slice(&message_hash).unwrap(),
&signature,
&recovery_id,
)
.map_err(|_| Secp256k1Error::InvalidSignature)?;
let eth_address = construct_eth_pubkey(&pubkey);
if eth_address_slice != eth_address {
return Err(Secp256k1Error::InvalidSignature);
}
}
Ok(())
}

View File

@ -0,0 +1 @@
solana_sdk::declare_id!("KeccakSecp256k11111111111111111111111111111");

View File

@ -1,6 +1,7 @@
//! Defines a Transaction type to package an atomic sequence of instructions.
use crate::sanitize::{Sanitize, SanitizeError};
use crate::secp256k1::verify_eth_addresses;
use crate::{
hash::Hash,
instruction::{CompiledInstruction, Instruction, InstructionError},
@ -330,6 +331,28 @@ impl Transaction {
}
}
pub fn verify_precompiles(&self) -> Result<()> {
for instruction in &self.message().instructions {
// The Transaction may not be sanitized at this point
if instruction.program_id_index as usize >= self.message().account_keys.len() {
return Err(TransactionError::AccountNotFound);
}
let program_id = &self.message().account_keys[instruction.program_id_index as usize];
if crate::secp256k1_program::check_id(program_id) {
let instruction_datas: Vec<_> = self
.message()
.instructions
.iter()
.map(|instruction| instruction.data.as_ref())
.collect();
let data = &instruction.data;
let e = verify_eth_addresses(data, &instruction_datas);
e.map_err(|_| TransactionError::InvalidAccountIndex)?;
}
}
Ok(())
}
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result<Vec<Option<usize>>> {
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {