oasis_runtime_sdk_contracts/abi/oasis/
crypto.rs

1//! Crypto function imports.
2use 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    /// Link crypto helper functions.
16    pub fn link_crypto<C: Context>(
17        instance: &mut wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
18    ) -> Result<(), Error> {
19        // crypto.ecdsa_recover(input) -> response
20        let _ = instance.link_function(
21            "crypto",
22            "ecdsa_recover",
23            |ctx, request: ((u32, u32), (u32, u32))| -> Result<(), wasm3::Trap> {
24                // Make sure function was called in valid context.
25                let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
26
27                // Charge gas.
28                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        // crypto.signature_verify(public_key, context, message, signature) -> response
54        #[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                // Make sure function was called in valid context.
68                let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
69
70                // Validate message length.
71                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                // Charge gas.
82                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        // crypto.random_bytes(dst) -> bytes_written
121        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                // Make sure function was called in valid context.
128                let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;
129
130                let num_bytes = dst_len.min(1024 /* 1 KiB */);
131
132                // Charge gas.
133                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); // This will certainly exhaust the gas limit.
140                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        // crypto.x25519_derive_symmetric(public_key, private_key) -> symmetric_key
166        #[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                // Make sure function was called in valid context.
174                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                    // Make sure function was called in valid context.
235                    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        // crypto.deoxysii_seal(key, nonce, plaintext_message, additional_data) -> encrypted_message
288        let _ = instance.link_function(
289            "crypto",
290            "deoxysii_seal",
291            deoxysii_factory(crypto::deoxysii::seal),
292        );
293
294        // crypto.deoxysii_open(key, nonce, encrypted_message, additional_data) -> plaintext_message
295        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    // cargo build --target wasm32-unknown-unknown --release
345    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}