Implemented seal_ecdsa_recovery function in the contract pallet (#9686)

* Implemented `seal_ecdsa_recovery` function in the contract pallet.
Added benchmark and unit test.

* Run `cargo fmt`

* Skip fmt for slices

* Changes according comments in pull request.

* Fix build without `unstable-interface` feature

* Applied suggestion from the review

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Changed RecoveryFailed to EcdsaRecoverFailed

* Manually updated weights.rs

* Apply suggestions from code review

Co-authored-by: Michael Müller <mich@elmueller.net>

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Michael Müller <mich@elmueller.net>
This commit is contained in:
GreenBaneling | Supercolony
2021-09-10 14:30:56 +03:00
committed by GitHub
parent 110ba540ec
commit a36e881783
13 changed files with 985 additions and 654 deletions
+56
View File
@@ -295,6 +295,7 @@ mod tests {
schedule: Schedule<Test>,
gas_meter: GasMeter<Test>,
debug_buffer: Vec<u8>,
ecdsa_recover: RefCell<Vec<([u8; 65], [u8; 32])>>,
}
/// The call is mocked and just returns this hardcoded value.
@@ -315,6 +316,7 @@ mod tests {
schedule: Default::default(),
gas_meter: GasMeter::new(10_000_000_000),
debug_buffer: Default::default(),
ecdsa_recover: Default::default(),
}
}
}
@@ -418,6 +420,15 @@ mod tests {
self.runtime_calls.borrow_mut().push(call);
Ok(Default::default())
}
fn ecdsa_recover(
&self,
signature: &[u8; 65],
message_hash: &[u8; 32],
) -> Result<[u8; 33], ()> {
self.ecdsa_recover.borrow_mut().push((signature.clone(), message_hash.clone()));
Ok([3; 33])
}
}
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
@@ -850,6 +861,51 @@ mod tests {
);
}
#[cfg(feature = "unstable-interface")]
const CODE_ECDSA_RECOVER: &str = r#"
(module
;; seal_ecdsa_recover(
;; signature_ptr: u32,
;; message_hash_ptr: u32,
;; output_ptr: u32
;; ) -> u32
(import "__unstable__" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(drop
(call $seal_ecdsa_recover
(i32.const 36) ;; Pointer to signature.
(i32.const 4) ;; Pointer to message hash.
(i32.const 36) ;; Pointer for output - public key.
)
)
)
(func (export "deploy"))
;; Hash of message.
(data (i32.const 4)
"\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"
)
;; Signature
(data (i32.const 36)
"\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"
"\01"
)
)
"#;
#[test]
#[cfg(feature = "unstable-interface")]
fn contract_ecdsa_recover() {
let mut mock_ext = MockExt::default();
assert_ok!(execute(&CODE_ECDSA_RECOVER, vec![], &mut mock_ext));
assert_eq!(mock_ext.ecdsa_recover.into_inner(), [([1; 65], [1; 32])]);
}
const CODE_GET_STORAGE: &str = r#"
(module
(import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
@@ -73,6 +73,9 @@ pub enum ReturnCode {
/// The call dispatched by `seal_call_runtime` was executed but returned an error.
#[cfg(feature = "unstable-interface")]
CallRuntimeReturnedError = 10,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
#[cfg(feature = "unstable-interface")]
EcdsaRecoverFailed = 11,
}
impl ConvertibleToWasm for ReturnCode {
@@ -199,6 +202,9 @@ pub enum RuntimeCosts {
HashBlake256(u32),
/// Weight of calling `seal_hash_blake2_128` for the given input size.
HashBlake128(u32),
/// Weight of calling `seal_ecdsa_recover`.
#[cfg(feature = "unstable-interface")]
EcdsaRecovery,
/// Weight charged by a chain extension through `seal_call_chain_extension`.
ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
@@ -265,6 +271,8 @@ impl RuntimeCosts {
HashBlake128(len) => s
.hash_blake2_128
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
#[cfg(feature = "unstable-interface")]
EcdsaRecovery => s.ecdsa_recover,
ChainExtension(amount) => amount,
#[cfg(feature = "unstable-interface")]
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
@@ -1712,4 +1720,44 @@ define_env!(Env, <E: Ext>,
Err(_) => Ok(ReturnCode::CallRuntimeReturnedError),
}
},
// Recovers the ECDSA public key from the given message hash and signature.
//
// Writes the public key into the given output buffer.
// Assumes the secp256k1 curve.
//
// # Parameters
//
// - `signature_ptr`: the pointer into the linear memory where the signature
// is placed. Should be decodable as a 65 bytes. Traps otherwise.
// - `message_hash_ptr`: the pointer into the linear memory where the message
// hash is placed. Should be decodable as a 32 bytes. Traps otherwise.
// - `output_ptr`: the pointer into the linear memory where the output
// data is placed. The buffer should be 33 bytes. Traps otherwise.
// The function will write the result directly into this buffer.
//
// # Errors
//
// `ReturnCode::EcdsaRecoverFailed`
[__unstable__] seal_ecdsa_recover(ctx, signature_ptr: u32, message_hash_ptr: u32, output_ptr: u32) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::EcdsaRecovery)?;
let mut signature: [u8; 65] = [0; 65];
ctx.read_sandbox_memory_into_buf(signature_ptr, &mut signature)?;
let mut message_hash: [u8; 32] = [0; 32];
ctx.read_sandbox_memory_into_buf(message_hash_ptr, &mut message_hash)?;
let result = ctx.ext.ecdsa_recover(&signature, &message_hash);
match result {
Ok(pub_key) => {
// Write the recovered compressed ecdsa public key back into the sandboxed output
// buffer.
ctx.write_sandbox_memory(output_ptr, pub_key.as_ref())?;
Ok(ReturnCode::Success)
},
Err(_) => Ok(ReturnCode::EcdsaRecoverFailed),
}
},
);