Skip to content

Commit

Permalink
evm: use abiencoding for precompiles
Browse files Browse the repository at this point in the history
  • Loading branch information
nhynes committed Dec 8, 2022
1 parent 2175780 commit 9fb592b
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 282 deletions.
258 changes: 70 additions & 188 deletions runtime-sdk/modules/evm/src/precompile/confidential.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use ethabi::ParamType;
use evm::{
executor::stack::{PrecompileFailure, PrecompileOutput},
Context, ExitError, ExitSucceed,
};
use hmac::{Hmac, Mac, NewMac as _};
use num::{BigUint, ToPrimitive as _};
use oasis_runtime_sdk::core::common::crypto::mrae::deoxysii::{DeoxysII, KEY_SIZE, NONCE_SIZE};

use crate::backend::EVMBackendExt;
Expand All @@ -17,22 +17,22 @@ const DEOXYSII_WORD_COST: u64 = 100;
/// Length of an EVM word, in bytes.
const WORD: usize = 32;

fn bigint_bytes_to_u64(item: &'static str, bytes: &[u8; 32]) -> Result<u64, PrecompileFailure> {
BigUint::from_bytes_be(bytes)
.to_u64()
.ok_or_else(|| PrecompileFailure::Error {
exit_status: ExitError::Other(format!("{} input is too big", item).into()),
})
}

pub(super) fn call_random_bytes<B: EVMBackendExt>(
input: &[u8],
target_gas: Option<u64>,
_context: &Context,
_is_static: bool,
backend: &B,
) -> PrecompileResult {
let (num_words, pers_str) = decode_random_bytes_call_args(input)?;
let mut call_args =
ethabi::decode(&[ParamType::Uint(256), ParamType::Bytes], input).map_err(|e| {
PrecompileFailure::Error {
exit_status: ExitError::Other(e.to_string().into()),
}
})?;
let pers_str = call_args.pop().unwrap().into_bytes().unwrap();
let num_words_big = call_args.pop().unwrap().into_uint().unwrap();
let num_words = num_words_big.try_into().unwrap_or(u64::max_value());
// This operation shouldn't be too cheap to start since it invokes a key manager.
// Each byte is generated using hashing, so it's neither expensive nor cheap.
// Thus:
Expand All @@ -55,7 +55,7 @@ pub(super) fn call_random_bytes<B: EVMBackendExt>(
60,
10_000,
)?;
let bytes = backend.random_bytes(num_bytes, pers_str);
let bytes = backend.random_bytes(num_bytes, &pers_str);
if bytes.len() != num_bytes as usize {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("not enough entropy".into()),
Expand All @@ -69,25 +69,6 @@ pub(super) fn call_random_bytes<B: EVMBackendExt>(
})
}

fn decode_random_bytes_call_args(input: &[u8]) -> Result<(u64, &[u8]), PrecompileFailure> {
const SLOTS: usize = 2;
if input.len() < SLOTS * WORD {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("input length must be at least 64 bytes".into()),
});
}
let (fixed_inputs, pers_str) = input.split_at(SLOTS * WORD);
let mut words = fixed_inputs.array_chunks::<WORD>();
let num_words = bigint_bytes_to_u64("num words", words.next().unwrap())?;
let pers_str_len = bigint_bytes_to_u64("pers length", words.next().unwrap())?;
if pers_str_len > (pers_str.len() as u64) {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("input too short".into()),
});
}
Ok((num_words, &pers_str[..(pers_str_len as usize)]))
}

pub(super) fn call_x25519_derive(
input: &[u8],
target_gas: Option<u64>,
Expand Down Expand Up @@ -131,46 +112,30 @@ pub(super) fn call_x25519_derive(
#[allow(clippy::type_complexity)]
fn decode_deoxysii_call_args(
input: &[u8],
) -> Result<([u8; KEY_SIZE], [u8; NONCE_SIZE], &[u8], &[u8]), PrecompileFailure> {
// Number of fixed words in the input (key, nonce word, two lengths; see
// comments in the precompiles).
const SLOTS: usize = 4;

if input.len() < SLOTS * WORD {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("input length must be at least 128 bytes".into()),
});
}

let mut words = input.array_chunks::<WORD>().take(SLOTS);
let key = words.next().unwrap();
let nonce_word = words.next().unwrap();
let text_len = words.next().unwrap();
let ad_len = words.next().unwrap();
) -> Result<([u8; KEY_SIZE], [u8; NONCE_SIZE], Vec<u8>, Vec<u8>), PrecompileFailure> {
let mut call_args = ethabi::decode(
&[
ParamType::FixedBytes(32), // key
ParamType::FixedBytes(32), // nonce
ParamType::Bytes, // plain or ciphertext
ParamType::Bytes, // associated data
],
input,
)
.map_err(|e| PrecompileFailure::Error {
exit_status: ExitError::Other(e.to_string().into()),
})?;
let ad = call_args.pop().unwrap().into_bytes().unwrap();
let text = call_args.pop().unwrap().into_bytes().unwrap();
let nonce_bytes = call_args.pop().unwrap().into_fixed_bytes().unwrap();
let key_bytes = call_args.pop().unwrap().into_fixed_bytes().unwrap();

// Only the initial NONCE_SIZE bytes of the nonce field are used - bytes at
// lower addresses in the input.
let mut nonce = [0u8; NONCE_SIZE];
nonce.copy_from_slice(&nonce_word[..NONCE_SIZE]);
nonce.copy_from_slice(&nonce_bytes[..NONCE_SIZE]);
let mut key = [0u8; KEY_SIZE];
key.copy_from_slice(&key_bytes);

let text_len = bigint_bytes_to_u64("text length", text_len)? as usize;
let text_size = text_len.saturating_add(31) & (!0x1f); // Round up to 32 bytes.

let ad_len = bigint_bytes_to_u64("ad length", ad_len)? as usize;
let ad_size = ad_len.saturating_add(31) & (!0x1f); // Round up to 32 bytes.
let input_len = ad_size
.checked_add(text_size)
.and_then(|s| s.checked_add(SLOTS * WORD));
if input_len != Some(input.len()) {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("input too short".into()),
});
}

let text = &input[(SLOTS * WORD)..(SLOTS * WORD + text_len)];
let ad = &input[(SLOTS * WORD + text_size)..(SLOTS * WORD + text_size + ad_len)];

Ok((*key, nonce, text, ad))
Ok((key, nonce, text, ad))
}

pub(super) fn call_deoxysii_seal(
Expand All @@ -185,13 +150,9 @@ pub(super) fn call_deoxysii_seal(
DEOXYSII_BASE_COST,
DEOXYSII_WORD_COST,
)?;

// Input encoding: bytes32 key || bytes32 nonce || uint plaintext_len || uint ad_len || plaintext || ad.
let (key, nonce, text, ad) = decode_deoxysii_call_args(input)?;

let deoxysii = DeoxysII::new(&key);
let encrypted = deoxysii.seal(&nonce, text, ad);

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
cost: gas_cost,
Expand All @@ -212,11 +173,8 @@ pub(super) fn call_deoxysii_open(
DEOXYSII_BASE_COST,
DEOXYSII_WORD_COST,
)?;

// Input encoding: bytes32 key || bytes32 nonce || uint ciphertext_len || uint ad_len || ciphertext || ad.
let (key, nonce, ciphertext, ad) = decode_deoxysii_call_args(input)?;
let ciphertext = ciphertext.to_vec();

let deoxysii = DeoxysII::new(&key);
match deoxysii.open(&nonce, ciphertext, ad) {
Ok(decrypted) => Ok(PrecompileOutput {
Expand All @@ -235,6 +193,7 @@ pub(super) fn call_deoxysii_open(
mod test {
extern crate test;

use ethabi::Token;
use rand::rngs::OsRng;
use test::Bencher;

Expand Down Expand Up @@ -324,105 +283,48 @@ mod test {
});
}

fn get_usize_bytes(u: usize) -> [u8; 32] {
let short = u.to_be_bytes();
let mut long = [0u8; 32];
long[(32 - short.len())..].copy_from_slice(&short);
long
}

#[test]
fn test_deoxysii() {
let mut key = [0u8; 32];
key.copy_from_slice(b"this must be the excelentest key");
let mut nonce = [0u8; 32];
nonce.copy_from_slice(b"complete noncence, and too long.");
let plaintext = b"plaintext";
let plaintext_len = get_usize_bytes(plaintext.len());
let key = b"this must be the excelentest key";
let nonce = b"complete noncence, and too long.";
let plaintext = b"0123456789";
let ad = b"additional data";
let ad_len = get_usize_bytes(ad.len());

// Compose the input blob and try calling with partial fragments.
let mut plain_input: Vec<u8> = Vec::new();
plain_input.extend_from_slice(&key);
plain_input.extend_from_slice(&nonce);
plain_input.extend_from_slice(&plaintext_len);
call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
]),
&plain_input,
1_000_000,
)
.expect("call should return something")
.expect_err("call should fail");

plain_input.extend_from_slice(&ad_len);
call_contract(
let ret_ct = call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
]),
&plain_input,
1_000_000,
)
.expect("call should return something")
.expect_err("call should fail");

plain_input.extend_from_slice(plaintext);
plain_input.resize((plain_input.len() + 31) & (!31), 0);
plain_input.extend_from_slice(ad);
call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
]),
&plain_input,
1_000_000,
)
.expect("call should return something")
.expect_err("call should fail");

plain_input.resize((plain_input.len() + 31) & (!31), 0);

// Get ciphertext.
let result = call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
&ethabi::encode(&[
Token::FixedBytes(key.to_vec()),
Token::FixedBytes(nonce.to_vec()),
Token::Bytes(plaintext.to_vec()),
Token::Bytes(ad.to_vec()),
]),
&plain_input,
1_000_000,
10_000_000,
)
.expect("call should return something")
.expect("call should succeed");
let ciphertext = result.output;
let ciphertext_len = get_usize_bytes(ciphertext.len());

// Compose input blob for decryption.
let mut cipher_input: Vec<u8> = Vec::new();
cipher_input.extend_from_slice(&key);
cipher_input.extend_from_slice(&nonce);
cipher_input.extend_from_slice(&ciphertext_len);
cipher_input.extend_from_slice(&ad_len);
cipher_input.extend_from_slice(&ciphertext);
cipher_input.resize((cipher_input.len() + 31) & (!31), 0);
cipher_input.extend_from_slice(ad);
cipher_input.resize((cipher_input.len() + 31) & (!31), 0);
assert_ne!(plaintext.as_slice(), ret_ct.output);

// Try decrypting and compare.
let result = call_contract(
let ret_pt = call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04,
]),
&cipher_input,
1_000_000,
&ethabi::encode(&[
Token::FixedBytes(key.to_vec()),
Token::FixedBytes(nonce.to_vec()),
Token::Bytes(ret_ct.output),
Token::Bytes(ad.to_vec()),
]),
10_000_000,
)
.expect("call should return something")
.expect("call should succeed");
assert_eq!(result.output, plaintext);
assert_eq!(plaintext.as_slice(), ret_pt.output);
}

#[test]
fn test_random_bytes() {
let input = "00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002beef000000000000000000000000000000000000000000000000000000000000";
let input = "000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002beef000000000000000000000000000000000000000000000000000000000000";
let ret = call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
Expand All @@ -436,31 +338,21 @@ mod test {

#[bench]
fn bench_deoxysii_short(b: &mut Bencher) {
let mut key = [0u8; 32];
key.copy_from_slice(b"this must be the excelentest key");
let mut nonce = [0u8; 32];
nonce.copy_from_slice(b"complete noncence, and too long.");
let key = b"this must be the excelentest key";
let nonce = b"complete noncence, and too long.";
let plaintext = b"01234567890123456789";
let plaintext_len = get_usize_bytes(plaintext.len());
let ad = b"additional data";
let ad_len = get_usize_bytes(ad.len());

let mut plain_input: Vec<u8> = Vec::new();
plain_input.extend_from_slice(&key);
plain_input.extend_from_slice(&nonce);
plain_input.extend_from_slice(&plaintext_len);
plain_input.extend_from_slice(&ad_len);
plain_input.extend_from_slice(plaintext);
plain_input.resize((plain_input.len() + 31) & (!31), 0);
plain_input.extend_from_slice(ad);
plain_input.resize((plain_input.len() + 31) & (!31), 0);

b.iter(|| {
call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
]),
&plain_input,
&ethabi::encode(&[
Token::FixedBytes(key.to_vec()),
Token::FixedBytes(nonce.to_vec()),
Token::Bytes(plaintext.to_vec()),
Token::Bytes(ad.to_vec()),
]),
10_000_000,
)
.expect("call should return something")
Expand All @@ -470,31 +362,21 @@ mod test {

#[bench]
fn bench_deoxysii_long(b: &mut Bencher) {
let mut key = [0u8; 32];
key.copy_from_slice(b"this must be the excelentest key");
let mut nonce = [0u8; 32];
nonce.copy_from_slice(b"complete noncence, and too long.");
let key = b"this must be the excelentest key";
let nonce = b"complete noncence, and too long.";
let plaintext = b"0123456789".repeat(200);
let plaintext_len = get_usize_bytes(plaintext.len());
let ad = b"additional data";
let ad_len = get_usize_bytes(ad.len());

let mut plain_input: Vec<u8> = Vec::new();
plain_input.extend_from_slice(&key);
plain_input.extend_from_slice(&nonce);
plain_input.extend_from_slice(&plaintext_len);
plain_input.extend_from_slice(&ad_len);
plain_input.extend_from_slice(&plaintext);
plain_input.resize((plain_input.len() + 31) & (!31), 0);
plain_input.extend_from_slice(ad);
plain_input.resize((plain_input.len() + 31) & (!31), 0);

b.iter(|| {
call_contract(
H160([
0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03,
]),
&plain_input,
&ethabi::encode(&[
Token::FixedBytes(key.to_vec()),
Token::FixedBytes(nonce.to_vec()),
Token::Bytes(plaintext.to_vec()),
Token::Bytes(ad.to_vec()),
]),
10_000_000,
)
.expect("call should return something")
Expand Down
Loading

0 comments on commit 9fb592b

Please sign in to comment.