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 declare the contract:
starknet-compile-deprecated signature_verification.cairo \
--output signature_compiled.json \
--abi signature_abi.json
starknet declare --contract signature_compiled.json --deprecated
Deploy the contract:
starknet deploy --class_hash ${SIGNATURE_CONTRACT_CLASS_HASH}
where ${SIGNATURE_CONTRACT_CLASS_HASH}
is the value of class_hash.
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