-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.py
More file actions
105 lines (84 loc) · 3.59 KB
/
auth.py
File metadata and controls
105 lines (84 loc) · 3.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""Convenience signer wrappers and auth-state helpers.
Provides :class:`LocalSigner` - a thin wrapper around ``eth_account.Account``
that satisfies the SDK's :class:`~hip4.types.hl.Signer` protocol. Users with
their own signing infrastructure (hardware wallets, KMS, custodian APIs) can
instead implement the ``Signer`` protocol directly.
"""
from __future__ import annotations
from typing import Any, Dict, List, Mapping, Optional
from eth_account import Account
from eth_account.messages import encode_typed_data
from hip4.types.hl import HLSignature
__all__ = ["LocalSigner"]
class LocalSigner:
"""A :class:`~hip4.types.hl.Signer` backed by an in-memory private key.
Example::
signer = LocalSigner("0xYOUR_PRIVATE_KEY_HEX")
# Pass to HIP4Adapter / sign_l1_action / etc.
Use this for development and tests. For production, prefer a hardware
wallet or KMS-backed signer that implements the ``Signer`` protocol.
"""
__slots__ = ("_account",)
def __init__(self, private_key: str) -> None:
self._account = Account.from_key(private_key)
@classmethod
def create(cls) -> "LocalSigner":
"""Generate a fresh signer with a random private key."""
acct = Account.create()
return cls(acct.key.hex())
@property
def private_key(self) -> str:
"""Hex-encoded private key (with ``0x`` prefix)."""
return self._account.key.hex()
def get_address(self) -> str:
"""Return the lowercase 0x-prefixed Ethereum address."""
return self._account.address.lower()
def sign_typed_data(
self,
domain: Mapping[str, Any],
types: Mapping[str, List[Dict[str, str]]],
value: Mapping[str, Any],
) -> HLSignature:
"""Sign an EIP-712 typed-data payload.
Returns ``{"r", "s", "v"}`` matching the HL on-wire shape - no further
normalization is required.
"""
full_types: Dict[str, List[Dict[str, str]]] = {
"EIP712Domain": _domain_type_descriptor(domain),
**{k: list(v) for k, v in types.items()},
}
primary_type = next(iter(types))
signable = encode_typed_data(
full_message={
"types": full_types,
"primaryType": primary_type,
"domain": dict(domain),
"message": dict(value),
},
)
signed = self._account.sign_message(signable)
# eth_account exposes r, s, v directly as ints / int.
return {
"r": "0x" + signed.r.to_bytes(32, "big").hex(),
"s": "0x" + signed.s.to_bytes(32, "big").hex(),
"v": int(signed.v),
}
def _domain_type_descriptor(domain: Mapping[str, Any]) -> List[Dict[str, str]]:
"""Build the ``EIP712Domain`` type descriptor matching the keys present in *domain*.
The EIP-712 spec requires ``EIP712Domain`` in the types map, listing only
the fields actually used by the domain. We derive that here from whichever
of ``name``, ``version``, ``chainId``, ``verifyingContract``, ``salt``
appear in *domain*.
"""
descriptor: List[Dict[str, str]] = []
if "name" in domain:
descriptor.append({"name": "name", "type": "string"})
if "version" in domain:
descriptor.append({"name": "version", "type": "string"})
if "chainId" in domain:
descriptor.append({"name": "chainId", "type": "uint256"})
if "verifyingContract" in domain:
descriptor.append({"name": "verifyingContract", "type": "address"})
if "salt" in domain:
descriptor.append({"name": "salt", "type": "bytes32"})
return descriptor