1use std::convert::TryInto;
3
4use oasis_contract_sdk_crypto as crypto;
5use oasis_contract_sdk_types::crypto::SignatureKind;
6use oasis_runtime_sdk::{context::Context, crypto::signature, state::CurrentState};
7
8use super::{memory::Region, OasisV1};
9use crate::{
10 abi::{gas, ExecutionContext},
11 Config, Error,
12};
13
14impl<Cfg: Config> OasisV1<Cfg> {
15 pub fn link_crypto<C: Context>(
17 instance: &mut wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
18 ) -> Result<(), Error> {
19 let _ = instance.link_function(
21 "crypto",
22 "ecdsa_recover",
23 |ctx, request: ((u32, u32), (u32, u32))| -> Result<(), wasm3::Trap> {
24 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
26
27 gas::use_gas(ctx.instance, ec.params.gas_costs.wasm_crypto_ecdsa_recover)?;
29
30 let rt = ctx.instance.runtime();
31 rt.try_with_memory(|mut memory| -> Result<_, wasm3::Trap> {
32 let input = Region::from_arg(request.0)
33 .as_slice(&memory)
34 .map_err(|_| wasm3::Trap::Abort)?
35 .to_vec();
36
37 let output: &mut [u8; 65] = Region::from_arg(request.1)
38 .as_slice_mut(&mut memory)
39 .map_err(|_| wasm3::Trap::Abort)?
40 .try_into()
41 .map_err(|_| wasm3::Trap::Abort)?;
42
43 match crypto::ecdsa::recover(&input) {
44 Ok(key) => output.copy_from_slice(&key),
45 Err(_) => output.iter_mut().for_each(|b| *b = 0),
46 }
47
48 Ok(())
49 })?
50 },
51 );
52
53 #[allow(clippy::type_complexity)]
55 let _ = instance.link_function(
56 "crypto",
57 "signature_verify",
58 |ctx,
59 (kind, key, context, message, signature): (
60 u32,
61 (u32, u32),
62 (u32, u32),
63 (u32, u32),
64 (u32, u32),
65 )|
66 -> Result<u32, wasm3::Trap> {
67 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
69
70 if message.1 > ec.params.max_crypto_signature_verify_message_size_bytes {
72 ec.aborted = Some(Error::CryptoMsgTooLarge(
73 message.1,
74 ec.params.max_crypto_signature_verify_message_size_bytes,
75 ));
76 return Err(wasm3::Trap::Abort);
77 }
78
79 let kind: SignatureKind = kind.try_into().map_err(|_| wasm3::Trap::Abort)?;
80
81 let cost = match kind {
83 SignatureKind::Ed25519 => {
84 ec.params.gas_costs.wasm_crypto_signature_verify_ed25519
85 }
86 SignatureKind::Secp256k1 => {
87 ec.params.gas_costs.wasm_crypto_signature_verify_secp256k1
88 }
89 SignatureKind::Sr25519 => {
90 ec.params.gas_costs.wasm_crypto_signature_verify_sr25519
91 }
92 };
93 gas::use_gas(ctx.instance, cost)?;
94
95 let rt = ctx.instance.runtime();
96 rt.try_with_memory(|memory| -> Result<_, wasm3::Trap> {
97 let key = get_key(kind, key, &memory).inspect_err(|_| {
98 ec.aborted = Some(Error::CryptoMalformedPublicKey);
99 })?;
100 let message = Region::from_arg(message)
101 .as_slice(&memory)
102 .map_err(|_| wasm3::Trap::Abort)?;
103 let signature: signature::Signature = Region::from_arg(signature)
104 .as_slice(&memory)
105 .map_err(|_| wasm3::Trap::Abort)?
106 .to_vec()
107 .into();
108 if context.0 != 0 && context.1 != 0 && matches!(kind, SignatureKind::Sr25519) {
109 let context = Region::from_arg(context)
110 .as_slice(&memory)
111 .map_err(|_| wasm3::Trap::Abort)?;
112 Ok(1 - key.verify(context, message, &signature).is_ok() as u32)
113 } else {
114 Ok(1 - key.verify_raw(message, &signature).is_ok() as u32)
115 }
116 })?
117 },
118 );
119
120 let _ = instance.link_function(
122 "crypto",
123 "random_bytes",
124 |ctx,
125 ((pers_ptr, pers_len, dst_ptr, dst_len),): ((u32, u32, u32, u32),)|
126 -> Result<u32, wasm3::Trap> {
127 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
129
130 let num_bytes = dst_len.min(1024 );
131
132 let cost = ec
134 .params
135 .gas_costs
136 .wasm_crypto_random_bytes_byte
137 .checked_mul(num_bytes as u64 + pers_len as u64)
138 .and_then(|g| g.checked_add(ec.params.gas_costs.wasm_crypto_random_bytes_base))
139 .unwrap_or(u64::MAX); gas::use_gas(ctx.instance, cost)?;
141
142 let rt = ctx.instance.runtime();
143 rt.try_with_memory(|mut memory| -> Result<_, wasm3::Trap> {
144 let pers = Region::from_arg((pers_ptr, pers_len))
145 .as_slice(&memory)
146 .map_err(|_| wasm3::Trap::Abort)?;
147 let mut rng = CurrentState::with(|state| state.rng().fork(ec.tx_context, pers))
148 .map_err(|e| {
149 ec.aborted = Some(Error::ExecutionFailed(e.into()));
150 wasm3::Trap::Abort
151 })?;
152
153 let output = Region::from_arg((dst_ptr, num_bytes))
154 .as_slice_mut(&mut memory)
155 .map_err(|_| wasm3::Trap::Abort)?;
156 rand_core::RngCore::try_fill_bytes(&mut rng, output).map_err(|e| {
157 ec.aborted = Some(Error::ExecutionFailed(e.into()));
158 wasm3::Trap::Abort
159 })?;
160 Ok(num_bytes)
161 })?
162 },
163 );
164
165 #[allow(clippy::type_complexity)]
167 let _ = instance.link_function(
168 "crypto",
169 "x25519_derive_symmetric",
170 |ctx,
171 (public_key, private_key, output_key): ((u32, u32), (u32, u32), (u32, u32))|
172 -> Result<u32, wasm3::Trap> {
173 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
175
176 gas::use_gas(
177 ctx.instance,
178 ec.params.gas_costs.wasm_crypto_x25519_derive_symmetric,
179 )?;
180
181 ctx.instance
182 .runtime()
183 .try_with_memory(|mut memory| -> Result<_, wasm3::Trap> {
184 let public = Region::from_arg(public_key)
185 .as_slice(&memory)
186 .map_err(|_| wasm3::Trap::Abort)?
187 .to_vec();
188 let private = Region::from_arg(private_key)
189 .as_slice(&memory)
190 .map_err(|_| wasm3::Trap::Abort)?
191 .to_vec();
192 let output: &mut [u8; crypto::x25519::KEY_SIZE] =
193 Region::from_arg(output_key)
194 .as_slice_mut(&mut memory)
195 .map_err(|_| wasm3::Trap::Abort)?
196 .try_into()
197 .map_err(|_| wasm3::Trap::Abort)?;
198 let derived =
199 crypto::x25519::derive_symmetric(&public, &private).map_err(|e| {
200 let err = match e {
201 crypto::x25519::Error::MalformedPublicKey => {
202 Error::CryptoMalformedPublicKey
203 }
204 crypto::x25519::Error::MalformedPrivateKey => {
205 Error::CryptoMalformedPrivateKey
206 }
207 crypto::x25519::Error::KeyDerivationFunctionFailure => {
208 Error::CryptoKeyDerivationFunctionFailure
209 }
210 };
211 ec.aborted = Some(err);
212 wasm3::Trap::Abort
213 })?;
214 if output.len() != derived.len() {
215 return Err(wasm3::Trap::Abort);
216 }
217 output.copy_from_slice(&derived);
218 Ok(0)
219 })?
220 },
221 );
222
223 #[allow(clippy::type_complexity)]
224 let deoxysii_factory =
225 |func: fn(&[u8], &[u8], &[u8], &[u8]) -> Result<Vec<u8>, crypto::deoxysii::Error>| {
226 move |ctx: wasm3::CallContext<'_, ExecutionContext<'_, C>>,
227 (key, nonce, message, additional_data): (
228 (u32, u32),
229 (u32, u32),
230 (u32, u32),
231 (u32, u32),
232 )|
233 -> Result<u32, wasm3::Trap> {
234 let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
236
237 gas::use_gas(
238 ctx.instance,
239 ec.params.gas_costs.wasm_crypto_deoxysii_base
240 + ec.params.gas_costs.wasm_crypto_deoxysii_byte
241 * (message.1 as u64 + additional_data.1 as u64),
242 )?;
243
244 let output = ctx.instance.runtime().try_with_memory(
245 |memory| -> Result<Option<Vec<u8>>, wasm3::Trap> {
246 let key = Region::from_arg(key)
247 .as_slice(&memory)
248 .map_err(|_| wasm3::Trap::Abort)?;
249 let nonce = Region::from_arg(nonce)
250 .as_slice(&memory)
251 .map_err(|_| wasm3::Trap::Abort)?;
252 let message = Region::from_arg(message)
253 .as_slice(&memory)
254 .map_err(|_| wasm3::Trap::Abort)?;
255 let additional_data = Region::from_arg(additional_data)
256 .as_slice(&memory)
257 .map_err(|_| wasm3::Trap::Abort)?;
258 func(key, nonce, message, additional_data)
259 .map(Some)
260 .or_else(|e| {
261 let err = match e {
262 crypto::deoxysii::Error::MalformedKey => {
263 Error::CryptoMalformedKey
264 }
265 crypto::deoxysii::Error::MalformedNonce => {
266 Error::CryptoMalformedNonce
267 }
268 crypto::deoxysii::Error::DecryptionFailed => {
269 return Ok(None);
270 }
271 };
272 ec.aborted = Some(err);
273 Err(wasm3::Trap::Abort)
274 })
275 },
276 )??;
277
278 if let Some(output) = output {
279 let output_region = Self::allocate_and_copy(ctx.instance, &output)?;
280 Self::allocate_region(ctx.instance, output_region).map_err(|e| e.into())
281 } else {
282 Ok(0)
283 }
284 }
285 };
286
287 let _ = instance.link_function(
289 "crypto",
290 "deoxysii_seal",
291 deoxysii_factory(crypto::deoxysii::seal),
292 );
293
294 let _ = instance.link_function(
296 "crypto",
297 "deoxysii_open",
298 deoxysii_factory(crypto::deoxysii::open),
299 );
300
301 Ok(())
302 }
303}
304
305fn get_key(
306 kind: SignatureKind,
307 key: (u32, u32),
308 memory: &wasm3::Memory<'_>,
309) -> Result<signature::PublicKey, wasm3::Trap> {
310 let region = Region::from_arg(key)
311 .as_slice(memory)
312 .map_err(|_| wasm3::Trap::Abort)?;
313
314 match kind {
315 SignatureKind::Ed25519 => {
316 let ed25519 = signature::ed25519::PublicKey::from_bytes(region)
317 .map_err(|_| wasm3::Trap::Abort)?;
318 Ok(signature::PublicKey::Ed25519(ed25519))
319 }
320 SignatureKind::Secp256k1 => {
321 let secp256k1 = signature::secp256k1::PublicKey::from_bytes(region)
322 .map_err(|_| wasm3::Trap::Abort)?;
323 Ok(signature::PublicKey::Secp256k1(secp256k1))
324 }
325 SignatureKind::Sr25519 => {
326 let sr25519 = signature::sr25519::PublicKey::from_bytes(region)
327 .map_err(|_| wasm3::Trap::Abort)?;
328 Ok(signature::PublicKey::Sr25519(sr25519))
329 }
330 }
331}
332
333#[cfg(all(feature = "benchmarks", test))]
334mod test {
335 extern crate test;
336 use super::*;
337 use test::Bencher;
338
339 use k256::{
340 self,
341 ecdsa::{self, signature::Verifier as _},
342 };
343
344 const BENCH_CODE: &[u8] = include_bytes!(
346 "../../../../../../tests/contracts/bench/target/wasm32-unknown-unknown/release/bench.wasm"
347 );
348 const MESSAGE: &[u8] =
349 include_bytes!("../../../../../../tests/contracts/bench/data/message.txt");
350 const SIGNATURE: &[u8] =
351 include_bytes!("../../../../../../tests/contracts/bench/data/signature.bin");
352 const KEY: &[u8] = include_bytes!("../../../../../../tests/contracts/bench/data/key.bin");
353
354 fn verify_signature(message: &[u8], signature: &[u8], key: &[u8]) -> Result<(), ()> {
355 let key = k256::EncodedPoint::from_bytes(key).map_err(|_| ())?;
356 let sig = ecdsa::Signature::from_der(signature).map_err(|_| ())?;
357 let verifying_key = ecdsa::VerifyingKey::from_encoded_point(&key).map_err(|_| ())?;
358 verifying_key.verify(message, &sig).map_err(|_| ())?;
359 Ok(())
360 }
361
362 #[bench]
363 fn bench_crypto_nonwasm_verify(b: &mut Bencher) {
364 b.iter(|| {
365 verify_signature(MESSAGE, SIGNATURE, KEY).unwrap();
366 });
367 }
368
369 #[bench]
370 fn bench_crypto_nonwasm_recover(b: &mut Bencher) {
371 let input = hex::decode("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce97100890f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301").unwrap();
372 b.iter(|| {
373 assert!(crypto::ecdsa::recover(&input).is_ok());
374 });
375 }
376
377 #[bench]
378 fn bench_crypto_nonwasm_x25519_derive(b: &mut Bencher) {
379 let public = <[u8; 32] as hex::FromHex>::from_hex(
380 "3046db3fa70ce605457dc47c48837ebd8bd0a26abfde5994d033e1ced68e2576",
381 )
382 .unwrap();
383 let private = <[u8; 32] as hex::FromHex>::from_hex(
384 "c07b151fbc1e7a11dff926111188f8d872f62eba0396da97c0a24adb75161750",
385 )
386 .unwrap();
387 b.iter(|| {
388 assert!(crypto::x25519::derive_symmetric(&public, &private).is_ok());
389 });
390 }
391
392 #[bench]
393 fn bench_crypto_nonwasm_deoxysii_seal_tiny(b: &mut Bencher) {
394 let key = <[u8; 32] as hex::FromHex>::from_hex(
395 "e69ac21066a8c2284e8fdc690e579af4513547b9b31dd144792c1904b45cf586",
396 )
397 .unwrap();
398 b.iter(|| {
399 assert!(crypto::deoxysii::seal(&key, b"0123456789abcde", b"b", b"").is_ok());
400 });
401 }
402
403 #[bench]
404 fn bench_crypto_nonwasm_deoxysii_seal_size1(b: &mut Bencher) {
405 let key = <[u8; 32] as hex::FromHex>::from_hex(
406 "e69ac21066a8c2284e8fdc690e579af4513547b9b31dd144792c1904b45cf586",
407 )
408 .unwrap();
409 b.iter(|| {
410 assert!(crypto::deoxysii::seal(&key, b"0123456789abcde", MESSAGE, b"").is_ok());
411 });
412 }
413
414 #[bench]
415 fn bench_crypto_nonwasm_deoxysii_seal_size2(b: &mut Bencher) {
416 let key = <[u8; 32] as hex::FromHex>::from_hex(
417 "e69ac21066a8c2284e8fdc690e579af4513547b9b31dd144792c1904b45cf586",
418 )
419 .unwrap();
420 let mut message = MESSAGE.to_vec();
421 message.extend_from_slice(MESSAGE);
422 b.iter(|| {
423 assert!(crypto::deoxysii::seal(&key, b"0123456789abcde", &message, b"").is_ok());
424 });
425 }
426
427 #[bench]
428 fn bench_crypto_called_from_wasm_included(b: &mut Bencher) {
429 let env =
430 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
431 let module = env
432 .parse_module(BENCH_CODE)
433 .expect("parsing the code should succeed");
434 let rt: wasm3::Runtime<'_, wasm3::CallContext<'_, ()>> = env
435 .new_runtime(1 * 1024 * 1024, None)
436 .expect("creating a new wasm3 runtime should succeed");
437 let mut instance = rt
438 .load_module(module)
439 .expect("instance creation should succeed");
440 let _ = instance.link_function(
441 "bench",
442 "verify_signature",
443 |ctx,
444 (message, signature, key): ((u32, u32), (u32, u32), (u32, u32))|
445 -> Result<(), wasm3::Trap> {
446 ctx.instance
447 .runtime()
448 .try_with_memory(|memory| -> Result<_, wasm3::Trap> {
449 let message = Region::from_arg(message)
450 .as_slice(&memory)
451 .map_err(|_| wasm3::Trap::Abort)?;
452 let signature = Region::from_arg(signature)
453 .as_slice(&memory)
454 .map_err(|_| wasm3::Trap::Abort)?;
455 let key = Region::from_arg(key)
456 .as_slice(&memory)
457 .map_err(|_| wasm3::Trap::Abort)?;
458 verify_signature(message, signature, key)
459 .map_err(|_| wasm3::Trap::Abort)?;
460 Ok(())
461 })?
462 },
463 );
464 let func = instance
465 .find_function::<(), ()>("call_verification_included")
466 .expect("finding the entrypoint function should succeed");
467 b.iter(|| {
468 func.call(()).expect("function call should succeed");
469 });
470 }
471
472 #[bench]
473 fn bench_crypto_computed_in_wasm(b: &mut Bencher) {
474 let env =
475 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
476 let module = env
477 .parse_module(BENCH_CODE)
478 .expect("parsing the code should succeed");
479 let rt: wasm3::Runtime<'_, wasm3::CallContext<'_, ()>> = env
480 .new_runtime(1 * 1024 * 1024, None)
481 .expect("creating a new wasm3 runtime should succeed");
482 let instance = rt
483 .load_module(module)
484 .expect("instance creation should succeed");
485 let func = instance
486 .find_function::<(), ()>("call_verification_internal")
487 .expect("finding the entrypoint function should succeed");
488 b.iter(|| {
489 func.call(()).expect("function call should succeed");
490 });
491 }
492
493 #[bench]
494 fn bench_crypto_computed_in_wasm_instrumented(_b: &mut Bencher) {
495 let mut module = walrus::ModuleConfig::new()
496 .generate_producers_section(false)
497 .parse(&BENCH_CODE)
498 .unwrap();
499 gas::transform(&mut module);
500 let new_code = module.emit_wasm();
501
502 let env =
503 wasm3::Environment::new().expect("creating a new wasm3 environment should succeed");
504 let module = env
505 .parse_module(&new_code)
506 .expect("parsing the code should succeed");
507 let rt: wasm3::Runtime<'_, wasm3::CallContext<'_, ()>> = env
508 .new_runtime(1 * 1024 * 1024, None)
509 .expect("creating a new wasm3 runtime should succeed");
510 let instance = rt
511 .load_module(module)
512 .expect("instance creation should succeed");
513 let initial_gas = 1_000_000_000_000u64;
514 instance
515 .set_global(gas::EXPORT_GAS_LIMIT, initial_gas)
516 .expect("setting gas limit should succeed");
517 let func = instance
518 .find_function::<(), ()>("call_verification_internal")
519 .expect("finding the entrypoint function should succeed");
520 func.call(()).expect("function call should succeed");
521
522 let gas_limit: u64 = instance
523 .get_global(gas::EXPORT_GAS_LIMIT)
524 .expect("getting gas limit global should succeed");
525 let gas_limit_exhausted: u64 = instance
526 .get_global(gas::EXPORT_GAS_LIMIT_EXHAUSTED)
527 .expect("getting gas limit exhausted global should succeed");
528 println!(
529 " signature verification done, gas remaining {} [used: {}, exhausted flag: {}]",
530 gas_limit,
531 initial_gas - gas_limit,
532 gas_limit_exhausted
533 );
534 }
535}