Verify an ECDSA Signature
Cairo has a builtin that allows an ECDSA signature to be verified. For example, a user registers their public key, then authorises a transfer of some number of coins. The contract verifies that the contract contains a message signed properly by the authorised person.
Steps:
- Make a message to sign. E.g., the number “13579”, which might represent a quantity.
- Hash the message using the Pedersen hash function.
- Obtain a private key. E.g., Generate a random one as shown below.
- Sign the message hash using the private key.
- Record the two parts of the signature, sig_r and sig_s.
Generate a random private key. With the Cairo python package installed run the following
code as a .py
file, or type python3
in the terminal and paste the following:
from starkware.crypto.signature.signature import (sign,
get_random_private_key, private_to_stark_key, pedersen_hash)
message = 13579
message_hash = pedersen_hash(message)
private_key = get_random_private_key()
signature = sign(msg_hash=message_hash, priv_key=private_key)
r = signature[0]
s = signature[1]
public_key = private_to_stark_key(private_key)
print(f"""
The pedersen hash of message {message} is:
{message_hash}
was signed by the public key:
{public_key}
The signature_r is:
{r}
The signature_s is:
{s}
""")
Result:
The pedersen hash of message 13579 is:
2255487090060981340412778270523682108366421523848318719210969003889439916982
was signed by the public key:
336651705928807190204460653884405974785205047669862626875647782176669707088
The signature_r is:
1893532933103991730127061797833157421284043895315515223059211080812570729772
The signature_s is:
2767847065606260480373088228849935790434610797527549053315487023265903912514
The function verify_ecdsa_signature()
from the Cairo common library
accepts the four numbers above as arguments:
- message_hash
- public_key
- signature_r
- signature_s
The curve is different from the one used on Ethereum, so a signed message by an L1 wallet cannot directly be checked inside an L2 contract as of Cairo v0.3.0.
Contract
Below is the contract that will check the signed message.
%lang starknet
%builtins ecdsa
from starkware.cairo.common.signature import verify_ecdsa_signature
from starkware.cairo.common.cairo_builtins import SignatureBuiltin
# ecdsa: a builtin that tracks the signature in the Cairo trace.
# SignatureBuiltin: a struct used to represent the signature.
# ecdsa_ptr: a pointer (*) to the signature struct.
# The pointer is passed implicitly '{}'.
@view
func check_signature{ecdsa_ptr: SignatureBuiltin*}(
message, public_key, sig_r, sig_s) -> ():
# If the signature is incorrect, this will fail.
verify_ecdsa_signature(message_hash, public_key, sig_r, sig_s)
return ()
end
Save as verify_ecdsa.cairo
.
Compile
Then, to compile:
starknet-compile verify_ecdsa.cairo \
--output verify_ecdsa_compiled.json \
--abi verify_ecdsa_contract_abi.json
Deploy
Then, to deploy:
starknet deploy --contract verify_ecdsa_compiled.json \
--network=alpha
Returns:
Deploy transaction was sent.
Contract address: 0x061ebf39a3dbdc61d2881ba9d9820d92174b84495c4aea0d41dc545086eceb29
Transaction ID: 152698
Note: Remove the zero after the x
, 0x[0]12345. E.g., 0x0123abc becomes 0x123abc.
Monitor
Check the status of the transaction:
starknet tx_status --network=alpha --id=152698
Returns:
{
"block_id": 34065,
"tx_status": "PENDING"
}
Interact
Then, to interact, call the function using the arguments from above (in same order, message_hash, public_key, sig_r, sig_s). This call would fail if the signature did not pass the checks enforced by the contract.
starknet call \
--network=alpha \
--address 0x61ebf39a3dbdc61d2881ba9d9820d92174b84495c4aea0d41dc545086eceb29 \
--abi verify_ecdsa_contract_abi.json \
--function check_signature \
--inputs \
2255487090060981340412778270523682108366421523848318719210969003889439916982 \
336651705928807190204460653884405974785205047669862626875647782176669707088 \
1893532933103991730127061797833157421284043895315515223059211080812570729772 \
2767847065606260480373088228849935790434610797527549053315487023265903912514
Status options:
- NOT_RECEIVED: The transaction has not been received yet (i.e., not written to storage).
- RECEIVED: The transaction was received by the operator.
- PENDING: The transaction passed the validation and is waiting to be sent on-chain.
- REJECTED: The transaction failed validation and thus was skipped.
- ACCEPTED_ONCHAIN: The transaction was accepted on-chain.
- PENDING: The transaction passed the validation and is waiting to be sent on-chain.
Visit the voyager explorer to see the transactions.