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