Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

sr25519 signature verification #1757

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ reentrancy
refcount
scalability
scalable
sr25519
stdin
stdout
tuple
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions crates/e2e/macro/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
return Err(format_err_spanned!(
arg,
"expected a string literal for `additional_contracts` ink! E2E test configuration argument",
))
));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you exclude this file from the PR, please?=)

}
} else if arg.name.is_ident("environment") {
if let Some((_, ast)) = environment {
Expand All @@ -75,7 +75,7 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
return Err(format_err_spanned!(
arg,
"expected a path for `environment` ink! E2E test configuration argument",
))
));
}
} else {
return Err(format_err_spanned!(
Expand Down
2 changes: 2 additions & 0 deletions crates/engine/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions crates/env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -67,6 +69,7 @@ std = [
"scale-encode",
"scale-info/std",
"secp256k1",
"schnorrkel",
"num-traits/std",
# Enables hashing crates for off-chain environment.
"sha2",
Expand Down
34 changes: 34 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.sr25519_verify(signature, message, pub_key)
})
}

/// Checks whether the specified account is a contract.
///
/// # Errors
Expand Down
12 changes: 12 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
kziemianek marked this conversation as resolved.
Show resolved Hide resolved
&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.
Expand Down
21 changes: 21 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,6 +120,7 @@ impl From<ext::Error> for crate::Error {
ext::Error::NotCallable => Self::NotCallable,
ext::Error::LoggingDisabled => Self::LoggingDisabled,
ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed,
ext::Error::Sr25519VerifyFailed => Self::Sr25519VerifyFailed,
}
}
}
Expand Down Expand Up @@ -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");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For further use, i think there should be an option to verify signature from any context. Right now there is such limitation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you create a SIGNING_CTX constant like in the substrate and add a comment with the link to the code on the substrate side(that substrate uses the same context)?

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<I, T, E, ErrorCode, F, D>(
&mut self,
func_id: u32,
Expand Down
17 changes: 17 additions & 0 deletions crates/env/src/engine/on_chain/ext/riscv32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
25 changes: 25 additions & 0 deletions crates/env/src/engine/on_chain/ext/wasm32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
9 changes: 9 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<I, T, E, ErrorCode, F, D>(
&mut self,
func_id: u32,
Expand Down
2 changes: 2 additions & 0 deletions crates/env/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions crates/ink/ir/src/ir/item_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ impl ItemMod {
.into_combine(format_err!(
overlap.span(),
"first ink! message with overlapping wildcard selector here",
)))
)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you exclude this file from the PR, please?=)

}
}
}
Expand All @@ -283,7 +283,7 @@ impl ItemMod {
.into_combine(format_err!(
overlap.span(),
"first ink! constructor with overlapping wildcard selector here",
)))
)));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ink/ir/src/ir/trait_def/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ impl InkItemTrait {
).into_combine(format_err_spanned!(
duplicate_selector,
"first ink! trait constructor or message with same selector found here",
)))
)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you exclude this file from the PR, please?=)

}
assert!(
duplicate_ident.is_none(),
Expand Down
10 changes: 10 additions & 0 deletions crates/ink/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,16 @@ where
.map_err(|_| Error::EcdsaRecoveryFailed)
}

pub fn sr25519_verify(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a comment here as well, please?=)

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
Expand Down