df_hyperliquid_core/account/keys/
signer.rs1#[cfg(feature = "sign")]
6use std::fmt;
7use std::future::Future;
8
9#[cfg(feature = "sign")]
10use k256::ecdsa::{RecoveryId, Signature as CurveSignature, SigningKey, VerifyingKey};
11use serde::{Serialize, Serializer, ser::SerializeStruct};
12#[cfg(feature = "sign")]
13use zeroize::{ZeroizeOnDrop, Zeroizing};
14
15use crate::utils::address::Address;
16#[cfg(feature = "sign")]
17use crate::utils::hex;
18use crate::utils::json::Json;
19use crate::utils::sign::eip712::{Domain, TypedStruct};
20#[cfg(feature = "sign")]
21use crate::utils::sign::keccak::keccak256;
22
23const V_OFFSET: u8 = 27;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Signature {
28 r: [u8; 32],
29 s: [u8; 32],
30 v: u8,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
34pub enum SignError {
35 #[error("the key could not sign this digest")]
36 Sign,
37 #[error("a signature is 65 bytes of hex")]
38 NotASignature,
39 #[error("the signature components are not a valid curve point")]
40 Malformed,
41 #[error("no public key recovers from this signature")]
42 Unrecoverable,
43 #[error("`{0}` is not an Ethereum recovery parameter; expected 27 or 28")]
44 BadV(u8),
45 #[error("the signature is from {recovered}, not {expected}")]
46 WrongSigner { expected: Address, recovered: Address },
47 #[error("the key is not a valid secp256k1 private key")]
48 NotAKey,
49}
50
51impl Signature {
52 #[must_use]
53 pub const fn from_parts(r: [u8; 32], s: [u8; 32], v: u8) -> Self {
54 Signature { r, s, v }
55 }
56
57 #[cfg(feature = "sign")]
62 pub fn from_hex(text: &str) -> Result<Signature, SignError> {
63 let bytes: [u8; 65] = hex::decode_array(text).map_err(|_| SignError::NotASignature)?;
64 let mut r = [0u8; 32];
65 let mut s = [0u8; 32];
66 r.copy_from_slice(&bytes[..32]);
67 s.copy_from_slice(&bytes[32..64]);
68 let parity = match bytes[64] {
69 0 | 27 => 0,
70 1 | 28 => 1,
71 other => return Err(SignError::BadV(other)),
72 };
73 let compact = Signature { r, s, v: V_OFFSET + parity }.to_compact();
74 let curve = CurveSignature::from_slice(&compact).map_err(|_| SignError::Malformed)?;
75 Ok(match curve.normalize_s() {
76 None => Signature { r, s, v: V_OFFSET + parity },
77 Some(low) => {
78 s.copy_from_slice(&low.to_bytes()[32..]);
79 Signature { r, s, v: V_OFFSET + (parity ^ 1) }
80 }
81 })
82 }
83
84 #[must_use]
85 pub const fn r(&self) -> &[u8; 32] {
86 &self.r
87 }
88
89 #[must_use]
90 pub const fn s(&self) -> &[u8; 32] {
91 &self.s
92 }
93
94 #[must_use]
96 pub const fn v(&self) -> u8 {
97 self.v
98 }
99
100 #[must_use]
101 pub const fn recovery_id(&self) -> Option<u8> {
102 self.v.checked_sub(V_OFFSET)
103 }
104
105 #[must_use]
106 pub fn r_hex(&self) -> String {
107 crate::utils::hex::encode_prefixed(&self.r)
108 }
109
110 #[must_use]
111 pub fn s_hex(&self) -> String {
112 crate::utils::hex::encode_prefixed(&self.s)
113 }
114
115 #[must_use]
116 pub fn to_compact(&self) -> [u8; 64] {
117 let mut bytes = [0u8; 64];
118 bytes[..32].copy_from_slice(&self.r);
119 bytes[32..].copy_from_slice(&self.s);
120 bytes
121 }
122
123 #[must_use]
126 pub fn to_json(&self) -> String {
127 self.json().compact()
128 }
129
130 pub(crate) fn json(&self) -> Json {
131 Json::Object(vec![
132 ("r".to_owned(), Json::String(self.r_hex())),
133 ("s".to_owned(), Json::String(self.s_hex())),
134 ("v".to_owned(), Json::Number(self.v.to_string())),
135 ])
136 }
137}
138
139impl Serialize for Signature {
140 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
141 let mut signature = serializer.serialize_struct("Signature", 3)?;
142 signature.serialize_field("r", &self.r_hex())?;
143 signature.serialize_field("s", &self.s_hex())?;
144 signature.serialize_field("v", &self.v)?;
145 signature.end()
146 }
147}
148
149#[cfg(feature = "sign")]
151pub fn recover(digest: &[u8; 32], signature: &Signature) -> Result<Address, SignError> {
152 let recovery_id = signature.recovery_id().and_then(RecoveryId::from_byte).ok_or(SignError::BadV(signature.v))?;
153 let curve_signature = CurveSignature::from_slice(&signature.to_compact()).map_err(|_| SignError::Malformed)?;
154 let key = VerifyingKey::recover_from_prehash(digest, &curve_signature, recovery_id)
155 .map_err(|_| SignError::Unrecoverable)?;
156 Ok(address_of_public_key(&key))
157}
158
159#[cfg(feature = "sign")]
162pub fn verify(digest: &[u8; 32], signature: &Signature, expected: Address) -> Result<(), SignError> {
163 let recovered = recover(digest, signature)?;
164 match recovered == expected {
165 true => Ok(()),
166 false => Err(SignError::WrongSigner { expected, recovered }),
167 }
168}
169
170#[cfg(feature = "sign")]
172#[must_use]
173pub fn address_of_public_key(key: &VerifyingKey) -> Address {
174 let point = key.to_encoded_point(false);
175 let coordinates = &point.as_bytes()[1..];
176 let hash = keccak256(coordinates);
177 let mut bytes = [0u8; 20];
178 bytes.copy_from_slice(&hash[12..]);
179 Address::from_bytes(bytes)
180}
181
182pub trait Signer {
186 fn address(&self) -> Address;
187
188 fn sign_typed(&self, domain: &Domain, data: &TypedStruct<'_>)
189 -> impl Future<Output = Result<Signature, SignError>>;
190}
191
192#[cfg(feature = "sign")]
194const _: fn() = || {
195 fn wiped_on_drop<T: ZeroizeOnDrop>() {}
196 wiped_on_drop::<SigningKey>();
197};
198
199#[cfg(feature = "sign")]
202pub struct LocalKey {
203 key: SigningKey,
204 address: Address,
205}
206
207#[cfg(feature = "sign")]
208impl LocalKey {
209 pub fn from_bytes(secret: &[u8; 32]) -> Result<Self, SignError> {
210 let key = SigningKey::from_bytes(secret.into()).map_err(|_| SignError::NotAKey)?;
211 let address = address_of_public_key(key.verifying_key());
212 Ok(LocalKey { key, address })
213 }
214
215 pub fn from_hex(secret: &str) -> Result<Self, SignError> {
217 let mut bytes = Zeroizing::new([0u8; 32]);
218 hex::decode_into(secret, bytes.as_mut_slice()).map_err(|_| SignError::NotAKey)?;
219 LocalKey::from_bytes(&bytes)
220 }
221
222 #[must_use]
223 pub const fn address(&self) -> Address {
224 self.address
225 }
226
227 pub fn sign_digest(&self, digest: &[u8; 32]) -> Result<Signature, SignError> {
229 let (signature, recovery_id): (CurveSignature, RecoveryId) =
230 self.key.sign_prehash_recoverable(digest).map_err(|_| SignError::Sign)?;
231 let bytes = signature.to_bytes();
232 let mut r = [0u8; 32];
233 let mut s = [0u8; 32];
234 r.copy_from_slice(&bytes[..32]);
235 s.copy_from_slice(&bytes[32..]);
236 Ok(Signature { r, s, v: recovery_id.to_byte() + V_OFFSET })
237 }
238}
239
240#[cfg(feature = "sign")]
241impl Signer for LocalKey {
242 fn address(&self) -> Address {
243 self.address
244 }
245
246 fn sign_typed(
247 &self,
248 domain: &Domain,
249 data: &TypedStruct<'_>,
250 ) -> impl Future<Output = Result<Signature, SignError>> {
251 std::future::ready(self.sign_digest(&data.digest(domain)))
252 }
253}
254
255#[cfg(feature = "sign")]
256impl fmt::Debug for LocalKey {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 f.debug_struct("LocalKey").field("address", &self.address.to_hex()).finish_non_exhaustive()
259 }
260}
261
262#[cfg(feature = "sign")]
264impl ZeroizeOnDrop for LocalKey {}
265
266#[cfg(test)]
267#[cfg(feature = "sign")]
268#[path = "tests/signer.rs"]
269mod tests;