pyth_lazer.move 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. module pyth_lazer::pyth_lazer;
  2. use pyth_lazer::state::{Self, State};
  3. use pyth_lazer::update::{Self, Update};
  4. use sui::bcs;
  5. use sui::clock::Clock;
  6. use sui::ecdsa_k1::secp256k1_ecrecover;
  7. const SECP256K1_SIG_LEN: u32 = 65;
  8. const UPDATE_MESSAGE_MAGIC: u32 = 1296547300;
  9. const PAYLOAD_MAGIC: u32 = 2479346549;
  10. // Error codes
  11. const ESignerNotTrusted: u64 = 2;
  12. const ESignerExpired: u64 = 3;
  13. const EInvalidMagic: u64 = 4;
  14. const EInvalidPayloadLength: u64 = 6;
  15. /// The `PYTH_LAZER` resource serves as the one-time witness.
  16. /// It has the `drop` ability, allowing it to be consumed immediately after use.
  17. /// See: https://move-book.com/programmability/one-time-witness
  18. public struct PYTH_LAZER has drop {}
  19. /// Initializes the module. Called at publish time.
  20. /// Creates and shares the singular State object.
  21. /// AdminCap is created and transferred in admin::init via a One-Time Witness.
  22. fun init(_: PYTH_LAZER, ctx: &mut TxContext) {
  23. let s = state::new(ctx);
  24. transfer::public_share_object(s);
  25. }
  26. /// Verify LE ECDSA message signature against trusted signers.
  27. ///
  28. /// This function recovers the public key from the signature and payload,
  29. /// then checks if the recovered public key is in the trusted signers list
  30. /// and has not expired.
  31. ///
  32. /// # Arguments
  33. /// * `s` - The pyth_lazer::state::State
  34. /// * `clock` - The sui::clock::Clock
  35. /// * `signature` - The ECDSA signature bytes (little endian)
  36. /// * `payload` - The message payload that was signed
  37. ///
  38. /// # Errors
  39. /// * `ESignerNotTrusted` - The recovered public key is not in the trusted signers list
  40. /// * `ESignerExpired` - The signer's certificate has expired
  41. public(package) fun verify_le_ecdsa_message(
  42. s: &State,
  43. clock: &Clock,
  44. signature: &vector<u8>,
  45. payload: &vector<u8>,
  46. ) {
  47. // 0 stands for keccak256 hash
  48. let pubkey = secp256k1_ecrecover(signature, payload, 0);
  49. // Check if the recovered pubkey is in the trusted signers list
  50. let trusted_signers = state::get_trusted_signers(s);
  51. let mut maybe_idx = state::find_signer_index(trusted_signers, &pubkey);
  52. if (option::is_some(&maybe_idx)) {
  53. let idx = option::extract(&mut maybe_idx);
  54. let found_signer = &trusted_signers[idx];
  55. let expires_at = state::expires_at(found_signer);
  56. assert!(clock.timestamp_ms() < expires_at, ESignerExpired);
  57. } else {
  58. abort ESignerNotTrusted
  59. }
  60. }
  61. /// Parse the Lazer update message and validate the signature within.
  62. /// The parsing logic is based on the Lazer rust protocol definition defined here:
  63. /// https://github.com/pyth-network/pyth-crosschain/tree/main/lazer/sdk/rust/protocol
  64. ///
  65. /// # Arguments
  66. /// * `s` - The pyth_lazer::state::State
  67. /// * `clock` - The sui::clock::Clock
  68. /// * `update` - The LeEcdsa formatted Lazer update
  69. ///
  70. /// # Errors
  71. /// * `EInvalidMagic` - Invalid magic number in update or payload
  72. /// * `EInvalidPayloadLength` - Payload length doesn't match actual data
  73. /// * `ESignerNotTrusted` - The recovered public key is not in the trusted signers list
  74. /// * `ESignerExpired` - The signer's certificate has expired
  75. public fun parse_and_verify_le_ecdsa_update(s: &State, clock: &Clock, update: vector<u8>): Update {
  76. let mut cursor = bcs::new(update);
  77. // Parse and validate message magic
  78. let magic = cursor.peel_u32();
  79. assert!(magic == UPDATE_MESSAGE_MAGIC, EInvalidMagic);
  80. // Parse signature
  81. let mut signature = vector::empty<u8>();
  82. let mut sig_i = 0;
  83. while (sig_i < SECP256K1_SIG_LEN) {
  84. signature.push_back(cursor.peel_u8());
  85. sig_i = sig_i + 1;
  86. };
  87. // Parse expected payload length and get remaining bytes as payload
  88. let payload_len = cursor.peel_u16();
  89. let payload = cursor.into_remainder_bytes();
  90. // Validate expectedpayload length
  91. assert!((payload_len as u64) == payload.length(), EInvalidPayloadLength);
  92. // Parse payload
  93. let mut payload_cursor = bcs::new(payload);
  94. let payload_magic = payload_cursor.peel_u32();
  95. assert!(payload_magic == PAYLOAD_MAGIC, EInvalidMagic);
  96. // Verify the signature against trusted signers
  97. verify_le_ecdsa_message(s, clock, &signature, &payload);
  98. update::parse_from_cursor(payload_cursor)
  99. }