Skip to content

Instantly share code, notes, and snippets.

@a2468834
Last active September 26, 2024 16:05
Show Gist options
  • Save a2468834/5fa6b5698c0b478296fbedfdc12edb0a to your computer and use it in GitHub Desktop.
Save a2468834/5fa6b5698c0b478296fbedfdc12edb0a to your computer and use it in GitHub Desktop.
;;
;; Example verifying signature smart contract in FunC
;;
;;
;; Storage layout
;;
;; uint256 public key: Signer's public key
;; uint32 nonce: A monotonic increasing uint counter starting from zero
;;
;;
;; Constants
;;
const int PREFIX = 0xffff;
const int PERMIT_TYPEHASH = "Permit(Address owner,Address spender,uint32 value,uint32 nonce,uint32 deadline)Address(int8 workchainId,uint256 accountId)"H;
const int ADDRESS_TYPEHASH = "Address(int8 workchainId,uint256 accountId)"H;
const int TEP181DOMAIN_TYPEHASH = "TEP181Domain(uint32 name,uint32 version,int8 workchainId,uint256 verifierContract)"H;
;;
;; Error codes
;;
int op::error_invalid_nonce() asm "0x1354 PUSHINT";
int op::error_signature_too_old() asm "0x435e PUSHINT";
int op::error_invalid_signature() asm "0x8abe PUSHINT";
;;
;; `recv_internal` functions
;;
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
;; Ignore empty internal message
if (in_msg.slice_empty?()) {
return ();
}
;; Load two cells from the incoming intMessage and convert them into slices
slice slice_1st = in_msg~load_ref().begin_parse();
slice slice_2nd = in_msg~load_ref().begin_parse();
;; Parse the components from the cells
slice signature = slice_1st~load_bits(512);
int owner_workchain_id = slice_2nd~load_int(8);
int owner_account_id = slice_2nd~load_uint(256);
int spender_workchain_id = slice_2nd~load_int(8);
int spender_account_id = slice_2nd~load_uint(256);
int value = slice_2nd~load_uint(32);
int nonce = slice_2nd~load_uint(32);
int deadline = slice_2nd~load_uint(32);
;; Load known information from smart contract storage
slice ds = get_data().begin_parse();
int public_key = ds~load_uint(256);
int latest_nonce = ds~load_uint(32);
(int workchain_id, int verifier_contract) = parse_std_addr(my_address());
;; Check the validity
throw_unless(op::error_invalid_nonce(), nonce == latest_nonce);
throw_unless(op::error_signature_too_old(), now() <= deadline);
;; Update `nonce`
set_data(
begin_cell()
.store_uint(public_key, 256)
.store_uint((latest_nonce + 1), 32)
.end_cell()
);
;; Construct structured data and their hashes from `in_msg`
int owner = cell_hash(
begin_cell()
.store_uint(ADDRESS_TYPEHASH, 256)
.store_int(owner_workchain_id, 8)
.store_uint(owner_account_id, 256)
.end_cell()
);
int spender = cell_hash(
begin_cell()
.store_uint(ADDRESS_TYPEHASH, 256)
.store_int(spender_workchain_id, 8)
.store_uint(spender_account_id, 256)
.end_cell()
);
int struct_hash = cell_hash(
begin_cell()
.store_uint(PERMIT_TYPEHASH, 256)
.store_uint(owner, 256) ;; owner
.store_uint(spender, 256) ;; spender
.store_uint(value, 32) ;; value
.store_uint(nonce, 32) ;; nonce
.store_uint(deadline, 32) ;; deadline
.end_cell()
);
;; Construct domain separator hash value from `in_msg`
int domain_separator_hash = cell_hash(
begin_cell()
.store_uint(TEP181DOMAIN_TYPEHASH, 256) ;;
.store_uint("Permit Ticket"h, 32) ;; name
.store_uint("1"h, 32) ;; version
.store_int(workchain_id, 8) ;; workchain_id
.store_uint(verifier_contract, 256) ;; verifier_contract_address
.end_cell()
);
;; Check Ed25519 signature
throw_unless(
op::error_invalid_signature(),
check_signature(
cell_hash(
begin_cell().store_uint(PREFIX, 16).store_uint(domain_separator_hash, 256).store_uint(struct_hash, 256).end_cell()
),
signature,
public_key
)
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment