mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-19 15:38:09 +00:00
fix: adjust signature encoding format
Resolves authentication flow edge case.
This commit is contained in:
Generated
-84
@@ -2650,7 +2650,6 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hmac",
|
"hmac",
|
||||||
"mockito",
|
"mockito",
|
||||||
"polymarket-rs-client",
|
|
||||||
"proptest",
|
"proptest",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -2671,28 +2670,6 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "polymarket-rs-client"
|
|
||||||
version = "0.1.1"
|
|
||||||
dependencies = [
|
|
||||||
"alloy-primitives",
|
|
||||||
"alloy-signer",
|
|
||||||
"alloy-signer-local",
|
|
||||||
"alloy-sol-types",
|
|
||||||
"anyhow",
|
|
||||||
"base64",
|
|
||||||
"hmac",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"reqwest",
|
|
||||||
"rust_decimal",
|
|
||||||
"serde",
|
|
||||||
"serde-json-fmt",
|
|
||||||
"serde_json",
|
|
||||||
"sha1",
|
|
||||||
"sha2",
|
|
||||||
"ureq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -3206,9 +3183,7 @@ version = "0.23.35"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
|
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"subtle",
|
||||||
@@ -3360,17 +3335,6 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-json-fmt"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a4a33b7a5f52a26d520099339add40c48baf2e5ada194c8cc1b18cafa2b5e419"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"smartstring",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -3541,17 +3505,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smartstring"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"static_assertions",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.10"
|
version = "0.5.10"
|
||||||
@@ -4181,25 +4134,6 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ureq"
|
|
||||||
version = "2.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"encoding_rs",
|
|
||||||
"flate2",
|
|
||||||
"log",
|
|
||||||
"once_cell",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"url",
|
|
||||||
"webpki-roots 0.26.11",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.7"
|
version = "2.5.7"
|
||||||
@@ -4390,24 +4324,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki-roots"
|
|
||||||
version = "0.26.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
|
||||||
dependencies = [
|
|
||||||
"webpki-roots 1.0.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki-roots"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
|
|
||||||
dependencies = [
|
|
||||||
"rustls-pki-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|||||||
@@ -291,17 +291,6 @@ let client = ClobClient::new("https://clob.polymarket.com");
|
|||||||
- **Internet**: 5 connections, 60s timeouts, full compression (bandwidth optimization)
|
- **Internet**: 5 connections, 60s timeouts, full compression (bandwidth optimization)
|
||||||
- **Standard**: 10 connections, 30s timeouts, balanced settings
|
- **Standard**: 10 connections, 30s timeouts, balanced settings
|
||||||
|
|
||||||
### Real-World Trading Impact
|
|
||||||
|
|
||||||
In a high-frequency trading environment, these optimizations compound:
|
|
||||||
|
|
||||||
- **Microsecond advantages**: 11% improvement on every API call adds up over thousands of requests
|
|
||||||
- **Cold start elimination**: 70% faster warm connections critical for trading session startup
|
|
||||||
- **Batch efficiency**: 200% improvement enables real-time multi-market monitoring
|
|
||||||
- **Fault tolerance**: Circuit breakers prevent trading halts during network issues
|
|
||||||
|
|
||||||
The combination of network optimizations with our computational advantages (fixed-point arithmetic, zero-allocation updates) creates a multiplicative performance benefit for latency-sensitive applications.
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
|||||||
+36
-8
@@ -113,6 +113,9 @@ pub fn sign_order_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build HMAC signature for L2 authentication
|
/// Build HMAC signature for L2 authentication
|
||||||
|
///
|
||||||
|
/// Performs cryptographic message authentication using SHA-256 with
|
||||||
|
/// specialized key derivation and encoding schemes for API compliance.
|
||||||
pub fn build_hmac_signature<T>(
|
pub fn build_hmac_signature<T>(
|
||||||
secret: &str,
|
secret: &str,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
@@ -123,10 +126,18 @@ pub fn build_hmac_signature<T>(
|
|||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
|
// Apply inverse transformation to key material for digest initialization
|
||||||
|
// This ensures compatibility with the expected cryptographic envelope format
|
||||||
|
let decoded_secret = base64::engine::general_purpose::URL_SAFE
|
||||||
|
.decode(secret)
|
||||||
|
.map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))?;
|
||||||
|
|
||||||
|
// Initialize MAC with transformed key material to maintain protocol coherence
|
||||||
|
let mut mac = Hmac::<Sha256>::new_from_slice(&decoded_secret)
|
||||||
.map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
|
.map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
|
||||||
|
|
||||||
// Build the message to sign: timestamp + method + path + body
|
// Construct canonical message representation for signature verification
|
||||||
|
// Message components are concatenated in strict order to preserve cryptographic binding
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -141,18 +152,29 @@ where
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Compute authentication tag over canonical message form
|
||||||
mac.update(message.as_bytes());
|
mac.update(message.as_bytes());
|
||||||
let result = mac.finalize();
|
let result = mac.finalize();
|
||||||
Ok(base64::engine::general_purpose::STANDARD.encode(result.into_bytes()))
|
|
||||||
|
// Apply URL-safe encoding transformation for transport layer compatibility
|
||||||
|
// This encoding scheme ensures proper signature validation across network boundaries
|
||||||
|
Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create L1 headers for authentication (using private key signature)
|
/// Create L1 headers for authentication (using private key signature)
|
||||||
|
///
|
||||||
|
/// Generates initial authentication envelope using elliptic curve cryptography
|
||||||
|
/// for establishing trusted communication channels with the distributed ledger API.
|
||||||
pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Result<Headers> {
|
pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Result<Headers> {
|
||||||
|
// Capture temporal context for replay prevention at protocol boundary
|
||||||
let timestamp = get_current_unix_time_secs().to_string();
|
let timestamp = get_current_unix_time_secs().to_string();
|
||||||
let nonce = nonce.unwrap_or(U256::ZERO);
|
let nonce = nonce.unwrap_or(U256::ZERO);
|
||||||
|
|
||||||
|
// Generate EIP-712 compliant signature for cryptographic proof of authority
|
||||||
let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;
|
let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;
|
||||||
let address = encode_prefixed(signer.address().as_slice());
|
let address = encode_prefixed(signer.address().as_slice());
|
||||||
|
|
||||||
|
// Assemble primary authentication header set with identity binding
|
||||||
Ok(HashMap::from([
|
Ok(HashMap::from([
|
||||||
(POLY_ADDR_HEADER, address),
|
(POLY_ADDR_HEADER, address),
|
||||||
(POLY_SIG_HEADER, signature),
|
(POLY_SIG_HEADER, signature),
|
||||||
@@ -162,6 +184,9 @@ pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option<U256>) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create L2 headers for API calls (using API key and HMAC)
|
/// Create L2 headers for API calls (using API key and HMAC)
|
||||||
|
///
|
||||||
|
/// Assembles authentication header set with computed signature digest
|
||||||
|
/// 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: &ApiCredentials,
|
||||||
@@ -172,12 +197,15 @@ pub fn create_l2_headers<T>(
|
|||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
|
// Extract identity from signing authority for header binding
|
||||||
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();
|
||||||
|
|
||||||
|
// Generate cryptographic authenticator using temporal and message context
|
||||||
let hmac_signature =
|
let hmac_signature =
|
||||||
build_hmac_signature(&api_creds.secret, timestamp, method, req_path, body)?;
|
build_hmac_signature(&api_creds.secret, timestamp, method, req_path, body)?;
|
||||||
|
|
||||||
|
// 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),
|
||||||
@@ -200,14 +228,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_hmac_signature() {
|
fn test_hmac_signature() {
|
||||||
let result =
|
let result =
|
||||||
build_hmac_signature::<String>("test_secret", 1234567890, "GET", "/test", None);
|
build_hmac_signature::<String>("dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", 1234567890, "GET", "/test", None);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hmac_signature_with_body() {
|
fn test_hmac_signature_with_body() {
|
||||||
let body = r#"{"test": "data"}"#;
|
let body = r#"{"test": "data"}"#;
|
||||||
let result = build_hmac_signature("test_secret", 1234567890, "POST", "/orders", Some(body));
|
let result = build_hmac_signature("dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", 1234567890, "POST", "/orders", Some(body));
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let signature = result.unwrap();
|
let signature = result.unwrap();
|
||||||
assert!(!signature.is_empty());
|
assert!(!signature.is_empty());
|
||||||
@@ -215,7 +243,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hmac_signature_consistency() {
|
fn test_hmac_signature_consistency() {
|
||||||
let secret = "test_secret";
|
let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
|
||||||
let timestamp = 1234567890;
|
let timestamp = 1234567890;
|
||||||
let method = "GET";
|
let method = "GET";
|
||||||
let path = "/test";
|
let path = "/test";
|
||||||
@@ -229,7 +257,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hmac_signature_different_inputs() {
|
fn test_hmac_signature_different_inputs() {
|
||||||
let secret = "test_secret";
|
let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
|
||||||
let timestamp = 1234567890;
|
let timestamp = 1234567890;
|
||||||
|
|
||||||
let sig1 = build_hmac_signature::<String>(secret, timestamp, "GET", "/test", None).unwrap();
|
let sig1 = build_hmac_signature::<String>(secret, timestamp, "GET", "/test", None).unwrap();
|
||||||
@@ -292,7 +320,7 @@ mod tests {
|
|||||||
|
|
||||||
let api_creds = ApiCredentials {
|
let api_creds = ApiCredentials {
|
||||||
api_key: "test_key".to_string(),
|
api_key: "test_key".to_string(),
|
||||||
secret: "test_secret".to_string(),
|
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
||||||
passphrase: "test_passphrase".to_string(),
|
passphrase: "test_passphrase".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -835,6 +835,8 @@ impl ClobClient {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
||||||
|
|
||||||
|
// Owner field must reference the credential principal identifier
|
||||||
|
// to maintain consistency with the authentication context layer
|
||||||
let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);
|
let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);
|
||||||
|
|
||||||
let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
|
let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
|
||||||
@@ -2268,7 +2270,6 @@ mod tests {
|
|||||||
let api_creds = result.unwrap();
|
let api_creds = result.unwrap();
|
||||||
assert_eq!(api_creds.api_key, "test-api-key-123");
|
assert_eq!(api_creds.api_key, "test-api-key-123");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_order_books_batch() {
|
async fn test_get_order_books_batch() {
|
||||||
let mut server = Server::new_async().await;
|
let mut server = Server::new_async().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user