Skip to content

Commit a667cd2

Browse files
authored
Merge pull request #1702 from tensorplex-labs/features/add-sr25519
Add sr25519 precompiled pallet
2 parents 32b0658 + b41d95e commit a667cd2

File tree

7 files changed

+251
-19
lines changed

7 files changed

+251
-19
lines changed

evm-tests/src/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ export const IEd25519VerifyABI = [
2020
},
2121
];
2222

23+
export const ISr25519VERIFY_ADDRESS = "0x0000000000000000000000000000000000000403";
24+
export const ISr25519VerifyABI = [
25+
{
26+
inputs: [
27+
{ internalType: "bytes32", name: "message", type: "bytes32" },
28+
{ internalType: "bytes32", name: "publicKey", type: "bytes32" },
29+
{ internalType: "bytes32", name: "r", type: "bytes32" },
30+
{ internalType: "bytes32", name: "s", type: "bytes32" },
31+
],
32+
name: "verify",
33+
outputs: [{ internalType: "bool", name: "", type: "bool" }],
34+
stateMutability: "pure",
35+
type: "function",
36+
},
37+
];
38+
2339
export const IBALANCETRANSFER_ADDRESS = "0x0000000000000000000000000000000000000800";
2440
export const IBalanceTransferABI = [
2541
{
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { ISr25519VERIFY_ADDRESS, ISr25519VerifyABI, ETH_LOCAL_URL } from '../src/config'
2+
import { getPublicClient } from "../src/utils";
3+
import { toHex, toBytes, keccak256, PublicClient } from 'viem'
4+
import { Keyring } from "@polkadot/keyring";
5+
import * as assert from "assert";
6+
7+
describe("Verfication of sr25519 signature", () => {
8+
// init eth part
9+
let ethClient: PublicClient;
10+
11+
before(async () => {
12+
ethClient = await getPublicClient(ETH_LOCAL_URL);
13+
});
14+
15+
it("Verification of sr25519 works", async () => {
16+
const keyring = new Keyring({ type: "sr25519" });
17+
const alice = keyring.addFromUri("//Alice");
18+
19+
//////////////////////////////////////////////////////////////////////
20+
// Generate a signature
21+
22+
// Your message to sign
23+
const message = "Sign this message";
24+
const messageU8a = new TextEncoder().encode(message);
25+
const messageHex = toHex(messageU8a); // Convert message to hex string
26+
const messageHash = keccak256(messageHex); // Hash the message to fit into bytes32
27+
console.log(`messageHash = ${messageHash}`);
28+
const hashedMessageBytes = toBytes(messageHash);
29+
console.log(`hashedMessageBytes = ${hashedMessageBytes}`);
30+
31+
// Sign the message
32+
const signature = await alice.sign(hashedMessageBytes);
33+
console.log(`Signature: ${toHex(signature)}`);
34+
35+
// Verify the signature locally
36+
const isValid = alice.verify(
37+
hashedMessageBytes,
38+
signature,
39+
alice.publicKey
40+
);
41+
console.log(`Is the signature valid? ${isValid}`);
42+
43+
//////////////////////////////////////////////////////////////////////
44+
// Verify the signature using the precompile contract
45+
46+
const publicKeyBytes = toHex(alice.publicKey);
47+
console.log(`publicKeyBytes = ${publicKeyBytes}`);
48+
49+
// Split signture into Commitment (R) and response (s)
50+
let r = signature.slice(0, 32); // Commitment, a.k.a. "r" - first 32 bytes
51+
let s = signature.slice(32, 64); // Response, a.k.a. "s" - second 32 bytes
52+
let rBytes = toHex(r);
53+
let sBytes = toHex(s);
54+
55+
const isPrecompileValid = await ethClient.readContract({
56+
address: ISr25519VERIFY_ADDRESS,
57+
abi: ISr25519VerifyABI,
58+
functionName: "verify",
59+
args: [messageHash,
60+
publicKeyBytes,
61+
rBytes,
62+
sBytes]
63+
64+
});
65+
66+
console.log(
67+
`Is the signature valid according to the smart contract? ${isPrecompileValid}`
68+
);
69+
assert.equal(isPrecompileValid, true)
70+
71+
//////////////////////////////////////////////////////////////////////
72+
// Verify the signature for bad data using the precompile contract
73+
74+
let brokenHashedMessageBytes = hashedMessageBytes;
75+
brokenHashedMessageBytes[0] = (brokenHashedMessageBytes[0] + 1) % 0xff;
76+
const brokenMessageHash = toHex(brokenHashedMessageBytes);
77+
console.log(`brokenMessageHash = ${brokenMessageHash}`);
78+
79+
const isPrecompileValidBadData = await ethClient.readContract({
80+
address: ISr25519VERIFY_ADDRESS,
81+
abi: ISr25519VerifyABI,
82+
functionName: "verify",
83+
args: [brokenMessageHash,
84+
publicKeyBytes,
85+
rBytes,
86+
sBytes]
87+
88+
});
89+
90+
console.log(
91+
`Is the signature valid according to the smart contract for broken data? ${isPrecompileValidBadData}`
92+
);
93+
assert.equal(isPrecompileValidBadData, false)
94+
95+
//////////////////////////////////////////////////////////////////////
96+
// Verify the bad signature for good data using the precompile contract
97+
98+
let brokenR = r;
99+
brokenR[0] = (brokenR[0] + 1) % 0xff;
100+
rBytes = toHex(r);
101+
const isPrecompileValidBadSignature = await ethClient.readContract({
102+
address: ISr25519VERIFY_ADDRESS,
103+
abi: ISr25519VerifyABI,
104+
functionName: "verify",
105+
args: [messageHash,
106+
publicKeyBytes,
107+
rBytes,
108+
sBytes]
109+
110+
});
111+
112+
console.log(
113+
`Is the signature valid according to the smart contract for broken signature? ${isPrecompileValidBadSignature}`
114+
);
115+
assert.equal(isPrecompileValidBadSignature, false)
116+
117+
});
118+
});

precompiles/src/ed25519.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use core::marker::PhantomData;
66
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
77
use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};
88

9-
use crate::PrecompileExt;
9+
use crate::{PrecompileExt, parse_slice};
1010

1111
pub(crate) struct Ed25519Verify<A>(PhantomData<A>);
1212

@@ -52,21 +52,3 @@ where
5252
Ok((ExitSucceed::Returned, buf.to_vec()))
5353
}
5454
}
55-
56-
/// Takes a slice from bytes with PrecompileFailure as Error
57-
fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
58-
let maybe_slice = data.get(from..to);
59-
if let Some(slice) = maybe_slice {
60-
Ok(slice)
61-
} else {
62-
log::error!(
63-
"fail to get slice from data, {:?}, from {}, to {}",
64-
&data,
65-
from,
66-
to
67-
);
68-
Err(PrecompileFailure::Error {
69-
exit_status: ExitError::InvalidRange,
70-
})
71-
}
72-
}

precompiles/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate alloc;
44

55
use core::marker::PhantomData;
66

7+
use fp_evm::{ExitError, PrecompileFailure};
78
use frame_support::{
89
dispatch::{GetDispatchInfo, PostDispatchInfo},
910
pallet_prelude::Decode,
@@ -29,6 +30,7 @@ use crate::ed25519::*;
2930
use crate::extensions::*;
3031
use crate::metagraph::*;
3132
use crate::neuron::*;
33+
use crate::sr25519::*;
3234
use crate::staking::*;
3335
use crate::storage_query::*;
3436
use crate::subnet::*;
@@ -40,6 +42,7 @@ mod ed25519;
4042
mod extensions;
4143
mod metagraph;
4244
mod neuron;
45+
mod sr25519;
4346
mod staking;
4447
mod storage_query;
4548
mod subnet;
@@ -104,6 +107,7 @@ where
104107
hash(1024),
105108
hash(1025),
106109
hash(Ed25519Verify::<R::AccountId>::INDEX),
110+
hash(Sr25519Verify::<R::AccountId>::INDEX),
107111
hash(BalanceTransferPrecompile::<R>::INDEX),
108112
hash(StakingPrecompile::<R>::INDEX),
109113
hash(SubnetPrecompile::<R>::INDEX),
@@ -153,6 +157,9 @@ where
153157
a if a == hash(Ed25519Verify::<R::AccountId>::INDEX) => {
154158
Some(Ed25519Verify::<R::AccountId>::execute(handle))
155159
}
160+
a if a == hash(Sr25519Verify::<R::AccountId>::INDEX) => {
161+
Some(Sr25519Verify::<R::AccountId>::execute(handle))
162+
}
156163
// Subtensor specific precompiles :
157164
a if a == hash(BalanceTransferPrecompile::<R>::INDEX) => {
158165
BalanceTransferPrecompile::<R>::try_execute::<R>(
@@ -199,3 +206,25 @@ where
199206
fn hash(a: u64) -> H160 {
200207
H160::from_low_u64_be(a)
201208
}
209+
210+
/*
211+
*
212+
* This is used to parse a slice from bytes with PrecompileFailure as Error
213+
*
214+
*/
215+
fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
216+
let maybe_slice = data.get(from..to);
217+
if let Some(slice) = maybe_slice {
218+
Ok(slice)
219+
} else {
220+
log::error!(
221+
"fail to get slice from data, {:?}, from {}, to {}",
222+
&data,
223+
from,
224+
to
225+
);
226+
Err(PrecompileFailure::Error {
227+
exit_status: ExitError::InvalidRange,
228+
})
229+
}
230+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"inputs": [
4+
{ "internalType": "bytes32", "name": "message", "type": "bytes32" },
5+
{ "internalType": "bytes32", "name": "publicKey", "type": "bytes32" },
6+
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
7+
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
8+
],
9+
"name": "verify",
10+
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
11+
"stateMutability": "pure",
12+
"type": "function"
13+
}
14+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.0;
3+
4+
address constant ISR25519VERIFY_ADDRESS = 0x0000000000000000000000000000000000000403;
5+
6+
interface ISR25519Verify {
7+
/**
8+
* @dev Verifies SR25519 signature using provided message and public key.
9+
*
10+
* @param message The 32-byte signature payload message.
11+
* @param publicKey 32-byte public key matching to private key used to sign the message.
12+
* @param r The SR25519 signature commitment (first 32 bytes).
13+
* @param s The SR25519 signature response (second 32 bytes).
14+
* @return bool Returns true if the signature is valid for the given message and public key, false otherwise.
15+
*/
16+
function verify(bytes32 message, bytes32 publicKey, bytes32 r, bytes32 s) external pure returns (bool);
17+
}

precompiles/src/sr25519.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
extern crate alloc;
2+
3+
use alloc::vec::Vec;
4+
use core::marker::PhantomData;
5+
use sp_core::sr25519;
6+
use sp_runtime::traits::Verify;
7+
8+
use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};
9+
10+
use crate::{PrecompileExt, parse_slice};
11+
12+
pub(crate) struct Sr25519Verify<A>(PhantomData<A>);
13+
14+
impl<A> PrecompileExt<A> for Sr25519Verify<A>
15+
where
16+
A: From<[u8; 32]>,
17+
{
18+
const INDEX: u64 = 1027;
19+
}
20+
21+
impl<A> LinearCostPrecompile for Sr25519Verify<A>
22+
where
23+
A: From<[u8; 32]>,
24+
{
25+
const BASE: u64 = 15;
26+
const WORD: u64 = 3;
27+
28+
fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), PrecompileFailure> {
29+
if input.len() < 132 {
30+
return Err(PrecompileFailure::Error {
31+
exit_status: ExitError::Other("input must contain 128 bytes".into()),
32+
});
33+
};
34+
35+
let mut buf = [0u8; 32];
36+
37+
let msg = parse_slice(input, 4, 36)?;
38+
let pk = sr25519::Public::try_from(parse_slice(input, 36, 68)?).map_err(|_| {
39+
PrecompileFailure::Error {
40+
exit_status: ExitError::Other("Public key recover failed".into()),
41+
}
42+
})?;
43+
let sig = sr25519::Signature::try_from(parse_slice(input, 68, 132)?).map_err(|_| {
44+
PrecompileFailure::Error {
45+
exit_status: ExitError::Other("Signature recover failed".into()),
46+
}
47+
})?;
48+
49+
let valid = sig.verify(msg, &pk);
50+
if valid {
51+
buf[31] = 1u8;
52+
}
53+
54+
Ok((ExitSucceed::Returned, buf.to_vec()))
55+
}
56+
}

0 commit comments

Comments
 (0)