Skip to content
Merged
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
16 changes: 16 additions & 0 deletions evm-tests/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down
118 changes: 118 additions & 0 deletions evm-tests/test/sr25519.precompile.verify.test.ts
Original file line number Diff line number Diff line change
@@ -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)

});
});
20 changes: 1 addition & 19 deletions precompiles/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<A>(PhantomData<A>);

Expand Down Expand Up @@ -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,
})
}
}
31 changes: 30 additions & 1 deletion precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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::*;
Expand All @@ -38,6 +40,7 @@ mod ed25519;
mod extensions;
mod metagraph;
mod neuron;
mod sr25519;
mod staking;
mod storage_query;
mod subnet;
Expand Down Expand Up @@ -91,7 +94,7 @@ where
Self(Default::default())
}

pub fn used_addresses() -> [H160; 17] {
pub fn used_addresses() -> [H160; 18] {
[
hash(1),
hash(2),
Expand All @@ -102,6 +105,7 @@ where
hash(1024),
hash(1025),
hash(Ed25519Verify::<R::AccountId>::INDEX),
hash(Sr25519Verify::<R::AccountId>::INDEX),
hash(BalanceTransferPrecompile::<R>::INDEX),
hash(StakingPrecompile::<R>::INDEX),
hash(SubnetPrecompile::<R>::INDEX),
Expand Down Expand Up @@ -150,6 +154,9 @@ where
a if a == hash(Ed25519Verify::<R::AccountId>::INDEX) => {
Some(Ed25519Verify::<R::AccountId>::execute(handle))
}
a if a == hash(Sr25519Verify::<R::AccountId>::INDEX) => {
Some(Sr25519Verify::<R::AccountId>::execute(handle))
}
// Subtensor specific precompiles :
a if a == hash(BalanceTransferPrecompile::<R>::INDEX) => {
BalanceTransferPrecompile::<R>::try_execute::<R>(
Expand Down Expand Up @@ -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,
})
}
}
14 changes: 14 additions & 0 deletions precompiles/src/solidity/sr25519Verify.abi
Original file line number Diff line number Diff line change
@@ -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"
}
]
17 changes: 17 additions & 0 deletions precompiles/src/solidity/sr25519Verify.sol
Original file line number Diff line number Diff line change
@@ -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);
}
56 changes: 56 additions & 0 deletions precompiles/src/sr25519.rs
Original file line number Diff line number Diff line change
@@ -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<A>(PhantomData<A>);

impl<A> PrecompileExt<A> for Sr25519Verify<A>
where
A: From<[u8; 32]>,
{
const INDEX: u64 = 1027;
}

impl<A> LinearCostPrecompile for Sr25519Verify<A>
where
A: From<[u8; 32]>,
{
const BASE: u64 = 15;
const WORD: u64 = 3;

fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), 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()))
}
}
Loading