Skip to content

Commit

Permalink
crypto: ser/de update and use nonrecoverable sig for secp256k1/r1 (#7…
Browse files Browse the repository at this point in the history
…423)

## What Changed

- For CLI and SDK, a ECDSA k1 and r1 signature is produced using the
nonrecoverable form. This means the signature is 64 bytes instead of 65.
- The signature verification in sui also uses the nonrecoverable option.
A valid signature should have 64 bytes.
- For wallet, since only Ed25519 is supported, the secp256k1 change
should not affect.
- Also exposes secp256k1_verify and secp256k1_verify_recoverable API in
move.
- Ser/de of public keys and signatures now uses the most compact
serialization with ToFromBytes.

## What Do You Need To Do
- If you are using SDK to produce a Secp256k1 signature, no change is
needed as long as you are using the latest version.
- If you are using something else to produce a signature, your old
signature will not be considered valid. You should just need to remove
the last byte (65->64 bytes) to make it a valid signature again.

Next:
- r1 verify and verify_recoverable added in
MystenLabs/sui#7773
  • Loading branch information
joyqvq authored Jan 30, 2023
1 parent b1f09f5 commit 9aedc85
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 22 deletions.
39 changes: 36 additions & 3 deletions crates/sui-framework/docs/ecdsa_k1.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Function `decompress_pubkey`](#0x2_ecdsa_k1_decompress_pubkey)
- [Function `keccak256`](#0x2_ecdsa_k1_keccak256)
- [Function `secp256k1_verify`](#0x2_ecdsa_k1_secp256k1_verify)
- [Function `secp256k1_verify_recoverable`](#0x2_ecdsa_k1_secp256k1_verify_recoverable)


<pre><code></code></pre>
Expand Down Expand Up @@ -126,9 +127,10 @@ Hash the input bytes using keccak256 and returns 32 bytes.

## Function `secp256k1_verify`

@param signature: A 65-bytes signature in form (r, s, v) that is signed using
Secp256k1. Reference implementation on signature generation using RFC6979:
https://github.com/MystenLabs/narwhal/blob/5d6f6df8ccee94446ff88786c0dbbc98be7cfc09/crypto/src/secp256k1.rs
@param signature: A 64-bytes signature in form (r, s) that is signed using
Secp256k1. This is an non-recoverable signature without recovery id.
Reference implementation on signature generation using RFC6979:
https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193

@param public_key: The public key to verify the signature against
@param hashed_msg: The hashed 32-bytes message, same as what the signature is signed against.
Expand All @@ -150,4 +152,35 @@ If the signature is valid to the pubkey and hashed message, return true. Else fa



</details>

<a name="0x2_ecdsa_k1_secp256k1_verify_recoverable"></a>

## Function `secp256k1_verify_recoverable`

@param signature: A 65-bytes signature in form (r, s, v) that is signed using
Secp256k1. This is an recoverable signature with recovery id denoted as v.
Reference implementation on signature generation using RFC6979:
https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193

@param public_key: The public key to verify the signature against
@param hashed_msg: The hashed 32-bytes message, same as what the signature is signed against.

If the signature is valid to the pubkey and hashed message, return true. Else false.


<pre><code><b>public</b> <b>fun</b> <a href="ecdsa_k1.md#0x2_ecdsa_k1_secp256k1_verify_recoverable">secp256k1_verify_recoverable</a>(signature: &<a href="">vector</a>&lt;u8&gt;, public_key: &<a href="">vector</a>&lt;u8&gt;, hashed_msg: &<a href="">vector</a>&lt;u8&gt;): bool
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>native</b> <b>fun</b> <a href="ecdsa_k1.md#0x2_ecdsa_k1_secp256k1_verify_recoverable">secp256k1_verify_recoverable</a>(signature: &<a href="">vector</a>&lt;u8&gt;, public_key: &<a href="">vector</a>&lt;u8&gt;, hashed_msg: &<a href="">vector</a>&lt;u8&gt;): bool;
</code></pre>



</details>
18 changes: 15 additions & 3 deletions crates/sui-framework/sources/crypto/ecdsa_k1.move
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,25 @@ module sui::ecdsa_k1 {
/// Hash the input bytes using keccak256 and returns 32 bytes.
public native fun keccak256(data: &vector<u8>): vector<u8>;

/// @param signature: A 65-bytes signature in form (r, s, v) that is signed using
/// Secp256k1. Reference implementation on signature generation using RFC6979:
/// https://github.com/MystenLabs/narwhal/blob/5d6f6df8ccee94446ff88786c0dbbc98be7cfc09/crypto/src/secp256k1.rs
/// @param signature: A 64-bytes signature in form (r, s) that is signed using
/// Secp256k1. This is an non-recoverable signature without recovery id.
/// Reference implementation on signature generation using RFC6979:
/// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193
///
/// @param public_key: The public key to verify the signature against
/// @param hashed_msg: The hashed 32-bytes message, same as what the signature is signed against.
///
/// If the signature is valid to the pubkey and hashed message, return true. Else false.
public native fun secp256k1_verify(signature: &vector<u8>, public_key: &vector<u8>, hashed_msg: &vector<u8>): bool;

/// @param signature: A 65-bytes signature in form (r, s, v) that is signed using
/// Secp256k1. This is an recoverable signature with recovery id denoted as v.
/// Reference implementation on signature generation using RFC6979:
/// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193
///
/// @param public_key: The public key to verify the signature against
/// @param hashed_msg: The hashed 32-bytes message, same as what the signature is signed against.
///
/// If the signature is valid to the pubkey and hashed message, return true. Else false.
public native fun secp256k1_verify_recoverable(signature: &vector<u8>, public_key: &vector<u8>, hashed_msg: &vector<u8>): bool;
}
57 changes: 50 additions & 7 deletions crates/sui-framework/src/natives/crypto/ecdsa_k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
use crate::legacy_empty_cost;
use fastcrypto::{
hash::{HashFunction, Keccak256},
secp256k1::{Secp256k1PublicKey, Secp256k1Signature},
secp256k1::{
recoverable::{Secp256k1RecoverablePublicKey, Secp256k1RecoverableSignature},
Secp256k1PublicKey, Secp256k1Signature,
},
traits::ToFromBytes,
};
use move_binary_format::errors::PartialVMResult;
Expand Down Expand Up @@ -50,8 +53,11 @@ pub fn ecrecover(
}
}

fn recover_pubkey(signature: &[u8], hashed_msg: &[u8]) -> Result<Secp256k1PublicKey, SuiError> {
match <Secp256k1Signature as ToFromBytes>::from_bytes(signature) {
fn recover_pubkey(
signature: &[u8],
hashed_msg: &[u8],
) -> Result<Secp256k1RecoverablePublicKey, SuiError> {
match <Secp256k1RecoverableSignature as ToFromBytes>::from_bytes(signature) {
Ok(signature) => match signature.recover(hashed_msg) {
Ok(pubkey) => Ok(pubkey),
Err(e) => Err(SuiError::KeyConversionError(e.to_string())),
Expand Down Expand Up @@ -136,8 +142,45 @@ pub fn secp256k1_verify(
Err(_) => return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])),
};

match public_key.verify_hashed(&hashed_msg_ref, &signature) {
Ok(_) => Ok(NativeResult::ok(cost, smallvec![Value::bool(true)])),
Err(_) => Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])),
}
let result = public_key
.verify_hashed(&hashed_msg_ref, &signature)
.is_ok();
Ok(NativeResult::ok(cost, smallvec![Value::bool(result)]))
}

pub fn secp256k1_verify_recoverable(
_context: &mut NativeContext,
ty_args: Vec<Type>,
mut args: VecDeque<Value>,
) -> PartialVMResult<NativeResult> {
debug_assert!(ty_args.is_empty());
debug_assert!(args.len() == 3);

let hashed_msg = pop_arg!(args, VectorRef);
let public_key_bytes = pop_arg!(args, VectorRef);
let signature_bytes = pop_arg!(args, VectorRef);

let hashed_msg_ref = hashed_msg.as_bytes_ref();
let public_key_bytes_ref = public_key_bytes.as_bytes_ref();
let signature_bytes_ref = signature_bytes.as_bytes_ref();

// TODO: implement native gas cost estimation https://github.com/MystenLabs/sui/issues/4086
let cost = legacy_empty_cost();

let signature =
match <Secp256k1RecoverableSignature as ToFromBytes>::from_bytes(&signature_bytes_ref) {
Ok(signature) => signature,
Err(_) => return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])),
};

let public_key =
match <Secp256k1RecoverablePublicKey as ToFromBytes>::from_bytes(&public_key_bytes_ref) {
Ok(public_key) => public_key,
Err(_) => return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])),
};

let result = public_key
.verify_hashed(&hashed_msg_ref, &signature)
.is_ok();
Ok(NativeResult::ok(cost, smallvec![Value::bool(result)]))
}
5 changes: 5 additions & 0 deletions crates/sui-framework/src/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ pub fn all_natives(
"secp256k1_verify",
make_native!(ecdsa_k1::secp256k1_verify),
),
(
"ecdsa_k1",
"secp256k1_verify_recoverable",
make_native!(ecdsa_k1::secp256k1_verify_recoverable),
),
(
"ed25519",
"ed25519_verify",
Expand Down
32 changes: 23 additions & 9 deletions crates/sui-framework/tests/crypto/ecdsa_k1_tests.move
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,46 @@ module sui::ecdsa_tests {
}

#[test]
fun test_secp256k1_valid_sig() {
fun test_secp256k1_verify_fails_with_recoverable_sig() {
let msg = x"57caa176af1ac0433c5df30e8dabcd2ec1af1e92a26eced5f719b88458777cd6";
let pk = x"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
let sig = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd601";
let verify = ecdsa_k1::secp256k1_verify(&sig, &pk, &msg);
assert!(verify == false, 0);

let sig_1 = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd600";
let verify_1 = ecdsa_k1::secp256k1_verify(&sig_1, &pk, &msg);
assert!(verify_1 == false, 0);
}

#[test]
fun test_secp256k1_verify_success_with_nonrecoverable_sig() {
let msg = x"57caa176af1ac0433c5df30e8dabcd2ec1af1e92a26eced5f719b88458777cd6";
let pk = x"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
let sig = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd6";
let verify = ecdsa_k1::secp256k1_verify(&sig, &pk, &msg);
assert!(verify == true, 0)
}

#[test]
fun test_secp256k1_invalid_sig() {
fun test_secp256k1_verify_recoverable_sig_success() {
let msg = x"57caa176af1ac0433c5df30e8dabcd2ec1af1e92a26eced5f719b88458777cd6";
let pk = x"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
// sig in the form of (r, s, 0) instead of (r, s, 1)
let sig = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd600";

let verify = ecdsa_k1::secp256k1_verify(&sig, &pk, &msg);
assert!(verify == false, 0)
let sig = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd601";
let verify = ecdsa_k1::secp256k1_verify_recoverable(&sig, &pk, &msg);
assert!(verify == true, 0);

let sig_1 = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd600";
let verify_1 = ecdsa_k1::secp256k1_verify_recoverable(&sig_1, &pk, &msg);
assert!(verify_1 == false, 0);
}

#[test]
fun test_secp256k1_invalid_sig_length() {
fun test_secp256k1_verify_recoverable_sig_fails() {
let msg = x"57caa176af1ac0433c5df30e8dabcd2ec1af1e92a26eced5f719b88458777cd6";
let pk = x"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
let sig = x"9c7a72ff1e7db1646b9f9443cb1a3563aa3a6344e4e513efb96258c7676ac4895953629d409a832472b710a028285dfec4733a2c1bb0a2749e465a18292b8bd6";
let verify = ecdsa_k1::secp256k1_verify(&sig, &pk, &msg);
let verify = ecdsa_k1::secp256k1_verify_recoverable(&sig, &pk, &msg);
assert!(verify == false, 0)
}

Expand Down

0 comments on commit 9aedc85

Please sign in to comment.