iota_move_natives_latest/crypto/
ecdsa_r1.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::VecDeque;
6
7use fastcrypto::{
8    error::FastCryptoError,
9    hash::{Keccak256, Sha256},
10    secp256r1::{
11        Secp256r1PublicKey, Secp256r1Signature, recoverable::Secp256r1RecoverableSignature,
12    },
13    traits::{RecoverableSignature, ToFromBytes},
14};
15use move_binary_format::errors::PartialVMResult;
16use move_core_types::gas_algebra::InternalGas;
17use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
18use move_vm_types::{
19    loaded_data::runtime_types::Type,
20    natives::function::NativeResult,
21    pop_arg,
22    values::{Value, VectorRef},
23};
24use smallvec::smallvec;
25
26use crate::NativesCostTable;
27
28pub const FAIL_TO_RECOVER_PUBKEY: u64 = 0;
29pub const INVALID_SIGNATURE: u64 = 1;
30
31pub const KECCAK256: u8 = 0;
32pub const SHA256: u8 = 1;
33
34const KECCAK256_BLOCK_SIZE: usize = 136;
35const SHA256_BLOCK_SIZE: usize = 64;
36
37#[derive(Clone)]
38pub struct EcdsaR1EcrecoverCostParams {
39    /// Base cost for invoking the `ecrecover` function with `hash=0` implying
40    /// KECCAK256
41    pub ecdsa_r1_ecrecover_keccak256_cost_base: InternalGas,
42    ///  Cost per byte of `msg` with `hash=0`implying KECCAK256
43    pub ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: InternalGas,
44    ///  Cost per block of `msg` with `hash=0`implying KECCAK256, with block
45    /// size = 136
46    pub ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: InternalGas,
47
48    /// Base cost for invoking the `ecrecover` function with `hash=1` implying
49    /// SHA256
50    pub ecdsa_r1_ecrecover_sha256_cost_base: InternalGas,
51    ///  Cost per byte of `msg` with `hash=1`implying SHA256
52    pub ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: InternalGas,
53    ///  Cost per block of `msg` with `hash=1`implying SHA256, with block size =
54    /// 64
55    pub ecdsa_r1_ecrecover_sha256_msg_cost_per_block: InternalGas,
56}
57/// ****************************************************************************
58/// ********************* native fun secp256r1_ecrecover
59/// Implementation of the Move native function `secp256r1_ecrecover(signature:
60/// &vector<u8>, msg: &vector<u8>, hash: u8): vector<u8>` This function has two
61/// cost modes depending on the hash being set to `KECCAK256` or `SHA256`. The
62/// core formula is same but constants differ. If hash = 0, we use the
63/// `keccak256` cost constants, otherwise we use the `sha256` cost constants.
64///   gas cost: ecdsa_r1_ecrecover_cost_base                    | covers various
65/// fixed costs in the oper
66///              + ecdsa_r1_ecrecover_msg_cost_per_byte    * size_of(msg) |
67///                covers cost of operating on each byte of `msg`
68///              + ecdsa_r1_ecrecover_msg_cost_per_block   * num_blocks(msg) |
69///                covers cost of operating on each block in `msg`
70/// Note: each block is of size `KECCAK256_BLOCK_SIZE` bytes for `keccak256` and
71/// `SHA256_BLOCK_SIZE` for `sha256`, and we round up.       `signature` is
72/// fixed size, so the cost is included in the base cost. **********************
73/// *************************************************************************
74pub fn ecrecover(
75    context: &mut NativeContext,
76    ty_args: Vec<Type>,
77    mut args: VecDeque<Value>,
78) -> PartialVMResult<NativeResult> {
79    debug_assert!(ty_args.is_empty());
80    debug_assert!(args.len() == 3);
81
82    let hash = pop_arg!(args, u8);
83
84    // Load the cost parameters from the protocol config
85    let (ecdsa_r1_ecrecover_cost_params, crypto_invalid_arguments_cost) = {
86        let cost_table = &context.extensions().get::<NativesCostTable>();
87        (
88            cost_table.ecdsa_r1_ecrecover_cost_params.clone(),
89            cost_table.crypto_invalid_arguments_cost,
90        )
91    };
92
93    let (base_cost, cost_per_byte, cost_per_block, block_size) = match hash {
94        KECCAK256 => (
95            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_keccak256_cost_base,
96            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte,
97            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_keccak256_msg_cost_per_block,
98            KECCAK256_BLOCK_SIZE,
99        ),
100        SHA256 => (
101            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_sha256_cost_base,
102            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_sha256_msg_cost_per_byte,
103            ecdsa_r1_ecrecover_cost_params.ecdsa_r1_ecrecover_sha256_msg_cost_per_block,
104            SHA256_BLOCK_SIZE,
105        ),
106        _ => {
107            // Charge for failure but dont fail if we run out of gas otherwise the actual
108            // error is masked by OUT_OF_GAS error
109            context.charge_gas(crypto_invalid_arguments_cost);
110
111            return Ok(NativeResult::err(
112                context.gas_used(),
113                FAIL_TO_RECOVER_PUBKEY,
114            ));
115        }
116    };
117
118    // Charge the base cost for this oper
119    native_charge_gas_early_exit!(context, base_cost);
120
121    let msg = pop_arg!(args, VectorRef);
122    let signature = pop_arg!(args, VectorRef);
123
124    let msg_ref = msg.as_bytes_ref();
125    let signature_ref = signature.as_bytes_ref();
126
127    // Charge the arg size dependent costs
128    native_charge_gas_early_exit!(
129        context,
130        cost_per_byte * (msg_ref.len() as u64).into()
131            + cost_per_block * (msg_ref.len().div_ceil(block_size) as u64).into()
132    );
133
134    let cost = context.gas_used();
135
136    let Ok(sig) = <Secp256r1RecoverableSignature as ToFromBytes>::from_bytes(&signature_ref) else {
137        return Ok(NativeResult::err(cost, INVALID_SIGNATURE));
138    };
139
140    let pk = match hash {
141        KECCAK256 => sig.recover_with_hash::<Keccak256>(&msg_ref),
142        SHA256 => sig.recover_with_hash::<Sha256>(&msg_ref),
143        _ => Err(FastCryptoError::InvalidInput),
144    };
145
146    match pk {
147        Ok(pk) => Ok(NativeResult::ok(
148            cost,
149            smallvec![Value::vector_u8(pk.as_bytes().to_vec())],
150        )),
151        Err(_) => Ok(NativeResult::err(cost, FAIL_TO_RECOVER_PUBKEY)),
152    }
153}
154
155#[derive(Clone)]
156pub struct EcdsaR1Secp256R1VerifyCostParams {
157    /// Base cost for invoking the `secp256r1_verify` function with `hash=0`
158    /// implying KECCAK256
159    pub ecdsa_r1_secp256r1_verify_keccak256_cost_base: InternalGas,
160    ///  Cost per byte of `msg` with `hash=0`implying KECCAK256
161    pub ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: InternalGas,
162    ///  Cost per block of `msg` with `hash=0`implying KECCAK256, with block
163    /// size = 136
164    pub ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: InternalGas,
165
166    /// Base cost for invoking the `secp256r1_verify` function with `hash=1`
167    /// implying SHA256
168    pub ecdsa_r1_secp256r1_verify_sha256_cost_base: InternalGas,
169    ///  Cost per byte of `msg` with `hash=1`implying SHA256
170    pub ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: InternalGas,
171    ///  Cost per block of `msg` with `hash=1`implying SHA256, with block size =
172    /// 64
173    pub ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: InternalGas,
174}
175/// ****************************************************************************
176/// ********************* native fun secp256r1_verify
177/// Implementation of the Move native function `secp256r1_verify(signature:
178/// &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>, hash: u8): bool`
179/// This function has two cost modes depending on the hash being set to
180/// `KECCAK256` or `SHA256`. The core formula is same but constants differ.
181/// If hash = 0, we use the `keccak256` cost constants, otherwise we use the
182/// `sha256` cost constants.   gas cost: ecdsa_r1_secp256r1_verify_cost_base
183/// | covers various fixed costs in the oper
184///              + ecdsa_r1_secp256r1_verify_msg_cost_per_byte    * size_of(msg)
185///                | covers cost of operating on each byte of `msg`
186///              + ecdsa_r1_secp256r1_verify_msg_cost_per_block   *
187///                num_blocks(msg)     | covers cost of operating on each block
188///                in `msg`
189/// Note: each block is of size `KECCAK256_BLOCK_SIZE` bytes for `keccak256` and
190/// `SHA256_BLOCK_SIZE` for `sha256`, and we round up.       `signature` and
191/// `public_key` are fixed size, so their costs are included in the base cost.
192/// ****************************************************************************
193/// *******************
194pub fn secp256r1_verify(
195    context: &mut NativeContext,
196    ty_args: Vec<Type>,
197    mut args: VecDeque<Value>,
198) -> PartialVMResult<NativeResult> {
199    debug_assert!(ty_args.is_empty());
200    debug_assert!(args.len() == 4);
201    // Load the cost parameters from the protocol config
202    let (ecdsa_r1_secp256_r1_verify_cost_params, crypto_invalid_arguments_cost) = {
203        let cost_table = &context.extensions().get::<NativesCostTable>();
204        (
205            cost_table.ecdsa_r1_secp256_r1_verify_cost_params.clone(),
206            cost_table.crypto_invalid_arguments_cost,
207        )
208    };
209    let hash = pop_arg!(args, u8);
210    let (base_cost, cost_per_byte, cost_per_block, block_size) = match hash {
211        KECCAK256 => (
212            ecdsa_r1_secp256_r1_verify_cost_params.ecdsa_r1_secp256r1_verify_keccak256_cost_base,
213            ecdsa_r1_secp256_r1_verify_cost_params
214                .ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte,
215            ecdsa_r1_secp256_r1_verify_cost_params
216                .ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block,
217            KECCAK256_BLOCK_SIZE,
218        ),
219        SHA256 => (
220            ecdsa_r1_secp256_r1_verify_cost_params.ecdsa_r1_secp256r1_verify_sha256_cost_base,
221            ecdsa_r1_secp256_r1_verify_cost_params
222                .ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte,
223            ecdsa_r1_secp256_r1_verify_cost_params
224                .ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block,
225            SHA256_BLOCK_SIZE,
226        ),
227        _ => {
228            // Charge for failure but dont fail if we run out of gas otherwise the actual
229            // error is masked by OUT_OF_GAS error
230            context.charge_gas(crypto_invalid_arguments_cost);
231            return Ok(NativeResult::ok(
232                context.gas_used(),
233                smallvec![Value::bool(false)],
234            ));
235        }
236    };
237
238    // Charge the base cost for this oper
239    native_charge_gas_early_exit!(context, base_cost);
240
241    let msg = pop_arg!(args, VectorRef);
242    let public_key_bytes = pop_arg!(args, VectorRef);
243    let signature_bytes = pop_arg!(args, VectorRef);
244
245    let msg_ref = msg.as_bytes_ref();
246    let public_key_bytes_ref = public_key_bytes.as_bytes_ref();
247    let signature_bytes_ref = signature_bytes.as_bytes_ref();
248
249    // Charge the arg size dependent costs
250    native_charge_gas_early_exit!(
251        context,
252        cost_per_byte * (msg_ref.len() as u64).into()
253            + cost_per_block * (msg_ref.len().div_ceil(block_size) as u64).into()
254    );
255
256    let cost = context.gas_used();
257
258    let Ok(sig) = <Secp256r1Signature as ToFromBytes>::from_bytes(&signature_bytes_ref) else {
259        return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)]));
260    };
261
262    let Ok(pk) = <Secp256r1PublicKey as ToFromBytes>::from_bytes(&public_key_bytes_ref) else {
263        return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)]));
264    };
265
266    let result = match hash {
267        KECCAK256 => pk.verify_with_hash::<Keccak256>(&msg_ref, &sig).is_ok(),
268        SHA256 => pk.verify_with_hash::<Sha256>(&msg_ref, &sig).is_ok(),
269        _ => false,
270    };
271
272    Ok(NativeResult::ok(cost, smallvec![Value::bool(result)]))
273}