py-near 1.1.50__tar.gz → 1.1.51__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {py_near-1.1.50 → py_near-1.1.51}/PKG-INFO +9 -5
  2. {py_near-1.1.50 → py_near-1.1.51}/pyproject.toml +22 -10
  3. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/account.py +10 -11
  4. py_near-1.1.51/src/py_near/mpc/__init__.py +0 -0
  5. py_near-1.1.51/src/py_near/mpc/__pycache__/__init__.cpython-311.pyc +0 -0
  6. py_near-1.1.51/src/py_near/mpc/__pycache__/models.cpython-311.pyc +0 -0
  7. py_near-1.1.51/src/py_near/mpc/__pycache__/wallet.cpython-311.pyc +0 -0
  8. py_near-1.1.51/src/py_near/mpc/auth/__init__.py +0 -0
  9. py_near-1.1.51/src/py_near/mpc/auth/__pycache__/__init__.cpython-311.pyc +0 -0
  10. py_near-1.1.51/src/py_near/mpc/auth/__pycache__/auth_2fa.cpython-311.pyc +0 -0
  11. py_near-1.1.51/src/py_near/mpc/auth/__pycache__/base.cpython-311.pyc +0 -0
  12. py_near-1.1.51/src/py_near/mpc/auth/__pycache__/default_auth.cpython-311.pyc +0 -0
  13. py_near-1.1.51/src/py_near/mpc/auth/__pycache__/keys_auth.cpython-311.pyc +0 -0
  14. py_near-1.1.51/src/py_near/mpc/auth/auth_2fa.py +97 -0
  15. py_near-1.1.51/src/py_near/mpc/auth/base.py +23 -0
  16. py_near-1.1.51/src/py_near/mpc/auth/core.py +0 -0
  17. py_near-1.1.51/src/py_near/mpc/auth/default_auth.py +29 -0
  18. py_near-1.1.51/src/py_near/mpc/auth/keys_auth.py +83 -0
  19. py_near-1.1.51/src/py_near/mpc/models.py +32 -0
  20. py_near-1.1.51/src/py_near/mpc/wallet.py +237 -0
  21. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/providers.py +6 -3
  22. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/transactions.py +5 -6
  23. {py_near-1.1.50 → py_near-1.1.51}/LICENSE +0 -0
  24. {py_near-1.1.50 → py_near-1.1.51}/README.md +0 -0
  25. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/__init__.py +0 -0
  26. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/constants.py +0 -0
  27. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__init__.py +0 -0
  28. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/__init__.cpython-311.pyc +0 -0
  29. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/core.cpython-311.pyc +0 -0
  30. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/fts.cpython-311.pyc +0 -0
  31. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/core.py +0 -0
  32. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__init__.py +0 -0
  33. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/__init__.cpython-311.pyc +0 -0
  34. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/async_client.cpython-311.pyc +0 -0
  35. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/exceptions.cpython-311.pyc +0 -0
  36. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/models.cpython-311.pyc +0 -0
  37. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/async_client.py +0 -0
  38. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/exceptions.py +0 -0
  39. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/models.py +0 -0
  40. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/fts.py +0 -0
  41. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/__init__.py +0 -0
  42. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/async_client.py +0 -0
  43. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/exceptions.py +0 -0
  44. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/models.py +0 -0
  45. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__init__.py +0 -0
  46. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/__init__.cpython-311.pyc +0 -0
  47. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/async_client.cpython-311.pyc +0 -0
  48. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/exceptions.cpython-311.pyc +0 -0
  49. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/models.cpython-311.pyc +0 -0
  50. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/async_client.py +0 -0
  51. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/exceptions.py +0 -0
  52. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/models.py +0 -0
  53. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__init__.py +0 -0
  54. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/__init__.cpython-311.pyc +0 -0
  55. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/exceptions.cpython-311.pyc +0 -0
  56. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/provider.cpython-311.pyc +0 -0
  57. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/exceptions.py +0 -0
  58. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/provider.py +0 -0
  59. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/models.py +0 -0
  60. {py_near-1.1.50 → py_near-1.1.51}/src/py_near/utils.py +0 -0
@@ -1,20 +1,24 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.50
3
+ Version: 1.1.51
4
4
  Summary: Pretty simple and fully asynchronous framework for working with NEAR blockchain
5
5
  Author: pvolnov
6
6
  Author-email: petr@herewallet.app
7
- Requires-Python: >=3.7,<4.0
7
+ Requires-Python: >=3.8,<3.12
8
8
  Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.7
10
9
  Classifier: Programming Language :: Python :: 3.8
11
10
  Classifier: Programming Language :: Python :: 3.9
12
11
  Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: ed25519 (==1.5)
13
+ Requires-Dist: base58 (>=2.1.1,<3.0.0)
14
+ Requires-Dist: eth-keys (>=0.6.1,<0.7.0)
15
+ Requires-Dist: eth-utils (>=5.2.0,<6.0.0)
16
+ Requires-Dist: frozenlist (>=1.4.1,<2.0.0)
16
17
  Requires-Dist: httpx (==0.26.0)
18
+ Requires-Dist: loguru (>=0.7.2,<0.8.0)
17
19
  Requires-Dist: py-near-primitives (==0.2.3)
20
+ Requires-Dist: pydantic (>=2.6.2,<3.0.0)
21
+ Requires-Dist: pynacl (>=1.5.0,<2.0.0)
18
22
  Description-Content-Type: text/markdown
19
23
 
20
24
  # py-near
@@ -1,33 +1,38 @@
1
1
  [tool.poetry]
2
2
  name = "py-near"
3
- version = "1.1.50"
3
+ version = "1.1.51"
4
4
  description="Pretty simple and fully asynchronous framework for working with NEAR blockchain"
5
5
  authors = ["pvolnov <petr@herewallet.app>"]
6
6
  readme = "README.md"
7
7
 
8
8
  [tool.poetry.dependencies]
9
- python = "^3.7"
10
- ed25519 = "1.5"
9
+ base58 = "^2.1.1"
10
+ python = ">=3.8,<3.12"
11
11
  httpx = "0.26.0"
12
12
  py-near-primitives = "0.2.3"
13
+ frozenlist = "^1.4.1"
14
+ pynacl = "^1.5.0"
15
+ loguru = "^0.7.2"
16
+ pydantic = "^2.6.2"
17
+ eth-utils = "^5.2.0"
18
+ eth-keys = "^0.6.1"
13
19
 
14
- [tool.poetry.dev-dependencies]
20
+ [tool.poetry.group.dev.dependencies]
15
21
  black = "^21.12b0"
16
22
  mypy = "^0.930"
17
- pytest = "^6.2.5"
23
+ pytest = ">=7.0,<8.0"
18
24
  flake8 = "^4.0.1"
19
25
  Sphinx = "^4.3.2"
20
26
  sphinx-rtd-theme = "^1.0.0"
21
- base58 = "^2.1.1"
27
+ pytest-asyncio = "^0.23.5"
22
28
 
23
29
  [project]
24
30
  name = "py-near"
25
31
  version = "1.1.50"
26
- description = "Pretty simple and fully asynchronous framework for working with NEAR blockchaink"
32
+ description = "Pretty simple and fully asynchronous framework for working with NEAR blockchain"
27
33
  authors = [ {name = "pvolnov", email = "petr@herewallet.app"} ]
28
- requires-python = ">=3.7"
29
34
  license = {file = "LICENSE"}
30
- dependencies=["base58", "ed25519", "httpx", "py-near-primitives", "pydantic"]
35
+
31
36
  keywords = ["python", "near", "async"]
32
37
  classifiers = [
33
38
  "Programming Language :: Python :: 3",
@@ -53,4 +58,11 @@ documentation = "https://py-near.readthedocs.io/en/latest"
53
58
  repository = "https://github.com/pvolnov/py-near"
54
59
  changelog = "https://github.com/pvolnov/py-near/blob/main/CHANGELOG.md"
55
60
  funding = "https://opencollective.com/py-near"
56
- twitter = "https://twitter.com/p_volnov"
61
+ twitter = "https://twitter.com/p_volnov"
62
+
63
+ [tool.pytest.ini_options]
64
+ addopts = "--capture=no --tb=native"
65
+ testpaths = [
66
+ "tests",
67
+ ]
68
+ asyncio_mode = "auto"
@@ -13,7 +13,7 @@ else:
13
13
  from collections import MutableSet
14
14
 
15
15
  import base58
16
- import ed25519
16
+ from nacl import signing, encoding
17
17
  from loguru import logger
18
18
  from py_near_primitives import DelegateAction
19
19
 
@@ -85,10 +85,8 @@ class Account(object):
85
85
  except UnicodeEncodeError:
86
86
  logger.error(f"Can't decode private key {pk[:10]}")
87
87
  continue
88
- private_key = ed25519.SigningKey(pk)
89
- public_key = base58.b58encode(
90
- private_key.get_verifying_key().to_bytes()
91
- ).decode("utf-8")
88
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
89
+ public_key = private_key.verify_key
92
90
  self._signer_by_pk[public_key] = pk
93
91
  self._free_signers.put_nowait(pk)
94
92
  self._signers.append(pk)
@@ -206,11 +204,12 @@ class Account(object):
206
204
  if pk is None:
207
205
  pk = self._signers[0]
208
206
 
209
- private_key = ed25519.SigningKey(pk)
210
- public_key = private_key.get_verifying_key()
207
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
208
+ public_key = private_key.verify_key
209
+ public_key.to_curve25519_public_key()
211
210
 
212
211
  resp = await self._provider.get_access_key(
213
- self.account_id, base58.b58encode(public_key.to_bytes()).decode("utf8")
212
+ self.account_id, base58.b58encode(public_key.encode()).decode("utf8")
214
213
  )
215
214
  if "error" in resp:
216
215
  raise ValueError(resp["error"])
@@ -449,8 +448,8 @@ class Account(object):
449
448
  access_key = await self.get_access_key(pk)
450
449
  await self._update_last_block_hash()
451
450
 
452
- private_key = ed25519.SigningKey(pk)
453
- verifying_key = private_key.get_verifying_key()
451
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
452
+ verifying_key = private_key.verify_key
454
453
  return DelegateActionModel(
455
454
  sender_id=self.account_id,
456
455
  receiver_id=receiver_id,
@@ -479,7 +478,7 @@ class Account(object):
479
478
  if public_key not in self._signer_by_pk:
480
479
  raise ValueError(f"Public key {public_key} not found in signer list")
481
480
 
482
- private_key = ed25519.SigningKey(self._signer_by_pk[public_key])
481
+ private_key = signing.SigningKey(self._signer_by_pk[public_key], encoder=encoding.RawEncoder)
483
482
  sign = private_key.sign(nep461_hash)
484
483
  return base58.b58encode(sign).decode("utf-8")
485
484
 
File without changes
File without changes
@@ -0,0 +1,97 @@
1
+ import datetime
2
+ from hashlib import sha256
3
+ from typing import Optional
4
+
5
+ import base58
6
+ from nacl import signing
7
+ from pydantic import BaseModel
8
+
9
+ from py_near.account import Account
10
+ from py_near.mpc.auth.base import AuthContract
11
+
12
+
13
+ class Wallet2FA(BaseModel):
14
+ wallet_id: str
15
+ public_key: str
16
+ delay_to_remove: int
17
+ cancellation_at: int
18
+ conditions: Optional[str] = None
19
+
20
+ @classmethod
21
+ def build(cls, data: dict, wallet_id):
22
+ return cls(
23
+ wallet_id=wallet_id,
24
+ public_key=data["public_key"],
25
+ cancellation_at=data["cancellation_at"],
26
+ delay_to_remove=data["delay_to_remove"],
27
+ conditions=data.get("conditions"),
28
+ )
29
+
30
+
31
+ class AuthContract2FA(AuthContract):
32
+ root_pk: signing.SigningKey
33
+ contract_id: str = "2fa.auth.hot.tg"
34
+
35
+ def __init__(self, root_pk: signing.SigningKey, near_account: Account = None):
36
+ self.root_pk = root_pk
37
+ self.near_account = near_account
38
+ super().__init__()
39
+
40
+ def generate_user_payload(self, msg_hash: bytes):
41
+ auth_signature = base58.b58encode(self.root_pk.sign(msg_hash).signature).decode(
42
+ "utf-8"
43
+ )
44
+ return auth_signature
45
+
46
+ async def get_2fa_for_wallet(self, wallet_id: str) -> Optional[Wallet2FA]:
47
+ if not self.near_account:
48
+ raise ValueError("Near account is required")
49
+ res = (
50
+ await self.near_account.view_function(
51
+ self.contract_id,
52
+ "get_wallet_2fa",
53
+ {
54
+ "wallet_id": wallet_id,
55
+ },
56
+ )
57
+ ).result
58
+ if res:
59
+ return Wallet2FA.build(res, wallet_id)
60
+
61
+ async def cancel_2fa(
62
+ self, user_pk: signing.SigningKey, wallet_id: str, rule_id: int = 0
63
+ ):
64
+ if not self.near_account:
65
+ raise ValueError("Near account is required")
66
+ two_fa = await self.get_2fa_for_wallet(wallet_id)
67
+ ts = int(datetime.datetime.utcnow().timestamp() * 10**9)
68
+
69
+ if two_fa.cancellation_at == 0:
70
+ msg_hash = sha256(f"CANCEL_2FA:{wallet_id}:{ts}".encode("utf-8")).digest()
71
+ auth_signature = base58.b58encode(user_pk.sign(msg_hash).signature).decode(
72
+ "utf-8"
73
+ )
74
+ return await self.near_account.function_call(
75
+ self.contract_id,
76
+ "cancel_2fa",
77
+ {
78
+ "wallet_id": wallet_id,
79
+ "user_signature": auth_signature,
80
+ "timestamp": ts,
81
+ },
82
+ included=True,
83
+ )
84
+ else:
85
+ if two_fa.cancellation_at < ts:
86
+ raise ValueError(
87
+ f"2FA already cancelled at {datetime.datetime.fromtimestamp(two_fa.cancellation_at / 10**9)}"
88
+ )
89
+ return await self.near_account.function_call(
90
+ self.contract_id,
91
+ "complete_cancel_2fa",
92
+ {
93
+ "wallet_id": wallet_id,
94
+ "rule_id": rule_id,
95
+ },
96
+ included=True,
97
+ )
@@ -0,0 +1,23 @@
1
+ from py_near.account import Account
2
+ from py_near.mpc.models import WalletAccessModel
3
+
4
+
5
+ class AuthContract:
6
+ contract_id: str
7
+
8
+ def __init__(self, near_account: Account = None):
9
+ self.near_account = near_account
10
+
11
+ async def grant_access(
12
+ self,
13
+ wallet_id: str,
14
+ access: WalletAccessModel,
15
+ near_account,
16
+ wallet_auth_method=0,
17
+ ):
18
+ raise NotImplementedError("Grant access is not implemented")
19
+
20
+ async def revoke_assess(
21
+ self, wallet_id: str, near_account: Account, access_id: int
22
+ ):
23
+ raise NotImplementedError("Revoke access is not implemented")
File without changes
@@ -0,0 +1,29 @@
1
+ import json
2
+
3
+ import base58
4
+ from nacl import signing
5
+
6
+ from py_near.mpc.auth.base import AuthContract
7
+
8
+
9
+ class DefaultAuthContract(AuthContract):
10
+ root_pk: signing.SigningKey
11
+ contract_id: str = "default.auth.hot.tg"
12
+
13
+ def __init__(self, root_pk: signing.SigningKey):
14
+ self.root_pk = root_pk
15
+ super().__init__()
16
+
17
+ def generate_user_payload(self, msg_hash: bytes):
18
+ auth_signature = base58.b58encode(self.root_pk.sign(msg_hash).signature).decode(
19
+ "utf-8"
20
+ )
21
+ return json.dumps(
22
+ dict(
23
+ signature=auth_signature,
24
+ public_key=base58.b58encode(
25
+ bytes(self.root_pk.verify_key.encode())
26
+ ).decode(),
27
+ )
28
+ )
29
+
@@ -0,0 +1,83 @@
1
+ import json
2
+ from hashlib import sha256
3
+ from typing import List
4
+
5
+ import base58
6
+ from nacl import signing
7
+
8
+ from py_near.account import Account
9
+ from py_near.mpc.auth.base import AuthContract
10
+ from py_near.mpc.models import WalletAccessModel
11
+
12
+
13
+ class KeysAuthContract(AuthContract):
14
+ auth_keys: List[signing.SigningKey]
15
+ auth_method: int
16
+ contract_id: str = "keys.auth.hot.tg"
17
+
18
+ def __init__(
19
+ self,
20
+ auth_keys: List[signing.SigningKey],
21
+ auth_method=0,
22
+ near_account: Account = None,
23
+ ):
24
+ self.auth_keys = auth_keys
25
+ self.auth_method = auth_method
26
+ super().__init__(near_account)
27
+
28
+ def generate_user_payload(self, msg_hash: bytes):
29
+ signatures = []
30
+ for key in self.auth_keys:
31
+ signatures.append(
32
+ base58.b58encode(key.sign(msg_hash).signature).decode("utf-8")
33
+ )
34
+ return json.dumps(
35
+ dict(
36
+ signatures=signatures,
37
+ auth_method=0,
38
+ )
39
+ )
40
+
41
+ async def grant_access(
42
+ self,
43
+ wallet_id: str,
44
+ access: WalletAccessModel,
45
+ near_account,
46
+ wallet_auth_method=0,
47
+ ):
48
+ access = access.model_dump()
49
+ proof_hash = sha256(
50
+ f"{json.dumps(access).replace(' ', '')}.{wallet_id}".encode("utf-8")
51
+ ).digest()
52
+ signatures = []
53
+ for pk in self.auth_keys:
54
+ signature = base58.b58encode(pk.sign(proof_hash).signature).decode("utf-8")
55
+ signatures.append(signature)
56
+
57
+ res = await near_account.function_call(
58
+ self.contract_id,
59
+ "add_wallet_auth_account_id",
60
+ {
61
+ "wallet_id": wallet_id,
62
+ "access": access,
63
+ "signatures": signatures,
64
+ "wallet_auth_method": wallet_auth_method,
65
+ "auth_method": self.auth_method,
66
+ },
67
+ included=True,
68
+ )
69
+ return res
70
+
71
+ async def get_rules(self, wallet_id: str):
72
+ if not self.near_account:
73
+ raise ValueError("Near account is required")
74
+ res = (
75
+ await self.near_account.view_function(
76
+ self.contract_id,
77
+ "get_wallet",
78
+ {
79
+ "wallet_id": wallet_id,
80
+ },
81
+ )
82
+ ).result
83
+ return res
@@ -0,0 +1,32 @@
1
+ from enum import Enum
2
+ from typing import Optional, List
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class CurveType(int, Enum):
8
+ SECP256K1 = 0
9
+ ED25519 = 1
10
+
11
+
12
+ _WALLET_REGISTER = "mpc.hot.tg"
13
+
14
+
15
+ class WalletAccessModel(BaseModel):
16
+ account_id: str
17
+ metadata: Optional[str] = None
18
+ chain_id: int
19
+ msg: Optional[str] = None
20
+
21
+
22
+ class WalletModel(BaseModel):
23
+ access_list: List[WalletAccessModel]
24
+ key_gen: int = 0
25
+
26
+ @classmethod
27
+ def build(cls, data: dict):
28
+ return cls(
29
+ access_list=[WalletAccessModel(**x) for x in data["access_list"]],
30
+ block_height=data["block_height"],
31
+ key_gen=data["key_gen"],
32
+ )
@@ -0,0 +1,237 @@
1
+ from hashlib import sha256
2
+ from typing import List, Optional
3
+
4
+ import base58
5
+ import httpx
6
+ from eth_keys.datatypes import Signature
7
+ from eth_keys.exceptions import BadSignature
8
+ from eth_utils import keccak
9
+ from loguru import logger
10
+ from nacl import encoding, signing
11
+
12
+ from py_near.account import Account
13
+ from py_near.mpc.auth.auth_2fa import AuthContract2FA
14
+ from py_near.mpc.auth.base import AuthContract
15
+ from py_near.mpc.auth.default_auth import DefaultAuthContract
16
+ from py_near.mpc.auth.keys_auth import KeysAuthContract
17
+ from py_near.mpc.models import WalletAccessModel, WalletModel, CurveType
18
+
19
+ _WALLET_REGISTER = "mpc.hot.tg"
20
+
21
+ AUTH_CLASS = {
22
+ "default.auth.hot.tg": DefaultAuthContract,
23
+ "keys.auth.hot.tg": KeysAuthContract,
24
+ "2fa.auth.hot.tg": AuthContract2FA,
25
+ }
26
+
27
+
28
+ class MPCWallet:
29
+ derive: bytes
30
+ near_account: Account
31
+ hot_rpc: str
32
+
33
+ def __init__(
34
+ self,
35
+ near_account: Account,
36
+ default_root_pk: Optional[bytes] = None,
37
+ hot_rpc: str = "https://rpc1.hotdao.ai",
38
+ derive: Optional[bytes] = None,
39
+ ):
40
+ """
41
+ :param near_account: Near account
42
+ :param derive: Derived key for wallet
43
+ :param hot_rpc: Hot RPC url
44
+ :param default_root_pk: Default auth key for HOT Protocol, if provided, derive = sha256(default_root_pk.public_key).digest()
45
+ """
46
+ self.near_account = near_account
47
+ if len(default_root_pk) != 32:
48
+ raise ValueError("Default root pk should be 32 bytes")
49
+ if default_root_pk:
50
+ self.default_root_pk = default_root_pk
51
+ private_key_obj = self.derive_private_key(0)
52
+ derive = sha256(private_key_obj.verify_key.encode()).digest()
53
+ elif derive is None:
54
+ logger.warning(
55
+ "Its recomended to use default_root_pk to generate derives, v0 wallets support will be deprecated"
56
+ )
57
+ raise ValueError("Derive is required")
58
+ self.derive = derive
59
+ self.hot_rpc = hot_rpc
60
+ self._client = httpx.AsyncClient()
61
+
62
+ def derive_private_key(self, gen=0):
63
+ private_key = self.default_root_pk
64
+ for _ in range(gen):
65
+ private_key = sha256(private_key).digest()
66
+ return signing.SigningKey(
67
+ private_key,
68
+ encoder=encoding.RawEncoder,
69
+ )
70
+
71
+ @property
72
+ def wallet_id(self):
73
+ return base58.b58encode(sha256(self.derive).digest()).decode("utf-8")
74
+
75
+ async def get_wallet(self):
76
+ wallet = await self.near_account.view_function(
77
+ _WALLET_REGISTER, "get_wallet", args={"wallet_id": self.wallet_id}
78
+ )
79
+ if wallet.result:
80
+ return WalletModel.build(wallet.result)
81
+
82
+ async def create_wallet_with_keys_auth(self, public_key: bytes, key_gen=1):
83
+ """
84
+ Create wallet with keys.auth.hot.tg auth method.
85
+ :param public_key: Public key for auth future signs on keys.auth.hot.tg
86
+ """
87
+ wallet = await self.get_wallet()
88
+ if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
89
+ raise ValueError(
90
+ "MPCWallet already exists with different auth method, please use another wallet_id"
91
+ )
92
+ root_pk = self.derive_private_key(0)
93
+ proof_hash = sha256(f"CREATE_WALLET:{self.wallet_id}".encode("utf-8")).digest()
94
+ signature = base58.b58encode(root_pk.sign(proof_hash).signature).decode("utf-8")
95
+
96
+ s = await self._client.post(
97
+ f"{self.hot_rpc}/create_wallet",
98
+ json=dict(
99
+ public_key=base58.b58encode(public_key).decode(),
100
+ wallet_id=self.wallet_id,
101
+ key_gen=key_gen,
102
+ signature=signature,
103
+ wallet_derive_public_key=base58.b58encode(
104
+ root_pk.verify_key.encode()
105
+ ).decode(),
106
+ ),
107
+ timeout=30,
108
+ )
109
+ return s.json()
110
+
111
+ async def get_ecdsa_public_key(self) -> bytes:
112
+ resp = (
113
+ await self._client.post(
114
+ f"{self.hot_rpc}/public_key",
115
+ json=dict(wallet_derive=base58.b58encode(self.derive).decode()),
116
+ timeout=10,
117
+ follow_redirects=True,
118
+ )
119
+ ).json()
120
+ return bytes.fromhex(resp["ecdsa"])
121
+
122
+ @property
123
+ def public_key(self):
124
+ # TODO Calculate with Rust Code
125
+ raise NotImplementedError("Public key calculation is not implemented yet")
126
+
127
+ async def sign_message(
128
+ self,
129
+ message_body: bytes,
130
+ curve_type: CurveType = CurveType.SECP256K1,
131
+ auth_methods: List[AuthContract] = None,
132
+ ):
133
+ if not self.default_root_pk:
134
+ raise ValueError("Default auth key is required")
135
+ wallet = await self.get_wallet()
136
+ user_payloads = []
137
+ msg_hash = keccak(message_body)
138
+ if len(auth_methods) != len(wallet.access_list):
139
+ raise ValueError("Auth methods count should be equal to wallet access list")
140
+
141
+ for auth_contract, auth_method in zip(auth_methods, wallet.access_list):
142
+ auth_class = AUTH_CLASS[auth_method.account_id]
143
+ if not isinstance(auth_contract, auth_class):
144
+ raise ValueError(
145
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
146
+ )
147
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
148
+
149
+ proof = {
150
+ "auth_id": 0,
151
+ "curve_type": curve_type,
152
+ "user_payloads": user_payloads,
153
+ "message_body": message_body.hex(),
154
+ }
155
+
156
+ resp = (
157
+ await self._client.post(
158
+ f"{self.hot_rpc}/sign_raw",
159
+ json=dict(
160
+ uid=self.derive.hex(),
161
+ message=msg_hash.hex(),
162
+ proof=proof,
163
+ key_type=curve_type,
164
+ ),
165
+ timeout=10,
166
+ follow_redirects=True,
167
+ )
168
+ ).json()
169
+ resp = resp["Ecdsa"]
170
+ r = int(resp["big_r"][2:], 16)
171
+ s = int(resp["signature"], 16)
172
+
173
+ pk = await self.get_ecdsa_public_key()
174
+ for v in (0, 1):
175
+ sig = Signature(vrs=(v, r, s))
176
+ recovered = sig.recover_public_key_from_msg_hash(msg_hash)
177
+ if recovered.to_compressed_bytes() == pk:
178
+ return (
179
+ r.to_bytes(32, "big") + s.to_bytes(32, "big") + bytes([v])
180
+ ).hex()
181
+
182
+ raise BadSignature("Cannot recover public key from signature")
183
+
184
+ async def add_new_access_rule(
185
+ self, access: WalletAccessModel, auth_contracts: List[AuthContract]
186
+ ):
187
+ wallet = await self.get_wallet()
188
+ access_json = access.model_dump_json()
189
+ message_body = f"ADD_AUTH_METHOD:{access_json}:{self.wallet_id}".encode("utf-8")
190
+ msg_hash = sha256(message_body).digest()
191
+ user_payloads = []
192
+ for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
193
+ auth_class = AUTH_CLASS[auth_method.account_id]
194
+ if not isinstance(auth_contract, auth_class):
195
+ raise ValueError(
196
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
197
+ )
198
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
199
+ return await self.near_account.function_call(
200
+ _WALLET_REGISTER,
201
+ "add_access_rule",
202
+ {
203
+ "wallet_id": self.wallet_id,
204
+ "rule": access.model_dump(),
205
+ "user_payloads": user_payloads,
206
+ },
207
+ included=True,
208
+ )
209
+
210
+ async def remove_access_rule(
211
+ self, access_id: int, auth_contracts: List[AuthContract]
212
+ ):
213
+ wallet = await self.get_wallet()
214
+ message_body = f"REMOVE_AUTH_METHOD:{access_id}:{self.wallet_id}".encode(
215
+ "utf-8"
216
+ )
217
+ msg_hash = sha256(message_body).digest()
218
+ user_payloads = []
219
+ if len(auth_contracts) != len(wallet.access_list):
220
+ raise ValueError("Auth methods count should be equal to wallet access list")
221
+ for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
222
+ auth_class = AUTH_CLASS[auth_method.account_id]
223
+ if not isinstance(auth_contract, auth_class):
224
+ raise ValueError(
225
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
226
+ )
227
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
228
+ return await self.near_account.function_call(
229
+ _WALLET_REGISTER,
230
+ "remove_access_rule",
231
+ {
232
+ "wallet_id": self.wallet_id,
233
+ "rule_id": access_id,
234
+ "user_payloads": user_payloads,
235
+ },
236
+ included=True,
237
+ )
@@ -220,8 +220,11 @@ class JsonProvider(object):
220
220
  break
221
221
  if not body:
222
222
  return error
223
- key, body = list(body.items())[0]
224
- if key in ERROR_CODE_TO_EXCEPTION:
223
+ if len(body) == 1 and list(body.keys())[0] in ERROR_CODE_TO_EXCEPTION:
224
+ key, body = list(body.items())[0]
225
+ if isinstance(body, str) and body in ERROR_CODE_TO_EXCEPTION:
226
+ key = body
227
+ body = {}
225
228
  error = ERROR_CODE_TO_EXCEPTION[key](
226
229
  body, error_json=content["error"]
227
230
  )
@@ -443,4 +446,4 @@ class JsonProvider(object):
443
446
  return await self.json_rpc("light_client_proof", params)
444
447
 
445
448
  async def get_next_light_client_block(self, last_block_hash):
446
- return await self.json_rpc("next_light_client_block", [last_block_hash])
449
+ return await self.json_rpc("next_light_client_block", [last_block_hash])
@@ -1,8 +1,7 @@
1
1
  import base64
2
2
  from typing import Union, List
3
-
3
+ from nacl import signing, encoding
4
4
  import base58
5
- import ed25519
6
5
  from py_near_primitives import (
7
6
  CreateAccountAction,
8
7
  AddKeyAction,
@@ -34,11 +33,11 @@ def sign_and_serialize_transaction(
34
33
  pk = base58.b58decode(private_key.replace("ed25519:", ""))
35
34
  else:
36
35
  pk = private_key
37
- private_key = ed25519.SigningKey(pk)
36
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
38
37
 
39
38
  transaction = Transaction(
40
39
  account_id,
41
- private_key.get_verifying_key().to_bytes(),
40
+ private_key.verify_key.encode(),
42
41
  nonce,
43
42
  receiver_id,
44
43
  block_hash,
@@ -61,11 +60,11 @@ def calc_trx_hash(
61
60
  pk = base58.b58decode(private_key.replace("ed25519:", ""))
62
61
  else:
63
62
  pk = private_key
64
- private_key = ed25519.SigningKey(pk)
63
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
65
64
 
66
65
  transaction = Transaction(
67
66
  account_id,
68
- private_key.get_verifying_key().to_bytes(),
67
+ private_key.verify_key.encode(),
69
68
  nonce,
70
69
  receiver_id,
71
70
  block_hash,
File without changes
File without changes
File without changes
File without changes