Skip to main content

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