ECDSA Verification

This is the Cairo adaptation of the Solidity by Example - Verifying Signature. Messages can be signed off chain and then verified on chain using a smart contract.

use core::starknet::eth_address::EthAddress;
use starknet::secp256_trait::{Signature};

// How to Sign and Verify
// # Signing
// 1. Create message to sign
// 2. Hash the message
// 3. Sign the hash (off chain, keep your private key secret)
#[starknet::interface]
trait IVerifySignature<TContractState> {
    fn get_signature(self: @TContractState, r: u256, s: u256, v: u32,) -> Signature;
    fn verify_eth_signature(
        self: @TContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32,
    );
    fn recover_public_key(
        self: @TContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32
    );
}

#[starknet::contract]
mod verifySignature {
    use super::IVerifySignature;
    use core::starknet::eth_address::EthAddress;
    use starknet::get_caller_address;
    use starknet::secp256_trait;
    use starknet::secp256k1::{Secp256k1Point};
    use starknet::{SyscallResult, SyscallResultTrait};
    use starknet::secp256_trait::{
        Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key,
        is_signature_entry_valid
    };
    use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address};

    #[storage]
    struct Storage {
        msg_hash: u256,
        signature: Signature,
        eth_address: EthAddress,
    }

    #[abi(embed_v0)]
    impl VerifySignature of IVerifySignature<ContractState> {
        /// This function returns the signature struct for the given parameters.
        ///
        /// # Arguments
        ///
        /// * `r` - The R component of the signature.
        /// * `s` - The S component of the signature.
        /// * `v` - The V component of the signature.
        ///
        /// # Returns
        ///
        /// * `Signature` - The signature struct.
        fn get_signature(self: @ContractState, r: u256, s: u256, v: u32,) -> Signature {
            // Create a Signature object from the given v, r, and s values.
            let signature: Signature = signature_from_vrs(v, r, s);
            signature
        }

        /// Verifies an Ethereum signature.
        ///
        /// # Arguments
        ///
        /// * `eth_address` - The Ethereum address to verify the signature against.
        /// * `msg_hash` - The hash of the message that was signed.
        /// * `r` - The R component of the signature.
        /// * `s` - The S component of the signature.
        /// * `v` - The V component of the signature.
        fn verify_eth_signature(
            self: @ContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32
        ) {
            let signature = self.get_signature(r, s, v);
            verify_eth_signature(:msg_hash, :signature, :eth_address);
        }

        /// Recovers the public key from an Ethereum signature and verifies that it matches the given Ethereum address.
        ///
        /// # Arguments
        ///
        /// * `eth_address` - The Ethereum address to verify the signature against.
        /// * `msg_hash` - The hash of the message that was signed.
        /// * `r` - The R component of the signature.
        /// * `s` - The S component of the signature.
        /// * `v` - The V component of the signature.
        fn recover_public_key(
            self: @ContractState, eth_address: EthAddress, msg_hash: u256, r: u256, s: u256, v: u32
        ) {
            let signature = self.get_signature(r, s, v);
            let public_key_point = recover_public_key::<Secp256k1Point>(msg_hash, signature)
                .unwrap();
            let calculated_eth_address = public_key_point_to_eth_address(:public_key_point);
            assert(calculated_eth_address == eth_address, 'Invalid Address');
        }
    }
}

#[cfg(test)]
mod tests {
    use starknet::secp256_trait::{
        Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key,
        is_signature_entry_valid
    };
    use starknet::EthAddress;
    use starknet::secp256k1::{Secp256k1Point};
    use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address};

    fn get_message_and_signature() -> (u256, Signature, EthAddress) {
        let msg_hash = 0x546ec3fa4f7d3308931816fafd47fa297afe9ac9a09651f77acc13c05a84734f;
        let r = 0xc0f30bcef72974dedaf165cf7848a83b0b9eb6a65167a14643df96698d753efb;
        let s = 0x7f189e3cb5eb992d8cd26e287a13e900326b87f58da2b7fb48fbd3977e3cab1c;
        let v = 27;

        let eth_address = 0x5F04693482cfC121FF244cB3c3733aF712F9df02_u256.into();
        let signature: Signature = signature_from_vrs(v, r, s);

        (msg_hash, signature, eth_address)
    }

    #[test]
    fn test_verify_eth_signature() {
        let (msg_hash, signature, eth_address) = get_message_and_signature();
        verify_eth_signature(:msg_hash, :signature, :eth_address);
    }

    #[test]
    fn test_secp256k1_recover_public_key() {
        let (msg_hash, signature, eth_address) = get_message_and_signature();
        let public_key_point = recover_public_key::<Secp256k1Point>(msg_hash, signature).unwrap();
        let calculated_eth_address = public_key_point_to_eth_address(:public_key_point);
        assert_eq!(calculated_eth_address, eth_address);
    }
}
Last change: 2024-08-08, commit: 393af3d