hippius 0.2.30__py3-none-any.whl → 0.2.32__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.
- {hippius-0.2.30.dist-info → hippius-0.2.32.dist-info}/METADATA +1 -1
- {hippius-0.2.30.dist-info → hippius-0.2.32.dist-info}/RECORD +10 -9
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/client.py +5 -3
- hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql +34 -0
- hippius_sdk/ipfs.py +57 -42
- hippius_sdk/key_storage.py +44 -79
- hippius_sdk/substrate.py +0 -3
- {hippius-0.2.30.dist-info → hippius-0.2.32.dist-info}/WHEEL +0 -0
- {hippius-0.2.30.dist-info → hippius-0.2.32.dist-info}/entry_points.txt +0 -0
@@ -1,23 +1,24 @@
|
|
1
|
-
hippius_sdk/__init__.py,sha256=
|
1
|
+
hippius_sdk/__init__.py,sha256=8FFxrpAn61D-ViCK1aikqZ6-5TLbGt1T4kTHygQ1nfA,1474
|
2
2
|
hippius_sdk/cli.py,sha256=aqKOYSBSWt7UhcpFt7wf9yIPJ3bznpsJ6ehOnuZ4usI,18235
|
3
3
|
hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
|
4
4
|
hippius_sdk/cli_handlers.py,sha256=TQNE9os87gRzRKLEO-SIwhFnBtEFMiaSESv-Bu7omfo,128909
|
5
5
|
hippius_sdk/cli_parser.py,sha256=z7UvgWvvy04ey-R56qZiCqYc_9RaNq1rVDkQyXoK3JU,21100
|
6
6
|
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
|
-
hippius_sdk/client.py,sha256=
|
7
|
+
hippius_sdk/client.py,sha256=4Y2OdoXay2tBOsZVvXyxXccDkmtQ3R8YTUtlAMZBKfU,22545
|
8
8
|
hippius_sdk/config.py,sha256=Hf_aUYzG9ylzqauA_ABUSSB5mBTYbp-VtB36VQt2XDw,21981
|
9
9
|
hippius_sdk/db/README.md,sha256=okDeI1qgkaZqXSlJ8L0xIE4UpuxO-qEGPIbXUvSHQjU,2030
|
10
10
|
hippius_sdk/db/env.db.template,sha256=_6hEC3IvkzCDOAzG1_yJUKRUfCTMciNaJUicZpMCat4,217
|
11
11
|
hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql,sha256=mi4-6OofgsmJq3PTG4ew8VNucoeb7iejyqd-bado7Mc,1613
|
12
|
+
hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql,sha256=jBBeo2UYlEQIsyyQRPf0YsAykWpGfafN2E6dlDwAA_E,1399
|
12
13
|
hippius_sdk/db/setup_database.sh,sha256=bDeIiTnMuX0AYCdefAfEI1CyXho6A6kLzufsDZFSXpE,3118
|
13
14
|
hippius_sdk/db_utils.py,sha256=-x0rbN0as7Tn3PJPZBYCgreZe52FLH40ppA1TLxsg90,1851
|
14
15
|
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
15
|
-
hippius_sdk/ipfs.py,sha256=
|
16
|
+
hippius_sdk/ipfs.py,sha256=1kjWAMeGMAn8vpIrrpjAxTHvnCwruhFtWjQ18iGXSU8,95383
|
16
17
|
hippius_sdk/ipfs_core.py,sha256=eOOgLoyP9mvwndnCjldnTc7z94ImYCXY3nm7JU3e_Mo,12676
|
17
|
-
hippius_sdk/key_storage.py,sha256=
|
18
|
-
hippius_sdk/substrate.py,sha256=
|
18
|
+
hippius_sdk/key_storage.py,sha256=SXFd6aGQw9MDLGX2vSBuAY7rdX-k5EvFm63z7_n-8yQ,8148
|
19
|
+
hippius_sdk/substrate.py,sha256=MFQcJARFw-Ej1WDSPGF8ryejXLmda5DdqcwPYS-KI10,49668
|
19
20
|
hippius_sdk/utils.py,sha256=rJ611yvwKSyiBpYU3w-SuyQxoghMGU-ePuslrPv5H5g,7388
|
20
|
-
hippius-0.2.
|
21
|
-
hippius-0.2.
|
22
|
-
hippius-0.2.
|
23
|
-
hippius-0.2.
|
21
|
+
hippius-0.2.32.dist-info/METADATA,sha256=8nDrlRkci7_eTNeKFQKsn2VDsQVSuAnT6esx9NVYyg8,30088
|
22
|
+
hippius-0.2.32.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
23
|
+
hippius-0.2.32.dist-info/entry_points.txt,sha256=bFAZjW3vndretf9-8s587jA2ebMVI7puhn_lVs8jPc8,149
|
24
|
+
hippius-0.2.32.dist-info/RECORD,,
|
hippius_sdk/__init__.py
CHANGED
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
|
|
26
26
|
from hippius_sdk.ipfs import IPFSClient, S3PublishResult, S3DownloadResult
|
27
27
|
from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
|
28
28
|
|
29
|
-
__version__ = "0.2.
|
29
|
+
__version__ = "0.2.32"
|
30
30
|
__all__ = [
|
31
31
|
"HippiusClient",
|
32
32
|
"IPFSClient",
|
hippius_sdk/client.py
CHANGED
@@ -516,6 +516,7 @@ class HippiusClient:
|
|
516
516
|
file_path: str,
|
517
517
|
encrypt: bool,
|
518
518
|
seed_phrase: str,
|
519
|
+
subaccount_id: str,
|
519
520
|
store_node: str = "http://localhost:5001",
|
520
521
|
pin_node: str = "https://store.hippius.network",
|
521
522
|
substrate_url: str = "wss://rpc.hippius.network",
|
@@ -548,6 +549,7 @@ class HippiusClient:
|
|
548
549
|
file_path,
|
549
550
|
encrypt,
|
550
551
|
seed_phrase,
|
552
|
+
subaccount_id,
|
551
553
|
store_node,
|
552
554
|
pin_node,
|
553
555
|
substrate_url,
|
@@ -557,7 +559,7 @@ class HippiusClient:
|
|
557
559
|
self,
|
558
560
|
cid: str,
|
559
561
|
output_path: str,
|
560
|
-
|
562
|
+
subaccount_id: str,
|
561
563
|
auto_decrypt: bool = True,
|
562
564
|
download_node: str = "http://localhost:5001",
|
563
565
|
) -> S3DownloadResult:
|
@@ -574,7 +576,7 @@ class HippiusClient:
|
|
574
576
|
Args:
|
575
577
|
cid: Content Identifier (CID) of the file to download
|
576
578
|
output_path: Path where the downloaded file will be saved
|
577
|
-
|
579
|
+
subaccount_id: The subaccount id as api key
|
578
580
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
579
581
|
download_node: IPFS node URL for download (default: local node)
|
580
582
|
|
@@ -587,5 +589,5 @@ class HippiusClient:
|
|
587
589
|
ValueError: If decryption fails
|
588
590
|
"""
|
589
591
|
return await self.ipfs_client.s3_download(
|
590
|
-
cid, output_path,
|
592
|
+
cid, output_path, subaccount_id, auto_decrypt, download_node
|
591
593
|
)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
-- migrate:up
|
2
|
+
|
3
|
+
-- For security reasons, completely drop all existing tables and data
|
4
|
+
-- This removes any stored seed phrases from the database
|
5
|
+
DROP TABLE IF EXISTS encryption_keys CASCADE;
|
6
|
+
DROP TABLE IF EXISTS seed_phrases CASCADE;
|
7
|
+
|
8
|
+
-- Create new simplified schema using only subaccount_id
|
9
|
+
-- No seed phrases are stored in the database anymore
|
10
|
+
CREATE TABLE encryption_keys (
|
11
|
+
id SERIAL PRIMARY KEY,
|
12
|
+
subaccount_id VARCHAR(255) NOT NULL,
|
13
|
+
encryption_key_b64 TEXT NOT NULL,
|
14
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
15
|
+
);
|
16
|
+
|
17
|
+
-- Index for efficient lookups of latest encryption key per subaccount
|
18
|
+
CREATE INDEX idx_encryption_keys_subaccount_created
|
19
|
+
ON encryption_keys(subaccount_id, created_at DESC);
|
20
|
+
|
21
|
+
-- Comments for documentation
|
22
|
+
COMMENT ON TABLE encryption_keys IS 'Stores versioned encryption keys per subaccount ID (never deleted, always use most recent)';
|
23
|
+
COMMENT ON COLUMN encryption_keys.subaccount_id IS 'Subaccount identifier for key association';
|
24
|
+
COMMENT ON COLUMN encryption_keys.encryption_key_b64 IS 'Base64 encoded encryption key';
|
25
|
+
|
26
|
+
-- migrate:down
|
27
|
+
|
28
|
+
-- Drop new table
|
29
|
+
DROP INDEX IF EXISTS idx_encryption_keys_subaccount_created;
|
30
|
+
DROP TABLE IF EXISTS encryption_keys;
|
31
|
+
|
32
|
+
-- Note: We do NOT recreate the old seed_phrases table in the down migration
|
33
|
+
-- This is intentional for security - once we've removed seed phrases from the DB,
|
34
|
+
-- we don't want to accidentally restore them
|
hippius_sdk/ipfs.py
CHANGED
@@ -21,8 +21,8 @@ from hippius_sdk.config import get_config_value, get_encryption_key
|
|
21
21
|
from hippius_sdk.errors import HippiusIPFSError, HippiusSubstrateError
|
22
22
|
from hippius_sdk.ipfs_core import AsyncIPFSClient
|
23
23
|
from hippius_sdk.key_storage import (
|
24
|
-
|
25
|
-
|
24
|
+
generate_and_store_key_for_subaccount,
|
25
|
+
get_key_for_subaccount,
|
26
26
|
is_key_storage_enabled,
|
27
27
|
)
|
28
28
|
from hippius_sdk.substrate import FileInput, SubstrateClient
|
@@ -1648,6 +1648,7 @@ class IPFSClient:
|
|
1648
1648
|
self,
|
1649
1649
|
cid: str,
|
1650
1650
|
cancel_from_blockchain: bool = True,
|
1651
|
+
unpin: bool = True,
|
1651
1652
|
seed_phrase: Optional[str] = None,
|
1652
1653
|
) -> Dict[str, Any]:
|
1653
1654
|
"""
|
@@ -1657,6 +1658,7 @@ class IPFSClient:
|
|
1657
1658
|
Args:
|
1658
1659
|
cid: Content Identifier (CID) of the file/directory to delete
|
1659
1660
|
cancel_from_blockchain: Whether to also cancel the storage request from the blockchain
|
1661
|
+
unpin: Whether to unpin the file from IPFS (default: True)
|
1660
1662
|
seed_phrase: Optional seed phrase to use for blockchain interactions (uses config if None)
|
1661
1663
|
|
1662
1664
|
Returns:
|
@@ -1709,21 +1711,26 @@ class IPFSClient:
|
|
1709
1711
|
):
|
1710
1712
|
# Recursive delete, but don't cancel from blockchain (we'll do that for parent)
|
1711
1713
|
await self.delete_file(
|
1712
|
-
link_hash, cancel_from_blockchain=False
|
1714
|
+
link_hash, cancel_from_blockchain=False, unpin=unpin
|
1713
1715
|
)
|
1714
1716
|
else:
|
1715
1717
|
# Regular file unpin
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1718
|
+
if unpin:
|
1719
|
+
try:
|
1720
|
+
await self.client.unpin(link_hash)
|
1721
|
+
print(
|
1722
|
+
f"Unpinned file: {link_name} (CID: {link_hash})"
|
1723
|
+
)
|
1724
|
+
except Exception as unpin_error:
|
1725
|
+
# Just note the error but don't let it stop the whole process
|
1726
|
+
# This is common with IPFS servers that may return 500 errors for
|
1727
|
+
# unpinning content that was never explicitly pinned
|
1728
|
+
print(
|
1729
|
+
f"Note: Could not unpin {link_name}: {str(unpin_error).split('For more information')[0]}"
|
1730
|
+
)
|
1731
|
+
else:
|
1725
1732
|
print(
|
1726
|
-
f"
|
1733
|
+
f"Skipped unpinning file: {link_name} (CID: {link_hash})"
|
1727
1734
|
)
|
1728
1735
|
except Exception as e:
|
1729
1736
|
print(
|
@@ -1737,27 +1744,32 @@ class IPFSClient:
|
|
1737
1744
|
# Continue with regular file unpin
|
1738
1745
|
|
1739
1746
|
# Now unpin the main file/directory
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
print("Successfully unpinned from IPFS")
|
1746
|
-
except Exception as e:
|
1747
|
-
# Handle 500 errors from IPFS server gracefully - they often occur
|
1748
|
-
# when the content wasn't explicitly pinned or was already unpinned
|
1749
|
-
error_str = str(e)
|
1750
|
-
if "500 Internal Server Error" in error_str:
|
1751
|
-
print(
|
1752
|
-
f"Note: IPFS server reported content may already be unpinned: {cid}"
|
1753
|
-
)
|
1754
|
-
result["unpin_result"] = {"Pins": [cid]} # Simulate successful unpin
|
1747
|
+
if unpin:
|
1748
|
+
try:
|
1749
|
+
print(f"Unpinning from IPFS: {cid}")
|
1750
|
+
unpin_result = await self.client.unpin(cid)
|
1751
|
+
result["unpin_result"] = unpin_result
|
1755
1752
|
result["success"] = True
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1753
|
+
print("Successfully unpinned from IPFS")
|
1754
|
+
except Exception as e:
|
1755
|
+
# Handle 500 errors from IPFS server gracefully - they often occur
|
1756
|
+
# when the content wasn't explicitly pinned or was already unpinned
|
1757
|
+
error_str = str(e)
|
1758
|
+
if "500 Internal Server Error" in error_str:
|
1759
|
+
print(
|
1760
|
+
f"Note: IPFS server reported content may already be unpinned: {cid}"
|
1761
|
+
)
|
1762
|
+
result["unpin_result"] = {"Pins": [cid]} # Simulate successful unpin
|
1763
|
+
result["success"] = True
|
1764
|
+
else:
|
1765
|
+
print(
|
1766
|
+
f"Warning: Failed to unpin from IPFS: {error_str.split('For more information')[0]}"
|
1767
|
+
)
|
1768
|
+
result["success"] = False
|
1769
|
+
else:
|
1770
|
+
print(f"Skipped unpinning from IPFS: {cid}")
|
1771
|
+
result["unpin_result"] = {"skipped": True}
|
1772
|
+
result["success"] = True
|
1761
1773
|
|
1762
1774
|
# Then, if requested, cancel from blockchain
|
1763
1775
|
if cancel_from_blockchain:
|
@@ -1939,6 +1951,7 @@ class IPFSClient:
|
|
1939
1951
|
file_path: str,
|
1940
1952
|
encrypt: bool,
|
1941
1953
|
seed_phrase: str,
|
1954
|
+
subaccount_id: str,
|
1942
1955
|
store_node: str = "http://localhost:5001",
|
1943
1956
|
pin_node: str = "https://store.hippius.network",
|
1944
1957
|
substrate_url: str = "wss://rpc.hippius.network",
|
@@ -1994,17 +2007,19 @@ class IPFSClient:
|
|
1994
2007
|
|
1995
2008
|
if key_storage_available:
|
1996
2009
|
# Try to get existing key for this seed phrase
|
1997
|
-
existing_key_b64 = await
|
2010
|
+
existing_key_b64 = await get_key_for_subaccount(subaccount_id)
|
1998
2011
|
|
1999
2012
|
if existing_key_b64:
|
2000
2013
|
# Use existing key
|
2001
|
-
logger.debug("Using existing encryption key for
|
2014
|
+
logger.debug("Using existing encryption key for subaccount")
|
2002
2015
|
encryption_key_bytes = base64.b64decode(existing_key_b64)
|
2003
2016
|
encryption_key_used = existing_key_b64
|
2004
2017
|
else:
|
2005
|
-
# Generate and store new key for this
|
2006
|
-
logger.info("Generating new encryption key for
|
2007
|
-
new_key_b64 = await
|
2018
|
+
# Generate and store new key for this subaccount
|
2019
|
+
logger.info("Generating new encryption key for subaccount")
|
2020
|
+
new_key_b64 = await generate_and_store_key_for_subaccount(
|
2021
|
+
subaccount_id
|
2022
|
+
)
|
2008
2023
|
encryption_key_bytes = base64.b64decode(new_key_b64)
|
2009
2024
|
encryption_key_used = new_key_b64
|
2010
2025
|
|
@@ -2120,7 +2135,7 @@ class IPFSClient:
|
|
2120
2135
|
self,
|
2121
2136
|
cid: str,
|
2122
2137
|
output_path: str,
|
2123
|
-
|
2138
|
+
subaccount_id: str,
|
2124
2139
|
auto_decrypt: bool = True,
|
2125
2140
|
download_node: str = "http://localhost:5001",
|
2126
2141
|
) -> S3DownloadResult:
|
@@ -2137,7 +2152,7 @@ class IPFSClient:
|
|
2137
2152
|
Args:
|
2138
2153
|
cid: Content Identifier (CID) of the file to download
|
2139
2154
|
output_path: Path where the downloaded file will be saved
|
2140
|
-
|
2155
|
+
subaccount_id: The subaccount id as api key
|
2141
2156
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
2142
2157
|
download_node: IPFS node URL for download (default: local node)
|
2143
2158
|
|
@@ -2218,11 +2233,11 @@ class IPFSClient:
|
|
2218
2233
|
if key_storage_available:
|
2219
2234
|
# Try to get the encryption key for this seed phrase
|
2220
2235
|
try:
|
2221
|
-
existing_key_b64 = await
|
2236
|
+
existing_key_b64 = await get_key_for_subaccount(subaccount_id)
|
2222
2237
|
|
2223
2238
|
if existing_key_b64:
|
2224
2239
|
logger.debug(
|
2225
|
-
"Found encryption key for
|
2240
|
+
"Found encryption key for subaccount, attempting decryption"
|
2226
2241
|
)
|
2227
2242
|
decryption_attempted = True
|
2228
2243
|
encryption_key_used = existing_key_b64
|
hippius_sdk/key_storage.py
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
"""
|
2
|
-
Key storage module for managing encryption keys per
|
2
|
+
Key storage module for managing encryption keys per subaccount ID.
|
3
3
|
|
4
4
|
This module provides PostgreSQL-backed storage for:
|
5
|
-
1.
|
6
|
-
2. Encryption keys associated with each seed phrase (versioned, never deleted)
|
5
|
+
1. Encryption keys associated with each subaccount ID (versioned, never deleted)
|
7
6
|
"""
|
8
7
|
|
9
8
|
import base64
|
10
9
|
import hashlib
|
11
|
-
import os
|
12
|
-
from datetime import datetime
|
13
10
|
from typing import Optional
|
14
11
|
|
15
12
|
from hippius_sdk.config import get_config_value
|
@@ -30,7 +27,7 @@ class KeyStorageError(Exception):
|
|
30
27
|
|
31
28
|
|
32
29
|
class KeyStorage:
|
33
|
-
"""PostgreSQL-backed key storage for
|
30
|
+
"""PostgreSQL-backed key storage for subaccount encryption keys."""
|
34
31
|
|
35
32
|
def __init__(self, database_url: Optional[str] = None):
|
36
33
|
"""
|
@@ -62,34 +59,23 @@ class KeyStorage:
|
|
62
59
|
|
63
60
|
async def _ensure_tables_exist(self):
|
64
61
|
"""Create tables if they don't exist."""
|
65
|
-
create_seed_phrases_table = """
|
66
|
-
CREATE TABLE IF NOT EXISTS seed_phrases (
|
67
|
-
id SERIAL PRIMARY KEY,
|
68
|
-
seed_hash VARCHAR(64) UNIQUE NOT NULL,
|
69
|
-
seed_phrase_b64 TEXT NOT NULL,
|
70
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
71
|
-
);
|
72
|
-
"""
|
73
|
-
|
74
62
|
create_encryption_keys_table = """
|
75
63
|
CREATE TABLE IF NOT EXISTS encryption_keys (
|
76
64
|
id SERIAL PRIMARY KEY,
|
77
|
-
|
65
|
+
subaccount_id VARCHAR(255) NOT NULL,
|
78
66
|
encryption_key_b64 TEXT NOT NULL,
|
79
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
80
|
-
FOREIGN KEY (seed_hash) REFERENCES seed_phrases(seed_hash)
|
67
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
81
68
|
);
|
82
69
|
"""
|
83
70
|
|
84
71
|
create_index = """
|
85
|
-
CREATE INDEX IF NOT EXISTS
|
86
|
-
ON encryption_keys(
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_encryption_keys_subaccount_created
|
73
|
+
ON encryption_keys(subaccount_id, created_at DESC);
|
87
74
|
"""
|
88
75
|
|
89
76
|
try:
|
90
77
|
conn = await self._get_connection()
|
91
78
|
try:
|
92
|
-
await conn.execute(create_seed_phrases_table)
|
93
79
|
await conn.execute(create_encryption_keys_table)
|
94
80
|
await conn.execute(create_index)
|
95
81
|
finally:
|
@@ -97,59 +83,37 @@ class KeyStorage:
|
|
97
83
|
except Exception as e:
|
98
84
|
raise KeyStorageError(f"Failed to create tables: {e}")
|
99
85
|
|
100
|
-
def
|
101
|
-
"""Create a SHA-256 hash of the
|
102
|
-
return hashlib.sha256(
|
103
|
-
|
104
|
-
async def _ensure_seed_phrase_exists(self, seed_phrase: str) -> str:
|
105
|
-
"""Ensure seed phrase exists in database and return its hash."""
|
106
|
-
seed_hash = self._hash_seed_phrase(seed_phrase)
|
107
|
-
seed_phrase_b64 = base64.b64encode(seed_phrase.encode("utf-8")).decode("utf-8")
|
108
|
-
|
109
|
-
try:
|
110
|
-
conn = await self._get_connection()
|
111
|
-
try:
|
112
|
-
# Try to insert, ignore if already exists
|
113
|
-
await conn.execute(
|
114
|
-
"""
|
115
|
-
INSERT INTO seed_phrases (seed_hash, seed_phrase_b64)
|
116
|
-
VALUES ($1, $2)
|
117
|
-
ON CONFLICT (seed_hash) DO NOTHING
|
118
|
-
""",
|
119
|
-
seed_hash,
|
120
|
-
seed_phrase_b64,
|
121
|
-
)
|
122
|
-
finally:
|
123
|
-
await conn.close()
|
124
|
-
return seed_hash
|
125
|
-
except Exception as e:
|
126
|
-
raise KeyStorageError(f"Failed to store seed phrase: {e}")
|
86
|
+
def _hash_subaccount_id(self, subaccount_id: str) -> str:
|
87
|
+
"""Create a SHA-256 hash of the subaccount ID for indexing."""
|
88
|
+
return hashlib.sha256(subaccount_id.encode("utf-8")).hexdigest()
|
127
89
|
|
128
|
-
async def
|
90
|
+
async def set_key_for_subaccount(
|
91
|
+
self, subaccount_id: str, encryption_key_b64: str
|
92
|
+
) -> None:
|
129
93
|
"""
|
130
|
-
Store a new encryption key for a
|
94
|
+
Store a new encryption key for a subaccount.
|
131
95
|
|
132
96
|
Creates a new row (doesn't update existing ones) to maintain key history.
|
133
97
|
|
134
98
|
Args:
|
135
|
-
|
99
|
+
subaccount_id: The subaccount identifier
|
136
100
|
encryption_key_b64: Base64-encoded encryption key
|
137
101
|
|
138
102
|
Raises:
|
139
103
|
KeyStorageError: If storage fails
|
140
104
|
"""
|
141
105
|
await self._ensure_tables_exist()
|
142
|
-
|
106
|
+
subaccount_hash = self._hash_subaccount_id(subaccount_id)
|
143
107
|
|
144
108
|
try:
|
145
109
|
conn = await self._get_connection()
|
146
110
|
try:
|
147
111
|
await conn.execute(
|
148
112
|
"""
|
149
|
-
INSERT INTO encryption_keys (
|
113
|
+
INSERT INTO encryption_keys (subaccount_id, encryption_key_b64)
|
150
114
|
VALUES ($1, $2)
|
151
115
|
""",
|
152
|
-
|
116
|
+
subaccount_hash,
|
153
117
|
encryption_key_b64,
|
154
118
|
)
|
155
119
|
finally:
|
@@ -157,12 +121,12 @@ class KeyStorage:
|
|
157
121
|
except Exception as e:
|
158
122
|
raise KeyStorageError(f"Failed to store encryption key: {e}")
|
159
123
|
|
160
|
-
async def
|
124
|
+
async def get_key_for_subaccount(self, subaccount_id: str) -> Optional[str]:
|
161
125
|
"""
|
162
|
-
Get the most recent encryption key for a
|
126
|
+
Get the most recent encryption key for a subaccount.
|
163
127
|
|
164
128
|
Args:
|
165
|
-
|
129
|
+
subaccount_id: The subaccount identifier
|
166
130
|
|
167
131
|
Returns:
|
168
132
|
Base64-encoded encryption key or None if not found
|
@@ -171,7 +135,7 @@ class KeyStorage:
|
|
171
135
|
KeyStorageError: If database operation fails
|
172
136
|
"""
|
173
137
|
await self._ensure_tables_exist()
|
174
|
-
|
138
|
+
subaccount_hash = self._hash_subaccount_id(subaccount_id)
|
175
139
|
|
176
140
|
try:
|
177
141
|
conn = await self._get_connection()
|
@@ -180,11 +144,11 @@ class KeyStorage:
|
|
180
144
|
"""
|
181
145
|
SELECT encryption_key_b64
|
182
146
|
FROM encryption_keys
|
183
|
-
WHERE
|
147
|
+
WHERE subaccount_id = $1
|
184
148
|
ORDER BY created_at DESC
|
185
149
|
LIMIT 1
|
186
150
|
""",
|
187
|
-
|
151
|
+
subaccount_hash,
|
188
152
|
)
|
189
153
|
|
190
154
|
return result["encryption_key_b64"] if result else None
|
@@ -193,12 +157,12 @@ class KeyStorage:
|
|
193
157
|
except Exception as e:
|
194
158
|
raise KeyStorageError(f"Failed to retrieve encryption key: {e}")
|
195
159
|
|
196
|
-
async def
|
160
|
+
async def generate_and_store_key_for_subaccount(self, subaccount_id: str) -> str:
|
197
161
|
"""
|
198
|
-
Generate a new encryption key and store it for the
|
162
|
+
Generate a new encryption key and store it for the subaccount.
|
199
163
|
|
200
164
|
Args:
|
201
|
-
|
165
|
+
subaccount_id: The subaccount identifier
|
202
166
|
|
203
167
|
Returns:
|
204
168
|
Base64-encoded encryption key that was generated and stored
|
@@ -206,17 +170,14 @@ class KeyStorage:
|
|
206
170
|
Raises:
|
207
171
|
KeyStorageError: If generation or storage fails
|
208
172
|
"""
|
209
|
-
# Generate a new encryption key
|
210
173
|
try:
|
211
174
|
import nacl.secret
|
212
175
|
import nacl.utils
|
213
176
|
|
214
|
-
# Generate a random key
|
215
177
|
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
|
216
178
|
key_b64 = base64.b64encode(key).decode("utf-8")
|
217
179
|
|
218
|
-
|
219
|
-
await self.set_key_for_seed(seed_phrase, key_b64)
|
180
|
+
await self.set_key_for_subaccount(subaccount_id, key_b64)
|
220
181
|
|
221
182
|
return key_b64
|
222
183
|
except ImportError:
|
@@ -261,38 +222,42 @@ def get_default_storage() -> KeyStorage:
|
|
261
222
|
return _default_storage
|
262
223
|
|
263
224
|
|
264
|
-
async def
|
225
|
+
async def get_key_for_subaccount(subaccount_id: str) -> Optional[str]:
|
265
226
|
"""
|
266
|
-
Get the most recent encryption key for a
|
227
|
+
Get the most recent encryption key for a subaccount.
|
267
228
|
|
268
229
|
Args:
|
269
|
-
|
230
|
+
subaccount_id: The subaccount identifier
|
270
231
|
|
271
232
|
Returns:
|
272
233
|
Base64-encoded encryption key or None if not found
|
273
234
|
"""
|
274
|
-
return await get_default_storage().
|
235
|
+
return await get_default_storage().get_key_for_subaccount(subaccount_id)
|
275
236
|
|
276
237
|
|
277
|
-
async def
|
238
|
+
async def set_key_for_subaccount(subaccount_id: str, encryption_key_b64: str) -> None:
|
278
239
|
"""
|
279
|
-
Store a new encryption key for a
|
240
|
+
Store a new encryption key for a subaccount.
|
280
241
|
|
281
242
|
Args:
|
282
|
-
|
243
|
+
subaccount_id: The subaccount identifier
|
283
244
|
encryption_key_b64: Base64-encoded encryption key
|
284
245
|
"""
|
285
|
-
return await get_default_storage().
|
246
|
+
return await get_default_storage().set_key_for_subaccount(
|
247
|
+
subaccount_id, encryption_key_b64
|
248
|
+
)
|
286
249
|
|
287
250
|
|
288
|
-
async def
|
251
|
+
async def generate_and_store_key_for_subaccount(subaccount_id: str) -> str:
|
289
252
|
"""
|
290
|
-
Generate a new encryption key and store it for the
|
253
|
+
Generate a new encryption key and store it for the subaccount.
|
291
254
|
|
292
255
|
Args:
|
293
|
-
|
256
|
+
subaccount_id: The subaccount identifier
|
294
257
|
|
295
258
|
Returns:
|
296
259
|
Base64-encoded encryption key that was generated and stored
|
297
260
|
"""
|
298
|
-
return await get_default_storage().
|
261
|
+
return await get_default_storage().generate_and_store_key_for_subaccount(
|
262
|
+
subaccount_id
|
263
|
+
)
|
hippius_sdk/substrate.py
CHANGED
@@ -1270,7 +1270,6 @@ class SubstrateClient:
|
|
1270
1270
|
return result.value
|
1271
1271
|
return None
|
1272
1272
|
|
1273
|
-
|
1274
1273
|
def is_main_account(self, account_id: str, seed_phrase: str) -> bool:
|
1275
1274
|
sub_account = self.query_sub_account(account_id, seed_phrase=seed_phrase)
|
1276
1275
|
return sub_account is None
|
@@ -1288,8 +1287,6 @@ class SubstrateClient:
|
|
1288
1287
|
# Return the u128 value (converted to int for Python compatibility)
|
1289
1288
|
return int(result.value) if result and result.value is not None else 0
|
1290
1289
|
|
1291
|
-
|
1292
|
-
|
1293
1290
|
def get_account_roles(self, account_id: str, seed_phrase) -> int:
|
1294
1291
|
if not self._substrate:
|
1295
1292
|
self.connect(seed_phrase)
|
File without changes
|
File without changes
|