diff --git a/evm-tests/src/config.ts b/evm-tests/src/config.ts
index 7fb40e67cb..1d12e3d584 100644
--- a/evm-tests/src/config.ts
+++ b/evm-tests/src/config.ts
@@ -20,6 +20,22 @@ export const IEd25519VerifyABI = [
},
];
+export const ISr25519VERIFY_ADDRESS = "0x0000000000000000000000000000000000000403";
+export const ISr25519VerifyABI = [
+ {
+ inputs: [
+ { internalType: "bytes32", name: "message", type: "bytes32" },
+ { internalType: "bytes32", name: "publicKey", type: "bytes32" },
+ { internalType: "bytes32", name: "r", type: "bytes32" },
+ { internalType: "bytes32", name: "s", type: "bytes32" },
+ ],
+ name: "verify",
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
+ stateMutability: "pure",
+ type: "function",
+ },
+];
+
export const IBALANCETRANSFER_ADDRESS = "0x0000000000000000000000000000000000000800";
export const IBalanceTransferABI = [
{
diff --git a/evm-tests/test/sr25519.precompile.verify.test.ts b/evm-tests/test/sr25519.precompile.verify.test.ts
new file mode 100644
index 0000000000..234638f195
--- /dev/null
+++ b/evm-tests/test/sr25519.precompile.verify.test.ts
@@ -0,0 +1,118 @@
+import { ISr25519VERIFY_ADDRESS, ISr25519VerifyABI, ETH_LOCAL_URL } from '../src/config'
+import { getPublicClient } from "../src/utils";
+import { toHex, toBytes, keccak256, PublicClient } from 'viem'
+import { Keyring } from "@polkadot/keyring";
+import * as assert from "assert";
+
+describe("Verfication of sr25519 signature", () => {
+ // init eth part
+ let ethClient: PublicClient;
+
+ before(async () => {
+ ethClient = await getPublicClient(ETH_LOCAL_URL);
+ });
+
+ it("Verification of sr25519 works", async () => {
+ const keyring = new Keyring({ type: "sr25519" });
+ const alice = keyring.addFromUri("//Alice");
+
+ //////////////////////////////////////////////////////////////////////
+ // Generate a signature
+
+ // Your message to sign
+ const message = "Sign this message";
+ const messageU8a = new TextEncoder().encode(message);
+ const messageHex = toHex(messageU8a); // Convert message to hex string
+ const messageHash = keccak256(messageHex); // Hash the message to fit into bytes32
+ console.log(`messageHash = ${messageHash}`);
+ const hashedMessageBytes = toBytes(messageHash);
+ console.log(`hashedMessageBytes = ${hashedMessageBytes}`);
+
+ // Sign the message
+ const signature = await alice.sign(hashedMessageBytes);
+ console.log(`Signature: ${toHex(signature)}`);
+
+ // Verify the signature locally
+ const isValid = alice.verify(
+ hashedMessageBytes,
+ signature,
+ alice.publicKey
+ );
+ console.log(`Is the signature valid? ${isValid}`);
+
+ //////////////////////////////////////////////////////////////////////
+ // Verify the signature using the precompile contract
+
+ const publicKeyBytes = toHex(alice.publicKey);
+ console.log(`publicKeyBytes = ${publicKeyBytes}`);
+
+ // Split signture into Commitment (R) and response (s)
+ let r = signature.slice(0, 32); // Commitment, a.k.a. "r" - first 32 bytes
+ let s = signature.slice(32, 64); // Response, a.k.a. "s" - second 32 bytes
+ let rBytes = toHex(r);
+ let sBytes = toHex(s);
+
+ const isPrecompileValid = await ethClient.readContract({
+ address: ISr25519VERIFY_ADDRESS,
+ abi: ISr25519VerifyABI,
+ functionName: "verify",
+ args: [messageHash,
+ publicKeyBytes,
+ rBytes,
+ sBytes]
+
+ });
+
+ console.log(
+ `Is the signature valid according to the smart contract? ${isPrecompileValid}`
+ );
+ assert.equal(isPrecompileValid, true)
+
+ //////////////////////////////////////////////////////////////////////
+ // Verify the signature for bad data using the precompile contract
+
+ let brokenHashedMessageBytes = hashedMessageBytes;
+ brokenHashedMessageBytes[0] = (brokenHashedMessageBytes[0] + 1) % 0xff;
+ const brokenMessageHash = toHex(brokenHashedMessageBytes);
+ console.log(`brokenMessageHash = ${brokenMessageHash}`);
+
+ const isPrecompileValidBadData = await ethClient.readContract({
+ address: ISr25519VERIFY_ADDRESS,
+ abi: ISr25519VerifyABI,
+ functionName: "verify",
+ args: [brokenMessageHash,
+ publicKeyBytes,
+ rBytes,
+ sBytes]
+
+ });
+
+ console.log(
+ `Is the signature valid according to the smart contract for broken data? ${isPrecompileValidBadData}`
+ );
+ assert.equal(isPrecompileValidBadData, false)
+
+ //////////////////////////////////////////////////////////////////////
+ // Verify the bad signature for good data using the precompile contract
+
+ let brokenR = r;
+ brokenR[0] = (brokenR[0] + 1) % 0xff;
+ rBytes = toHex(r);
+ const isPrecompileValidBadSignature = await ethClient.readContract({
+ address: ISr25519VERIFY_ADDRESS,
+ abi: ISr25519VerifyABI,
+ functionName: "verify",
+ args: [messageHash,
+ publicKeyBytes,
+ rBytes,
+ sBytes]
+
+ });
+
+ console.log(
+ `Is the signature valid according to the smart contract for broken signature? ${isPrecompileValidBadSignature}`
+ );
+ assert.equal(isPrecompileValidBadSignature, false)
+
+ });
+});
\ No newline at end of file
diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs
index ae23a70e78..5e186f344f 100644
--- a/precompiles/src/ed25519.rs
+++ b/precompiles/src/ed25519.rs
@@ -6,7 +6,7 @@ use core::marker::PhantomData;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};
-use crate::PrecompileExt;
+use crate::{PrecompileExt, parse_slice};
pub(crate) struct Ed25519Verify(PhantomData);
@@ -52,21 +52,3 @@ where
Ok((ExitSucceed::Returned, buf.to_vec()))
}
}
-
-/// Takes a slice from bytes with PrecompileFailure as Error
-fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
- let maybe_slice = data.get(from..to);
- if let Some(slice) = maybe_slice {
- Ok(slice)
- } else {
- log::error!(
- "fail to get slice from data, {:?}, from {}, to {}",
- &data,
- from,
- to
- );
- Err(PrecompileFailure::Error {
- exit_status: ExitError::InvalidRange,
- })
- }
-}
diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs
index 89902ec9ee..354bee838d 100644
--- a/precompiles/src/lib.rs
+++ b/precompiles/src/lib.rs
@@ -4,6 +4,7 @@ extern crate alloc;
use core::marker::PhantomData;
+use fp_evm::{ExitError, PrecompileFailure};
use frame_support::{
dispatch::{GetDispatchInfo, PostDispatchInfo},
pallet_prelude::Decode,
@@ -28,6 +29,7 @@ use crate::ed25519::*;
use crate::extensions::*;
use crate::metagraph::*;
use crate::neuron::*;
+use crate::sr25519::*;
use crate::staking::*;
use crate::storage_query::*;
use crate::subnet::*;
@@ -38,6 +40,7 @@ mod ed25519;
mod extensions;
mod metagraph;
mod neuron;
+mod sr25519;
mod staking;
mod storage_query;
mod subnet;
@@ -91,7 +94,7 @@ where
Self(Default::default())
}
- pub fn used_addresses() -> [H160; 17] {
+ pub fn used_addresses() -> [H160; 18] {
[
hash(1),
hash(2),
@@ -102,6 +105,7 @@ where
hash(1024),
hash(1025),
hash(Ed25519Verify::::INDEX),
+ hash(Sr25519Verify::::INDEX),
hash(BalanceTransferPrecompile::::INDEX),
hash(StakingPrecompile::::INDEX),
hash(SubnetPrecompile::::INDEX),
@@ -150,6 +154,9 @@ where
a if a == hash(Ed25519Verify::::INDEX) => {
Some(Ed25519Verify::::execute(handle))
}
+ a if a == hash(Sr25519Verify::::INDEX) => {
+ Some(Sr25519Verify::::execute(handle))
+ }
// Subtensor specific precompiles :
a if a == hash(BalanceTransferPrecompile::::INDEX) => {
BalanceTransferPrecompile::::try_execute::(
@@ -193,3 +200,25 @@ where
fn hash(a: u64) -> H160 {
H160::from_low_u64_be(a)
}
+
+/*
+ *
+ * This is used to parse a slice from bytes with PrecompileFailure as Error
+ *
+ */
+fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
+ let maybe_slice = data.get(from..to);
+ if let Some(slice) = maybe_slice {
+ Ok(slice)
+ } else {
+ log::error!(
+ "fail to get slice from data, {:?}, from {}, to {}",
+ &data,
+ from,
+ to
+ );
+ Err(PrecompileFailure::Error {
+ exit_status: ExitError::InvalidRange,
+ })
+ }
+}
diff --git a/precompiles/src/solidity/sr25519Verify.abi b/precompiles/src/solidity/sr25519Verify.abi
new file mode 100644
index 0000000000..05d75ae6cc
--- /dev/null
+++ b/precompiles/src/solidity/sr25519Verify.abi
@@ -0,0 +1,14 @@
+[
+ {
+ "inputs": [
+ { "internalType": "bytes32", "name": "message", "type": "bytes32" },
+ { "internalType": "bytes32", "name": "publicKey", "type": "bytes32" },
+ { "internalType": "bytes32", "name": "r", "type": "bytes32" },
+ { "internalType": "bytes32", "name": "s", "type": "bytes32" }
+ ],
+ "name": "verify",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "pure",
+ "type": "function"
+ }
+]
\ No newline at end of file
diff --git a/precompiles/src/solidity/sr25519Verify.sol b/precompiles/src/solidity/sr25519Verify.sol
new file mode 100644
index 0000000000..1c6cbff45a
--- /dev/null
+++ b/precompiles/src/solidity/sr25519Verify.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-3.0
+pragma solidity ^0.8.0;
+
+address constant ISR25519VERIFY_ADDRESS = 0x0000000000000000000000000000000000000403;
+
+interface ISR25519Verify {
+ /**
+ * @dev Verifies SR25519 signature using provided message and public key.
+ *
+ * @param message The 32-byte signature payload message.
+ * @param publicKey 32-byte public key matching to private key used to sign the message.
+ * @param r The SR25519 signature commitment (first 32 bytes).
+ * @param s The SR25519 signature response (second 32 bytes).
+ * @return bool Returns true if the signature is valid for the given message and public key, false otherwise.
+ */
+ function verify(bytes32 message, bytes32 publicKey, bytes32 r, bytes32 s) external pure returns (bool);
+}
diff --git a/precompiles/src/sr25519.rs b/precompiles/src/sr25519.rs
new file mode 100644
index 0000000000..c81443dc19
--- /dev/null
+++ b/precompiles/src/sr25519.rs
@@ -0,0 +1,56 @@
+extern crate alloc;
+
+use alloc::vec::Vec;
+use core::marker::PhantomData;
+use sp_core::sr25519;
+use sp_runtime::traits::Verify;
+
+use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};
+
+use crate::{PrecompileExt, parse_slice};
+
+pub(crate) struct Sr25519Verify(PhantomData);
+
+impl PrecompileExt for Sr25519Verify
+where
+ A: From<[u8; 32]>,
+{
+ const INDEX: u64 = 1027;
+}
+
+impl LinearCostPrecompile for Sr25519Verify
+where
+ A: From<[u8; 32]>,
+{
+ const BASE: u64 = 15;
+ const WORD: u64 = 3;
+
+ fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec), PrecompileFailure> {
+ if input.len() < 132 {
+ return Err(PrecompileFailure::Error {
+ exit_status: ExitError::Other("input must contain 128 bytes".into()),
+ });
+ };
+
+ let mut buf = [0u8; 32];
+
+ let msg = parse_slice(input, 4, 36)?;
+ let pk = sr25519::Public::try_from(parse_slice(input, 36, 68)?).map_err(|_| {
+ PrecompileFailure::Error {
+ exit_status: ExitError::Other("Public key recover failed".into()),
+ }
+ })?;
+ let sig = sr25519::Signature::try_from(parse_slice(input, 68, 132)?).map_err(|_| {
+ PrecompileFailure::Error {
+ exit_status: ExitError::Other("Signature recover failed".into()),
+ }
+ })?;
+
+ let valid = sig.verify(msg, &pk);
+ if valid {
+ buf[31] = 1u8;
+ }
+
+ Ok((ExitSucceed::Returned, buf.to_vec()))
+ }
+}