iota_move_natives_latest/crypto/
ecdsa_k1.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    secp256k1::{
11        Secp256k1KeyPair, Secp256k1PrivateKey, Secp256k1PublicKey, Secp256k1Signature,
12        recoverable::Secp256k1RecoverableSignature,
13    },
14    traits::{RecoverableSignature, RecoverableSigner, ToFromBytes},
15};
16use iota_types::crypto::KeypairTraits;
17use move_binary_format::errors::PartialVMResult;
18use move_core_types::gas_algebra::InternalGas;
19use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
20use move_vm_types::{
21    loaded_data::runtime_types::Type,
22    natives::function::NativeResult,
23    pop_arg,
24    values::{self, Value, VectorRef},
25};
26use rand::{SeedableRng, rngs::StdRng};
27use smallvec::smallvec;
28
29use crate::NativesCostTable;
30
31pub const FAIL_TO_RECOVER_PUBKEY: u64 = 0;
32pub const INVALID_SIGNATURE: u64 = 1;
33pub const INVALID_PUBKEY: u64 = 2;
34pub const INVALID_PRIVKEY: u64 = 3;
35pub const INVALID_HASH_FUNCTION: u64 = 4;
36pub const INVALID_SEED: u64 = 5;
37
38pub const KECCAK256: u8 = 0;
39pub const SHA256: u8 = 1;
40
41const KECCAK256_BLOCK_SIZE: usize = 136;
42const SHA256_BLOCK_SIZE: usize = 64;
43const SEED_LENGTH: usize = 32;
44
45#[derive(Clone)]
46pub struct EcdsaK1EcrecoverCostParams {
47    /// Base cost for invoking the `ecrecover` function with `hash=0` implying
48    /// KECCAK256
49    pub ecdsa_k1_ecrecover_keccak256_cost_base: InternalGas,
50    ///  Cost per byte of `msg` with `hash=0`implying KECCAK256
51    pub ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: InternalGas,
52    ///  Cost per block of `msg` with `hash=0`implying KECCAK256, with block
53    /// size = 136
54    pub ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: InternalGas,
55
56    /// Base cost for invoking the `ecrecover` function with `hash=1` implying
57    /// SHA256
58    pub ecdsa_k1_ecrecover_sha256_cost_base: InternalGas,
59    ///  Cost per byte of `msg` with `hash=1`implying SHA256
60    pub ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: InternalGas,
61    ///  Cost per block of `msg` with `hash=1`implying SHA256, with block size =
62    /// 64
63    pub ecdsa_k1_ecrecover_sha256_msg_cost_per_block: InternalGas,
64}
65/// ****************************************************************************
66/// ********************* native fun secp256k1_ecrecover
67/// Implementation of the Move native function `secp256k1_ecrecover(signature:
68/// &vector<u8>, msg: &vector<u8>, hash: u8): vector<u8>` This function has two
69/// cost modes depending on the hash being set to `KECCAK256` or `SHA256`. The
70/// core formula is same but constants differ. If hash = 0, we use the
71/// `keccak256` cost constants, otherwise we use the `sha256` cost constants.
72///   gas cost: ecdsa_k1_ecrecover_cost_base                    | covers various
73/// fixed costs in the oper
74///              + ecdsa_k1_ecrecover_msg_cost_per_byte    * size_of(msg) |
75///                covers cost of operating on each byte of `msg`
76///              + ecdsa_k1_ecrecover_msg_cost_per_block   * num_blocks(msg) |
77///                covers cost of operating on each block in `msg`
78/// Note: each block is of size `KECCAK256_BLOCK_SIZE` bytes for `keccak256` and
79/// `SHA256_BLOCK_SIZE` for `sha256`, and we round up.       `signature` is
80/// fixed size, so the cost is included in the base cost. **********************
81/// *************************************************************************
82pub fn ecrecover(
83    context: &mut NativeContext,
84    ty_args: Vec<Type>,
85    mut args: VecDeque<Value>,
86) -> PartialVMResult<NativeResult> {
87    debug_assert!(ty_args.is_empty());
88    debug_assert!(args.len() == 3);
89
90    let hash = pop_arg!(args, u8);
91
92    // Load the cost parameters from the protocol config
93    let (ecdsa_k1_ecrecover_cost_params, crypto_invalid_arguments_cost) = {
94        let cost_table = &context.extensions().get::<NativesCostTable>();
95        (
96            cost_table.ecdsa_k1_ecrecover_cost_params.clone(),
97            cost_table.crypto_invalid_arguments_cost,
98        )
99    };
100    let (base_cost, cost_per_byte, cost_per_block, block_size) = match hash {
101        KECCAK256 => (
102            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_keccak256_cost_base,
103            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte,
104            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_keccak256_msg_cost_per_block,
105            KECCAK256_BLOCK_SIZE,
106        ),
107        SHA256 => (
108            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_sha256_cost_base,
109            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_sha256_msg_cost_per_byte,
110            ecdsa_k1_ecrecover_cost_params.ecdsa_k1_ecrecover_sha256_msg_cost_per_block,
111            SHA256_BLOCK_SIZE,
112        ),
113        _ => {
114            // Charge for failure but dont fail if we run out of gas otherwise the actual
115            // error is masked by OUT_OF_GAS error
116            context.charge_gas(crypto_invalid_arguments_cost);
117            return Ok(NativeResult::err(
118                context.gas_used(),
119                FAIL_TO_RECOVER_PUBKEY,
120            ));
121        }
122    };
123
124    // Charge the base cost for this oper
125    native_charge_gas_early_exit!(context, base_cost);
126
127    let msg = pop_arg!(args, VectorRef);
128    let signature = pop_arg!(args, VectorRef);
129
130    let msg_ref = msg.as_bytes_ref();
131    let signature_ref = signature.as_bytes_ref();
132
133    // Charge the arg size dependent costs
134    native_charge_gas_early_exit!(
135        context,
136        cost_per_byte * (msg_ref.len() as u64).into()
137            + cost_per_block * (msg_ref.len().div_ceil(block_size) as u64).into()
138    );
139
140    let cost = context.gas_used();
141
142    let Ok(sig) = <Secp256k1RecoverableSignature as ToFromBytes>::from_bytes(&signature_ref) else {
143        return Ok(NativeResult::err(cost, INVALID_SIGNATURE));
144    };
145
146    let pk = match hash {
147        KECCAK256 => sig.recover_with_hash::<Keccak256>(&msg_ref),
148        SHA256 => sig.recover_with_hash::<Sha256>(&msg_ref),
149        _ => Err(FastCryptoError::InvalidInput), // We should never reach here
150    };
151
152    match pk {
153        Ok(pk) => Ok(NativeResult::ok(
154            cost,
155            smallvec![Value::vector_u8(pk.as_bytes().to_vec())],
156        )),
157        Err(_) => Ok(NativeResult::err(cost, FAIL_TO_RECOVER_PUBKEY)),
158    }
159}
160
161#[derive(Clone)]
162pub struct EcdsaK1DecompressPubkeyCostParams {
163    pub ecdsa_k1_decompress_pubkey_cost_base: InternalGas,
164}
165pub fn decompress_pubkey(
166    context: &mut NativeContext,
167    ty_args: Vec<Type>,
168    mut args: VecDeque<Value>,
169) -> PartialVMResult<NativeResult> {
170    debug_assert!(ty_args.is_empty());
171    debug_assert!(args.len() == 1);
172
173    // Load the cost parameters from the protocol config
174    let ecdsa_k1_decompress_pubkey_cost_params = &context
175        .extensions()
176        .get::<NativesCostTable>()
177        .ecdsa_k1_decompress_pubkey_cost_params
178        .clone();
179    // Charge the base cost for this oper
180    native_charge_gas_early_exit!(
181        context,
182        ecdsa_k1_decompress_pubkey_cost_params.ecdsa_k1_decompress_pubkey_cost_base
183    );
184
185    let pubkey = pop_arg!(args, VectorRef);
186    let pubkey_ref = pubkey.as_bytes_ref();
187
188    let cost = context.gas_used();
189
190    match Secp256k1PublicKey::from_bytes(&pubkey_ref) {
191        Ok(pubkey) => {
192            let uncompressed = &pubkey.pubkey.serialize_uncompressed();
193            Ok(NativeResult::ok(
194                cost,
195                smallvec![Value::vector_u8(uncompressed.to_vec())],
196            ))
197        }
198        Err(_) => Ok(NativeResult::err(cost, INVALID_PUBKEY)),
199    }
200}
201
202#[derive(Clone)]
203pub struct EcdsaK1Secp256k1VerifyCostParams {
204    /// Base cost for invoking the `secp256k1_verify` function with `hash=0`
205    /// implying KECCAK256
206    pub ecdsa_k1_secp256k1_verify_keccak256_cost_base: InternalGas,
207    ///  Cost per byte of `msg` with `hash=0`implying KECCAK256
208    pub ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: InternalGas,
209    ///  Cost per block of `msg` with `hash=0`implying KECCAK256, with block
210    /// size = 136
211    pub ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: InternalGas,
212
213    /// Base cost for invoking the `secp256k1_verify` function with `hash=1`
214    /// implying SHA256
215    pub ecdsa_k1_secp256k1_verify_sha256_cost_base: InternalGas,
216    ///  Cost per byte of `msg` with `hash=1`implying SHA256
217    pub ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: InternalGas,
218    ///  Cost per block of `msg` with `hash=1`implying SHA256, with block size =
219    /// 64
220    pub ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: InternalGas,
221}
222/// ****************************************************************************
223/// ********************* native fun secp256k1_verify
224/// Implementation of the Move native function `secp256k1_verify(signature:
225/// &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>, hash: u8): bool`
226/// This function has two cost modes depending on the hash being set
227/// to`KECCAK256` or `SHA256`. The core formula is same but constants differ. If
228/// hash = 0, we use the `keccak256` cost constants, otherwise we use the
229/// `sha256` cost constants.   gas cost: ecdsa_k1_secp256k1_verify_cost_base
230/// | covers various fixed costs in the oper
231///              + ecdsa_k1_secp256k1_verify_msg_cost_per_byte    * size_of(msg)
232///                | covers cost of operating on each byte of `msg`
233///              + ecdsa_k1_secp256k1_verify_msg_cost_per_block   *
234///                num_blocks(msg)     | covers cost of operating on each block
235///                in `msg`
236/// Note: each block is of size `KECCAK256_BLOCK_SIZE` bytes for `keccak256` and
237/// `SHA256_BLOCK_SIZE` for `sha256`, and we round up.       `signature` and
238/// `public_key` are fixed size, so their costs are included in the base cost.
239/// ****************************************************************************
240/// *******************
241pub fn secp256k1_verify(
242    context: &mut NativeContext,
243    ty_args: Vec<Type>,
244    mut args: VecDeque<Value>,
245) -> PartialVMResult<NativeResult> {
246    debug_assert!(ty_args.is_empty());
247    debug_assert!(args.len() == 4);
248
249    let hash = pop_arg!(args, u8);
250
251    // Load the cost parameters from the protocol config
252    let (ecdsa_k1_secp256k1_verify_cost_params, crypto_invalid_arguments_cost) = {
253        let cost_table = &context.extensions().get::<NativesCostTable>();
254        (
255            cost_table.ecdsa_k1_secp256k1_verify_cost_params.clone(),
256            cost_table.crypto_invalid_arguments_cost,
257        )
258    };
259
260    let (base_cost, cost_per_byte, cost_per_block, block_size) = match hash {
261        KECCAK256 => (
262            ecdsa_k1_secp256k1_verify_cost_params.ecdsa_k1_secp256k1_verify_keccak256_cost_base,
263            ecdsa_k1_secp256k1_verify_cost_params
264                .ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte,
265            ecdsa_k1_secp256k1_verify_cost_params
266                .ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block,
267            KECCAK256_BLOCK_SIZE,
268        ),
269        SHA256 => (
270            ecdsa_k1_secp256k1_verify_cost_params.ecdsa_k1_secp256k1_verify_sha256_cost_base,
271            ecdsa_k1_secp256k1_verify_cost_params
272                .ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte,
273            ecdsa_k1_secp256k1_verify_cost_params
274                .ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block,
275            SHA256_BLOCK_SIZE,
276        ),
277        _ => {
278            // Charge for failure but dont fail if we run out of gas otherwise the actual
279            // error is masked by OUT_OF_GAS error
280            context.charge_gas(crypto_invalid_arguments_cost);
281
282            return Ok(NativeResult::ok(
283                context.gas_used(),
284                smallvec![Value::bool(false)],
285            ));
286        }
287    };
288    // Charge the base cost for this oper
289    native_charge_gas_early_exit!(context, base_cost);
290
291    let msg = pop_arg!(args, VectorRef);
292    let public_key_bytes = pop_arg!(args, VectorRef);
293    let signature_bytes = pop_arg!(args, VectorRef);
294
295    let msg_ref = msg.as_bytes_ref();
296    let public_key_bytes_ref = public_key_bytes.as_bytes_ref();
297    let signature_bytes_ref = signature_bytes.as_bytes_ref();
298
299    // Charge the arg size dependent costs
300    native_charge_gas_early_exit!(
301        context,
302        cost_per_byte * (msg_ref.len() as u64).into()
303            + cost_per_block * (msg_ref.len().div_ceil(block_size) as u64).into()
304    );
305
306    let cost = context.gas_used();
307
308    let Ok(sig) = <Secp256k1Signature as ToFromBytes>::from_bytes(&signature_bytes_ref) else {
309        return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)]));
310    };
311
312    let Ok(pk) = <Secp256k1PublicKey as ToFromBytes>::from_bytes(&public_key_bytes_ref) else {
313        return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)]));
314    };
315
316    let result = match hash {
317        KECCAK256 => pk.verify_with_hash::<Keccak256>(&msg_ref, &sig).is_ok(),
318        SHA256 => pk.verify_with_hash::<Sha256>(&msg_ref, &sig).is_ok(),
319        _ => false,
320    };
321
322    Ok(NativeResult::ok(cost, smallvec![Value::bool(result)]))
323}
324
325/// ****************************************************************************
326/// ********************* native fun secp256k1_sign (TEST ONLY)
327/// Implementation of the Move native function `secp256k1_sign(private_key:
328/// &vector<u8>, msg: &vector<u8>, hash: u8): vector<u8>` This function has two
329/// cost modes depending on the hash being set to`KECCAK256` or `SHA256`. The
330/// core formula is same but constants differ. If hash = 0, we use the
331/// `keccak256` cost constants, otherwise we use the `sha256` cost constants.
332///   gas cost: 0 (because it is only for test purposes)
333/// ****************************************************************************
334/// *******************
335pub fn secp256k1_sign(
336    _context: &mut NativeContext,
337    ty_args: Vec<Type>,
338    mut args: VecDeque<Value>,
339) -> PartialVMResult<NativeResult> {
340    debug_assert!(ty_args.is_empty());
341    debug_assert!(args.len() == 4);
342
343    // The corresponding Move function, iota::ecdsa_k1::secp256k1_sign, is only used
344    // for testing, so we don't need to charge any gas.
345    let cost = 0.into();
346
347    let recoverable = pop_arg!(args, bool);
348    let hash = pop_arg!(args, u8);
349    let msg = pop_arg!(args, VectorRef);
350    let private_key_bytes = pop_arg!(args, VectorRef);
351
352    let msg_ref = msg.as_bytes_ref();
353    let private_key_bytes_ref = private_key_bytes.as_bytes_ref();
354
355    let sk = match <Secp256k1PrivateKey as ToFromBytes>::from_bytes(&private_key_bytes_ref) {
356        Ok(sk) => sk,
357        Err(_) => return Ok(NativeResult::err(cost, INVALID_PRIVKEY)),
358    };
359
360    let kp = Secp256k1KeyPair::from(sk);
361
362    let signature = match (hash, recoverable) {
363        (KECCAK256, true) => kp
364            .sign_recoverable_with_hash::<Keccak256>(&msg_ref)
365            .as_bytes()
366            .to_vec(),
367        (KECCAK256, false) => kp.sign_with_hash::<Keccak256>(&msg_ref).as_bytes().to_vec(),
368        (SHA256, true) => kp
369            .sign_recoverable_with_hash::<Sha256>(&msg_ref)
370            .as_bytes()
371            .to_vec(),
372        (SHA256, false) => kp.sign_with_hash::<Sha256>(&msg_ref).as_bytes().to_vec(),
373        _ => return Ok(NativeResult::err(cost, INVALID_HASH_FUNCTION)),
374    };
375
376    Ok(NativeResult::ok(
377        cost,
378        smallvec![Value::vector_u8(signature)],
379    ))
380}
381
382/// ****************************************************************************
383/// ********************* native fun secp256k1_keypair_from_seed (TEST ONLY)
384/// Implementation of the Move native function `secp256k1_sign(seed:
385/// &vector<u8>): KeyPair` Seed must be exactly 32 bytes long.
386///   gas cost: 0 (because it is only for test purposes)
387/// ****************************************************************************
388/// *******************
389pub fn secp256k1_keypair_from_seed(
390    _context: &mut NativeContext,
391    ty_args: Vec<Type>,
392    mut args: VecDeque<Value>,
393) -> PartialVMResult<NativeResult> {
394    debug_assert!(ty_args.is_empty());
395    debug_assert!(args.len() == 1);
396
397    // The corresponding Move function, iota::ecdsa_k1::secp256k1_keypair_from_seed,
398    // is only used for testing, so we don't need to charge any gas.
399    let cost = 0.into();
400
401    let seed = pop_arg!(args, VectorRef);
402    let seed_ref = seed.as_bytes_ref();
403
404    if seed_ref.len() != SEED_LENGTH {
405        return Ok(NativeResult::err(cost, INVALID_SEED));
406    }
407    let mut seed_array = [0u8; SEED_LENGTH];
408    seed_array.clone_from_slice(&seed_ref);
409
410    let kp = Secp256k1KeyPair::generate(&mut StdRng::from_seed(seed_array));
411
412    let pk_bytes = kp.public().as_bytes().to_vec();
413    let sk_bytes = kp.private().as_bytes().to_vec();
414
415    Ok(NativeResult::ok(
416        cost,
417        smallvec![Value::struct_(values::Struct::pack(vec![
418            Value::vector_u8(sk_bytes),
419            Value::vector_u8(pk_bytes),
420        ]))],
421    ))
422}