Skip to content

Commit

Permalink
Implemented ECDSA recover function. (#914)
Browse files Browse the repository at this point in the history
* Implemented ecdsa recovery function.
Added method `to_eth_address` and `to_account_id`.
Added tests.

* Cargo fmt

* Added `ECDSA` and `Ethereum` to dictionary

* Fixed comments according a new spellcheck

* Fixes according comments in review.

* Fixed build issue for wasm

* Use struct instead of alias for `EthereumAddress`.

* cargo fmt --all

* Simplified `ecdsa_recover`.
USed symbolic links instead files.

* Added documentation for `to_eth_address` and `to_account_id` methods.

* Renamed `to_account_id` into `to_default_account_id`

* Cargo fmt

* Removed DeRef trait. Now field of `EthereumAddress` and `ECDSAPublicKey` is private.

* Fixed doc test for ecdsa_recover in EnvAccess
  • Loading branch information
xgreenx authored Oct 14, 2021
1 parent 2f4f4f0 commit 284e4e5
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ AST
BLAKE2
BLAKE2b
DApp
ECDSA
ERC
Ethereum
FFI
Gnosis
GPL
Expand Down
3 changes: 3 additions & 0 deletions crates/engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ sha2 = { version = "0.9" }
sha3 = { version = "0.9" }
blake2 = { version = "0.9" }

# ECDSA for the off-chain environment.
libsecp256k1 = { version = "0.3.5", default-features = false }

[features]
default = ["std"]
std = [
Expand Down
41 changes: 41 additions & 0 deletions crates/engine/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ define_error_codes! {
/// The call to `seal_debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled = 9,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoverFailed = 11,
}

/// The raw return code returned by the host side.
Expand Down Expand Up @@ -417,6 +419,45 @@ impl Engine {
"off-chain environment does not yet support `call_chain_extension`"
);
}

/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
/// and stores the result in `output`.
pub fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result {
use secp256k1::{
recover,
Message,
RecoveryId,
Signature,
};

// In most implementations, the v is just 0 or 1 internally, but 27 was added
// as an arbitrary number for signing Bitcoin messages and Ethereum adopted that as well.
let recovery_byte = if signature[64] > 26 {
signature[64] - 27
} else {
signature[64]
};
let message = Message::parse(message_hash);
let signature = Signature::parse_slice(&signature[0..64])
.unwrap_or_else(|error| panic!("Unable to parse the signature: {}", error));

let recovery_id = RecoveryId::parse(recovery_byte)
.unwrap_or_else(|error| panic!("Unable to parse the recovery id: {}", error));

let pub_key = recover(&message, &signature, &recovery_id);
match pub_key {
Ok(pub_key) => {
*output = pub_key.serialize_compressed();
Ok(())
}
Err(_) => Err(Error::EcdsaRecoverFailed),
}
}
}

/// Copies the `slice` into `output`.
Expand Down
3 changes: 3 additions & 0 deletions crates/env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ sha2 = { version = "0.9", optional = true }
sha3 = { version = "0.9", optional = true }
blake2 = { version = "0.9", optional = true }

# ECDSA for the off-chain environment.
libsecp256k1 = { version = "0.3.5", default-features = false }

# Only used in the off-chain environment.
#
# Sadly couldn't be marked as dev-dependency.
Expand Down
36 changes: 36 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,39 @@ where
instance.hash_encoded::<H, T>(input, output)
})
}

/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
/// and stores the result in `output`.
///
/// # Example
///
/// ```
/// const signature: [u8; 65] = [
/// 161, 234, 203, 74, 147, 96, 51, 212, 5, 174, 231, 9, 142, 48, 137, 201,
/// 162, 118, 192, 67, 239, 16, 71, 216, 125, 86, 167, 139, 70, 7, 86, 241,
/// 33, 87, 154, 251, 81, 29, 160, 4, 176, 239, 88, 211, 244, 232, 232, 52,
/// 211, 234, 100, 115, 230, 47, 80, 44, 152, 166, 62, 50, 8, 13, 86, 175,
/// 28,
/// ];
/// const message_hash: [u8; 32] = [
/// 162, 28, 244, 179, 96, 76, 244, 178, 188, 83, 230, 248, 143, 106, 77, 117,
/// 239, 95, 244, 171, 65, 95, 62, 153, 174, 166, 182, 28, 130, 73, 196, 208
/// ];
/// const EXPECTED_COMPRESSED_PUBLIC_KEY: [u8; 33] = [
/// 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11,
/// 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23,
/// 152,
/// ];
/// let mut output = [0; 33];
/// ink_env::ecdsa_recover(&signature, &message_hash, &mut output);
/// assert_eq!(output, EXPECTED_COMPRESSED_PUBLIC_KEY);
/// ```
pub fn ecdsa_recover(
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<()> {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.ecdsa_recover(signature, message_hash, output)
})
}
9 changes: 9 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ pub trait EnvBackend {
H: CryptoHash,
T: scale::Encode;

/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
/// and stores the result in `output`.
fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<()>;

/// Low-level interface to call a chain extension method.
///
/// Returns the output of the chain extension of the specified type.
Expand Down
38 changes: 38 additions & 0 deletions crates/env/src/engine/experimental_off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ impl From<ext::Error> for crate::Error {
ext::Error::CodeNotFound => Self::CodeNotFound,
ext::Error::NotCallable => Self::NotCallable,
ext::Error::LoggingDisabled => Self::LoggingDisabled,
ext::Error::EcdsaRecoverFailed => Self::EcdsaRecoverFailed,
}
}
}
Expand Down Expand Up @@ -248,6 +249,43 @@ impl EnvBackend for EnvInstance {
<H as CryptoHash>::hash(enc_input, output)
}

fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<()> {
use secp256k1::{
recover,
Message,
RecoveryId,
Signature,
};

// In most implementations, the v is just 0 or 1 internally, but 27 was added
// as an arbitrary number for signing Bitcoin messages and Ethereum adopted that as well.
let recovery_byte = if signature[64] > 26 {
signature[64] - 27
} else {
signature[64]
};
let message = Message::parse(message_hash);
let signature = Signature::parse_slice(&signature[0..64])
.unwrap_or_else(|error| panic!("Unable to parse the signature: {}", error));

let recovery_id = RecoveryId::parse(recovery_byte)
.unwrap_or_else(|error| panic!("Unable to parse the recovery id: {}", error));

let pub_key = recover(&message, &signature, &recovery_id);
match pub_key {
Ok(pub_key) => {
*output = pub_key.serialize_compressed();
Ok(())
}
Err(_) => Err(crate::Error::EcdsaRecoverFailed),
}
}

fn call_chain_extension<I, T, E, ErrorCode, F, D>(
&mut self,
func_id: u32,
Expand Down
37 changes: 37 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,43 @@ impl EnvBackend for EnvInstance {
self.hash_bytes::<H>(&encoded[..], output)
}

fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<()> {
use secp256k1::{
recover,
Message,
RecoveryId,
Signature,
};

// In most implementations, the v is just 0 or 1 internally, but 27 was added
// as an arbitrary number for signing Bitcoin messages and Ethereum adopted that as well.
let recovery_byte = if signature[64] > 26 {
signature[64] - 27
} else {
signature[64]
};
let message = Message::parse(message_hash);
let signature = Signature::parse_slice(&signature[0..64])
.unwrap_or_else(|error| panic!("Unable to parse the signature: {}", error));

let recovery_id = RecoveryId::parse(recovery_byte)
.unwrap_or_else(|error| panic!("Unable to parse the recovery id: {}", error));

let pub_key = recover(&message, &signature, &recovery_id);
match pub_key {
Ok(pub_key) => {
*output = pub_key.serialize_compressed();
Ok(())
}
Err(_) => Err(Error::EcdsaRecoverFailed),
}
}

fn call_chain_extension<I, T, E, ErrorCode, F, D>(
&mut self,
func_id: u32,
Expand Down
25 changes: 25 additions & 0 deletions crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ define_error_codes! {
/// The call to `seal_debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled = 9,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoverFailed = 11,
}

/// Thin-wrapper around a `u32` representing a pointer for Wasm32.
Expand Down Expand Up @@ -358,6 +360,14 @@ mod sys {
output_ptr: Ptr32Mut<[u8]>,
output_len_ptr: Ptr32Mut<u32>,
);

pub fn seal_ecdsa_recover(
// 65 bytes of ecdsa signature
signature_ptr: Ptr32<[u8]>,
// 32 bytes hash of the message
message_hash_ptr: Ptr32<[u8]>,
output_ptr: Ptr32Mut<[u8]>,
) -> ReturnCode;
}
}

Expand Down Expand Up @@ -707,3 +717,18 @@ impl_hash_fn!(sha2_256, 32);
impl_hash_fn!(keccak_256, 32);
impl_hash_fn!(blake2_256, 32);
impl_hash_fn!(blake2_128, 16);

pub fn ecdsa_recover(
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result {
let ret_code = unsafe {
sys::seal_ecdsa_recover(
Ptr32::from_slice(signature),
Ptr32::from_slice(message_hash),
Ptr32Mut::from_slice(output),
)
};
ret_code.into()
}
10 changes: 10 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ impl From<ext::Error> for Error {
ext::Error::CodeNotFound => Self::CodeNotFound,
ext::Error::NotCallable => Self::NotCallable,
ext::Error::LoggingDisabled => Self::LoggingDisabled,
ext::Error::EcdsaRecoverFailed => Self::EcdsaRecoverFailed,
}
}
}
Expand Down Expand Up @@ -277,6 +278,15 @@ impl EnvBackend for EnvInstance {
<H as CryptoHash>::hash(enc_input, output)
}

fn ecdsa_recover(
&mut self,
signature: &[u8; 65],
message_hash: &[u8; 32],
output: &mut [u8; 33],
) -> Result<()> {
ext::ecdsa_recover(signature, message_hash, output).map_err(Into::into)
}

fn call_chain_extension<I, T, E, ErrorCode, F, D>(
&mut self,
func_id: u32,
Expand Down
2 changes: 2 additions & 0 deletions crates/env/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum Error {
/// The call to `seal_debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoverFailed,
}

/// A result of environmental operations.
Expand Down
25 changes: 25 additions & 0 deletions crates/eth_compatibility/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "ink_eth_compatibility"
version = "3.0.0-rc5"
authors = ["Parity Technologies <[email protected]>"]
edition = "2018"

license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/paritytech/ink"
documentation = "https://docs.rs/ink_eth_compatibility/"
homepage = "https://www.parity.io/"
description = "[ink!] Ethereum related stuff."
keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl", "ethereum"]
categories = ["no-std", "embedded"]
include = ["Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]

[dependencies]
ink_env = { version = "3.0.0-rc5", path = "../env", default-features = false }
libsecp256k1 = { version = "0.3.5", default-features = false }

[features]
default = ["std"]
std = [
"ink_env/std",
]
1 change: 1 addition & 0 deletions crates/eth_compatibility/LICENSE
1 change: 1 addition & 0 deletions crates/eth_compatibility/README.md
Loading

0 comments on commit 284e4e5

Please sign in to comment.