Skip to content

Commit

Permalink
feat(runtime): Implement ECRecover in the Math API. (#3921)
Browse files Browse the repository at this point in the history
  • Loading branch information
artob authored and joshuajbouw committed Mar 26, 2021
1 parent bb5bee4 commit a718719
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions chain/jsonrpc/res/rpc_errors_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
"method_name": ""
}
},
"InvalidECDSASignature": {
"name": "InvalidECDSASignature",
"subtypes": [],
"props": {}
},
"Deserialization": {
"name": "Deserialization",
"subtypes": [],
Expand Down
3 changes: 3 additions & 0 deletions runtime/near-vm-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ pub enum HostError {
ContractSizeExceeded { size: u64, limit: u64 },
/// The host function was deprecated.
Deprecated { method_name: String },
/// Invalid ECDSA signature.
InvalidECDSASignature,
/// Deserialization error for alt_bn128 functions
#[cfg(feature = "protocol_feature_alt_bn128")]
AltBn128DeserializationError { msg: String },
Expand Down Expand Up @@ -473,6 +475,7 @@ impl std::fmt::Display for HostError {
ReturnedValueLengthExceeded { length, limit } => write!(f, "The length of a returned value {} exceeds the limit {}", length, limit),
ContractSizeExceeded { size, limit } => write!(f, "The size of a contract code in DeployContract action {} exceeds the limit {}", size, limit),
Deprecated {method_name}=> write!(f, "Attempted to call deprecated host function {}", method_name),
InvalidECDSASignature => write!(f, "Invalid ECDSA signature"),
#[cfg(feature = "protocol_feature_alt_bn128")]
AltBn128DeserializationError { msg } => write!(f, "AltBn128 deserialization error: {}", msg),
#[cfg(feature = "protocol_feature_alt_bn128")]
Expand Down
2 changes: 2 additions & 0 deletions runtime/near-vm-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ blake2 = "0.9.1"
borsh = "0.8.1"
bs58 = "0.4"
byteorder = "1.2"
libsecp256k1 = "0.3.5"
ripemd160 = "0.9.0"
serde = { version = "1", features = ["derive"] }
sha2 = ">=0.8,<0.10"
Expand All @@ -30,6 +31,7 @@ near-runtime-utils = { path = "../near-runtime-utils", version = "3.0.0" }
bn = { package = "zeropool-bn", version = "0.5.9", features = [], optional = true }

[dev-dependencies]
hex-literal = "0.2"
serde_json = {version= "1", features= ["preserve_order"]}

[features]
Expand Down
40 changes: 38 additions & 2 deletions runtime/near-vm-logic/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,10 +1001,46 @@ impl<'a> VMLogic<'a> {
/// # Cost
///
/// TODO
pub fn ecrecover(&mut self, hash_ptr: u64, v: u32, r_ptr: u64, s_ptr: u64, register_id: u64) -> Result<()> {
pub fn ecrecover(
&mut self,
hash_ptr: u64,
v: u32,
r_ptr: u64,
s_ptr: u64,
register_id: u64,
) -> Result<()> {
self.gas_counter.pay_base(ecrecover_base)?;
if v != 27 && v != 28 {
return Err(HostError::InvalidECDSASignature.into());
}
let hash = self.memory_get_vec(hash_ptr, 32)?;
let v = (v & 0xFF) as u8;
let r = self.memory_get_vec(r_ptr, 32)?;
let s = self.memory_get_vec(s_ptr, 32)?;

let hash = secp256k1::Message::parse_slice(hash.as_slice()).unwrap();

let mut signature = [0u8; 64];
signature[0..32].copy_from_slice(r.as_slice());
signature[32..64].copy_from_slice(s.as_slice());
let signature = secp256k1::Signature::parse(&signature);

let recovery_id = match secp256k1::RecoveryId::parse_rpc(v) {
Err(_) => return Err(HostError::InvalidECDSASignature.into()),
Ok(rid) => rid,
};

let public_key = match secp256k1::recover(&hash, &signature, &recovery_id) {
Err(_) => return Err(HostError::InvalidECDSASignature.into()),
Ok(pk) => pk,
};

use sha3::Digest;
let result = sha3::Keccak256::digest(&public_key.serialize()[1..]);
let mut address = [0u8; 20];
address.copy_from_slice(&result[12..]);

Ok(()) // TODO
self.internal_write_register(register_id, address.to_vec())
}

/// Called by gas metering injected into Wasm. Counts both towards `burnt_gas` and `used_gas`.
Expand Down
34 changes: 31 additions & 3 deletions runtime/near-vm-logic/tests/test_miscs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use fixtures::get_context;
use helpers::*;
use hex_literal::hex;
use near_vm_errors::HostError;
use near_vm_logic::ExtCosts;
use vm_logic_builder::VMLogicBuilder;
Expand Down Expand Up @@ -534,8 +535,8 @@ fn test_keccak512() {
fn test_ripemd160() {
let mut logic_builder = VMLogicBuilder::default();
let mut logic = logic_builder.build(get_context(vec![], false));
let data = b"tesdsst";

let data = b"tesdsst";
logic.ripemd160(data.len() as _, data.as_ptr() as _, 0).unwrap();
let res = &vec![0u8; 20];
logic.read_register(0, res.as_ptr() as _).expect("OK");
Expand Down Expand Up @@ -563,8 +564,8 @@ fn test_ripemd160() {
fn test_blake2b() {
let mut logic_builder = VMLogicBuilder::default();
let mut logic = logic_builder.build(get_context(vec![], false));
let data = b"tesdsst";

let data = b"tesdsst";
logic.blake2b(data.len() as _, data.as_ptr() as _, 0).unwrap();
let res = &vec![0u8; 64];
logic.read_register(0, res.as_ptr() as _).expect("OK");
Expand Down Expand Up @@ -595,7 +596,34 @@ fn test_blake2b() {

#[test]
fn test_ecrecover() {
// TODO
let mut logic_builder = VMLogicBuilder::default();
let mut logic = logic_builder.build(get_context(vec![], false));

// See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/cryptography/ECDSA.test.js
use sha3::Digest;
let hash = sha3::Keccak256::digest(b"OpenZeppelin");
let signature = hex!("5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b");
let signer = hex!("2cc1166f6212628A0deEf2B33BEFB2187D35b86c");

let (r, s, v) = (&signature[0..32], &signature[32..64], signature[64] as u32);
logic.ecrecover(hash.as_ptr() as _, v, r.as_ptr() as _, s.as_ptr() as _, 0).unwrap();

let result = &vec![0u8; 20];
logic.read_register(0, result.as_ptr() as _).expect("OK");

assert_eq!(result.to_vec(), signer);
assert_costs(map! {
ExtCosts::base: 1,
ExtCosts::read_memory_base: 3,
ExtCosts::read_memory_byte: 96,
ExtCosts::write_memory_base: 1,
ExtCosts::write_memory_byte: 20,
ExtCosts::read_register_base: 1,
ExtCosts::read_register_byte: 20,
ExtCosts::write_register_base: 1,
ExtCosts::write_register_byte: 20,
ExtCosts::ecrecover_base: 1,
});
}

#[test]
Expand Down
5 changes: 4 additions & 1 deletion runtime/runtime-params-estimator/test-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,11 @@ pub unsafe fn blake2b_10kib_10k() {
// Compute ecrecover 10k times.
#[no_mangle]
pub unsafe fn ecrecover_10k() {
let hash = [0u8; 32];
let signature = [0u8; 65];
let (r, s, v) = (&signature[0..32], &signature[32..64], 27);
for _ in 0..10_000 {
// TODO
ecrecover(hash.as_ptr() as _, v, r.as_ptr() as _, s.as_ptr() as _, 0);
}
}

Expand Down

0 comments on commit a718719

Please sign in to comment.