Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented ECDSA recover function. #914

Merged
merged 17 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ ABI
AST
BLAKE2
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(
Robbepop marked this conversation as resolved.
Show resolved Hide resolved
&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] > 27 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something that I'm missing is an explanation of this magic number. How about creating a const with a mnemonic name and adding a comment to that const?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please turn the 27 into a const with proper documentation of where it comes. Ideally a link.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the comment before the if section. It is more clear than a comment near a constant. This value is used in several crates, so I can't create one global constant. Define it in every crate seems overkill and better to leave the comment because 27 is used in one place.

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 @@ -140,6 +140,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] > 27 {
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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we can get rid of the huge code dupe once we eliminate the old off-chain engine.

&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] > 27 {
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 <admin@parity.io>"]
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",
]