1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
(** Cryptographic utilities for Polymarket API authentication.
This module provides EIP-712 signing, HMAC-SHA256, and Ethereum address
derivation for API authentication. *)
type private_key = string
let private_key_of_string s = s
let private_key_to_string s = s
(** {1 Hashing} *)
let keccak256 data =
let hash = Digestif.KECCAK_256.digest_string data in
"0x" ^ Digestif.KECCAK_256.to_hex hash
let keccak256_hex hex_str =
let bytes = Hex.to_string (`Hex hex_str) in
keccak256 bytes
(** {1 HMAC-SHA256} *)
let hmac_sha256 ~key message =
Digestif.SHA256.(to_raw_string (hmac_string ~key message))
let sign_l2_request ~secret ~timestamp ~method_ ~path ~body =
let key = Base64.decode_exn secret in
let message = timestamp ^ method_ ^ path ^ body in
let signature = hmac_sha256 ~key message in
Base64.encode_exn signature
(** {1 EIP-712 Helpers} *)
(** Pad hex string to 64 chars (32 bytes) with leading zeros *)
let pad_hex_32 hex =
let len = String.length hex in
if len >= 64 then hex else String.make (64 - len) '0' ^ hex
(** Encode uint256 as 32-byte hex *)
let encode_uint256 n =
let hex = Printf.sprintf "%x" n in
pad_hex_32 hex
(** EIP-712 type hash for domain: keccak256("EIP712Domain(string name,string
version,uint256 chainId)") *)
let domain_type_hash =
let type_string =
"EIP712Domain(string name,string version,uint256 chainId)"
in
let hash = Digestif.KECCAK_256.digest_string type_string in
Digestif.KECCAK_256.to_hex hash
(** EIP-712 type hash for ClobAuth: keccak256("ClobAuth(address address,string
timestamp,uint256 nonce,string message)") *)
let clob_auth_type_hash =
let type_string =
"ClobAuth(address address,string timestamp,uint256 nonce,string message)"
in
let hash = Digestif.KECCAK_256.digest_string type_string in
Digestif.KECCAK_256.to_hex hash
(** Compute EIP-712 domain separator *)
let compute_domain_separator () =
let name_hash =
Digestif.KECCAK_256.(to_hex (digest_string Constants.clob_domain_name))
in
let version_hash =
Digestif.KECCAK_256.(to_hex (digest_string Constants.clob_domain_version))
in
let chain_id_hex = encode_uint256 Constants.polygon_chain_id in
let data = domain_type_hash ^ name_hash ^ version_hash ^ chain_id_hex in
let bytes = Hex.to_string (`Hex data) in
Digestif.KECCAK_256.(to_hex (digest_string bytes))
(** Compute struct hash for ClobAuth message *)
let compute_clob_auth_hash ~address ~timestamp ~nonce =
let addr_hex =
if String.length address > 2 && String.sub address 0 2 = "0x" then
String.sub address 2 (String.length address - 2)
else address
in
let addr_padded = pad_hex_32 addr_hex in
let timestamp_hash = Digestif.KECCAK_256.(to_hex (digest_string timestamp)) in
let message_hash =
Digestif.KECCAK_256.(to_hex (digest_string Constants.auth_message_text))
in
let nonce_hex = encode_uint256 nonce in
let data =
clob_auth_type_hash ^ addr_padded ^ timestamp_hash ^ nonce_hex
^ message_hash
in
let bytes = Hex.to_string (`Hex data) in
Digestif.KECCAK_256.(to_hex (digest_string bytes))
(** Compute EIP-712 hash to sign *)
let compute_eip712_hash ~address ~timestamp ~nonce =
let domain_separator = compute_domain_separator () in
let struct_hash = compute_clob_auth_hash ~address ~timestamp ~nonce in
let prefix = "\x19\x01" in
let domain_bytes = Hex.to_string (`Hex domain_separator) in
let struct_bytes = Hex.to_string (`Hex struct_hash) in
let data = prefix ^ domain_bytes ^ struct_bytes in
Digestif.KECCAK_256.(to_hex (digest_string data))
(** {1 Secp256k1 Signing} *)
(** Sign a 32-byte hash with private key, returns signature with recovery id *)
let sign_hash ~private_key hash_hex =
let open Libsecp256k1.External in
let ctx = Context.create ~sign:true ~verify:true () in
let sk_bytes = Bigstring.of_string (Hex.to_string (`Hex private_key)) in
let sk = Key.read_sk_exn ctx sk_bytes in
let hash_bytes = Bigstring.of_string (Hex.to_string (`Hex hash_hex)) in
let signature = Sign.sign_recoverable_exn ctx ~sk hash_bytes in
let sig_bytes = Sign.to_bytes ctx signature in
let sig_str = Bigstring.to_string sig_bytes in
let rs_hex = Hex.of_string (String.sub sig_str 0 64) in
let recid = Char.code sig_str.[64] in
let v = recid + 27 in
let (`Hex rs_str) = rs_hex in
Printf.sprintf "0x%s%02x" rs_str v
(** {1 Public API} *)
let sign_clob_auth_message ~private_key ~address ~timestamp ~nonce =
let hash = compute_eip712_hash ~address ~timestamp ~nonce in
sign_hash ~private_key hash
let private_key_to_address private_key =
let open Libsecp256k1.External in
let ctx = Context.create ~sign:true ~verify:true () in
let sk_bytes = Bigstring.of_string (Hex.to_string (`Hex private_key)) in
let sk = Key.read_sk_exn ctx sk_bytes in
let pk = Key.neuterize_exn ctx sk in
let pk_bytes = Key.to_bytes ~compress:false ctx pk in
let pk_str = Bigstring.to_string pk_bytes in
let pk_data = String.sub pk_str 1 64 in
let hash = Digestif.KECCAK_256.digest_string pk_data in
let hash_hex = Digestif.KECCAK_256.to_hex hash in
"0x" ^ String.sub hash_hex (String.length hash_hex - 40) 40
let current_timestamp_ms () =
let t = Unix.gettimeofday () in
Printf.sprintf "%.0f" (t *. 1000.0)
module Private = struct
let pad_hex_32 = pad_hex_32
let encode_uint256 = encode_uint256
let sign_hash = sign_hash
end