starknet_lib.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. use hex;
  2. use num_bigint::BigUint;
  3. use sha2::{Digest, Sha256};
  4. use starknet::core::crypto::ecdsa_sign;
  5. use starknet::core::types::Felt;
  6. use std::str::FromStr;
  7. use crate::utils::starknet_messages::{
  8. AssetId, OffChainMessage, Order, PositionId, StarknetDomain, Timestamp, TransferArgs,
  9. };
  10. use utils::starknet_messages;
  11. use crate::utils;
  12. #[allow(dead_code)]
  13. pub struct StarkSignature {
  14. pub r: Felt,
  15. pub s: Felt,
  16. pub v: Felt,
  17. }
  18. #[allow(dead_code)]
  19. #[allow(deprecated)]
  20. fn grind_key(key_seed: BigUint) -> BigUint {
  21. let two_256 = BigUint::from_str(
  22. "115792089237316195423570985008687907853269984665640564039457584007913129639936",
  23. )
  24. .unwrap();
  25. let key_value_limit = BigUint::from_str(
  26. "3618502788666131213697322783095070105526743751716087489154079457884512865583",
  27. )
  28. .unwrap();
  29. let max_allowed_value = two_256.clone() - (two_256.clone() % (&key_value_limit));
  30. let mut index = BigUint::ZERO;
  31. loop {
  32. let hash_input = {
  33. let mut input = Vec::new();
  34. input.extend_from_slice(&key_seed.to_bytes_be());
  35. input.extend_from_slice(&index.to_bytes_be());
  36. input
  37. };
  38. let hash_result = Sha256::digest(&hash_input);
  39. let hash = hash_result.as_slice();
  40. let key = BigUint::from_bytes_be(&hash);
  41. if key < max_allowed_value {
  42. return key % (&key_value_limit);
  43. }
  44. index += BigUint::from_str("1").unwrap();
  45. }
  46. }
  47. #[allow(dead_code)]
  48. pub fn get_private_key_from_eth_signature(signature: &str) -> Result<Felt, String> {
  49. let eth_sig_truncated = signature.trim_start_matches("0x");
  50. if eth_sig_truncated.len() < 64 {
  51. return Err("Invalid signature length".to_string());
  52. }
  53. let r = &eth_sig_truncated[..64];
  54. let r_bytes = hex::decode(r).map_err(|e| format!("Failed to decode r as hex: {:?}", e))?;
  55. let r_int = BigUint::from_bytes_be(&r_bytes);
  56. let ground_key = grind_key(r_int);
  57. return Ok(Felt::from_hex(&ground_key.to_str_radix(16)).unwrap());
  58. }
  59. #[allow(dead_code)]
  60. pub fn sign_message(message: &Felt, private_key: &Felt) -> Result<StarkSignature, String> {
  61. return ecdsa_sign(private_key, &message)
  62. .map(|extended_signature| StarkSignature {
  63. r: extended_signature.r,
  64. s: extended_signature.s,
  65. v: extended_signature.v,
  66. })
  67. .map_err(|e| format!("Failed to sign message: {:?}", e));
  68. }
  69. // these functions are designed to be called from other languages, such as Python or JavaScript,
  70. // so they take string arguments.
  71. #[allow(dead_code)]
  72. pub fn get_order_hash(
  73. position_id: String,
  74. base_asset_id_hex: String,
  75. base_amount: String,
  76. quote_asset_id_hex: String,
  77. quote_amount: String,
  78. fee_asset_id_hex: String,
  79. fee_amount: String,
  80. expiration: String,
  81. salt: String,
  82. user_public_key_hex: String,
  83. domain_name: String,
  84. domain_version: String,
  85. domain_chain_id: String,
  86. domain_revision: String,
  87. ) -> Result<Felt, String> {
  88. // println!("position_id: {}", position_id);
  89. // println!("base_asset_id_hex: {}", base_asset_id_hex);
  90. // println!("base_amount: {}", base_amount);
  91. // println!("quote_asset_id_hex: {}", quote_asset_id_hex);
  92. // println!("quote_amount: {}", quote_amount);
  93. // println!("fee_asset_id_hex: {}", fee_asset_id_hex);
  94. // println!("fee_amount: {}", fee_amount);
  95. // println!("expiration: {}", expiration);
  96. // println!("salt: {}", salt);
  97. // println!("user_public_key_hex: {}", user_public_key_hex);
  98. // println!("domain_name: {}", domain_name);
  99. // println!("domain_version: {}", domain_version);
  100. // println!("domain_chain_id: {}", domain_chain_id);
  101. // println!("domain_revision: {}", domain_revision);
  102. let base_asset_id = Felt::from_hex(&base_asset_id_hex)
  103. .map_err(|e| format!("Invalid base_asset_id_hex: {:?}", e))?;
  104. let quote_asset_id = Felt::from_hex(&quote_asset_id_hex)
  105. .map_err(|e| format!("Invalid quote_asset_id_hex: {:?}", e))?;
  106. let fee_asset_id = Felt::from_hex(&fee_asset_id_hex)
  107. .map_err(|e| format!("Invalid fee_asset_id_hex: {:?}", e))?;
  108. let user_key = Felt::from_hex(&user_public_key_hex)
  109. .map_err(|e| format!("Invalid user_public_key_hex: {:?}", e))?;
  110. let position_id = u32::from_str_radix(&position_id, 10)
  111. .map_err(|e| format!("Invalid position_id: {:?}", e))?;
  112. let base_amount = i64::from_str_radix(&base_amount, 10)
  113. .map_err(|e| format!("Invalid base_amount: {:?}", e))?;
  114. let quote_amount = i64::from_str_radix(&quote_amount, 10)
  115. .map_err(|e| format!("Invalid quote_amount: {:?}", e))?;
  116. let fee_amount =
  117. u64::from_str_radix(&fee_amount, 10).map_err(|e| format!("Invalid fee_amount: {:?}", e))?;
  118. let expiration =
  119. u64::from_str_radix(&expiration, 10).map_err(|e| format!("Invalid expiration: {:?}", e))?;
  120. let salt = u64::from_str_radix(&salt, 10).map_err(|e| format!("Invalid salt: {:?}", e))?;
  121. let revision = u32::from_str_radix(&domain_revision, 10)
  122. .map_err(|e| format!("Invalid domain_revision: {:?}", e))?;
  123. let order = Order {
  124. position_id: PositionId { value: position_id },
  125. base_asset_id: AssetId {
  126. value: base_asset_id,
  127. },
  128. base_amount,
  129. quote_asset_id: AssetId {
  130. value: quote_asset_id,
  131. },
  132. quote_amount,
  133. fee_asset_id: AssetId {
  134. value: fee_asset_id,
  135. },
  136. fee_amount,
  137. expiration: Timestamp {
  138. seconds: expiration,
  139. },
  140. salt: salt
  141. .try_into()
  142. .map_err(|e| format!("Invalid salt vault: {:?}", e))?,
  143. };
  144. let domain = StarknetDomain {
  145. name: domain_name,
  146. version: domain_version,
  147. chain_id: domain_chain_id,
  148. revision,
  149. };
  150. order
  151. .message_hash(&domain, user_key)
  152. .map_err(|e| format!("Failed to compute message hash: {:?}", e))
  153. }
  154. #[allow(dead_code)]
  155. pub fn get_transfer_hash(
  156. recipient_position_id: String,
  157. sender_position_id: String,
  158. collateral_id_hex: String,
  159. amount: String,
  160. expiration: String,
  161. salt: String,
  162. user_public_key_hex: String,
  163. domain_name: String,
  164. domain_version: String,
  165. domain_chain_id: String,
  166. domain_revision: String,
  167. ) -> Result<Felt, String> {
  168. let collateral_id = Felt::from_hex(&collateral_id_hex)
  169. .map_err(|e| format!("Invalid collateral_id_hex: {:?}", e))?;
  170. let user_key = Felt::from_hex(&user_public_key_hex)
  171. .map_err(|e| format!("Invalid user_public_key_hex: {:?}", e))?;
  172. let recipient = u32::from_str_radix(&recipient_position_id, 10)
  173. .map_err(|e| format!("Invalid recipient_position_id: {:?}", e))?;
  174. let position_id = u32::from_str_radix(&sender_position_id, 10)
  175. .map_err(|e| format!("Invalid sender_position_id: {:?}", e))?;
  176. let amount =
  177. u64::from_str_radix(&amount, 10).map_err(|e| format!("Invalid amount: {:?}", e))?;
  178. let expiration =
  179. u64::from_str_radix(&expiration, 10).map_err(|e| format!("Invalid expiration: {:?}", e))?;
  180. let salt = Felt::from_dec_str(&salt).map_err(|e| format!("Invalid salt: {:?}", e))?;
  181. let revision = u32::from_str_radix(&domain_revision, 10)
  182. .map_err(|e| format!("Invalid domain_revision: {:?}", e))?;
  183. let transfer_args = TransferArgs {
  184. recipient: PositionId { value: recipient },
  185. position_id: PositionId { value: position_id },
  186. collateral_id: AssetId {
  187. value: collateral_id,
  188. },
  189. amount,
  190. expiration: Timestamp {
  191. seconds: expiration,
  192. },
  193. salt,
  194. };
  195. let domain = StarknetDomain {
  196. name: domain_name,
  197. version: domain_version,
  198. chain_id: domain_chain_id,
  199. revision,
  200. };
  201. transfer_args
  202. .message_hash(&domain, user_key)
  203. .map_err(|e| format!("Failed to compute message hash: {:?}", e))
  204. }
  205. #[allow(dead_code)]
  206. pub fn get_withdrawal_hash(
  207. recipient_hex: String,
  208. position_id: String,
  209. collateral_id_hex: String,
  210. amount: String,
  211. expiration: String,
  212. salt: String,
  213. user_public_key_hex: String,
  214. domain_name: String,
  215. domain_version: String,
  216. domain_chain_id: String,
  217. domain_revision: String,
  218. ) -> Result<Felt, String> {
  219. let collateral_id = Felt::from_hex(&collateral_id_hex)
  220. .map_err(|e| format!("Invalid collateral_id_hex: {:?}", e))?;
  221. let user_key = Felt::from_hex(&user_public_key_hex)
  222. .map_err(|e| format!("Invalid user_public_key_hex: {:?}", e))?;
  223. let recipient =
  224. Felt::from_hex(&recipient_hex).map_err(|e| format!("Invalid recipient_hex: {:?}", e))?;
  225. let position_id = u32::from_str_radix(&position_id, 10)
  226. .map_err(|e| format!("Invalid position_id: {:?}", e))?;
  227. let amount =
  228. u64::from_str_radix(&amount, 10).map_err(|e| format!("Invalid amount: {:?}", e))?;
  229. let expiration =
  230. u64::from_str_radix(&expiration, 10).map_err(|e| format!("Invalid expiration: {:?}", e))?;
  231. let salt = Felt::from_dec_str(&salt).map_err(|e| format!("Invalid salt: {:?}", e))?;
  232. let revision = u32::from_str_radix(&domain_revision, 10)
  233. .map_err(|e| format!("Invalid domain_revision: {:?}", e))?;
  234. let withdrawal_args = starknet_messages::WithdrawalArgs {
  235. recipient,
  236. position_id: PositionId { value: position_id },
  237. collateral_id: AssetId {
  238. value: collateral_id,
  239. },
  240. amount,
  241. expiration: Timestamp {
  242. seconds: expiration,
  243. },
  244. salt,
  245. };
  246. let domain = StarknetDomain {
  247. name: domain_name,
  248. version: domain_version,
  249. chain_id: domain_chain_id,
  250. revision,
  251. };
  252. withdrawal_args
  253. .message_hash(&domain, user_key)
  254. .map_err(|e| {
  255. format!(
  256. "Failed to compute message hash for withdrawal args: {:?}",
  257. e
  258. )
  259. })
  260. }
  261. #[cfg(test)]
  262. mod tests {
  263. use super::*;
  264. #[test]
  265. fn test_get_private_key_from_eth_signature() {
  266. let signature = "0x9ef64d5936681edf44b4a7ad713f3bc24065d4039562af03fccf6a08d6996eab367df11439169b417b6a6d8ce81d409edb022597ce193916757c7d5d9cbf97301c";
  267. let result = get_private_key_from_eth_signature(signature);
  268. match result {
  269. Ok(private_key) => {
  270. assert_eq!(private_key, Felt::from_dec_str("3554363360756768076148116215296798451844584215587910826843139626172125285444").unwrap());
  271. }
  272. Err(err) => {
  273. panic!("Expected Ok, got Err: {}", err);
  274. }
  275. }
  276. }
  277. #[test]
  278. fn test_get_transfer_msg() {
  279. let recipient_position_id = "1".to_string();
  280. let sender_position_id = "2".to_string();
  281. let collateral_id_hex = "0x3".to_string();
  282. let amount = "4".to_string();
  283. let expiration = "5".to_string();
  284. let salt = "6".to_string();
  285. let user_public_key_hex =
  286. "0x5d05989e9302dcebc74e241001e3e3ac3f4402ccf2f8e6f74b034b07ad6a904".to_string();
  287. let domain_name = "Perpetuals".to_string();
  288. let domain_version = "v0".to_string();
  289. let domain_chain_id = "SN_SEPOLIA".to_string();
  290. let domain_revision = "1".to_string();
  291. let result = get_transfer_hash(
  292. recipient_position_id,
  293. sender_position_id,
  294. collateral_id_hex,
  295. amount,
  296. expiration,
  297. salt,
  298. user_public_key_hex,
  299. domain_name,
  300. domain_version,
  301. domain_chain_id,
  302. domain_revision,
  303. );
  304. match result {
  305. Ok(hash) => {
  306. assert_eq!(
  307. hash,
  308. Felt::from_hex(
  309. "0x56c7b21d13b79a33d7700dda20e22246c25e89818249504148174f527fc3f8f"
  310. )
  311. .unwrap()
  312. );
  313. }
  314. Err(err) => {
  315. panic!("Expected Ok, got Err: {}", err);
  316. }
  317. }
  318. }
  319. #[test]
  320. fn test_get_order_hash() {
  321. let position_id = "100".to_string();
  322. let base_asset_id_hex = "0x2".to_string();
  323. let base_amount = "100".to_string();
  324. let quote_asset_id_hex = "0x1".to_string();
  325. let quote_amount = "-156".to_string();
  326. let fee_asset_id_hex = "0x1".to_string();
  327. let fee_amount = "74".to_string();
  328. let expiration = "100".to_string();
  329. let salt = "123".to_string();
  330. let user_public_key_hex =
  331. "0x5d05989e9302dcebc74e241001e3e3ac3f4402ccf2f8e6f74b034b07ad6a904".to_string();
  332. let domain_name = "Perpetuals".to_string();
  333. let domain_version = "v0".to_string();
  334. let domain_chain_id = "SN_SEPOLIA".to_string();
  335. let domain_revision = "1".to_string();
  336. let result = get_order_hash(
  337. position_id,
  338. base_asset_id_hex,
  339. base_amount,
  340. quote_asset_id_hex,
  341. quote_amount,
  342. fee_asset_id_hex,
  343. fee_amount,
  344. expiration,
  345. salt,
  346. user_public_key_hex,
  347. domain_name,
  348. domain_version,
  349. domain_chain_id,
  350. domain_revision,
  351. );
  352. match result {
  353. Ok(hash) => {
  354. assert_eq!(
  355. hash,
  356. Felt::from_hex(
  357. "0x4de4c009e0d0c5a70a7da0e2039fb2b99f376d53496f89d9f437e736add6b48"
  358. )
  359. .unwrap()
  360. );
  361. }
  362. Err(err) => {
  363. panic!("Expected Ok, got Err: {}", err);
  364. }
  365. }
  366. }
  367. #[test]
  368. fn test_get_withdrawal_hash() {
  369. let recipient_hex = Felt::from_dec_str(
  370. "206642948138484946401984817000601902748248360221625950604253680558965863254",
  371. )
  372. .unwrap()
  373. .to_hex_string();
  374. let position_id = "2".to_string();
  375. let collateral_id_hex = Felt::from_dec_str(
  376. "1386727789535574059419576650469753513512158569780862144831829362722992755422",
  377. )
  378. .unwrap()
  379. .to_hex_string();
  380. let amount = "1000".to_string();
  381. let expiration = "0".to_string();
  382. let salt = "0".to_string();
  383. let user_public_key_hex =
  384. "0x5D05989E9302DCEBC74E241001E3E3AC3F4402CCF2F8E6F74B034B07AD6A904".to_string();
  385. let domain_name = "Perpetuals".to_string();
  386. let domain_version = "v0".to_string();
  387. let domain_chain_id = "SN_SEPOLIA".to_string();
  388. let domain_revision = "1".to_string();
  389. let result = get_withdrawal_hash(
  390. recipient_hex,
  391. position_id,
  392. collateral_id_hex,
  393. amount,
  394. expiration,
  395. salt,
  396. user_public_key_hex,
  397. domain_name,
  398. domain_version,
  399. domain_chain_id,
  400. domain_revision,
  401. );
  402. match result {
  403. Ok(hash) => {
  404. assert_eq!(
  405. hash,
  406. Felt::from_dec_str(
  407. "2182119571682827544073774098906745929330860211691330979324731407862023927178"
  408. )
  409. .unwrap()
  410. );
  411. }
  412. Err(err) => {
  413. panic!("Expected Ok, got Err: {}", err);
  414. }
  415. }
  416. }
  417. }