Signature verification

Cairo allows one to verify ECDSA signatures over the STARK-friendly elliptic curve (for technical details see STARK Curve).

Note that in most cases the best way to handle authentication is by using account contracts, rather than verifying the signature directly in the contract. However, for the sake of completeness, in the following section we explain how to verify signatures in Cairo.

Consider the following implementation of increase_balance(), which can replace the corresponding function in the contract found under Adding User Authentication:

from starkware.cairo.common.cairo_builtins import (
    HashBuiltin,
    SignatureBuiltin,
)
from starkware.cairo.common.hash import hash2
from starkware.cairo.common.signature import (
    verify_ecdsa_signature,
)

// Increases the balance of the given user by the given amount.
@external
func increase_balance{
    syscall_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
    ecdsa_ptr: SignatureBuiltin*,
}(user: felt, amount: felt, sig: (felt, felt)) {
    // Compute the hash of the message.
    // The hash of (x, 0) is equivalent to the hash of (x).
    let (amount_hash) = hash2{hash_ptr=pedersen_ptr}(amount, 0);

    // Verify the user's signature.
    verify_ecdsa_signature(
        message=amount_hash,
        public_key=user,
        signature_r=sig[0],
        signature_s=sig[1],
    );

    let (res) = balance.read(user=user);
    balance.write(user, res + amount);
    return ();
}

Note that in this example user is represented by a public key (and not the address of an account contract, as was the case in the original example).

verify_ecdsa_signature() behaves like an assert – in case the signature is invalid, the function will revert the entire transaction.

Note that we don’t handle replay attacks here – once the user signs a transaction someone may call it multiple times. One way to prevent replay attacks is to add a nonce component to the signed message.

Compile and deploy

Save the new contract file as signature_verification.cairo. You can find the full Cairo file here.

Compile and deploy the file:

starknet-compile signature_verification.cairo \
    --output signature_compiled.json \
    --abi signature_abi.json

starknet deploy --contract signature_compiled.json --no_wallet

Interacting with the contract

First, we need to generate a pair of public and private keys. We will use a constant private key (of course, in a real application choosing a secure random private key is imperative). Then, we sign a message to increase the balance by 4321. For this, we will use the following Python statements:

from starkware.crypto.signature.signature import (
    pedersen_hash, private_to_stark_key, sign)
private_key = 12345
message_hash = pedersen_hash(4321)
public_key = private_to_stark_key(private_key)
signature = sign(
    msg_hash=message_hash, priv_key=private_key)
print(f'Public key: {public_key}')
print(f'Signature: {signature}')

You should get:

Public key: 1628448741648245036800002906075225705100596136133912895015035902954123957052
Signature: (1225578735933442828068102633747590437426782890965066746429241472187377583468, 3568809569741913715045370357918125425757114920266578211811626257903121825123)

Using this signature, we may now update the balance:

starknet invoke \
    --address ${CONTRACT_ADDRESS} \
    --abi signature_abi.json \
    --function increase_balance \
    --inputs \
        1628448741648245036800002906075225705100596136133912895015035902954123957052 \
        4321 \
        1225578735933442828068102633747590437426782890965066746429241472187377583468 \
        3568809569741913715045370357918125425757114920266578211811626257903121825123