From b5cc44a1ccd7bbfd27ec088b433c56f9322a3f54 Mon Sep 17 00:00:00 2001 From: Mason Fischer Date: Mon, 18 Sep 2023 19:36:47 -0400 Subject: [PATCH] Expose `secp256k1` and `keccak256` in the SDK (#1028) ### What Expose `secp256k1` and `keccak256` in the SDK. ### Why `secp256k1` is a widely used cryptographic signature algorithm and `keccak256` is a widely used hashing algorithm. Exposing these functions will allow smart contract developers to utilize these algorithms in their applications without having to implement them themselves. ### Follow on work * ~Pending [this discussion](https://github.com/stellar/rs-soroban-sdk/pull/1023#discussion_r1243937201) on best practices I will call `check_env` where applicable.~ ### Known limitations N/A ------ **Github Issue**: [surface new crypto functions in SDK](https://github.com/stellar/rs-soroban-sdk/issues/970) --------- Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- soroban-sdk/src/crypto.rs | 29 +++++++++++++++++++++++ soroban-sdk/src/tests.rs | 2 ++ soroban-sdk/src/tests/crypto_keccak256.rs | 13 ++++++++++ soroban-sdk/src/tests/crypto_secp256k1.rs | 26 ++++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 soroban-sdk/src/tests/crypto_keccak256.rs create mode 100644 soroban-sdk/src/tests/crypto_secp256k1.rs diff --git a/soroban-sdk/src/crypto.rs b/soroban-sdk/src/crypto.rs index 1df3039eb..215e067b3 100644 --- a/soroban-sdk/src/crypto.rs +++ b/soroban-sdk/src/crypto.rs @@ -22,6 +22,13 @@ impl Crypto { unsafe { BytesN::unchecked_new(env.clone(), bin) } } + /// Returns the Keccak-256 hash of the data. + pub fn keccak256(&self, data: &Bytes) -> BytesN<32> { + let env = self.env(); + let bin = internal::Env::compute_hash_keccak256(env, data.into()).unwrap_infallible(); + unsafe { BytesN::unchecked_new(env.clone(), bin) } + } + /// Verifies an ed25519 signature. /// /// The signature is verified as a valid signature of the message by the @@ -39,4 +46,26 @@ impl Crypto { signature.to_object(), ); } + + /// Recovers the ECDSA secp256k1 public key. + /// + /// The public key returned is the SEC-1-encoded ECDSA secp256k1 public key + /// that produced the 64-byte signature over a given 32-byte message digest, + /// for a given recovery_id byte. + pub fn secp256k1_recover( + &self, + message_digest: &BytesN<32>, + signature: &BytesN<64>, + recorvery_id: u32, + ) -> BytesN<65> { + let env = self.env(); + let bytes = internal::Env::recover_key_ecdsa_secp256k1( + env, + message_digest.to_object(), + signature.to_object(), + recorvery_id.into(), + ) + .unwrap_infallible(); + unsafe { BytesN::unchecked_new(env.clone(), bytes) } + } } diff --git a/soroban-sdk/src/tests.rs b/soroban-sdk/src/tests.rs index 2cd816dc4..99d10500b 100644 --- a/soroban-sdk/src/tests.rs +++ b/soroban-sdk/src/tests.rs @@ -17,6 +17,8 @@ mod contract_udt_struct_tuple; mod contractimport; mod contractimport_with_error; mod crypto_ed25519; +mod crypto_keccak256; +mod crypto_secp256k1; mod crypto_sha256; mod env; mod prng; diff --git a/soroban-sdk/src/tests/crypto_keccak256.rs b/soroban-sdk/src/tests/crypto_keccak256.rs new file mode 100644 index 000000000..932c9ae4a --- /dev/null +++ b/soroban-sdk/src/tests/crypto_keccak256.rs @@ -0,0 +1,13 @@ +use crate::{bytesn, Env, IntoVal}; + +#[test] +fn test_keccak256() { + let env = Env::default(); + + let bytes = b"test vector for soroban".into_val(&env); + let expect = bytesn!( + &env, + 0x352fe2eaddf44eb02eb3eab1f8d6ff4ba426df4f1734b1e3f210d621ee8853d9 + ); + assert_eq!(env.crypto().keccak256(&bytes), expect); +} diff --git a/soroban-sdk/src/tests/crypto_secp256k1.rs b/soroban-sdk/src/tests/crypto_secp256k1.rs new file mode 100644 index 000000000..250d93327 --- /dev/null +++ b/soroban-sdk/src/tests/crypto_secp256k1.rs @@ -0,0 +1,26 @@ +use crate::{bytesn, Env}; + +#[test] +fn test_recover_key_ecdsa_secp256k1() { + let env = Env::default(); + + // From: https://github.com/ethereum/go-ethereum/blob/90d5bd85bcf2919ac2735a47fde675213348a0a6/crypto/secp256k1/secp256_test.go#L204-L217 + let message_digest = bytesn!( + &env, + 0xce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008 + ); + let signature = bytesn!( + &env, + 0x90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc93 + ); + let recovery_id = 1; + let expected_public_key = bytesn!( + &env, + 0x04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652 + ); + assert_eq!( + env.crypto() + .secp256k1_recover(&message_digest, &signature, recovery_id), + expected_public_key + ); +}