py-near 1.1.53__py3-none-any.whl → 1.1.55__py3-none-any.whl
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.
- py_near/account.py +9 -7
- py_near/mpc/__init__.py +0 -0
- py_near/mpc/auth/__init__.py +0 -0
- py_near/mpc/auth/auth_2fa.py +97 -0
- py_near/mpc/auth/base.py +23 -0
- py_near/mpc/auth/core.py +0 -0
- py_near/mpc/auth/default_auth.py +29 -0
- py_near/mpc/auth/keys_auth.py +83 -0
- py_near/mpc/models.py +32 -0
- py_near/mpc/wallet.py +261 -0
- {py_near-1.1.53.dist-info → py_near-1.1.55.dist-info}/METADATA +38 -17
- {py_near-1.1.53.dist-info → py_near-1.1.55.dist-info}/RECORD +20 -10
- {py_near-1.1.53.dist-info → py_near-1.1.55.dist-info}/WHEEL +2 -1
- py_near-1.1.55.dist-info/top_level.txt +1 -0
- {py_near-1.1.53.dist-info → py_near-1.1.55.dist-info/licenses}/LICENSE +0 -0
py_near/account.py
CHANGED
@@ -4,6 +4,8 @@ import json
|
|
4
4
|
import sys
|
5
5
|
from typing import List, Union, Dict, Optional
|
6
6
|
|
7
|
+
from nacl.signing import VerifyKey
|
8
|
+
|
7
9
|
if sys.version_info.major == 3 and sys.version_info.minor >= 10:
|
8
10
|
from collections.abc import MutableSet, MutableMapping
|
9
11
|
|
@@ -85,9 +87,11 @@ class Account(object):
|
|
85
87
|
except UnicodeEncodeError:
|
86
88
|
logger.error(f"Can't decode private key {pk[:10]}")
|
87
89
|
continue
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
if len(pk) > 32:
|
91
|
+
pk = pk[:32]
|
92
|
+
private_key = signing.SigningKey(pk, encoder=encoding.RawEncoder)
|
93
|
+
public_key_b58 = base58.b58encode(private_key.verify_key.encode()).decode("utf-8")
|
94
|
+
self._signer_by_pk[public_key_b58] = pk
|
91
95
|
self._free_signers.put_nowait(pk)
|
92
96
|
self._signers.append(pk)
|
93
97
|
|
@@ -206,8 +210,6 @@ class Account(object):
|
|
206
210
|
|
207
211
|
private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
|
208
212
|
public_key = private_key.verify_key
|
209
|
-
public_key.to_curve25519_public_key()
|
210
|
-
|
211
213
|
resp = await self._provider.get_access_key(
|
212
214
|
self.account_id, base58.b58encode(public_key.encode()).decode("utf8")
|
213
215
|
)
|
@@ -449,14 +451,14 @@ class Account(object):
|
|
449
451
|
await self._update_last_block_hash()
|
450
452
|
|
451
453
|
private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
|
452
|
-
verifying_key = private_key.verify_key
|
454
|
+
verifying_key: VerifyKey = private_key.verify_key
|
453
455
|
return DelegateActionModel(
|
454
456
|
sender_id=self.account_id,
|
455
457
|
receiver_id=receiver_id,
|
456
458
|
actions=actions,
|
457
459
|
nonce=access_key.nonce + 1,
|
458
460
|
max_block_height=self._latest_block_height + 1000,
|
459
|
-
public_key=base58.b58encode(verifying_key.
|
461
|
+
public_key=base58.b58encode(verifying_key.encode()).decode("utf-8"),
|
460
462
|
)
|
461
463
|
|
462
464
|
def sign_delegate_transaction(
|
py_near/mpc/__init__.py
ADDED
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
|
+
)
|
py_near/mpc/auth/base.py
ADDED
@@ -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")
|
py_near/mpc/auth/core.py
ADDED
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
|
py_near/mpc/models.py
ADDED
@@ -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
|
+
)
|
py_near/mpc/wallet.py
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
import json
|
2
|
+
from hashlib import sha256
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
import base58
|
6
|
+
import httpx
|
7
|
+
from eth_keys.datatypes import Signature
|
8
|
+
from eth_keys.exceptions import BadSignature
|
9
|
+
from eth_utils import keccak
|
10
|
+
from loguru import logger
|
11
|
+
from nacl import encoding, signing
|
12
|
+
|
13
|
+
from py_near.account import Account
|
14
|
+
from py_near.mpc.auth.auth_2fa import AuthContract2FA
|
15
|
+
from py_near.mpc.auth.base import AuthContract
|
16
|
+
from py_near.mpc.auth.default_auth import DefaultAuthContract
|
17
|
+
from py_near.mpc.auth.keys_auth import KeysAuthContract
|
18
|
+
from py_near.mpc.models import WalletAccessModel, WalletModel, CurveType
|
19
|
+
|
20
|
+
_WALLET_REGISTER = "mpc.hot.tg"
|
21
|
+
|
22
|
+
AUTH_CLASS = {
|
23
|
+
"default.auth.hot.tg": DefaultAuthContract,
|
24
|
+
"keys.auth.hot.tg": KeysAuthContract,
|
25
|
+
"2fa.auth.hot.tg": AuthContract2FA,
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
class MPCWallet:
|
30
|
+
derive: bytes
|
31
|
+
near_account: Account
|
32
|
+
hot_rpc: str
|
33
|
+
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
near_account: Account,
|
37
|
+
default_root_pk: Optional[bytes] = None,
|
38
|
+
hot_rpc: str = "https://rpc1.hotdao.ai",
|
39
|
+
derive: Optional[bytes] = None,
|
40
|
+
):
|
41
|
+
"""
|
42
|
+
:param near_account: Near account
|
43
|
+
:param derive: Derived key for wallet
|
44
|
+
:param hot_rpc: Hot RPC url
|
45
|
+
:param default_root_pk: Default auth key for HOT Protocol, if provided, derive = sha256(default_root_pk.public_key).digest()
|
46
|
+
"""
|
47
|
+
self.near_account = near_account
|
48
|
+
if len(default_root_pk) != 32:
|
49
|
+
raise ValueError("Default root pk should be 32 bytes")
|
50
|
+
if default_root_pk:
|
51
|
+
self.default_root_pk = default_root_pk
|
52
|
+
private_key_obj = self.derive_private_key(0)
|
53
|
+
derive = sha256(private_key_obj.verify_key.encode()).digest()
|
54
|
+
elif derive is None:
|
55
|
+
logger.warning(
|
56
|
+
"Its recomended to use default_root_pk to generate derives, v0 wallets support will be deprecated"
|
57
|
+
)
|
58
|
+
raise ValueError("Derive is required")
|
59
|
+
self.derive = derive
|
60
|
+
self.hot_rpc = hot_rpc
|
61
|
+
self._client = httpx.AsyncClient()
|
62
|
+
|
63
|
+
def derive_private_key(self, gen=0):
|
64
|
+
private_key = self.default_root_pk
|
65
|
+
for _ in range(gen):
|
66
|
+
private_key = sha256(private_key).digest()
|
67
|
+
return signing.SigningKey(
|
68
|
+
private_key,
|
69
|
+
encoder=encoding.RawEncoder,
|
70
|
+
)
|
71
|
+
|
72
|
+
@property
|
73
|
+
def wallet_id(self):
|
74
|
+
return base58.b58encode(sha256(self.derive).digest()).decode("utf-8")
|
75
|
+
|
76
|
+
async def get_wallet(self):
|
77
|
+
wallet = await self.near_account.view_function(
|
78
|
+
_WALLET_REGISTER, "get_wallet", args={"wallet_id": self.wallet_id}
|
79
|
+
)
|
80
|
+
if wallet.result:
|
81
|
+
return WalletModel.build(wallet.result)
|
82
|
+
|
83
|
+
async def create_wallet_with_keys_auth(self, public_key: bytes, key_gen=1):
|
84
|
+
"""
|
85
|
+
Create wallet with keys.auth.hot.tg auth method.
|
86
|
+
:param public_key: Public key for auth future signs on keys.auth.hot.tg
|
87
|
+
"""
|
88
|
+
wallet = await self.get_wallet()
|
89
|
+
if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
|
90
|
+
raise ValueError(
|
91
|
+
"MPCWallet already exists with different auth method, please use another wallet_id"
|
92
|
+
)
|
93
|
+
auth_to_add_msg = json.dumps(
|
94
|
+
dict(public_keys=[base58.b58encode(public_key).decode()], rules=[])
|
95
|
+
).replace(" ", "")
|
96
|
+
return await self.create_wallet(
|
97
|
+
"keys.auth.hot.tg", None, auth_to_add_msg, key_gen
|
98
|
+
)
|
99
|
+
|
100
|
+
async def create_wallet(
|
101
|
+
self, auth_account_id: str, metadata: str = "", auth_to_add_msg="", key_gen=1
|
102
|
+
):
|
103
|
+
"""
|
104
|
+
Create wallet with keys.auth.hot.tg auth method.
|
105
|
+
:param public_key: Public key for auth future signs on keys.auth.hot.tg
|
106
|
+
"""
|
107
|
+
wallet = await self.get_wallet()
|
108
|
+
if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
|
109
|
+
raise ValueError(
|
110
|
+
"MPCWallet already exists with different auth method, please use another wallet_id"
|
111
|
+
)
|
112
|
+
root_pk = self.derive_private_key(0)
|
113
|
+
proof_hash = sha256(
|
114
|
+
f"CREATE_WALLET:{self.wallet_id}:{auth_account_id}:{metadata}:{auth_to_add_msg}".encode(
|
115
|
+
"utf-8"
|
116
|
+
)
|
117
|
+
).digest()
|
118
|
+
signature = base58.b58encode(root_pk.sign(proof_hash).signature).decode("utf-8")
|
119
|
+
|
120
|
+
s = await self._client.post(
|
121
|
+
f"{self.hot_rpc}/create_wallet",
|
122
|
+
json=dict(
|
123
|
+
wallet_id=self.wallet_id,
|
124
|
+
key_gen=key_gen,
|
125
|
+
signature=signature,
|
126
|
+
wallet_derive_public_key=base58.b58encode(
|
127
|
+
root_pk.verify_key.encode()
|
128
|
+
).decode(),
|
129
|
+
auth={
|
130
|
+
"auth_account_id": auth_account_id,
|
131
|
+
"msg": auth_to_add_msg,
|
132
|
+
"metadata": metadata or None,
|
133
|
+
},
|
134
|
+
),
|
135
|
+
timeout=30,
|
136
|
+
)
|
137
|
+
return s.json()
|
138
|
+
|
139
|
+
async def get_ecdsa_public_key(self) -> bytes:
|
140
|
+
resp = (
|
141
|
+
await self._client.post(
|
142
|
+
f"{self.hot_rpc}/public_key",
|
143
|
+
json=dict(wallet_derive=base58.b58encode(self.derive).decode()),
|
144
|
+
timeout=10,
|
145
|
+
follow_redirects=True,
|
146
|
+
)
|
147
|
+
).json()
|
148
|
+
return bytes.fromhex(resp["ecdsa"])
|
149
|
+
|
150
|
+
@property
|
151
|
+
def public_key(self):
|
152
|
+
# TODO Calculate with Rust Code
|
153
|
+
raise NotImplementedError("Public key calculation is not implemented yet")
|
154
|
+
|
155
|
+
async def sign_message(
|
156
|
+
self,
|
157
|
+
msg_hash: bytes,
|
158
|
+
message_body: Optional[bytes] = None,
|
159
|
+
curve_type: CurveType = CurveType.SECP256K1,
|
160
|
+
auth_methods: List[AuthContract] = None,
|
161
|
+
):
|
162
|
+
if not self.default_root_pk:
|
163
|
+
raise ValueError("Default auth key is required")
|
164
|
+
wallet = await self.get_wallet()
|
165
|
+
user_payloads = []
|
166
|
+
if len(auth_methods) != len(wallet.access_list):
|
167
|
+
raise ValueError("Auth methods count should be equal to wallet access list")
|
168
|
+
|
169
|
+
for auth_contract, auth_method in zip(auth_methods, wallet.access_list):
|
170
|
+
user_payloads.append(auth_contract.generate_user_payload(msg_hash))
|
171
|
+
|
172
|
+
proof = {
|
173
|
+
"auth_id": 0,
|
174
|
+
"curve_type": curve_type,
|
175
|
+
"user_payloads": user_payloads,
|
176
|
+
"message_body": message_body.hex() if message_body else "",
|
177
|
+
}
|
178
|
+
|
179
|
+
resp = await self._client.post(
|
180
|
+
f"{self.hot_rpc}/sign_raw",
|
181
|
+
json=dict(
|
182
|
+
uid=self.derive.hex(),
|
183
|
+
message=msg_hash.hex(),
|
184
|
+
proof=proof,
|
185
|
+
key_type=curve_type,
|
186
|
+
),
|
187
|
+
timeout=10,
|
188
|
+
follow_redirects=True,
|
189
|
+
)
|
190
|
+
resp = resp.json()
|
191
|
+
if "Ecdsa" not in resp:
|
192
|
+
raise ValueError(f"Invalid response from server: {resp}")
|
193
|
+
resp = resp["Ecdsa"]
|
194
|
+
r = int(resp["big_r"][2:], 16)
|
195
|
+
s = int(resp["signature"], 16)
|
196
|
+
|
197
|
+
pk = await self.get_ecdsa_public_key()
|
198
|
+
for v in (0, 1):
|
199
|
+
sig = Signature(vrs=(v, r, s))
|
200
|
+
recovered = sig.recover_public_key_from_msg_hash(msg_hash)
|
201
|
+
if recovered.to_compressed_bytes() == pk:
|
202
|
+
return (
|
203
|
+
r.to_bytes(32, "big") + s.to_bytes(32, "big") + bytes([v])
|
204
|
+
).hex()
|
205
|
+
|
206
|
+
raise BadSignature("Cannot recover public key from signature")
|
207
|
+
|
208
|
+
async def add_new_access_rule(
|
209
|
+
self, access: WalletAccessModel, auth_contracts: List[AuthContract]
|
210
|
+
):
|
211
|
+
wallet = await self.get_wallet()
|
212
|
+
access_json = access.model_dump_json()
|
213
|
+
message_body = f"ADD_AUTH_METHOD:{access_json}:{self.wallet_id}".encode("utf-8")
|
214
|
+
msg_hash = sha256(message_body).digest()
|
215
|
+
user_payloads = []
|
216
|
+
for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
|
217
|
+
auth_class = AUTH_CLASS[auth_method.account_id]
|
218
|
+
if not isinstance(auth_contract, auth_class):
|
219
|
+
raise ValueError(
|
220
|
+
f"Auth method {auth_method.account_id} is not supported for this auth class"
|
221
|
+
)
|
222
|
+
user_payloads.append(auth_contract.generate_user_payload(msg_hash))
|
223
|
+
return await self.near_account.function_call(
|
224
|
+
_WALLET_REGISTER,
|
225
|
+
"add_access_rule",
|
226
|
+
{
|
227
|
+
"wallet_id": self.wallet_id,
|
228
|
+
"rule": access.model_dump(),
|
229
|
+
"user_payloads": user_payloads,
|
230
|
+
},
|
231
|
+
included=True,
|
232
|
+
)
|
233
|
+
|
234
|
+
async def remove_access_rule(
|
235
|
+
self, access_id: int, auth_contracts: List[AuthContract]
|
236
|
+
):
|
237
|
+
wallet = await self.get_wallet()
|
238
|
+
message_body = f"REMOVE_AUTH_METHOD:{access_id}:{self.wallet_id}".encode(
|
239
|
+
"utf-8"
|
240
|
+
)
|
241
|
+
msg_hash = sha256(message_body).digest()
|
242
|
+
user_payloads = []
|
243
|
+
if len(auth_contracts) != len(wallet.access_list):
|
244
|
+
raise ValueError("Auth methods count should be equal to wallet access list")
|
245
|
+
for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
|
246
|
+
auth_class = AUTH_CLASS[auth_method.account_id]
|
247
|
+
if not isinstance(auth_contract, auth_class):
|
248
|
+
raise ValueError(
|
249
|
+
f"Auth method {auth_method.account_id} is not supported for this auth class"
|
250
|
+
)
|
251
|
+
user_payloads.append(auth_contract.generate_user_payload(msg_hash))
|
252
|
+
return await self.near_account.function_call(
|
253
|
+
_WALLET_REGISTER,
|
254
|
+
"remove_access_rule",
|
255
|
+
{
|
256
|
+
"wallet_id": self.wallet_id,
|
257
|
+
"rule_id": access_id,
|
258
|
+
"user_payloads": user_payloads,
|
259
|
+
},
|
260
|
+
included=True,
|
261
|
+
)
|
@@ -1,23 +1,45 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: py-near
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.55
|
4
4
|
Summary: Pretty simple and fully asynchronous framework for working with NEAR blockchain
|
5
|
-
Author: pvolnov
|
6
|
-
|
7
|
-
|
5
|
+
Author-email: pvolnov <petr@herewallet.app>
|
6
|
+
License: Permission is hereby granted, free of charge, to any
|
7
|
+
person obtaining a copy of this software and associated
|
8
|
+
documentation files (the "Software"), to deal in the
|
9
|
+
Software without restriction, including without
|
10
|
+
limitation the rights to use, copy, modify, merge,
|
11
|
+
publish, distribute, sublicense, and/or sell copies of
|
12
|
+
the Software, and to permit persons to whom the Software
|
13
|
+
is furnished to do so, subject to the following
|
14
|
+
conditions:
|
15
|
+
|
16
|
+
The above copyright notice and this permission notice
|
17
|
+
shall be included in all copies or substantial portions
|
18
|
+
of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
21
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
22
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
23
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
24
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
26
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
27
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
28
|
+
DEALINGS IN THE SOFTWARE.
|
29
|
+
|
30
|
+
Project-URL: homepage, https://github.com/pvolnov/py-near
|
31
|
+
Project-URL: documentation, https://py-near.readthedocs.io/en/latest
|
32
|
+
Project-URL: repository, https://github.com/pvolnov/py-near
|
33
|
+
Project-URL: changelog, https://github.com/pvolnov/py-near/blob/main/CHANGELOG.md
|
34
|
+
Project-URL: funding, https://opencollective.com/py-near
|
35
|
+
Project-URL: twitter, https://twitter.com/p_volnov
|
36
|
+
Keywords: python,near,async
|
8
37
|
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier:
|
10
|
-
Classifier:
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
13
|
-
Requires-Dist: base58 (>=2.1.1,<3.0.0)
|
14
|
-
Requires-Dist: frozenlist (>=1.4.1,<2.0.0)
|
15
|
-
Requires-Dist: httpx (>=0.26.0)
|
16
|
-
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
17
|
-
Requires-Dist: py-near-primitives (==0.2.4)
|
18
|
-
Requires-Dist: pydantic (>=2.6.2,<3.0.0)
|
19
|
-
Requires-Dist: pynacl (>=1.5.0,<2.0.0)
|
38
|
+
Classifier: License :: OSI Approved :: MIT License
|
39
|
+
Classifier: Operating System :: OS Independent
|
20
40
|
Description-Content-Type: text/markdown
|
41
|
+
License-File: LICENSE
|
42
|
+
Dynamic: license-file
|
21
43
|
|
22
44
|
# py-near
|
23
45
|
|
@@ -141,4 +163,3 @@ for t in task:
|
|
141
163
|
|
142
164
|
This project exists thanks to all the people who contribute. [[Code of conduct](CODE_OF_CONDUCT.md)].
|
143
165
|
<a href="https://github.com/pvolnov/py-near/graphs/contributors"><img src="https://opencollective.com/py-near/contributors.svg?width=890&button=false" /></a>
|
144
|
-
|
@@ -1,13 +1,17 @@
|
|
1
1
|
py_near/__init__.py,sha256=t5fAxjaU8dN8xpQR2vz0ZGhfTkdVy2RCbkhJhZFglk4,50
|
2
|
-
py_near/account.py,sha256=
|
2
|
+
py_near/account.py,sha256=khay55bKlJ3x70TVpiYhhuH1ASm8zis2eN-FTYBWQ-E,18302
|
3
3
|
py_near/constants.py,sha256=inaWIuwmF1EB5JSB0ynnZY5rKY_QsxhF9KuCOhPsM6k,164
|
4
|
+
py_near/models.py,sha256=85ynmLVJnzWvFKq-Z8iIO0waU-2iP-CSkaOGX4mYoL0,11590
|
5
|
+
py_near/providers.py,sha256=X_GfddFzmafBatqFJH41Dp_lYvTNcz1Q3Ko2tEnN0VU,16193
|
6
|
+
py_near/transactions.py,sha256=QwEP3qOGxb8OB2vZA_rU20jvkPA0FmJowUKDxbuzOuI,3251
|
7
|
+
py_near/utils.py,sha256=FirRH93ydH1cwjn0-sNrZeIn3BRD6QHedrP2VkAdJ6g,126
|
4
8
|
py_near/dapps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
9
|
py_near/dapps/core.py,sha256=LtN9aW2gw2mvEdhzQcQJIidtjv-XL1xjb0LK8DzqtqE,231
|
10
|
+
py_near/dapps/fts.py,sha256=MFe_wC96K_RfpFc7soeZr-ejuMWPl7SJqaX2mTbZHy0,959
|
6
11
|
py_near/dapps/ft/__init__.py,sha256=hx8qh4yEs37Ul_JhDWn8LHd-9lzsaATivxKOJPaUqTE,29
|
7
12
|
py_near/dapps/ft/async_client.py,sha256=Oir5EQOGueq0lsNn0ybe_1uvMnmV5K1m_mHFuFWIUu4,6717
|
8
13
|
py_near/dapps/ft/exceptions.py,sha256=UjXLFsDQX0vDGS9CGO7HE9XpLD0vovFNUzCb11WKAtc,92
|
9
14
|
py_near/dapps/ft/models.py,sha256=_URS8nXZvfuxjU7Ov2E-nteBak5qKtPKN3bLOR7sC9k,186
|
10
|
-
py_near/dapps/fts.py,sha256=MFe_wC96K_RfpFc7soeZr-ejuMWPl7SJqaX2mTbZHy0,959
|
11
15
|
py_near/dapps/keypom/__init__.py,sha256=6fWGxsVr_lb_wTf_MHrbwBl1fgf78b5ezmsVa3JeFjI,32
|
12
16
|
py_near/dapps/keypom/async_client.py,sha256=DS594S00xhcyvVtD1W0PjmtmT3YhvXTZZ3hskbn1-1w,1422
|
13
17
|
py_near/dapps/keypom/exceptions.py,sha256=pGz0w1Ubh2A1aHEWvUX4dVaCcO9r2Y-h2JwhBY5KJbU,45
|
@@ -19,11 +23,17 @@ py_near/dapps/staking/models.py,sha256=zC5M_pc1oMqHq4GaYif1uwFbW6acD2BsiA9rbyiaU
|
|
19
23
|
py_near/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
24
|
py_near/exceptions/exceptions.py,sha256=DEFipaAHm0y7oCuN2QKzHsiQvUTUQVl-Ce36Ag7n7hs,5509
|
21
25
|
py_near/exceptions/provider.py,sha256=K-wexgjPJ8sw42JePwaP7R5dJEIn9DoFJRvVcURsx6s,7718
|
22
|
-
py_near/
|
23
|
-
py_near/
|
24
|
-
py_near/
|
25
|
-
py_near/
|
26
|
-
py_near
|
27
|
-
py_near
|
28
|
-
py_near
|
29
|
-
py_near
|
26
|
+
py_near/mpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
py_near/mpc/models.py,sha256=9MSfBDqfqkaJu6eJxnwyjgihsexZboehbJ6vAGa4BTU,663
|
28
|
+
py_near/mpc/wallet.py,sha256=sNr9UlcyVDPF_Zho_4iTupbUoZct3paQlxxDHlHK67k,9768
|
29
|
+
py_near/mpc/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
py_near/mpc/auth/auth_2fa.py,sha256=ZxO8mGO95ixb0qXnKrfmVxP7ivpDKerH4-udV_vUmbs,3124
|
31
|
+
py_near/mpc/auth/base.py,sha256=IoFdRWUQcerYZq8TCkpjXJQRcENnzA9WwFbVPgmHgcs,629
|
32
|
+
py_near/mpc/auth/core.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
py_near/mpc/auth/default_auth.py,sha256=i70412yXYx3MlFSsPxHLKbF0CXTjC39Qtl34et8kAEw,751
|
34
|
+
py_near/mpc/auth/keys_auth.py,sha256=DJPi37DnV6r6cSskHEhHBiR_QiaAQJkg0AEsYp6cePA,2359
|
35
|
+
py_near-1.1.55.dist-info/licenses/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
|
36
|
+
py_near-1.1.55.dist-info/METADATA,sha256=e8ex0o1yHlb7ivf4FPTwxQy8cQEa87t4ib2k93ffu_A,6012
|
37
|
+
py_near-1.1.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
38
|
+
py_near-1.1.55.dist-info/top_level.txt,sha256=p8CZgfKEVmXnRfVX5VHR0Ls7OVFC2ZVEwthvNB8Rb2Q,8
|
39
|
+
py_near-1.1.55.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
py_near
|
File without changes
|