Verifying signatures
Recover the signer of an ethSign signature and act on it once. The signed message includes the contract's address and a nonce, so a signature can't be replayed here or against another contract.
signatures.lithe
(* Verify an off-chain signature, then act on it once. The signer signs
keccak256(abi.encode(contract, to, amount, nonce)) with `ethSign`, which adds the
EIP-191 prefix "\x19Ethereum Signed Message:\n32". *)
import "std/abi";
import "std/address";
import "std/crypto";
import "std/evm";
error InvalidSignature;
error NonceUsed of uint;
storage is record {
signer : address;
used : map(uint, bool);
credited : map(address, uint);
}
external function init(const signer : address) is {
storage.signer := signer;
}
view function messageHash(const to : address; const amount : uint; const nonce : uint) : bytes32 is
Crypto.keccak256(Abi.encode(Evm.self(), to, amount, nonce))
pure function ethSigned(const hash : bytes32) : bytes32 is {
const prefix : bytes := "\x19Ethereum Signed Message:\n32";
} with Crypto.keccak256(Abi.encodePacked(prefix, hash))
external function claim(
const to : address;
const amount : uint;
const nonce : uint;
const v : uint8;
const r : bytes32;
const s : bytes32
)
raises InvalidSignature | NonceUsed
is {
assert(not storage.used[nonce], NonceUsed(nonce));
const digest : bytes32 := ethSigned(messageHash(to, amount, nonce));
const recovered : address := Crypto.ecrecover(digest, v, r, s);
assert(recovered <> Address.zero and recovered = storage.signer, InvalidSignature);
storage.used[nonce] := true;
storage.credited[to] := storage.credited[to] + amount;
}ecrecoverreturnsAddress.zerofor an invalid signature, so check for it explicitly.- For production use, also reject high-
ssignatures, which are malleable.

