From 95c94ed27d0dcd0dc386965a08b43f892014bd3b Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 10 Sep 2021 15:17:19 +0300 Subject: [PATCH 01/14] Implemented ecdsa recovery function. Added method `to_eth_address` and `to_account_id`. Added tests. --- crates/engine/Cargo.toml | 3 + crates/engine/src/ext.rs | 21 +++++++ crates/env/Cargo.toml | 3 + crates/env/src/api.rs | 31 ++++++++++ crates/env/src/backend.rs | 3 + .../engine/experimental_off_chain/impls.rs | 20 ++++++ crates/env/src/engine/off_chain/impls.rs | 19 ++++++ crates/env/src/engine/on_chain/ext.rs | 21 +++++++ crates/env/src/engine/on_chain/impls.rs | 5 ++ crates/env/src/error.rs | 2 + crates/lang/Cargo.toml | 1 + crates/lang/src/env_access.rs | 58 +++++++++++++++++ crates/lang/src/lib.rs | 2 + crates/lang/src/traits.rs | 62 +++++++++++++++++++ crates/lang/tests/traits.rs | 40 ++++++++++++ 15 files changed, 291 insertions(+) create mode 100644 crates/lang/tests/traits.rs diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 2648622d3d..28261cbcdc 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -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 = [ diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index e6fa38add6..de22904cd2 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -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. @@ -417,6 +419,25 @@ impl Engine { "off-chain environment does not yet support `call_chain_extension`" ); } + + pub fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result { + use secp256k1::{Signature, RecoveryId, Message, recover}; + + 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(); + + let recovery_id = RecoveryId::parse(recovery_byte).unwrap(); + + 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`. diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index e9ec3eaeb9..8cefcf8afa 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -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. diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 3a9081db16..232690d08c 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -604,3 +604,34 @@ where instance.hash_encoded::(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<()> { + ::on_instance(|instance| { + instance.ecdsa_recover(signature, message_hash, output) + }) +} diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index f4525b977f..4db1f55566 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -140,6 +140,9 @@ 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. diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 171a630b9d..65740a6d3f 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -112,6 +112,7 @@ impl From for crate::Error { ext::Error::CodeNotFound => Self::CodeNotFound, ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, + ext::Error::EcdsaRecoverFailed => Self::EcdsaRecoverFailed, } } } @@ -248,6 +249,25 @@ impl EnvBackend for EnvInstance { ::hash(enc_input, output) } + fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result<()> { + use secp256k1::{Signature, RecoveryId, Message, recover}; + + 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(); + + let recovery_id = RecoveryId::parse(recovery_byte).unwrap(); + + 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( &mut self, func_id: u32, diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 00a260777d..cf889a8f51 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -195,6 +195,25 @@ impl EnvBackend for EnvInstance { self.hash_bytes::(&encoded[..], output) } + fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result<()> { + use secp256k1::{Signature, RecoveryId, Message, recover}; + + 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(); + + let recovery_id = RecoveryId::parse(recovery_byte).unwrap(); + + 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( &mut self, func_id: u32, diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs index b7de2d9935..e33a8fae18 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -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. @@ -358,6 +360,14 @@ mod sys { output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut, ); + + 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; } } @@ -707,3 +717,14 @@ 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() +} \ No newline at end of file diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index f5b801aa6f..6a2e62bc39 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -111,6 +111,7 @@ impl From for Error { ext::Error::CodeNotFound => Self::CodeNotFound, ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, + ext::Error::EcdsaRecoverFailed => Self::EcdsaRecoverFailed, } } } @@ -277,6 +278,10 @@ impl EnvBackend for EnvInstance { ::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( &mut self, func_id: u32, diff --git a/crates/env/src/error.rs b/crates/env/src/error.rs index fd47ce0f53..a6b9be65de 100644 --- a/crates/env/src/error.rs +++ b/crates/env/src/error.rs @@ -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. diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index 57a2e3183b..aceb16577d 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -25,6 +25,7 @@ ink_lang_macro = { version = "3.0.0-rc5", path = "macro", default-features = fal scale = { package = "parity-scale-codec", version = "2.1", default-features = false, features = ["derive", "full"] } derive_more = { version = "0.99", default-features = false, features = ["from"] } static_assertions = "1.1" +libsecp256k1 = { version = "0.3.5", default-features = false } [dev-dependencies] # required for the doctest of `env_access::EnvAccess::instantiate_contract` diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 66924b9f20..37bfcf12b1 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -27,10 +27,12 @@ use ink_env::{ RentParams, RentStatus, Result, + Error, }; use ink_primitives::Key; use crate::ChainExtensionInstance; +use crate::traits::ECDSAPublicKey; /// The environment of the compiled ink! smart contract. pub trait ContractEnv { @@ -1013,4 +1015,60 @@ where ink_env::hash_encoded::(value, &mut output); output } + + /// Recovers the compressed ecdsa public key for given `signature` and `message_hash`, and stores the result in `output`. + /// + /// # Example + /// + /// ``` + /// # use ink_lang as ink; + /// # #[ink::contract] + /// # pub mod my_contract { + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// /// Recovery from pre-defined signature and message hash + /// #[ink(message)] + /// pub fn ecdsa_recover(&self) { + /// 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 result = self.env().ecdsa_recover(&signature, &message_hash); + /// assert!(result.is_ok()); + /// assert_eq!(*result.unwrap(), EXPECTED_COMPRESSED_PUBLIC_KEY); + /// } + /// # + /// # } + /// # } + /// ``` + pub fn ecdsa_recover(self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result { + let mut output = [0; 33]; + let result = ink_env::ecdsa_recover(signature, message_hash, &mut output); + + match result.is_ok() { + true => Ok(ECDSAPublicKey { + 0: output, + }), + false => Err(Error::EcdsaRecoverFailed) + } + } } diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 2bd938fe6a..aaa3bc25f9 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -74,6 +74,8 @@ pub use self::{ MessageMut, MessageRef, True, + ECDSAPublicKey, + EthereumAddress, }, }; pub use ::static_assertions; diff --git a/crates/lang/src/traits.rs b/crates/lang/src/traits.rs index 2f8f31d918..e950fa0d6c 100644 --- a/crates/lang/src/traits.rs +++ b/crates/lang/src/traits.rs @@ -23,6 +23,7 @@ use ink_env::{ Selector, }, Environment, + DefaultEnvironment, }; use ink_storage::traits::SpreadLayout; @@ -120,3 +121,64 @@ pub trait MessageMut: FnInput + FnOutput + FnSelector + FnState { /// Indicates that some compile time expression is expected to be `true`. #[doc(hidden)] pub trait True {} + +/// The ECDSA compressed public key. +#[derive(Debug, Copy, Clone)] +pub struct ECDSAPublicKey(pub [u8; 33]); + +impl Default for ECDSAPublicKey { + fn default() -> Self { + Self { + 0: [0; 33] + } + } +} + +impl core::ops::Deref for ECDSAPublicKey { + type Target = [u8; 33]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for ECDSAPublicKey { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Address of ethereum account +pub type EthereumAddress = [u8; 20]; + +impl ECDSAPublicKey { + pub fn to_eth_address(&self) -> EthereumAddress { + use secp256k1::PublicKey; + use ink_env::hash; + + // Transform compressed public key into uncompressed. + let pub_key = PublicKey::parse_compressed(&self.0).expect("Unable to parse the compressed ecdsa public key"); + let uncompressed = pub_key.serialize(); + + // Hash the uncompressed public key without first byte by Keccak256 algorithm. + let mut hash = ::Type::default(); + ink_env::hash_bytes::(&uncompressed[1..], &mut hash); + + // Take the last 20 bytes as an Address + let mut result = EthereumAddress::default(); + result.copy_from_slice(&hash[12..]); + + result + } + + pub fn to_account_id(&self) -> ::AccountId { + use ink_env::hash; + + let mut output = ::Type::default(); + ink_env::hash_bytes::(&self.0[..], &mut output); + + output.into() + } +} \ No newline at end of file diff --git a/crates/lang/tests/traits.rs b/crates/lang/tests/traits.rs new file mode 100644 index 0000000000..6a538c3a25 --- /dev/null +++ b/crates/lang/tests/traits.rs @@ -0,0 +1,40 @@ +use ink_lang::{ECDSAPublicKey, EthereumAddress}; + +#[test] +fn correct_to_eth_address() { + #[rustfmt::skip] + let pub_key: ECDSAPublicKey = ECDSAPublicKey { + 0: [ + 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, + ] + }; + + #[rustfmt::skip] + const EXPECTED_ETH_ADDRESS: EthereumAddress = [ + 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 + ]; + + assert_eq!(pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); +} + +#[test] +fn correct_to_account_id() { + #[rustfmt::skip] + let pub_key: ECDSAPublicKey = ECDSAPublicKey { + 0: [ + 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, + ] + }; + + #[rustfmt::skip] + const EXPECTED_ACCOUNT_ID: [u8; 32] = [ + 41, 117, 241, 210, 139, 146, 182, 232, 68, 153, 184, 59, 7, 151, 239, 82, + 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, + ]; + + assert_eq!(pub_key.to_account_id(), EXPECTED_ACCOUNT_ID.into()); +} \ No newline at end of file From 3b1cf2a0e6f6dbc43d32be8b2088e7bd3fe8d393 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 10 Sep 2021 15:22:25 +0300 Subject: [PATCH 02/14] Cargo fmt --- crates/engine/src/ext.rs | 28 ++++++++++++++----- crates/env/src/api.rs | 6 +++- crates/env/src/backend.rs | 7 ++++- .../engine/experimental_off_chain/impls.rs | 26 +++++++++++++---- crates/env/src/engine/off_chain/impls.rs | 24 ++++++++++++---- crates/env/src/engine/on_chain/ext.rs | 12 +++++--- crates/env/src/engine/on_chain/impls.rs | 7 ++++- crates/lang/src/env_access.rs | 20 +++++++------ crates/lang/src/lib.rs | 4 +-- crates/lang/src/traits.rs | 13 ++++----- crates/lang/tests/traits.rs | 21 ++++++++++++-- 11 files changed, 124 insertions(+), 44 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index de22904cd2..f730fd0b93 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -94,8 +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, + /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. + EcdsaRecoverFailed = 11, } /// The raw return code returned by the host side. @@ -420,10 +420,24 @@ impl Engine { ); } - pub fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result { - use secp256k1::{Signature, RecoveryId, Message, recover}; + pub fn ecdsa_recover( + &mut self, + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result { + use secp256k1::{ + recover, + Message, + RecoveryId, + Signature, + }; - let recovery_byte = if signature[64] > 27 { signature[64] - 27 } else { signature[64] }; + 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(); @@ -434,8 +448,8 @@ impl Engine { Ok(pub_key) => { *output = pub_key.serialize_compressed(); Ok(()) - }, - Err(_) => Err(Error::EcdsaRecoverFailed) + } + Err(_) => Err(Error::EcdsaRecoverFailed), } } } diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 232690d08c..cad2a0d24e 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -630,7 +630,11 @@ where /// 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<()> { +pub fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], +) -> Result<()> { ::on_instance(|instance| { instance.ecdsa_recover(signature, message_hash, output) }) diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 4db1f55566..1fc0f6c354 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -141,7 +141,12 @@ pub trait EnvBackend { 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<()>; + 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. /// diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 65740a6d3f..d6b38a6aeb 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -249,10 +249,24 @@ impl EnvBackend for EnvInstance { ::hash(enc_input, output) } - fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result<()> { - use secp256k1::{Signature, RecoveryId, Message, recover}; - - let recovery_byte = if signature[64] > 27 { signature[64] - 27 } else { signature[64] }; + fn ecdsa_recover( + &mut self, + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result<()> { + use secp256k1::{ + recover, + Message, + RecoveryId, + Signature, + }; + + 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(); @@ -263,8 +277,8 @@ impl EnvBackend for EnvInstance { Ok(pub_key) => { *output = pub_key.serialize_compressed(); Ok(()) - }, - Err(_) => Err(crate::Error::EcdsaRecoverFailed) + } + Err(_) => Err(crate::Error::EcdsaRecoverFailed), } } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index cf889a8f51..b9fdf90a04 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -195,10 +195,24 @@ impl EnvBackend for EnvInstance { self.hash_bytes::(&encoded[..], output) } - fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result<()> { - use secp256k1::{Signature, RecoveryId, Message, recover}; + fn ecdsa_recover( + &mut self, + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result<()> { + use secp256k1::{ + recover, + Message, + RecoveryId, + Signature, + }; - let recovery_byte = if signature[64] > 27 { signature[64] - 27 } else { signature[64] }; + 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(); @@ -209,8 +223,8 @@ impl EnvBackend for EnvInstance { Ok(pub_key) => { *output = pub_key.serialize_compressed(); Ok(()) - }, - Err(_) => Err(Error::EcdsaRecoverFailed) + } + Err(_) => Err(Error::EcdsaRecoverFailed), } } diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs index e33a8fae18..636f152b1a 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -79,8 +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, + /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. + EcdsaRecoverFailed = 11, } /// Thin-wrapper around a `u32` representing a pointer for Wasm32. @@ -718,7 +718,11 @@ 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 { +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), @@ -727,4 +731,4 @@ pub fn ecdsa_recover(signature: &[u8; 65], message_hash: &[u8; 32], output: &mut ) }; ret_code.into() -} \ No newline at end of file +} diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 6a2e62bc39..ed0351b280 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -278,7 +278,12 @@ impl EnvBackend for EnvInstance { ::hash(enc_input, output) } - fn ecdsa_recover(&mut self, signature: &[u8; 65], message_hash: &[u8; 32], output: &mut [u8; 33]) -> Result<()> { + 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) } diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 37bfcf12b1..3ceb246473 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -24,15 +24,17 @@ use ink_env::{ HashOutput, }, Environment, + Error, RentParams, RentStatus, Result, - Error, }; use ink_primitives::Key; -use crate::ChainExtensionInstance; -use crate::traits::ECDSAPublicKey; +use crate::{ + traits::ECDSAPublicKey, + ChainExtensionInstance, +}; /// The environment of the compiled ink! smart contract. pub trait ContractEnv { @@ -1060,15 +1062,17 @@ where /// # } /// # } /// ``` - pub fn ecdsa_recover(self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result { + pub fn ecdsa_recover( + self, + signature: &[u8; 65], + message_hash: &[u8; 32], + ) -> Result { let mut output = [0; 33]; let result = ink_env::ecdsa_recover(signature, message_hash, &mut output); match result.is_ok() { - true => Ok(ECDSAPublicKey { - 0: output, - }), - false => Err(Error::EcdsaRecoverFailed) + true => Ok(ECDSAPublicKey { 0: output }), + false => Err(Error::EcdsaRecoverFailed), } } } diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index aaa3bc25f9..66d30b1bf6 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -66,6 +66,8 @@ pub use self::{ traits::{ CheckedInkTrait, Constructor, + ECDSAPublicKey, + EthereumAddress, FnInput, FnOutput, FnSelector, @@ -74,8 +76,6 @@ pub use self::{ MessageMut, MessageRef, True, - ECDSAPublicKey, - EthereumAddress, }, }; pub use ::static_assertions; diff --git a/crates/lang/src/traits.rs b/crates/lang/src/traits.rs index e950fa0d6c..9cdf034083 100644 --- a/crates/lang/src/traits.rs +++ b/crates/lang/src/traits.rs @@ -22,8 +22,8 @@ use ink_env::{ ExecutionInput, Selector, }, - Environment, DefaultEnvironment, + Environment, }; use ink_storage::traits::SpreadLayout; @@ -128,9 +128,7 @@ pub struct ECDSAPublicKey(pub [u8; 33]); impl Default for ECDSAPublicKey { fn default() -> Self { - Self { - 0: [0; 33] - } + Self { 0: [0; 33] } } } @@ -155,11 +153,12 @@ pub type EthereumAddress = [u8; 20]; impl ECDSAPublicKey { pub fn to_eth_address(&self) -> EthereumAddress { - use secp256k1::PublicKey; use ink_env::hash; + use secp256k1::PublicKey; // Transform compressed public key into uncompressed. - let pub_key = PublicKey::parse_compressed(&self.0).expect("Unable to parse the compressed ecdsa public key"); + let pub_key = PublicKey::parse_compressed(&self.0) + .expect("Unable to parse the compressed ecdsa public key"); let uncompressed = pub_key.serialize(); // Hash the uncompressed public key without first byte by Keccak256 algorithm. @@ -181,4 +180,4 @@ impl ECDSAPublicKey { output.into() } -} \ No newline at end of file +} diff --git a/crates/lang/tests/traits.rs b/crates/lang/tests/traits.rs index 6a538c3a25..db3085d7fc 100644 --- a/crates/lang/tests/traits.rs +++ b/crates/lang/tests/traits.rs @@ -1,4 +1,21 @@ -use ink_lang::{ECDSAPublicKey, EthereumAddress}; +// Copyright 2018-2021 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ink_lang::{ + ECDSAPublicKey, + EthereumAddress, +}; #[test] fn correct_to_eth_address() { @@ -37,4 +54,4 @@ fn correct_to_account_id() { ]; assert_eq!(pub_key.to_account_id(), EXPECTED_ACCOUNT_ID.into()); -} \ No newline at end of file +} From c65216257ab92b4a84660e61e83b30f9b8c0c20c Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 10 Sep 2021 20:03:16 +0300 Subject: [PATCH 03/14] Added `ECDSA` and `Ethereum` to dictionary --- .config/cargo_spellcheck.dic | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 63a06223f3..b38ac20881 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -4,7 +4,9 @@ ABI AST BLAKE2 DApp +ECDSA ERC +Ethereum FFI Gnosis GPL From 460441e55584515042011f9f7eb2b1e15806d5bb Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 10 Sep 2021 20:13:30 +0300 Subject: [PATCH 04/14] Fixed comments according a new spellcheck --- crates/env/src/api.rs | 2 +- crates/env/src/backend.rs | 2 +- crates/lang/src/env_access.rs | 2 +- crates/lang/src/traits.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index cad2a0d24e..92bad793d6 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -605,7 +605,7 @@ where }) } -/// Recovers the compressed ecdsa public key for given `signature` and `message_hash`, and stores the result in `output`. +/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, and stores the result in `output`. /// /// # Example /// diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 1fc0f6c354..c7648b64e4 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -140,7 +140,7 @@ 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`. + /// 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], diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 3ceb246473..3495e1d63c 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -1018,7 +1018,7 @@ where output } - /// Recovers the compressed ecdsa public key for given `signature` and `message_hash`, and stores the result in `output`. + /// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, and stores the result in `output`. /// /// # Example /// diff --git a/crates/lang/src/traits.rs b/crates/lang/src/traits.rs index 9cdf034083..bc4f13cb58 100644 --- a/crates/lang/src/traits.rs +++ b/crates/lang/src/traits.rs @@ -148,7 +148,7 @@ impl core::ops::DerefMut for ECDSAPublicKey { } } -/// Address of ethereum account +/// Address of Ethereum account pub type EthereumAddress = [u8; 20]; impl ECDSAPublicKey { @@ -158,7 +158,7 @@ impl ECDSAPublicKey { // Transform compressed public key into uncompressed. let pub_key = PublicKey::parse_compressed(&self.0) - .expect("Unable to parse the compressed ecdsa public key"); + .expect("Unable to parse the compressed ECDSA public key"); let uncompressed = pub_key.serialize(); // Hash the uncompressed public key without first byte by Keccak256 algorithm. From e6637f614b1fac86ce578d53319897068ecc4f81 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Wed, 15 Sep 2021 00:42:46 +0300 Subject: [PATCH 05/14] Fixes according comments in review. --- crates/engine/src/ext.rs | 10 +- crates/env/src/api.rs | 3 +- crates/env/src/backend.rs | 3 +- .../engine/experimental_off_chain/impls.rs | 8 +- crates/env/src/engine/off_chain/impls.rs | 8 +- crates/eth_compatibility/Cargo.toml | 23 ++ crates/eth_compatibility/LICENSE | 203 ++++++++++++++ crates/eth_compatibility/README.md | 262 ++++++++++++++++++ crates/eth_compatibility/src/lib.rs | 80 ++++++ .../tests/conversion.rs} | 2 +- crates/lang/Cargo.toml | 2 +- crates/lang/src/env_access.rs | 16 +- crates/lang/src/lib.rs | 2 - crates/lang/src/traits.rs | 61 ---- 14 files changed, 605 insertions(+), 78 deletions(-) create mode 100644 crates/eth_compatibility/Cargo.toml create mode 100644 crates/eth_compatibility/LICENSE create mode 100644 crates/eth_compatibility/README.md create mode 100644 crates/eth_compatibility/src/lib.rs rename crates/{lang/tests/traits.rs => eth_compatibility/tests/conversion.rs} (98%) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index f730fd0b93..4e7b8174b3 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -420,6 +420,8 @@ impl Engine { ); } + /// 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], @@ -433,15 +435,19 @@ impl Engine { 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(); + 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(); + 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 { diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 92bad793d6..4036a4cab7 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -605,7 +605,8 @@ where }) } -/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, and stores the result in `output`. +/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, +/// and stores the result in `output`. /// /// # Example /// diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index c7648b64e4..cb6ac05c0b 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -140,7 +140,8 @@ 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`. + /// 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], diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index d6b38a6aeb..03ccb7d975 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -262,15 +262,19 @@ impl EnvBackend for EnvInstance { 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(); + 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(); + 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 { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index b9fdf90a04..e43c3e6772 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -208,15 +208,19 @@ impl EnvBackend for EnvInstance { 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(); + 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(); + 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 { diff --git a/crates/eth_compatibility/Cargo.toml b/crates/eth_compatibility/Cargo.toml new file mode 100644 index 0000000000..3b3157e16e --- /dev/null +++ b/crates/eth_compatibility/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ink_eth_compatibility" +version = "3.0.0-rc5" +authors = ["Parity Technologies "] +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 = [] diff --git a/crates/eth_compatibility/LICENSE b/crates/eth_compatibility/LICENSE new file mode 100644 index 0000000000..6b0b1270ff --- /dev/null +++ b/crates/eth_compatibility/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/crates/eth_compatibility/README.md b/crates/eth_compatibility/README.md new file mode 100644 index 0000000000..d1bb0b6cad --- /dev/null +++ b/crates/eth_compatibility/README.md @@ -0,0 +1,262 @@ +
+ ink! +

+ Parity's ink! for writing smart contracts +

+ +[![linux][a1]][a2] [![codecov][c1]][c2] [![coveralls][d1]][d2] [![loc][e1]][e2] [![matrix][k1]][k2] [![discord][l1]][l2] + +[a1]: https://gitlab.parity.io/parity/ink/badges/master/pipeline.svg +[a2]: https://gitlab.parity.io/parity/ink/pipelines?ref=master +[c1]: https://codecov.io/gh/paritytech/ink/branch/master/graph/badge.svg +[c2]: https://codecov.io/gh/paritytech/ink/branch/master +[d1]: https://coveralls.io/repos/github/paritytech/ink/badge.svg?branch=master +[d2]: https://coveralls.io/github/paritytech/ink?branch=master +[e1]: https://tokei.rs/b1/github/paritytech/ink?category=code +[e2]: https://github.com/Aaronepower/tokei#badges +[f1]: https://img.shields.io/badge/click-blue.svg +[f2]: https://paritytech.github.io/ink/ink_storage +[g1]: https://img.shields.io/badge/click-blue.svg +[g2]: https://paritytech.github.io/ink/ink_env +[i1]: https://img.shields.io/badge/click-blue.svg +[i2]: https://paritytech.github.io/ink/ink_prelude +[j1]: https://img.shields.io/badge/click-blue.svg +[j2]: https://paritytech.github.io/ink/ink_lang +[k1]: https://img.shields.io/badge/matrix-chat-brightgreen.svg?style=flat +[k2]: https://riot.im/app/#/room/#ink:matrix.parity.io +[l1]: https://img.shields.io/discord/722223075629727774?style=flat-square&label=discord +[l2]: https://discord.com/invite/wGUDt2p + +> squink, the ink! mascotink! is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to write smart contracts in Rust for blockchains built on the [Substrate](https://github.com/paritytech/substrate) framework. ink! contracts are compiled to WebAssembly. + +
+ +[Guided Tutorial for Beginners](https://substrate.dev/substrate-contracts-workshop/#/0/building-your-contract)  •   +[ink! Documentation Portal](https://paritytech.github.io/ink-docs) + +
+
+ +More relevant links: +* Talk to us on [Element][k2] or in [Discord][l2] + on the [`ink_smart-contracts`](https://discord.com/channels/722223075629727774/765280480609828864) channel +* [`cargo-contract`](https://github.com/paritytech/cargo-contract) ‒ CLI tool for ink! contracts +* [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload) ‒ Frontend for contract deployment and interaction +* [Substrate Contracts Node](https://github.com/paritytech/substrate-contracts-node) ‒ Simple Substrate blockchain which includes smart contract functionality + + +## Table of Contents + +* [Play with It](#play-with-it) +* [Usage](#usage) +* [Hello, World! ‒ The Flipper](#hello-world--the-flipper) +* [Examples](#examples) +* [How it Works](#how-it-works) +* [ink! Macros & Attributes Overview](#ink-macros--attributes-overview) + * [Entry Point](#entry-point) + * [Trait Definitions](#trait-definitions) + * [Off-chain Testing](#off-chain-testing) +* [Developer Documentation](#developer-documentation) +* [Contributing](#contributing) +* [License](#license) + + +## Play with It + +If you want to have a local setup you can use our [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node) for a quickstart. +It's a simple Substrate blockchain which includes the Substrate module for smart contract functionality ‒ the `contracts` pallet (see [How it Works](#how-it-works) for more). + +The [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload) can be used to deploy your contract to a chain and interact with it. + +We also have [a demonstration testnet](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fcanvas-rpc.parity.io) running. +You can request some tokens to play with from our [Faucet](https://riot.im/app/#/room/#canvas_faucet:matrix.parity.io) and deploy your contracts via the [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload). + + +## Usage + +A prerequisite for compiling smart contracts is to have Rust and Cargo installed. Here's [an installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html). + +We recommend installing [`cargo-contract`](https://github.com/paritytech/cargo-contract) as well. +It's a CLI tool which helps set up and manage WebAssembly smart contracts written with ink!: + +``` +cargo install cargo-contract --force +``` + +Use the `--force` to ensure you are updated to the most recent `cargo-contract` version. + +In order to initialize a new ink! project you can use: + +``` +cargo contract new flipper +``` + +This will create a folder `flipper` in your work directory. +The folder contains a scaffold `Cargo.toml` and a `lib.rs`, which both contain the necessary building blocks for using ink!. + +The `lib.rs` contains our hello world contract ‒ the `Flipper`, which we explain in the next section. + +In order to build the contract just execute this command in the `flipper` folder: +``` +cargo +nightly contract build +``` + +As a result you'll get a file `target/flipper.wasm` file, a `metadata.json` file and a `.contract` file in the `target` folder of your contract. +The `.contract` file combines the Wasm and metadata into one file and needs to be used when deploying the contract. + + +## Hello, World! ‒ The Flipper + +The `Flipper` contract is a simple contract containing only a single `bool` value. +It provides methods to +* flip its value from `true` to `false` (and vice versa) and +* return the current state. + + +Below you can see the code using the `ink_lang` version of ink!. + +```rust +use ink_lang as ink; + +#[ink::contract] +mod flipper { + /// The storage of the flipper contract. + #[ink(storage)] + pub struct Flipper { + /// The single `bool` value. + value: bool, + } + + impl Flipper { + /// Instantiates a new Flipper contract and initializes + /// `value` to `init_value`. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { + value: init_value, + } + } + + /// Flips `value` from `true` to `false` or vice versa. + #[ink(message)] + pub fn flip(&mut self) { + self.value = !self.value; + } + + /// Returns the current state of `value`. + #[ink(message)] + pub fn get(&self) -> bool { + self.value + } + } + + /// Simply execute `cargo test` in order to test your contract + /// using the below unit tests. + #[cfg(test)] + mod tests { + use super::*; + use ink_lang as ink; + + #[ink::test] + fn it_works() { + let mut flipper = Flipper::new(false); + assert_eq!(flipper.get(), false); + flipper.flip(); + assert_eq!(flipper.get(), true); + } + } +} +``` + +The [`flipper/src/lib.rs`](https://github.com/paritytech/ink/blob/master/examples/flipper/lib.rs) +file in our examples folder contains exactly this code. Run `cargo contract build` to build your +first ink! smart contract. + +## Examples + +In the `examples` folder you'll find a number of examples written in ink!. + +Some of the most interesting ones: + +* `delegator` ‒ Implements cross-contract calling. +* `trait-erc20` ‒ Defines a trait for `Erc20` contracts and implements it. +* `erc721` ‒ An exemplary implementation of `Erc721` NFT tokens. +* `dns` ‒ A simple `DomainNameService` smart contract. +* …and more, just rummage through the folder 🙃. + +To build a single example navigate to the root of the example and run: +``` +cargo contract build +``` + +You should now have an `.contract` file in the `target` folder of the contract. + +For information on how to deploy this to a chain, please have a look at the [Play with It](#play-with-it) section or our [smart contracts workshop](https://substrate.dev/substrate-contracts-workshop/). + + +## How it Works + +* Substrate's [Framework for Runtime Aggregation of Modularised Entities (FRAME)](https://substrate.dev/docs/en/knowledgebase/runtime/frame) contains +a module which implements an API for typical functions smart contracts need (storage, querying information about accounts, …). +This module is called the `contracts` pallet, +* The `contracts` pallet requires smart contracts to be uploaded to the blockchain as a Wasm blob. +* ink! is a smart contract language which targets the API exposed by `contracts`. +Hence ink! contracts are compiled to Wasm. +* When executing `cargo contract build` an additional file `metadata.json` is created. +It contains information about e.g. what methods the contract provides for others to call. + +## ink! Macros & Attributes Overview + +### Entry Point + +In a module annotated with `#[ink::contract]` these attributes are available: + +| Attribute | Where Applicable | Description | +|:--|:--|:--| +| `#[ink(storage)]` | On `struct` definitions. | Defines the ink! storage struct. There can only be one ink! storage definition per contract. | +| `#[ink(event)]` | On `struct` definitions. | Defines an ink! event. A contract can define multiple such ink! events. | +| `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. | +| `#[ink(topic)]` | Applicate on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic field. Similar semantics as to indexed event arguments in Solidity. | +| `#[ink(message)]` | Applicable to methods. | Flags a method for the ink! storage struct as message making it available to the API for calling the contract. | +| `#[ink(constructor)]` | Applicable to method. | Flags a method for the ink! storage struct as constructor making it available to the API for instantiating the contract. | +| `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. | +| `#[ink(selector = "..")]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. | +| `#[ink(namespace = "..")]` | Applicable to ink! trait implementation blocks. | Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation. Allows to disambiguate between trait implementations with overlapping message or constructor names. Use only with great care and consideration! | +| `#[ink(impl)]` | Applicable to ink! implementation blocks. | Tells the ink! codegen that some implementation block shall be granted access to ink! internals even without it containing any ink! messages or ink! constructors. | + +See [here](https://paritytech.github.io/ink/ink_lang/attr.contract.html) for a more detailed description of those and also for details on the `#[ink::contract]` macro. + +### Trait Definitions + +Use `#[ink::trait_definition]` to define your very own trait definitions that are then implementable by ink! smart contracts. +See e.g. the [`examples/trait-erc20`](https://github.com/paritytech/ink/blob/v3.0.0-rc5/examples/trait-erc20/lib.rs#L35-L37) contract on how to utilize it or [the documentation](https://paritytech.github.io/ink/ink_lang/attr.trait_definition.html) for details. + +### Off-chain Testing + +The `#[ink::test]` procedural macro enables off-chain testing. See e.g. the [`examples/erc20`](https://github.com/paritytech/ink/blob/v3.0.0-rc5/examples/erc20/lib.rs#L248-L250) contract on how to utilize those or [the documentation](https://paritytech.github.io/ink/ink_lang/attr.test.html) for details. + +## Developer Documentation + +We have [a very comprehensive documentation portal](https://paritytech.github.io/ink-docs), +but if you are looking for the crate level documentation itself, then these are +the relevant links: + +| Crate | Docs | Description | +|:--|:--|:--| +`ink_lang` | [![][j1]][j2] | Language features expose by ink!. See [here](https://paritytech.github.io/ink/ink_lang/attr.contract.html) for a detailed description of attributes which you can use in an `#[ink::contract]`. | +`ink_storage` | [![][f1]][f2] | Data structures available in ink!. | +`ink_env` | [![][g1]][g2] | Low-level interface for interacting with the smart contract Wasm executor. | +`ink_prelude` | [![][i1]][i2] | Common API for no_std and std to access alloc crate types. | + + +## Contributing + +Visit our [contribution guidelines](CONTRIBUTING.md) for more information. + +Use the scripts provided under `scripts/check-*` directory in order to run checks on either the workspace or all examples. Please do this before pushing work in a PR. + +## License + +The entire code within this repository is licensed under the [Apache License 2.0](LICENSE). + +Please [contact us](https://www.parity.io/contact/) if you have questions about the licensing of our products. diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs new file mode 100644 index 0000000000..3a1b1f47fa --- /dev/null +++ b/crates/eth_compatibility/src/lib.rs @@ -0,0 +1,80 @@ +// Copyright 2018-2021 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ink_env::{ + DefaultEnvironment, + Environment, +}; + +/// The ECDSA compressed public key. +#[derive(Debug, Copy, Clone)] +pub struct ECDSAPublicKey(pub [u8; 33]); + +impl Default for ECDSAPublicKey { + fn default() -> Self { + Self { 0: [0; 33] } + } +} + +impl core::ops::Deref for ECDSAPublicKey { + type Target = [u8; 33]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for ECDSAPublicKey { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Address of Ethereum account +pub type EthereumAddress = [u8; 20]; + +impl ECDSAPublicKey { + pub fn to_eth_address(&self) -> EthereumAddress { + use ink_env::hash; + use secp256k1::PublicKey; + + // Transform compressed public key into uncompressed. + let pub_key = PublicKey::parse_compressed(&self.0) + .expect("Unable to parse the compressed ECDSA public key"); + let uncompressed = pub_key.serialize(); + + // Hash the uncompressed public key by Keccak256 algorithm. + let mut hash = ::Type::default(); + // The first byte indicates that the public key is uncompressed. + // Let's skip it for hashing the public key directly. + ink_env::hash_bytes::(&uncompressed[1..], &mut hash); + + // Take the last 20 bytes as an Address + let mut result = EthereumAddress::default(); + result.copy_from_slice(&hash[12..]); + + result + } + + pub fn to_account_id(&self) -> ::AccountId { + use ink_env::hash; + + let mut output = ::Type::default(); + ink_env::hash_bytes::(&self.0[..], &mut output); + + output.into() + } +} diff --git a/crates/lang/tests/traits.rs b/crates/eth_compatibility/tests/conversion.rs similarity index 98% rename from crates/lang/tests/traits.rs rename to crates/eth_compatibility/tests/conversion.rs index db3085d7fc..d18daf926b 100644 --- a/crates/lang/tests/traits.rs +++ b/crates/eth_compatibility/tests/conversion.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ink_lang::{ +use ink_eth_compatibility::{ ECDSAPublicKey, EthereumAddress, }; diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index aceb16577d..ec117e568c 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -20,12 +20,12 @@ ink_storage = { version = "3.0.0-rc5", path = "../storage", default-features = f ink_primitives = { version = "3.0.0-rc5", path = "../primitives", default-features = false } ink_metadata = { version = "3.0.0-rc5", path = "../metadata", default-features = false, optional = true } ink_prelude = { version = "3.0.0-rc5", path = "../prelude", default-features = false } +ink_eth_compatibility = { version = "3.0.0-rc5", path = "../eth_compatibility", default-features = false } ink_lang_macro = { version = "3.0.0-rc5", path = "macro", default-features = false } scale = { package = "parity-scale-codec", version = "2.1", default-features = false, features = ["derive", "full"] } derive_more = { version = "0.99", default-features = false, features = ["from"] } static_assertions = "1.1" -libsecp256k1 = { version = "0.3.5", default-features = false } [dev-dependencies] # required for the doctest of `env_access::EnvAccess::instantiate_contract` diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 3495e1d63c..4812e33ea4 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -31,10 +31,8 @@ use ink_env::{ }; use ink_primitives::Key; -use crate::{ - traits::ECDSAPublicKey, - ChainExtensionInstance, -}; +use crate::ChainExtensionInstance; +use ink_eth_compatibility::ECDSAPublicKey; /// The environment of the compiled ink! smart contract. pub trait ContractEnv { @@ -1018,7 +1016,8 @@ where output } - /// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, and stores the result in `output`. + /// Recovers the compressed ECDSA public key for given `signature` and `message_hash`, + /// and stores the result in `output`. /// /// # Example /// @@ -1057,6 +1056,13 @@ where /// let result = self.env().ecdsa_recover(&signature, &message_hash); /// assert!(result.is_ok()); /// assert_eq!(*result.unwrap(), EXPECTED_COMPRESSED_PUBLIC_KEY); + /// + /// // Pass invalid zero message hash + /// let failed_result = self.env().ecdsa_recover(&signature, &[0; 32]); + /// assert!(failed_result.is_err()); + /// if let Err(e) = failed_result { + /// assert_eq!(e, ink_env::Error::EcdsaRecoverFailed); + /// } /// } /// # /// # } diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 66d30b1bf6..2bd938fe6a 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -66,8 +66,6 @@ pub use self::{ traits::{ CheckedInkTrait, Constructor, - ECDSAPublicKey, - EthereumAddress, FnInput, FnOutput, FnSelector, diff --git a/crates/lang/src/traits.rs b/crates/lang/src/traits.rs index bc4f13cb58..2f8f31d918 100644 --- a/crates/lang/src/traits.rs +++ b/crates/lang/src/traits.rs @@ -22,7 +22,6 @@ use ink_env::{ ExecutionInput, Selector, }, - DefaultEnvironment, Environment, }; use ink_storage::traits::SpreadLayout; @@ -121,63 +120,3 @@ pub trait MessageMut: FnInput + FnOutput + FnSelector + FnState { /// Indicates that some compile time expression is expected to be `true`. #[doc(hidden)] pub trait True {} - -/// The ECDSA compressed public key. -#[derive(Debug, Copy, Clone)] -pub struct ECDSAPublicKey(pub [u8; 33]); - -impl Default for ECDSAPublicKey { - fn default() -> Self { - Self { 0: [0; 33] } - } -} - -impl core::ops::Deref for ECDSAPublicKey { - type Target = [u8; 33]; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl core::ops::DerefMut for ECDSAPublicKey { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Address of Ethereum account -pub type EthereumAddress = [u8; 20]; - -impl ECDSAPublicKey { - pub fn to_eth_address(&self) -> EthereumAddress { - use ink_env::hash; - use secp256k1::PublicKey; - - // Transform compressed public key into uncompressed. - let pub_key = PublicKey::parse_compressed(&self.0) - .expect("Unable to parse the compressed ECDSA public key"); - let uncompressed = pub_key.serialize(); - - // Hash the uncompressed public key without first byte by Keccak256 algorithm. - let mut hash = ::Type::default(); - ink_env::hash_bytes::(&uncompressed[1..], &mut hash); - - // Take the last 20 bytes as an Address - let mut result = EthereumAddress::default(); - result.copy_from_slice(&hash[12..]); - - result - } - - pub fn to_account_id(&self) -> ::AccountId { - use ink_env::hash; - - let mut output = ::Type::default(); - ink_env::hash_bytes::(&self.0[..], &mut output); - - output.into() - } -} From 57e34466112363044c47791d7337ffe0cab39e54 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Wed, 15 Sep 2021 17:58:23 +0300 Subject: [PATCH 06/14] Fixed build issue for wasm --- crates/eth_compatibility/Cargo.toml | 4 +++- crates/eth_compatibility/src/lib.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/eth_compatibility/Cargo.toml b/crates/eth_compatibility/Cargo.toml index 3b3157e16e..1389aa330c 100644 --- a/crates/eth_compatibility/Cargo.toml +++ b/crates/eth_compatibility/Cargo.toml @@ -20,4 +20,6 @@ libsecp256k1 = { version = "0.3.5", default-features = false } [features] default = ["std"] -std = [] +std = [ + "ink_env/std", +] diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 3a1b1f47fa..91e007b47c 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(not(feature = "std"), no_std)] use ink_env::{ DefaultEnvironment, Environment, From b0ba645ab4807f46f117c5e400be186c5ef7d2e0 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 17 Sep 2021 11:14:49 +0300 Subject: [PATCH 07/14] Use struct instead of alias for `EthereumAddress`. --- crates/eth_compatibility/src/lib.rs | 29 ++++++++++++++++++-- crates/eth_compatibility/tests/conversion.rs | 5 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 91e007b47c..f09df8ecf4 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -44,8 +44,33 @@ impl core::ops::DerefMut for ECDSAPublicKey { } } -/// Address of Ethereum account -pub type EthereumAddress = [u8; 20]; +/// The address of an Ethereum account. +#[derive(Debug, Copy, Clone)] +pub struct EthereumAddress(pub [u8; 20]); + +impl Default for EthereumAddress { + fn default() -> Self { + Self { + 0: [0; 20] + } + } +} + +impl core::ops::Deref for EthereumAddress { + type Target = [u8; 20]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for EthereumAddress { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} impl ECDSAPublicKey { pub fn to_eth_address(&self) -> EthereumAddress { diff --git a/crates/eth_compatibility/tests/conversion.rs b/crates/eth_compatibility/tests/conversion.rs index d18daf926b..d0360a75eb 100644 --- a/crates/eth_compatibility/tests/conversion.rs +++ b/crates/eth_compatibility/tests/conversion.rs @@ -14,7 +14,6 @@ use ink_eth_compatibility::{ ECDSAPublicKey, - EthereumAddress, }; #[test] @@ -29,11 +28,11 @@ fn correct_to_eth_address() { }; #[rustfmt::skip] - const EXPECTED_ETH_ADDRESS: EthereumAddress = [ + const EXPECTED_ETH_ADDRESS: [u8; 20] = [ 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 ]; - assert_eq!(pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); + assert_eq!(*pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); } #[test] From 1758ef727da7a8a05976586e9f8c60b211ec3fa3 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 17 Sep 2021 11:35:09 +0300 Subject: [PATCH 08/14] cargo fmt --all --- crates/eth_compatibility/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index f09df8ecf4..6c4e6def45 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -50,9 +50,7 @@ pub struct EthereumAddress(pub [u8; 20]); impl Default for EthereumAddress { fn default() -> Self { - Self { - 0: [0; 20] - } + Self { 0: [0; 20] } } } From 043a78d05756c3302343645132ccb3bd6fb9d607 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Fri, 17 Sep 2021 12:10:10 +0300 Subject: [PATCH 09/14] Simplified `ecdsa_recover`. USed symbolic links instead files. --- crates/eth_compatibility/LICENSE | 204 +------------- crates/eth_compatibility/README.md | 263 +------------------ crates/eth_compatibility/src/lib.rs | 2 +- crates/eth_compatibility/tests/conversion.rs | 4 +- crates/lang/src/env_access.rs | 9 +- 5 files changed, 7 insertions(+), 475 deletions(-) mode change 100644 => 120000 crates/eth_compatibility/LICENSE mode change 100644 => 120000 crates/eth_compatibility/README.md diff --git a/crates/eth_compatibility/LICENSE b/crates/eth_compatibility/LICENSE deleted file mode 100644 index 6b0b1270ff..0000000000 --- a/crates/eth_compatibility/LICENSE +++ /dev/null @@ -1,203 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/crates/eth_compatibility/LICENSE b/crates/eth_compatibility/LICENSE new file mode 120000 index 0000000000..30cff7403d --- /dev/null +++ b/crates/eth_compatibility/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/crates/eth_compatibility/README.md b/crates/eth_compatibility/README.md deleted file mode 100644 index d1bb0b6cad..0000000000 --- a/crates/eth_compatibility/README.md +++ /dev/null @@ -1,262 +0,0 @@ -
- ink! -

- Parity's ink! for writing smart contracts -

- -[![linux][a1]][a2] [![codecov][c1]][c2] [![coveralls][d1]][d2] [![loc][e1]][e2] [![matrix][k1]][k2] [![discord][l1]][l2] - -[a1]: https://gitlab.parity.io/parity/ink/badges/master/pipeline.svg -[a2]: https://gitlab.parity.io/parity/ink/pipelines?ref=master -[c1]: https://codecov.io/gh/paritytech/ink/branch/master/graph/badge.svg -[c2]: https://codecov.io/gh/paritytech/ink/branch/master -[d1]: https://coveralls.io/repos/github/paritytech/ink/badge.svg?branch=master -[d2]: https://coveralls.io/github/paritytech/ink?branch=master -[e1]: https://tokei.rs/b1/github/paritytech/ink?category=code -[e2]: https://github.com/Aaronepower/tokei#badges -[f1]: https://img.shields.io/badge/click-blue.svg -[f2]: https://paritytech.github.io/ink/ink_storage -[g1]: https://img.shields.io/badge/click-blue.svg -[g2]: https://paritytech.github.io/ink/ink_env -[i1]: https://img.shields.io/badge/click-blue.svg -[i2]: https://paritytech.github.io/ink/ink_prelude -[j1]: https://img.shields.io/badge/click-blue.svg -[j2]: https://paritytech.github.io/ink/ink_lang -[k1]: https://img.shields.io/badge/matrix-chat-brightgreen.svg?style=flat -[k2]: https://riot.im/app/#/room/#ink:matrix.parity.io -[l1]: https://img.shields.io/discord/722223075629727774?style=flat-square&label=discord -[l2]: https://discord.com/invite/wGUDt2p - -> squink, the ink! mascotink! is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to write smart contracts in Rust for blockchains built on the [Substrate](https://github.com/paritytech/substrate) framework. ink! contracts are compiled to WebAssembly. - -
- -[Guided Tutorial for Beginners](https://substrate.dev/substrate-contracts-workshop/#/0/building-your-contract)  •   -[ink! Documentation Portal](https://paritytech.github.io/ink-docs) - -
-
- -More relevant links: -* Talk to us on [Element][k2] or in [Discord][l2] - on the [`ink_smart-contracts`](https://discord.com/channels/722223075629727774/765280480609828864) channel -* [`cargo-contract`](https://github.com/paritytech/cargo-contract) ‒ CLI tool for ink! contracts -* [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload) ‒ Frontend for contract deployment and interaction -* [Substrate Contracts Node](https://github.com/paritytech/substrate-contracts-node) ‒ Simple Substrate blockchain which includes smart contract functionality - - -## Table of Contents - -* [Play with It](#play-with-it) -* [Usage](#usage) -* [Hello, World! ‒ The Flipper](#hello-world--the-flipper) -* [Examples](#examples) -* [How it Works](#how-it-works) -* [ink! Macros & Attributes Overview](#ink-macros--attributes-overview) - * [Entry Point](#entry-point) - * [Trait Definitions](#trait-definitions) - * [Off-chain Testing](#off-chain-testing) -* [Developer Documentation](#developer-documentation) -* [Contributing](#contributing) -* [License](#license) - - -## Play with It - -If you want to have a local setup you can use our [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node) for a quickstart. -It's a simple Substrate blockchain which includes the Substrate module for smart contract functionality ‒ the `contracts` pallet (see [How it Works](#how-it-works) for more). - -The [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload) can be used to deploy your contract to a chain and interact with it. - -We also have [a demonstration testnet](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fcanvas-rpc.parity.io) running. -You can request some tokens to play with from our [Faucet](https://riot.im/app/#/room/#canvas_faucet:matrix.parity.io) and deploy your contracts via the [Canvas UI](https://paritytech.github.io/canvas-ui/#/upload). - - -## Usage - -A prerequisite for compiling smart contracts is to have Rust and Cargo installed. Here's [an installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html). - -We recommend installing [`cargo-contract`](https://github.com/paritytech/cargo-contract) as well. -It's a CLI tool which helps set up and manage WebAssembly smart contracts written with ink!: - -``` -cargo install cargo-contract --force -``` - -Use the `--force` to ensure you are updated to the most recent `cargo-contract` version. - -In order to initialize a new ink! project you can use: - -``` -cargo contract new flipper -``` - -This will create a folder `flipper` in your work directory. -The folder contains a scaffold `Cargo.toml` and a `lib.rs`, which both contain the necessary building blocks for using ink!. - -The `lib.rs` contains our hello world contract ‒ the `Flipper`, which we explain in the next section. - -In order to build the contract just execute this command in the `flipper` folder: -``` -cargo +nightly contract build -``` - -As a result you'll get a file `target/flipper.wasm` file, a `metadata.json` file and a `.contract` file in the `target` folder of your contract. -The `.contract` file combines the Wasm and metadata into one file and needs to be used when deploying the contract. - - -## Hello, World! ‒ The Flipper - -The `Flipper` contract is a simple contract containing only a single `bool` value. -It provides methods to -* flip its value from `true` to `false` (and vice versa) and -* return the current state. - - -Below you can see the code using the `ink_lang` version of ink!. - -```rust -use ink_lang as ink; - -#[ink::contract] -mod flipper { - /// The storage of the flipper contract. - #[ink(storage)] - pub struct Flipper { - /// The single `bool` value. - value: bool, - } - - impl Flipper { - /// Instantiates a new Flipper contract and initializes - /// `value` to `init_value`. - #[ink(constructor)] - pub fn new(init_value: bool) -> Self { - Self { - value: init_value, - } - } - - /// Flips `value` from `true` to `false` or vice versa. - #[ink(message)] - pub fn flip(&mut self) { - self.value = !self.value; - } - - /// Returns the current state of `value`. - #[ink(message)] - pub fn get(&self) -> bool { - self.value - } - } - - /// Simply execute `cargo test` in order to test your contract - /// using the below unit tests. - #[cfg(test)] - mod tests { - use super::*; - use ink_lang as ink; - - #[ink::test] - fn it_works() { - let mut flipper = Flipper::new(false); - assert_eq!(flipper.get(), false); - flipper.flip(); - assert_eq!(flipper.get(), true); - } - } -} -``` - -The [`flipper/src/lib.rs`](https://github.com/paritytech/ink/blob/master/examples/flipper/lib.rs) -file in our examples folder contains exactly this code. Run `cargo contract build` to build your -first ink! smart contract. - -## Examples - -In the `examples` folder you'll find a number of examples written in ink!. - -Some of the most interesting ones: - -* `delegator` ‒ Implements cross-contract calling. -* `trait-erc20` ‒ Defines a trait for `Erc20` contracts and implements it. -* `erc721` ‒ An exemplary implementation of `Erc721` NFT tokens. -* `dns` ‒ A simple `DomainNameService` smart contract. -* …and more, just rummage through the folder 🙃. - -To build a single example navigate to the root of the example and run: -``` -cargo contract build -``` - -You should now have an `.contract` file in the `target` folder of the contract. - -For information on how to deploy this to a chain, please have a look at the [Play with It](#play-with-it) section or our [smart contracts workshop](https://substrate.dev/substrate-contracts-workshop/). - - -## How it Works - -* Substrate's [Framework for Runtime Aggregation of Modularised Entities (FRAME)](https://substrate.dev/docs/en/knowledgebase/runtime/frame) contains -a module which implements an API for typical functions smart contracts need (storage, querying information about accounts, …). -This module is called the `contracts` pallet, -* The `contracts` pallet requires smart contracts to be uploaded to the blockchain as a Wasm blob. -* ink! is a smart contract language which targets the API exposed by `contracts`. -Hence ink! contracts are compiled to Wasm. -* When executing `cargo contract build` an additional file `metadata.json` is created. -It contains information about e.g. what methods the contract provides for others to call. - -## ink! Macros & Attributes Overview - -### Entry Point - -In a module annotated with `#[ink::contract]` these attributes are available: - -| Attribute | Where Applicable | Description | -|:--|:--|:--| -| `#[ink(storage)]` | On `struct` definitions. | Defines the ink! storage struct. There can only be one ink! storage definition per contract. | -| `#[ink(event)]` | On `struct` definitions. | Defines an ink! event. A contract can define multiple such ink! events. | -| `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. | -| `#[ink(topic)]` | Applicate on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic field. Similar semantics as to indexed event arguments in Solidity. | -| `#[ink(message)]` | Applicable to methods. | Flags a method for the ink! storage struct as message making it available to the API for calling the contract. | -| `#[ink(constructor)]` | Applicable to method. | Flags a method for the ink! storage struct as constructor making it available to the API for instantiating the contract. | -| `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. | -| `#[ink(selector = "..")]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. | -| `#[ink(namespace = "..")]` | Applicable to ink! trait implementation blocks. | Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation. Allows to disambiguate between trait implementations with overlapping message or constructor names. Use only with great care and consideration! | -| `#[ink(impl)]` | Applicable to ink! implementation blocks. | Tells the ink! codegen that some implementation block shall be granted access to ink! internals even without it containing any ink! messages or ink! constructors. | - -See [here](https://paritytech.github.io/ink/ink_lang/attr.contract.html) for a more detailed description of those and also for details on the `#[ink::contract]` macro. - -### Trait Definitions - -Use `#[ink::trait_definition]` to define your very own trait definitions that are then implementable by ink! smart contracts. -See e.g. the [`examples/trait-erc20`](https://github.com/paritytech/ink/blob/v3.0.0-rc5/examples/trait-erc20/lib.rs#L35-L37) contract on how to utilize it or [the documentation](https://paritytech.github.io/ink/ink_lang/attr.trait_definition.html) for details. - -### Off-chain Testing - -The `#[ink::test]` procedural macro enables off-chain testing. See e.g. the [`examples/erc20`](https://github.com/paritytech/ink/blob/v3.0.0-rc5/examples/erc20/lib.rs#L248-L250) contract on how to utilize those or [the documentation](https://paritytech.github.io/ink/ink_lang/attr.test.html) for details. - -## Developer Documentation - -We have [a very comprehensive documentation portal](https://paritytech.github.io/ink-docs), -but if you are looking for the crate level documentation itself, then these are -the relevant links: - -| Crate | Docs | Description | -|:--|:--|:--| -`ink_lang` | [![][j1]][j2] | Language features expose by ink!. See [here](https://paritytech.github.io/ink/ink_lang/attr.contract.html) for a detailed description of attributes which you can use in an `#[ink::contract]`. | -`ink_storage` | [![][f1]][f2] | Data structures available in ink!. | -`ink_env` | [![][g1]][g2] | Low-level interface for interacting with the smart contract Wasm executor. | -`ink_prelude` | [![][i1]][i2] | Common API for no_std and std to access alloc crate types. | - - -## Contributing - -Visit our [contribution guidelines](CONTRIBUTING.md) for more information. - -Use the scripts provided under `scripts/check-*` directory in order to run checks on either the workspace or all examples. Please do this before pushing work in a PR. - -## License - -The entire code within this repository is licensed under the [Apache License 2.0](LICENSE). - -Please [contact us](https://www.parity.io/contact/) if you have questions about the licensing of our products. diff --git a/crates/eth_compatibility/README.md b/crates/eth_compatibility/README.md new file mode 120000 index 0000000000..fe84005413 --- /dev/null +++ b/crates/eth_compatibility/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 6c4e6def45..abe9a815bf 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] use ink_env::{ DefaultEnvironment, Environment, diff --git a/crates/eth_compatibility/tests/conversion.rs b/crates/eth_compatibility/tests/conversion.rs index d0360a75eb..ce682a996b 100644 --- a/crates/eth_compatibility/tests/conversion.rs +++ b/crates/eth_compatibility/tests/conversion.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ink_eth_compatibility::{ - ECDSAPublicKey, -}; +use ink_eth_compatibility::ECDSAPublicKey; #[test] fn correct_to_eth_address() { diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 4812e33ea4..b41c747420 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -1074,11 +1074,8 @@ where message_hash: &[u8; 32], ) -> Result { let mut output = [0; 33]; - let result = ink_env::ecdsa_recover(signature, message_hash, &mut output); - - match result.is_ok() { - true => Ok(ECDSAPublicKey { 0: output }), - false => Err(Error::EcdsaRecoverFailed), - } + ink_env::ecdsa_recover(signature, message_hash, &mut output) + .map(|_| ECDSAPublicKey { 0: output }) + .map_err(|_| Error::EcdsaRecoverFailed) } } From cde2bda9f2577aeab47aab471def35734070c853 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Wed, 22 Sep 2021 12:00:32 +0300 Subject: [PATCH 10/14] Added documentation for `to_eth_address` and `to_account_id` methods. --- crates/engine/src/ext.rs | 2 +- .../engine/experimental_off_chain/impls.rs | 2 +- crates/env/src/engine/off_chain/impls.rs | 2 +- crates/eth_compatibility/src/lib.rs | 66 +++++++++++++++---- crates/eth_compatibility/tests/conversion.rs | 54 --------------- 5 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 crates/eth_compatibility/tests/conversion.rs diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 4e7b8174b3..da29948ee1 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -437,7 +437,7 @@ impl Engine { // 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 { + let recovery_byte = if signature[64] > 26 { signature[64] - 27 } else { signature[64] diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 37bea874c8..db773c0ffc 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -264,7 +264,7 @@ impl EnvBackend for EnvInstance { // 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 { + let recovery_byte = if signature[64] > 26 { signature[64] - 27 } else { signature[64] diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 702fb2d444..0663aed8a7 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -210,7 +210,7 @@ impl EnvBackend for EnvInstance { // 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 { + let recovery_byte = if signature[64] > 26 { signature[64] - 27 } else { signature[64] diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index abe9a815bf..3c943155dd 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -13,10 +13,6 @@ // limitations under the License. #![no_std] -use ink_env::{ - DefaultEnvironment, - Environment, -}; /// The ECDSA compressed public key. #[derive(Debug, Copy, Clone)] @@ -24,7 +20,10 @@ pub struct ECDSAPublicKey(pub [u8; 33]); impl Default for ECDSAPublicKey { fn default() -> Self { - Self { 0: [0; 33] } + // Default is not implemented for [u8; 33], so we can't derive it for ECDSAPublicKey + // But clippy thinks that it is possible. So it is workaround for clippy. + let empty = [0; 33]; + Self { 0: empty } } } @@ -45,15 +44,9 @@ impl core::ops::DerefMut for ECDSAPublicKey { } /// The address of an Ethereum account. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct EthereumAddress(pub [u8; 20]); -impl Default for EthereumAddress { - fn default() -> Self { - Self { 0: [0; 20] } - } -} - impl core::ops::Deref for EthereumAddress { type Target = [u8; 20]; @@ -71,6 +64,26 @@ impl core::ops::DerefMut for EthereumAddress { } impl ECDSAPublicKey { + /// Returns Ethereum address from the ECDSA compressed public key. + /// + /// # Example + /// + /// ``` + /// use ink_eth_compatibility::ECDSAPublicKey; + /// let pub_key: ECDSAPublicKey = ECDSAPublicKey { + /// 0: [ + /// 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, + /// ] + /// }; + /// + /// const EXPECTED_ETH_ADDRESS: [u8; 20] = [ + /// 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 + /// ]; + /// + /// assert_eq!(*pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); + /// ``` pub fn to_eth_address(&self) -> EthereumAddress { use ink_env::hash; use secp256k1::PublicKey; @@ -93,12 +106,37 @@ impl ECDSAPublicKey { result } - pub fn to_account_id(&self) -> ::AccountId { + /// Returns the `[u8; 32]` from the ECDSA compressed public key. + /// It hashes the compressed public key with the blake2b256 algorithm like in substrate. + /// + /// # Note + /// + /// `[u8; 32]` can be converted into default `AccountId` with `into()` method. + /// + /// # Example + /// + /// ``` + /// use ink_eth_compatibility::ECDSAPublicKey; + /// let pub_key: ECDSAPublicKey = ECDSAPublicKey { + /// 0: [ + /// 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, + /// ] + /// }; + /// + /// const EXPECTED_ACCOUNT_ID: [u8; 32] = [ + /// 41, 117, 241, 210, 139, 146, 182, 232, 68, 153, 184, 59, 7, 151, 239, 82, + /// 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, + /// ]; + /// + /// assert_eq!(pub_key.to_account_id(), EXPECTED_ACCOUNT_ID); + pub fn to_account_id(&self) -> [u8; 32] { use ink_env::hash; let mut output = ::Type::default(); ink_env::hash_bytes::(&self.0[..], &mut output); - output.into() + output } } diff --git a/crates/eth_compatibility/tests/conversion.rs b/crates/eth_compatibility/tests/conversion.rs deleted file mode 100644 index ce682a996b..0000000000 --- a/crates/eth_compatibility/tests/conversion.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018-2021 Parity Technologies (UK) Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ink_eth_compatibility::ECDSAPublicKey; - -#[test] -fn correct_to_eth_address() { - #[rustfmt::skip] - let pub_key: ECDSAPublicKey = ECDSAPublicKey { - 0: [ - 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, - ] - }; - - #[rustfmt::skip] - const EXPECTED_ETH_ADDRESS: [u8; 20] = [ - 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 - ]; - - assert_eq!(*pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); -} - -#[test] -fn correct_to_account_id() { - #[rustfmt::skip] - let pub_key: ECDSAPublicKey = ECDSAPublicKey { - 0: [ - 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, - ] - }; - - #[rustfmt::skip] - const EXPECTED_ACCOUNT_ID: [u8; 32] = [ - 41, 117, 241, 210, 139, 146, 182, 232, 68, 153, 184, 59, 7, 151, 239, 82, - 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, - ]; - - assert_eq!(pub_key.to_account_id(), EXPECTED_ACCOUNT_ID.into()); -} From f5833c750d92e9564cbf8d5f32a31da42c36ca61 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Thu, 23 Sep 2021 15:14:20 +0300 Subject: [PATCH 11/14] Renamed `to_account_id` into `to_default_account_id` --- crates/eth_compatibility/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 3c943155dd..1f702f5a5f 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -13,6 +13,10 @@ // limitations under the License. #![no_std] +use ink_env::{ + DefaultEnvironment, + Environment, +}; /// The ECDSA compressed public key. #[derive(Debug, Copy, Clone)] @@ -106,13 +110,9 @@ impl ECDSAPublicKey { result } - /// Returns the `[u8; 32]` from the ECDSA compressed public key. + /// Returns the default Substrate's `AccountId` from the ECDSA compressed public key. /// It hashes the compressed public key with the blake2b256 algorithm like in substrate. /// - /// # Note - /// - /// `[u8; 32]` can be converted into default `AccountId` with `into()` method. - /// /// # Example /// /// ``` @@ -130,13 +130,13 @@ impl ECDSAPublicKey { /// 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, /// ]; /// - /// assert_eq!(pub_key.to_account_id(), EXPECTED_ACCOUNT_ID); - pub fn to_account_id(&self) -> [u8; 32] { + /// assert_eq!(pub_key.to_default_account_id(), EXPECTED_ACCOUNT_ID); + pub fn to_default_account_id(&self) -> ::AccountId { use ink_env::hash; let mut output = ::Type::default(); ink_env::hash_bytes::(&self.0[..], &mut output); - output + output.into() } } From 226cc55be934056c36c4c9f86c794b2574652ee0 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Thu, 23 Sep 2021 18:27:41 +0300 Subject: [PATCH 12/14] Cargo fmt --- crates/eth_compatibility/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 1f702f5a5f..735ab5b8bc 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -130,8 +130,10 @@ impl ECDSAPublicKey { /// 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, /// ]; /// - /// assert_eq!(pub_key.to_default_account_id(), EXPECTED_ACCOUNT_ID); - pub fn to_default_account_id(&self) -> ::AccountId { + /// assert_eq!(pub_key.to_default_account_id(), EXPECTED_ACCOUNT_ID.into()); + pub fn to_default_account_id( + &self, + ) -> ::AccountId { use ink_env::hash; let mut output = ::Type::default(); From 43855b219f5180f50b676f3049b710fb56dccae2 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Tue, 28 Sep 2021 19:42:49 +0200 Subject: [PATCH 13/14] Removed DeRef trait. Now field of `EthereumAddress` and `ECDSAPublicKey` is private. --- crates/eth_compatibility/src/lib.rs | 74 ++++++++++++++--------------- crates/lang/src/env_access.rs | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs index 735ab5b8bc..4c27cad1ef 100644 --- a/crates/eth_compatibility/src/lib.rs +++ b/crates/eth_compatibility/src/lib.rs @@ -20,7 +20,7 @@ use ink_env::{ /// The ECDSA compressed public key. #[derive(Debug, Copy, Clone)] -pub struct ECDSAPublicKey(pub [u8; 33]); +pub struct ECDSAPublicKey([u8; 33]); impl Default for ECDSAPublicKey { fn default() -> Self { @@ -31,62 +31,64 @@ impl Default for ECDSAPublicKey { } } -impl core::ops::Deref for ECDSAPublicKey { - type Target = [u8; 33]; - - #[inline] - fn deref(&self) -> &Self::Target { +impl AsRef<[u8; 33]> for ECDSAPublicKey { + fn as_ref(&self) -> &[u8; 33] { &self.0 } } -impl core::ops::DerefMut for ECDSAPublicKey { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { +impl AsMut<[u8; 33]> for ECDSAPublicKey { + fn as_mut(&mut self) -> &mut [u8; 33] { &mut self.0 } } +impl From<[u8; 33]> for ECDSAPublicKey { + fn from(bytes: [u8; 33]) -> Self { + Self { 0: bytes } + } +} + /// The address of an Ethereum account. #[derive(Debug, Default, Copy, Clone)] -pub struct EthereumAddress(pub [u8; 20]); - -impl core::ops::Deref for EthereumAddress { - type Target = [u8; 20]; +pub struct EthereumAddress([u8; 20]); - #[inline] - fn deref(&self) -> &Self::Target { +impl AsRef<[u8; 20]> for EthereumAddress { + fn as_ref(&self) -> &[u8; 20] { &self.0 } } -impl core::ops::DerefMut for EthereumAddress { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { +impl AsMut<[u8; 20]> for EthereumAddress { + fn as_mut(&mut self) -> &mut [u8; 20] { &mut self.0 } } +impl From<[u8; 20]> for EthereumAddress { + fn from(bytes: [u8; 20]) -> Self { + Self { 0: bytes } + } +} + impl ECDSAPublicKey { /// Returns Ethereum address from the ECDSA compressed public key. /// /// # Example /// /// ``` - /// use ink_eth_compatibility::ECDSAPublicKey; - /// let pub_key: ECDSAPublicKey = ECDSAPublicKey { - /// 0: [ - /// 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, - /// ] - /// }; + /// use ink_eth_compatibility::{ECDSAPublicKey, EthereumAddress}; + /// let pub_key: ECDSAPublicKey = [ + /// 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, + /// ].into(); /// - /// const EXPECTED_ETH_ADDRESS: [u8; 20] = [ + /// let EXPECTED_ETH_ADDRESS: EthereumAddress = [ /// 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 - /// ]; + /// ].into(); /// - /// assert_eq!(*pub_key.to_eth_address(), EXPECTED_ETH_ADDRESS); + /// assert_eq!(pub_key.to_eth_address().as_ref(), EXPECTED_ETH_ADDRESS.as_ref()); /// ``` pub fn to_eth_address(&self) -> EthereumAddress { use ink_env::hash; @@ -105,7 +107,7 @@ impl ECDSAPublicKey { // Take the last 20 bytes as an Address let mut result = EthereumAddress::default(); - result.copy_from_slice(&hash[12..]); + result.as_mut().copy_from_slice(&hash[12..]); result } @@ -117,13 +119,11 @@ impl ECDSAPublicKey { /// /// ``` /// use ink_eth_compatibility::ECDSAPublicKey; - /// let pub_key: ECDSAPublicKey = ECDSAPublicKey { - /// 0: [ - /// 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 pub_key: ECDSAPublicKey = [ + /// 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, + /// ].into(); /// /// const EXPECTED_ACCOUNT_ID: [u8; 32] = [ /// 41, 117, 241, 210, 139, 146, 182, 232, 68, 153, 184, 59, 7, 151, 239, 82, diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index b41c747420..46fab3d085 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -1075,7 +1075,7 @@ where ) -> Result { let mut output = [0; 33]; ink_env::ecdsa_recover(signature, message_hash, &mut output) - .map(|_| ECDSAPublicKey { 0: output }) + .map(|_| output.into()) .map_err(|_| Error::EcdsaRecoverFailed) } } From a1e6fcb927fa005cc05845e1b9bf37b010450139 Mon Sep 17 00:00:00 2001 From: xgreenx Date: Tue, 28 Sep 2021 19:50:39 +0200 Subject: [PATCH 14/14] Fixed doc test for ecdsa_recover in EnvAccess --- crates/lang/src/env_access.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 46fab3d085..5457746bde 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -1048,14 +1048,14 @@ where /// 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] = [ + /// let 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, - /// ]; + /// ].into(); /// let result = self.env().ecdsa_recover(&signature, &message_hash); /// assert!(result.is_ok()); - /// assert_eq!(*result.unwrap(), EXPECTED_COMPRESSED_PUBLIC_KEY); + /// assert_eq!(result.unwrap().as_ref(), EXPECTED_COMPRESSED_PUBLIC_KEY.as_ref()); /// /// // Pass invalid zero message hash /// let failed_result = self.env().ecdsa_recover(&signature, &[0; 32]);