sui_move_natives_latest/crypto/
ecdsa_k1.rs

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