Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

contracts: add sr25519_verify #13724

Merged
merged 45 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
dab8913
wip
pgherveou Mar 29, 2023
d2da027
fix
pgherveou Mar 29, 2023
dd14d7f
wip
pgherveou Mar 29, 2023
d57ffcb
fix lint
pgherveou Mar 29, 2023
1a0f97d
rm fixture fix
pgherveou Mar 29, 2023
feba43d
missing comment
pgherveou Mar 29, 2023
d560208
fix lint
pgherveou Mar 29, 2023
c7e5430
add comment to the wsm file
pgherveou Mar 30, 2023
505919c
fix comment
pgherveou Mar 30, 2023
c5fba3a
Apply suggestions from code review
pgherveou Apr 3, 2023
a2ed618
wip
pgherveou Apr 4, 2023
1c305a4
wip weights
pgherveou Apr 4, 2023
5aa4bf1
wip weights
pgherveou Apr 4, 2023
57120be
Merge branch 'master' into pg/contracts-add-sr25519_recover
pgherveou Apr 4, 2023
2e7b44d
PR comment: test with return code
pgherveou Apr 4, 2023
f93e934
wip
pgherveou Apr 4, 2023
5084dbb
PR review add mock test
pgherveou Apr 4, 2023
90fd7ca
remove
pgherveou Apr 4, 2023
24fcbd4
lint
pgherveou Apr 4, 2023
66a9322
Update frame/contracts/fixtures/sr25519_verify.wat
pgherveou Apr 4, 2023
03ad784
fix comments
pgherveou Apr 5, 2023
dcd849d
Update frame/contracts/src/benchmarking/mod.rs
pgherveou Apr 5, 2023
b896c4c
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 5, 2023
8a30502
Update frame/contracts/fixtures/sr25519_verify.wat
pgherveou Apr 5, 2023
d49c633
Update frame/contracts/src/benchmarking/mod.rs
pgherveou Apr 5, 2023
851111a
fix lint
pgherveou Apr 5, 2023
f7dda14
Merge branch 'master' of https://github.com/paritytech/substrate into…
Apr 5, 2023
a7ab12b
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts
Apr 5, 2023
d11b430
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 5, 2023
9534b05
PR: review use unstable + remove arbitrary index 4
pgherveou Apr 5, 2023
c0a436a
Add benchmark for calculating overhead of calling sr25519_verify
pgherveou Apr 6, 2023
a39c784
fix message length encoding
pgherveou Apr 6, 2023
4f2b8e9
fix weights
pgherveou Apr 6, 2023
6fdb544
Merge branch 'master' of https://github.com/paritytech/substrate into…
Apr 6, 2023
3fbe20f
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts
Apr 6, 2023
44c0dca
Apply suggestions from code review
pgherveou Apr 7, 2023
5907075
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 7, 2023
6c1bbdb
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 7, 2023
4c1cee8
Update frame/contracts/src/benchmarking/mod.rs
pgherveou Apr 7, 2023
e2d01de
Update frame/contracts/src/benchmarking/mod.rs
pgherveou Apr 7, 2023
7d206cf
Update frame/contracts/src/schedule.rs
pgherveou Apr 12, 2023
5f1eb72
Update frame/contracts/src/schedule.rs
pgherveou Apr 12, 2023
9529c0a
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 12, 2023
3b9b849
Update frame/contracts/src/wasm/runtime.rs
pgherveou Apr 12, 2023
e5c9723
PR review
pgherveou Apr 12, 2023
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
55 changes: 55 additions & 0 deletions frame/contracts/fixtures/sr25519_verify.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
;; This contract:
;; 1) Reads signature, message and public key from the input
;; 2) Calls and return the result of sr25519_verify

(module
;; import the host functions from the seal0 module
(import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))

;; give the program 1 page of memory
(import "env" "memory" (memory 1 1))

;; [0, 4) length of signature + message + public key - 64 + 11 + 32 = 107 bytes
;; write the length of the input (6b = 107) bytes at offset 0
(data (i32.const 0) "\6b")

(func (export "deploy"))

(func (export "call")
;; define local variables
(local $signature_ptr i32)
(local $pub_key_ptr i32)
(local $message_len i32)
(local $message_ptr i32)

;; set the pointers to the memory locations
;; Memory layout during `call`
;; [10, 74) signature
;; [74, 106) public key
;; [106, 117) message (11 bytes)
(local.set $signature_ptr (i32.const 10))
(local.set $pub_key_ptr (i32.const 74))
(local.set $message_ptr (i32.const 106))

;; store the input into the memory, starting at the signature and
;; up to 107 bytes stored at offset 0
(call $seal_input (local.get $signature_ptr) (i32.const 0))

;; call sr25519_verify and store the return code
(i32.store
(i32.const 0)
(call $sr25519_verify
(local.get $signature_ptr)
(local.get $pub_key_ptr)
(i32.const 11)
(local.get $message_ptr)
)
)

;; exit with success and take transfer return code to the output buffer
(call $seal_return (i32.const 0) (i32.const 0) (i32.const 4))
)
)

107 changes: 106 additions & 1 deletion frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2008,9 +2008,114 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

// `n`: Message input length to verify in bytes.
#[pov_mode = Measured]
seal_sr25519_verify_per_byte {
let n in 0 .. T::MaxCodeLen::get() - 255; // need some buffer so the code size does not
// exceed the max code size.

let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>().encode();
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
let message_len = message.len() as i32;

let key_type = sp_core::crypto::KeyTypeId(*b"code");
let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();

let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "sr25519_verify",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
offset: 0,
value: sig,
},
DataSegment {
offset: 64,
value: pub_key.to_vec(),
},
DataSegment {
offset: 96,
value: message,
},
],
call_body: Some(body::plain(vec![
Instruction::I32Const(0), // signature_ptr
Instruction::I32Const(64), // pub_key_ptr
Instruction::I32Const(message_len), // message_len
Instruction::I32Const(96), // message_ptr
Instruction::Call(0),
Instruction::Drop,
Instruction::End,
])),
.. Default::default()
});

let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

// Only calling the function itself with valid arguments.
// It generates different private keys and signatures for the message "Hello world".
// This is a slow call: We redeuce the number of runs.
// This is a slow call: We reduce the number of runs.
#[pov_mode = Measured]
seal_sr25519_verify {
let r in 0 .. API_BENCHMARK_RUNS / 10;

let message = b"Hello world".to_vec().encode();
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
let message_len = message.len() as i32;
let key_type = sp_core::crypto::KeyTypeId(*b"code");
let sig_params = (0..r)
.map(|i| {
let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
let data: [u8; 96] = [AsRef::<[u8]>::as_ref(&sig), AsRef::<[u8]>::as_ref(&pub_key)].concat().try_into().unwrap();
data
})
.flatten()
.collect::<Vec<_>>();
let sig_params_len = sig_params.len() as i32;

let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "sr25519_verify",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
return_type: Some(ValueType::I32),
}],
data_segments: vec![
DataSegment {
offset: 0,
value: sig_params
},
DataSegment {
offset: sig_params_len as u32,
value: message,
},
],
call_body: Some(body::repeated_dyn(r, vec![
Counter(0, 96), // signature_ptr
Counter(64, 96), // pub_key_ptr
Regular(Instruction::I32Const(message_len)), // message_len
Regular(Instruction::I32Const(sig_params_len)), // message_ptr
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])

// Only calling the function itself with valid arguments.
// It generates different private keys and signatures for the message "Hello world".
// This is a slow call: We reduce the number of runs.
#[pov_mode = Measured]
seal_ecdsa_recover {
let r in 0 .. API_BENCHMARK_RUNS / 10;
Expand Down
16 changes: 15 additions & 1 deletion frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ use frame_support::{
use frame_system::RawOrigin;
use pallet_contracts_primitives::ExecReturnValue;
use smallvec::{Array, SmallVec};
use sp_core::ecdsa::Public as ECDSAPublic;
use sp_core::{
ecdsa::Public as ECDSAPublic,
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
};
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
use sp_runtime::traits::{Convert, Hash};
use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec};
Expand Down Expand Up @@ -272,6 +275,9 @@ pub trait Ext: sealing::Sealed {
/// Recovers ECDSA compressed public key based on signature and message hash.
fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>;

/// Verify a sr25519 signature.
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool;

/// Returns Ethereum address from the ECDSA compressed public key.
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>;

Expand Down Expand Up @@ -1347,6 +1353,14 @@ where
secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ())
}

fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool {
sp_io::crypto::sr25519_verify(
&SR25519Signature(*signature),
message,
&SR25519Public(*pub_key),
)
}

fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> {
ECDSAPublic(*pk).to_eth_address()
}
Expand Down
10 changes: 9 additions & 1 deletion frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl Limits {
/// Describes the weight for all categories of supported wasm instructions.
///
/// There there is one field for each wasm instruction that describes the weight to
/// execute one instruction of that name. There are a few execptions:
/// execute one instruction of that name. There are a few exceptions:
///
/// 1. If there is a i64 and a i32 variant of an instruction we use the weight
/// of the former for both.
Expand Down Expand Up @@ -409,6 +409,12 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_ecdsa_to_eth_address`.
pub ecdsa_to_eth_address: Weight,

/// Weight of calling `seal_sr25519_verify`.
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
pub sr25519_verify: Weight,

/// Weight per byte of calling `seal_sr25519_verify`.
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
pub sr25519_verify_per_byte: Weight,

/// Weight of calling `reentrance_count`.
pub reentrance_count: Weight,

Expand Down Expand Up @@ -616,6 +622,8 @@ impl<T: Config> Default for HostFnWeights<T> {
hash_blake2_128: cost!(seal_hash_blake2_128),
hash_blake2_128_per_byte: cost!(seal_hash_blake2_128_per_byte),
ecdsa_recover: cost!(seal_ecdsa_recover),
sr25519_verify: cost!(seal_sr25519_verify),
sr25519_verify_per_byte: cost!(seal_sr25519_verify_per_byte),
ecdsa_to_eth_address: cost!(seal_ecdsa_to_eth_address),
reentrance_count: cost!(seal_reentrance_count),
account_reentrance_count: cost!(seal_account_reentrance_count),
Expand Down
66 changes: 66 additions & 0 deletions frame/contracts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2900,6 +2900,72 @@ fn ecdsa_recover() {
})
}

#[test]
fn sr25519_verify() {
let (wasm, _code_hash) = compile_module::<Test>("sr25519_verify").unwrap();

ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);

// Instantiate the sr25519_verify contract.
let addr = Contracts::bare_instantiate(
ALICE,
100_000,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
false,
)
.result
.unwrap()
.account_id;

let call_with = |message: &[u8; 11]| {
// Alice's signature for "hello world"
#[rustfmt::skip]
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,
];

// Alice's public key
#[rustfmt::skip]
let public_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 mut params = vec![];
params.extend_from_slice(&signature);
params.extend_from_slice(&public_key);
params.extend_from_slice(&message.to_vec());
pgherveou marked this conversation as resolved.
Show resolved Hide resolved

<Pallet<Test>>::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
None,
params,
false,
Determinism::Enforced,
)
.result
.unwrap()
};

// verification should succeed for "hello world"
assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success);

// verification should fail for other messages
assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed);
})
}

#[test]
fn upload_code_works() {
let (wasm, code_hash) = compile_module::<Test>("dummy").unwrap();
Expand Down
49 changes: 49 additions & 0 deletions frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ mod tests {
gas_meter: GasMeter<Test>,
debug_buffer: Vec<u8>,
ecdsa_recover: RefCell<Vec<([u8; 65], [u8; 32])>>,
sr25519_verify: RefCell<Vec<([u8; 64], Vec<u8>, [u8; 32])>>,
code_hashes: Vec<CodeHash<Test>>,
}

Expand All @@ -458,6 +459,7 @@ mod tests {
gas_meter: GasMeter::new(Weight::from_parts(10_000_000_000, 10 * 1024 * 1024)),
debug_buffer: Default::default(),
ecdsa_recover: Default::default(),
sr25519_verify: Default::default(),
}
}
}
Expand Down Expand Up @@ -612,6 +614,10 @@ mod tests {
self.ecdsa_recover.borrow_mut().push((*signature, *message_hash));
Ok([3; 33])
}
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool {
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
self.sr25519_verify.borrow_mut().push((*signature, message.to_vec(), *pub_key));
true
}
fn contract_info(&mut self) -> &mut crate::ContractInfo<Self::T> {
unimplemented!()
}
Expand Down Expand Up @@ -1319,6 +1325,49 @@ mod tests {
);
}

#[test]
fn contract_sr25519() {
const CODE_SR25519: &str = r#"
(module
(import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(drop
(call $sr25519_verify
(i32.const 0) ;; Pointer to signature.
(i32.const 64) ;; Pointer to public key.
(i32.const 16) ;; message length.
(i32.const 96) ;; Pointer to message.
)
)
)
(func (export "deploy"))

;; Signature (64 bytes)
(data (i32.const 0)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)

;; public key (32 bytes)
(data (i32.const 64)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)

;; message. (16 bytes)
(data (i32.const 96)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)
)
"#;
let mut mock_ext = MockExt::default();
assert_ok!(execute(&CODE_SR25519, vec![], &mut mock_ext));
assert_eq!(mock_ext.sr25519_verify.into_inner(), [([1; 64], [1; 16].to_vec(), [1; 32])]);
}

const CODE_GET_STORAGE: &str = r#"
(module
(import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
Expand Down
Loading