From c6279e67b1c70a89683fd50ed628b887dcb7096a Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Thu, 28 May 2026 10:13:00 +0900 Subject: [PATCH 1/2] Add gemstone auth hash signer --- gemstone/src/auth.rs | 60 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/gemstone/src/auth.rs b/gemstone/src/auth.rs index 3693f048c..b2d398f6d 100644 --- a/gemstone/src/auth.rs +++ b/gemstone/src/auth.rs @@ -1,5 +1,8 @@ +use crate::GemstoneError; use gem_auth::create_auth_hash; -use primitives::{AuthMessage, AuthNonce, Chain}; +use primitives::{AuthMessage, AuthNonce, Chain, hex::encode_with_0x}; +use signer::Signer; +use zeroize::Zeroizing; pub type GemAuthNonce = AuthNonce; @@ -28,3 +31,58 @@ pub fn create_auth_message(chain: Chain, address: &str, auth_nonce: GemAuthNonce hash: data.hash.to_vec(), } } + +#[uniffi::export] +pub fn sign_auth_message_hash(hash: Vec, private_key: Vec) -> Result { + if hash.len() != 32 { + return Err(GemstoneError::from("Invalid auth message hash")); + } + let private_key = Zeroizing::new(private_key); + let signature = Signer::sign_ethereum_digest(&hash, &private_key)?; + Ok(encode_with_0x(&signature)) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Address, keccak256}; + use gem_auth::verify_auth_signature; + use signer::secp256k1_uncompressed_public_key; + + const TEST_PRIVATE_KEY: [u8; 32] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1e, 0x1f, 0x20, + ]; + + #[test] + fn test_sign_auth_message_hash() { + let address = address_from_private_key(&TEST_PRIVATE_KEY); + let auth_nonce = AuthNonce { + nonce: "test-nonce-123".to_string(), + timestamp: 1734100000, + }; + let auth_message = AuthMessage { + chain: Chain::Ethereum, + address: address.clone(), + auth_nonce: auth_nonce.clone(), + }; + let message = create_auth_message(Chain::Ethereum, &address, auth_nonce); + + let signature = sign_auth_message_hash(message.hash, TEST_PRIVATE_KEY.to_vec()).unwrap(); + + assert!(verify_auth_signature(&auth_message, &signature)); + } + + #[test] + fn test_sign_auth_message_hash_rejects_invalid_hash_length() { + let result = sign_auth_message_hash(vec![0; 31], TEST_PRIVATE_KEY.to_vec()); + + assert!(result.is_err()); + } + + fn address_from_private_key(private_key: &[u8]) -> String { + let public_key = secp256k1_uncompressed_public_key(private_key).unwrap(); + let hash = keccak256(&public_key[1..]); + Address::from_slice(&hash[12..]).to_checksum(None) + } +} From 9661a8fa85092e0217337b68e1f42acf7073e5c3 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Thu, 28 May 2026 18:15:40 +0900 Subject: [PATCH 2/2] remove chain from create_auth_message --- crates/gem_auth/Cargo.toml | 1 + crates/gem_auth/src/signature.rs | 7 +------ gemstone/src/auth.rs | 29 +++++++++++++---------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/crates/gem_auth/Cargo.toml b/crates/gem_auth/Cargo.toml index e01a408da..1718ddfdb 100644 --- a/crates/gem_auth/Cargo.toml +++ b/crates/gem_auth/Cargo.toml @@ -24,3 +24,4 @@ jsonwebtoken = { workspace = true, optional = true } [dev-dependencies] alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } +primitives = { path = "../primitives", features = ["testkit"] } diff --git a/crates/gem_auth/src/signature.rs b/crates/gem_auth/src/signature.rs index 941d017f2..10e4022de 100644 --- a/crates/gem_auth/src/signature.rs +++ b/crates/gem_auth/src/signature.rs @@ -50,12 +50,7 @@ mod tests { use super::*; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; - use primitives::{AuthNonce, Chain}; - - const TEST_PRIVATE_KEY: [u8; 32] = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x1d, 0x1e, 0x1f, 0x20, - ]; + use primitives::{AuthNonce, Chain, testkit::signer_mock::TEST_PRIVATE_KEY}; fn sign_auth_message(auth_message: &AuthMessage, signer: &PrivateKeySigner) -> String { let message = serde_json::to_string(auth_message).unwrap(); diff --git a/gemstone/src/auth.rs b/gemstone/src/auth.rs index b2d398f6d..6f6937609 100644 --- a/gemstone/src/auth.rs +++ b/gemstone/src/auth.rs @@ -4,6 +4,8 @@ use primitives::{AuthMessage, AuthNonce, Chain, hex::encode_with_0x}; use signer::Signer; use zeroize::Zeroizing; +const AUTH_SIGNING_BYTES_LENGTH: usize = 32; + pub type GemAuthNonce = AuthNonce; #[uniffi::remote(Record)] @@ -19,9 +21,9 @@ pub struct GemAuthMessage { } #[uniffi::export] -pub fn create_auth_message(chain: Chain, address: &str, auth_nonce: GemAuthNonce) -> GemAuthMessage { +pub fn create_auth_message(address: &str, auth_nonce: GemAuthNonce) -> GemAuthMessage { let auth_message = AuthMessage { - chain, + chain: Chain::Ethereum, address: address.to_string(), auth_nonce, }; @@ -34,11 +36,11 @@ pub fn create_auth_message(chain: Chain, address: &str, auth_nonce: GemAuthNonce #[uniffi::export] pub fn sign_auth_message_hash(hash: Vec, private_key: Vec) -> Result { - if hash.len() != 32 { - return Err(GemstoneError::from("Invalid auth message hash")); - } let private_key = Zeroizing::new(private_key); - let signature = Signer::sign_ethereum_digest(&hash, &private_key)?; + if hash.len() != AUTH_SIGNING_BYTES_LENGTH || private_key.len() != AUTH_SIGNING_BYTES_LENGTH { + return Err(GemstoneError::from("Invalid auth message signing input")); + } + let signature = Signer::sign_ethereum_digest(&hash, private_key.as_slice())?; Ok(encode_with_0x(&signature)) } @@ -47,13 +49,9 @@ mod tests { use super::*; use alloy_primitives::{Address, keccak256}; use gem_auth::verify_auth_signature; + use primitives::testkit::signer_mock::TEST_PRIVATE_KEY; use signer::secp256k1_uncompressed_public_key; - const TEST_PRIVATE_KEY: [u8; 32] = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x1d, 0x1e, 0x1f, 0x20, - ]; - #[test] fn test_sign_auth_message_hash() { let address = address_from_private_key(&TEST_PRIVATE_KEY); @@ -66,7 +64,7 @@ mod tests { address: address.clone(), auth_nonce: auth_nonce.clone(), }; - let message = create_auth_message(Chain::Ethereum, &address, auth_nonce); + let message = create_auth_message(&address, auth_nonce); let signature = sign_auth_message_hash(message.hash, TEST_PRIVATE_KEY.to_vec()).unwrap(); @@ -74,10 +72,9 @@ mod tests { } #[test] - fn test_sign_auth_message_hash_rejects_invalid_hash_length() { - let result = sign_auth_message_hash(vec![0; 31], TEST_PRIVATE_KEY.to_vec()); - - assert!(result.is_err()); + fn test_sign_auth_message_hash_rejects_invalid_input_length() { + assert!(sign_auth_message_hash(vec![0; 31], TEST_PRIVATE_KEY.to_vec()).is_err()); + assert!(sign_auth_message_hash(vec![0; 32], vec![0; 31]).is_err()); } fn address_from_private_key(private_key: &[u8]) -> String {