mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-22 08:58:12 +00:00
Merge pull request #47 from floor-licker/fix/hmac-body-benchmark-and-credentials
fix: prepare hmac credentials for auth paths
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use alloy_signer_local::PrivateKeySigner;
|
use alloy_signer_local::PrivateKeySigner;
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use polyfill_rs::{
|
use polyfill_rs::{
|
||||||
auth::create_l2_headers,
|
auth::{create_l2_headers_with_body_bytes, PreparedApiCredentials},
|
||||||
orders::{OrderBuilder, BYTES32_ZERO},
|
orders::{OrderBuilder, BYTES32_ZERO},
|
||||||
types::{
|
types::{
|
||||||
ApiCredentials, CreateOrderOptions, FastOrderDelta, OrderDelta, OrderType, PostOrder,
|
ApiCredentials, CreateOrderOptions, FastOrderDelta, OrderDelta, OrderType, PostOrder,
|
||||||
@@ -108,6 +108,7 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) {
|
|||||||
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
||||||
passphrase: "benchmark-passphrase".to_string(),
|
passphrase: "benchmark-passphrase".to_string(),
|
||||||
};
|
};
|
||||||
|
let prepared_api_creds = PreparedApiCredentials::new(api_creds.clone());
|
||||||
|
|
||||||
c.bench_function("order_submit_body_and_l2_headers", |b| {
|
c.bench_function("order_submit_body_and_l2_headers", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
@@ -117,8 +118,14 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) {
|
|||||||
black_box(post_options),
|
black_box(post_options),
|
||||||
);
|
);
|
||||||
let body_bytes = serde_json::to_vec(black_box(&body)).unwrap();
|
let body_bytes = serde_json::to_vec(black_box(&body)).unwrap();
|
||||||
let headers =
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
create_l2_headers(&signer, &api_creds, "POST", "/order", Some(&body)).unwrap();
|
&signer,
|
||||||
|
&prepared_api_creds,
|
||||||
|
"POST",
|
||||||
|
"/order",
|
||||||
|
Some(&body_bytes),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
black_box((body_bytes, headers))
|
black_box((body_bytes, headers))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
+99
-35
@@ -13,8 +13,10 @@ use base64::engine::Engine;
|
|||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, LazyLock, RwLock};
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
// Header constants
|
// Header constants
|
||||||
@@ -26,8 +28,75 @@ const POLY_API_KEY_HEADER: &str = "poly_api_key";
|
|||||||
const POLY_PASS_HEADER: &str = "poly_passphrase";
|
const POLY_PASS_HEADER: &str = "poly_passphrase";
|
||||||
|
|
||||||
type Headers = HashMap<&'static str, String>;
|
type Headers = HashMap<&'static str, String>;
|
||||||
static DECODED_SECRET_CACHE: LazyLock<RwLock<HashMap<String, Arc<[u8]>>>> =
|
|
||||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
pub trait HmacApiCredentials {
|
||||||
|
fn api_key(&self) -> &str;
|
||||||
|
fn passphrase(&self) -> &str;
|
||||||
|
fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PreparedApiCredentials {
|
||||||
|
credentials: ApiCredentials,
|
||||||
|
decoded_secret: std::result::Result<Arc<[u8]>, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreparedApiCredentials {
|
||||||
|
pub fn new(credentials: ApiCredentials) -> Self {
|
||||||
|
let decoded_secret = base64::engine::general_purpose::URL_SAFE
|
||||||
|
.decode(&credentials.secret)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(|e| format!("Failed to decode base64 secret: {}", e));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
credentials,
|
||||||
|
decoded_secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn credentials(&self) -> &ApiCredentials {
|
||||||
|
&self.credentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for PreparedApiCredentials {
|
||||||
|
type Target = ApiCredentials;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.credentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HmacApiCredentials for ApiCredentials {
|
||||||
|
fn api_key(&self) -> &str {
|
||||||
|
&self.api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn passphrase(&self) -> &str {
|
||||||
|
&self.passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
|
||||||
|
Ok(Cow::Owned(decode_secret_bytes(&self.secret)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HmacApiCredentials for PreparedApiCredentials {
|
||||||
|
fn api_key(&self) -> &str {
|
||||||
|
&self.credentials.api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn passphrase(&self) -> &str {
|
||||||
|
&self.credentials.passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
|
||||||
|
match &self.decoded_secret {
|
||||||
|
Ok(decoded_secret) => Ok(Cow::Borrowed(decoded_secret.as_ref())),
|
||||||
|
Err(err) => Err(PolyfillError::crypto(err.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// EIP-712 struct for CLOB authentication
|
// EIP-712 struct for CLOB authentication
|
||||||
sol! {
|
sol! {
|
||||||
@@ -159,7 +228,7 @@ pub fn build_hmac_signature<T>(
|
|||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
let decoded_secret = decoded_secret_bytes(secret)?;
|
let decoded_secret = decode_secret_bytes(secret)?;
|
||||||
let body_bytes =
|
let body_bytes =
|
||||||
match body {
|
match body {
|
||||||
Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
|
Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
|
||||||
@@ -207,28 +276,10 @@ pub fn build_hmac_signature_bytes(
|
|||||||
Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
|
Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decoded_secret_bytes(secret: &str) -> Result<Arc<[u8]>> {
|
fn decode_secret_bytes(secret: &str) -> Result<Vec<u8>> {
|
||||||
if let Some(decoded) = DECODED_SECRET_CACHE
|
base64::engine::general_purpose::URL_SAFE
|
||||||
.read()
|
|
||||||
.map_err(|_| PolyfillError::internal_simple("Decoded secret cache lock poisoned"))?
|
|
||||||
.get(secret)
|
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
return Ok(decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
let decoded: Arc<[u8]> = base64::engine::general_purpose::URL_SAFE
|
|
||||||
.decode(secret)
|
.decode(secret)
|
||||||
.map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))?
|
.map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))
|
||||||
.into();
|
|
||||||
|
|
||||||
let mut cache = DECODED_SECRET_CACHE
|
|
||||||
.write()
|
|
||||||
.map_err(|_| PolyfillError::internal_simple("Decoded secret cache lock poisoned"))?;
|
|
||||||
Ok(cache
|
|
||||||
.entry(secret.to_string())
|
|
||||||
.or_insert_with(|| decoded.clone())
|
|
||||||
.clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create L1 headers for authentication (using private key signature)
|
/// Create L1 headers for authentication (using private key signature)
|
||||||
@@ -259,7 +310,7 @@ pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Resu
|
|||||||
/// to satisfy bilateral verification requirements at the protocol layer.
|
/// to satisfy bilateral verification requirements at the protocol layer.
|
||||||
pub fn create_l2_headers<T>(
|
pub fn create_l2_headers<T>(
|
||||||
signer: &PrivateKeySigner,
|
signer: &PrivateKeySigner,
|
||||||
api_creds: &ApiCredentials,
|
api_creds: &(impl HmacApiCredentials + ?Sized),
|
||||||
method: &str,
|
method: &str,
|
||||||
req_path: &str,
|
req_path: &str,
|
||||||
body: Option<&T>,
|
body: Option<&T>,
|
||||||
@@ -272,29 +323,42 @@ where
|
|||||||
let timestamp = get_current_unix_time_secs();
|
let timestamp = get_current_unix_time_secs();
|
||||||
|
|
||||||
// Generate cryptographic authenticator using temporal and message context
|
// Generate cryptographic authenticator using temporal and message context
|
||||||
let hmac_signature =
|
let decoded_secret = api_creds.decoded_secret_bytes()?;
|
||||||
build_hmac_signature(&api_creds.secret, timestamp, method, req_path, body)?;
|
let body_bytes =
|
||||||
|
match body {
|
||||||
|
Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
|
||||||
|
PolyfillError::parse(format!("Failed to serialize body: {}", e), None)
|
||||||
|
})?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let hmac_signature = build_hmac_signature_bytes(
|
||||||
|
&decoded_secret,
|
||||||
|
timestamp,
|
||||||
|
method,
|
||||||
|
req_path,
|
||||||
|
body_bytes.as_deref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Construct header map with authentication primitives in canonical order
|
// Construct header map with authentication primitives in canonical order
|
||||||
Ok(HashMap::from([
|
Ok(HashMap::from([
|
||||||
(POLY_ADDR_HEADER, address),
|
(POLY_ADDR_HEADER, address),
|
||||||
(POLY_SIG_HEADER, hmac_signature),
|
(POLY_SIG_HEADER, hmac_signature),
|
||||||
(POLY_TS_HEADER, timestamp.to_string()),
|
(POLY_TS_HEADER, timestamp.to_string()),
|
||||||
(POLY_API_KEY_HEADER, api_creds.api_key.clone()),
|
(POLY_API_KEY_HEADER, api_creds.api_key().to_string()),
|
||||||
(POLY_PASS_HEADER, api_creds.passphrase.clone()),
|
(POLY_PASS_HEADER, api_creds.passphrase().to_string()),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_l2_headers_with_body_bytes(
|
pub fn create_l2_headers_with_body_bytes(
|
||||||
signer: &PrivateKeySigner,
|
signer: &PrivateKeySigner,
|
||||||
api_creds: &ApiCredentials,
|
api_creds: &(impl HmacApiCredentials + ?Sized),
|
||||||
method: &str,
|
method: &str,
|
||||||
req_path: &str,
|
req_path: &str,
|
||||||
body_bytes: Option<&[u8]>,
|
body_bytes: Option<&[u8]>,
|
||||||
) -> Result<Headers> {
|
) -> Result<Headers> {
|
||||||
let address = encode_prefixed(signer.address().as_slice());
|
let address = encode_prefixed(signer.address().as_slice());
|
||||||
let timestamp = get_current_unix_time_secs();
|
let timestamp = get_current_unix_time_secs();
|
||||||
let decoded_secret = decoded_secret_bytes(&api_creds.secret)?;
|
let decoded_secret = api_creds.decoded_secret_bytes()?;
|
||||||
let hmac_signature =
|
let hmac_signature =
|
||||||
build_hmac_signature_bytes(&decoded_secret, timestamp, method, req_path, body_bytes)?;
|
build_hmac_signature_bytes(&decoded_secret, timestamp, method, req_path, body_bytes)?;
|
||||||
|
|
||||||
@@ -302,8 +366,8 @@ pub fn create_l2_headers_with_body_bytes(
|
|||||||
(POLY_ADDR_HEADER, address),
|
(POLY_ADDR_HEADER, address),
|
||||||
(POLY_SIG_HEADER, hmac_signature),
|
(POLY_SIG_HEADER, hmac_signature),
|
||||||
(POLY_TS_HEADER, timestamp.to_string()),
|
(POLY_TS_HEADER, timestamp.to_string()),
|
||||||
(POLY_API_KEY_HEADER, api_creds.api_key.clone()),
|
(POLY_API_KEY_HEADER, api_creds.api_key().to_string()),
|
||||||
(POLY_PASS_HEADER, api_creds.passphrase.clone()),
|
(POLY_PASS_HEADER, api_creds.passphrase().to_string()),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +428,7 @@ mod tests {
|
|||||||
let timestamp = 1234567890;
|
let timestamp = 1234567890;
|
||||||
let body = serde_json::json!({"orderID": "abc123"});
|
let body = serde_json::json!({"orderID": "abc123"});
|
||||||
let body_bytes = serde_json::to_vec(&body).unwrap();
|
let body_bytes = serde_json::to_vec(&body).unwrap();
|
||||||
let decoded_secret = decoded_secret_bytes(secret).unwrap();
|
let decoded_secret = decode_secret_bytes(secret).unwrap();
|
||||||
|
|
||||||
let object_signature =
|
let object_signature =
|
||||||
build_hmac_signature(secret, timestamp, "delete", "/order", Some(&body)).unwrap();
|
build_hmac_signature(secret, timestamp, "delete", "/order", Some(&body)).unwrap();
|
||||||
|
|||||||
+8
-4
@@ -3,7 +3,9 @@
|
|||||||
//! This module provides a production-ready client for interacting with
|
//! This module provides a production-ready client for interacting with
|
||||||
//! Polymarket, optimized for high-frequency trading environments.
|
//! Polymarket, optimized for high-frequency trading environments.
|
||||||
|
|
||||||
use crate::auth::{create_l1_headers, create_l2_headers, create_l2_headers_with_body_bytes};
|
use crate::auth::{
|
||||||
|
create_l1_headers, create_l2_headers, create_l2_headers_with_body_bytes, PreparedApiCredentials,
|
||||||
|
};
|
||||||
use crate::errors::{PolyfillError, Result};
|
use crate::errors::{PolyfillError, Result};
|
||||||
use crate::http_config::{create_colocated_client, create_internet_client, prewarm_connections};
|
use crate::http_config::{create_colocated_client, create_internet_client, prewarm_connections};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
@@ -94,7 +96,7 @@ pub struct ClobClient {
|
|||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
signer: Option<PrivateKeySigner>,
|
signer: Option<PrivateKeySigner>,
|
||||||
api_creds: Option<ApiCreds>,
|
api_creds: Option<PreparedApiCredentials>,
|
||||||
builder_code: Option<String>,
|
builder_code: Option<String>,
|
||||||
order_builder: Option<crate::orders::OrderBuilder>,
|
order_builder: Option<crate::orders::OrderBuilder>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -134,12 +136,14 @@ impl ClobClient {
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|signer| crate::orders::OrderBuilder::new(signer, auth.sig_type, auth.funder));
|
.map(|signer| crate::orders::OrderBuilder::new(signer, auth.sig_type, auth.funder));
|
||||||
|
|
||||||
|
let api_creds = auth.api_creds.map(PreparedApiCredentials::new);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
http_client,
|
http_client,
|
||||||
base_url: host.to_string(),
|
base_url: host.to_string(),
|
||||||
chain_id,
|
chain_id,
|
||||||
signer: auth.signer,
|
signer: auth.signer,
|
||||||
api_creds: auth.api_creds,
|
api_creds,
|
||||||
builder_code: auth.builder_code,
|
builder_code: auth.builder_code,
|
||||||
order_builder,
|
order_builder,
|
||||||
dns_cache: None,
|
dns_cache: None,
|
||||||
@@ -257,7 +261,7 @@ impl ClobClient {
|
|||||||
|
|
||||||
/// Set API credentials
|
/// Set API credentials
|
||||||
pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
|
pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
|
||||||
self.api_creds = Some(api_creds);
|
self.api_creds = Some(PreparedApiCredentials::new(api_creds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start background keep-alive to maintain warm connection
|
/// Start background keep-alive to maintain warm connection
|
||||||
|
|||||||
Reference in New Issue
Block a user