diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 284c18234a..15a24e5d69 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -60,6 +60,7 @@ reentrancy refcount scalability scalable +sr25519 stdin stdout tuple diff --git a/CHANGELOG.md b/CHANGELOG.md index 213c1c0eb3..ccdc62edb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Persist `Environment` in metadata - [#1741](https://github.com/paritytech/ink/pull/1741) +- Added `sr25519_verify` function to `ink_env` [#1757](https://github.com/paritytech/ink/pull/1757) ### Changed - Upgraded `syn` to version `2` - [#1731](https://github.com/paritytech/ink/pull/1731) diff --git a/crates/e2e/macro/src/config.rs b/crates/e2e/macro/src/config.rs index a97c09e454..9754aaab13 100644 --- a/crates/e2e/macro/src/config.rs +++ b/crates/e2e/macro/src/config.rs @@ -63,7 +63,7 @@ impl TryFrom for E2EConfig { return Err(format_err_spanned!( arg, "expected a string literal for `additional_contracts` ink! E2E test configuration argument", - )) + )); } } else if arg.name.is_ident("environment") { if let Some((_, ast)) = environment { @@ -75,7 +75,7 @@ impl TryFrom for E2EConfig { return Err(format_err_spanned!( arg, "expected a path for `environment` ink! E2E test configuration argument", - )) + )); } } else { return Err(format_err_spanned!( diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 49d74c38c2..22b0eafa70 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -97,6 +97,8 @@ define_error_codes! { LoggingDisabled = 9, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed = 11, + /// sr25519 signature verification failed. + Sr25519VerifyFailed = 12, } /// The raw return code returned by the host side. diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index d9c662ec2f..57ed563bab 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -42,6 +42,8 @@ blake2 = { version = "0.10", optional = true } # ECDSA for the off-chain environment. secp256k1 = { version = "0.27.0", features = ["recovery", "global-context"], optional = true } +# schnorrkel for the off-chain environment. +schnorrkel = { version = "0.10.2", optional = true } # Only used in the off-chain environment. # @@ -67,6 +69,7 @@ std = [ "scale-encode", "scale-info/std", "secp256k1", + "schnorrkel", "num-traits/std", # Enables hashing crates for off-chain environment. "sha2", diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index c1a9a660ff..a2ef8899c3 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -550,6 +550,40 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result< }) } +/// Verifies a sr25519 signature. +/// +/// # Example +/// +/// ``` +/// let signature: [u8; 64] = [ +/// 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, +/// 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, +/// 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, +/// 255, 228, 54, 115, 63, 30, 207, 205, 131, +/// ]; +/// let message: &[u8; 11] = b"hello world"; +/// let pub_key: [u8; 32] = [ +/// 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, +/// 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, +/// ]; +/// +/// let result = ink::env::sr25519_verify(&signature, message.as_slice(), &pub_key); +/// assert!(result.is_ok()) +/// ``` +/// +/// # Errors +/// +/// - If sr25519 signature cannot be verified. +pub fn sr25519_verify( + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], +) -> Result<()> { + ::on_instance(|instance| { + instance.sr25519_verify(signature, message, pub_key) + }) +} + /// Checks whether the specified account is a contract. /// /// # Errors diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index fb6b00350c..4599849742 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -290,6 +290,18 @@ pub trait EnvBackend { output: &mut [u8; 20], ) -> Result<()>; + /// Verifies a sr25519 signature. + /// + /// # Errors + /// + /// - If the signature verification failed. + fn sr25519_verify( + &mut self, + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], + ) -> 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/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 5dbdf6a773..c7017341fb 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -47,6 +47,11 @@ use ink_engine::{ ext::Engine, }; use ink_storage_traits::Storable; +use schnorrkel::{ + signing_context, + PublicKey, + Signature, +}; /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size @@ -115,6 +120,7 @@ impl From for crate::Error { ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed, + ext::Error::Sr25519VerifyFailed => Self::Sr25519VerifyFailed, } } } @@ -333,6 +339,21 @@ impl EnvBackend for EnvInstance { Ok(()) } + fn sr25519_verify( + &mut self, + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], + ) -> Result<()> { + let context = signing_context(b"substrate"); + let signature: Signature = Signature::from_bytes(signature).unwrap(); + let public_key: PublicKey = PublicKey::from_bytes(pub_key).unwrap(); + + public_key + .verify(context.bytes(message), &signature) + .map_err(|_| Error::Sr25519VerifyFailed) + } + fn call_chain_extension( &mut self, func_id: u32, diff --git a/crates/env/src/engine/on_chain/ext/riscv32.rs b/crates/env/src/engine/on_chain/ext/riscv32.rs index ba030eb3d9..b8aa433990 100644 --- a/crates/env/src/engine/on_chain/ext/riscv32.rs +++ b/crates/env/src/engine/on_chain/ext/riscv32.rs @@ -372,6 +372,23 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result ret_code.into() } +/// **WARNING**: this function is from the [unstable interface](https://github.com/paritytech/substrate/tree/master/frame/contracts#unstable-interfaces), +/// which is unsafe and normally is not available on production chains. +pub fn sr25519_verify( + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], +) -> Result { + let ret_code = ( + Ptr32::from_slice(signature), + Ptr32::from_slice(pub_key), + message.len() as u32, + Ptr32::from_slice(message), + ) + .using_encoded(|in_data| sys::call(FUNC_ID, Ptr32::from_slice(in_data))); + ret_code.into() +} + pub fn is_contract(account_id: &[u8]) -> bool { let ret_val = sys::call(FUNC_ID, Ptr32::from_slice(account_id)); ret_val.into_bool() diff --git a/crates/env/src/engine/on_chain/ext/wasm32.rs b/crates/env/src/engine/on_chain/ext/wasm32.rs index 790eabc0d3..cf5c60353c 100644 --- a/crates/env/src/engine/on_chain/ext/wasm32.rs +++ b/crates/env/src/engine/on_chain/ext/wasm32.rs @@ -146,6 +146,15 @@ mod sys { output_ptr: Ptr32Mut<[u8]>, ) -> ReturnCode; + /// **WARNING**: this function is from the [unstable interface](https://github.com/paritytech/substrate/tree/master/frame/contracts#unstable-interfaces), + /// which is unsafe and normally is not available on production chains. + pub fn sr25519_verify( + signature_ptr: Ptr32<[u8]>, + public_key_ptr: Ptr32<[u8]>, + message_len: u32, + message_ptr: Ptr32<[u8]>, + ) -> ReturnCode; + pub fn take_storage( key_ptr: Ptr32<[u8]>, key_len: u32, @@ -597,6 +606,22 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result ret_code.into() } +pub fn sr25519_verify( + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], +) -> Result { + let ret_code = unsafe { + sys::sr25519_verify( + Ptr32::from_slice(signature), + Ptr32::from_slice(pub_key), + message.len() as u32, + Ptr32::from_slice(message), + ) + }; + ret_code.into() +} + pub fn is_contract(account_id: &[u8]) -> bool { let ret_val = unsafe { sys::is_contract(Ptr32::from_slice(account_id)) }; ret_val.into_bool() diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 69747bba5c..86cda89a3a 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -330,6 +330,15 @@ impl EnvBackend for EnvInstance { ext::ecdsa_to_eth_address(pubkey, output).map_err(Into::into) } + fn sr25519_verify( + &mut self, + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], + ) -> Result<()> { + ext::sr25519_verify(signature, message, pub_key).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 6db524b966..f4ece5f8fe 100644 --- a/crates/env/src/error.rs +++ b/crates/env/src/error.rs @@ -51,6 +51,8 @@ pub enum Error { CallRuntimeFailed, /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed, + /// sr25519 signature verification failed. + Sr25519VerifyFailed, } /// A result of environmental operations. diff --git a/crates/ink/ir/src/ir/item_mod.rs b/crates/ink/ir/src/ir/item_mod.rs index 58f1920ae9..664ca2e352 100644 --- a/crates/ink/ir/src/ir/item_mod.rs +++ b/crates/ink/ir/src/ir/item_mod.rs @@ -263,7 +263,7 @@ impl ItemMod { .into_combine(format_err!( overlap.span(), "first ink! message with overlapping wildcard selector here", - ))) + ))); } } } @@ -283,7 +283,7 @@ impl ItemMod { .into_combine(format_err!( overlap.span(), "first ink! constructor with overlapping wildcard selector here", - ))) + ))); } } } diff --git a/crates/ink/ir/src/ir/trait_def/item/mod.rs b/crates/ink/ir/src/ir/trait_def/item/mod.rs index 69b8f95b26..6297a20acc 100644 --- a/crates/ink/ir/src/ir/trait_def/item/mod.rs +++ b/crates/ink/ir/src/ir/trait_def/item/mod.rs @@ -384,7 +384,7 @@ impl InkItemTrait { ).into_combine(format_err_spanned!( duplicate_selector, "first ink! trait constructor or message with same selector found here", - ))) + ))); } assert!( duplicate_ident.is_none(), diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 63c42d01f8..0ec7f6bf53 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -880,6 +880,16 @@ where .map_err(|_| Error::EcdsaRecoveryFailed) } + pub fn sr25519_verify( + self, + signature: &[u8; 64], + message: &[u8], + pub_key: &[u8; 32], + ) -> Result<()> { + ink_env::sr25519_verify(signature, message, pub_key) + .map_err(|_| Error::Sr25519VerifyFailed) + } + /// Checks whether a specified account belongs to a contract. /// /// # Example