shielded-transfers 0.1.1__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.
- shielded_transfers-0.1.1/LICENSE +7 -0
- shielded_transfers-0.1.1/PKG-INFO +530 -0
- shielded_transfers-0.1.1/README.md +497 -0
- shielded_transfers-0.1.1/pyproject.toml +56 -0
- shielded_transfers-0.1.1/setup.cfg +4 -0
- shielded_transfers-0.1.1/setup.py +5 -0
- shielded_transfers-0.1.1/shielded_transfers/__init__.py +42 -0
- shielded_transfers-0.1.1/shielded_transfers/cli.py +89 -0
- shielded_transfers-0.1.1/shielded_transfers/client.py +611 -0
- shielded_transfers-0.1.1/shielded_transfers/commitment.py +59 -0
- shielded_transfers-0.1.1/shielded_transfers/constants.py +26 -0
- shielded_transfers-0.1.1/shielded_transfers/exceptions.py +36 -0
- shielded_transfers-0.1.1/shielded_transfers/networks.py +45 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_circomlibjs.py +30 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_helper.js +26 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_helper.mjs +40 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_onchain.py +51 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_polkadot.py +33 -0
- shielded_transfers-0.1.1/shielded_transfers/poseidon_precompile.py +81 -0
- shielded_transfers-0.1.1/shielded_transfers/revive.py +506 -0
- shielded_transfers-0.1.1/shielded_transfers/tree.py +164 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/PKG-INFO +530 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/SOURCES.txt +25 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/dependency_links.txt +1 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/entry_points.txt +3 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/requires.txt +11 -0
- shielded_transfers-0.1.1/shielded_transfers.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Kusama Shield Developers on behalf of the Kusama DAO
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shielded-transfers
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python SDK for the Kusama Shield v7 privacy pool (ZK deposits & withdrawals) on Polkadot AssetHub
|
|
5
|
+
Author: Kusama Shield
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://shield.markets
|
|
8
|
+
Project-URL: Documentation, https://kusamashield.codeberg.page
|
|
9
|
+
Keywords: shield,privacy,zeroknowledge,zk,polkadot,assethub,shielded,tornado
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security :: Cryptography
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: web3>=6.0.0
|
|
23
|
+
Requires-Dist: eth-account>=0.9.0
|
|
24
|
+
Requires-Dist: eth-abi>=4.0.0
|
|
25
|
+
Requires-Dist: eth-utils>=2.0.0
|
|
26
|
+
Requires-Dist: light-poseidon-python>=0.1.3
|
|
27
|
+
Requires-Dist: substrateinterface>=1.0.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build; extra == "dev"
|
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Shielded Transfers Python SDK
|
|
35
|
+
|
|
36
|
+
A Python library for interacting with the Kusama Shield v7 privacy pool on Polkadot AssetHub (Paseo).
|
|
37
|
+
|
|
38
|
+
## Overview
|
|
39
|
+
|
|
40
|
+
This SDK enables shielded (privacy-preserving) deposits and withdrawals using zero-knowledge proofs. It implements the same cryptographic primitives and Merkle tree logic as the Solidity contracts.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd /home/pi/zk/shielded-transfers-python
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or install dependencies only:
|
|
50
|
+
```bash
|
|
51
|
+
pip install web3>=6.0.0 eth-account>=0.9.0 eth-abi>=4.0.0 eth-utils>=2.0.0 requests
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- **Node.js** - Required for Poseidon hashing (poseidon-lite)
|
|
57
|
+
- **snarkjs** - For ZK proof generation (`npm install -g snarkjs`)
|
|
58
|
+
- **Circuit files** - Located at `/home/pi/zk/shielded-transfers/public/` (override via
|
|
59
|
+
the `SHIELDED_CIRCUIT_DIR` env var or the `circuit_dir` constructor arg):
|
|
60
|
+
- `withdraw_phase2_fixed_v7.wasm`
|
|
61
|
+
- `withdraw_phase2_fixed_v7_0001.zkey`
|
|
62
|
+
- **substrateinterface** *(optional, only for `ReviveShieldedClient`)* — requires
|
|
63
|
+
the **modern** API (`Keypair.create_from_uri`, `SubstrateInterface.compose_call`).
|
|
64
|
+
⚠️ PyPI's `substrateinterface 1.0.0` is too old; use the newer git checkout
|
|
65
|
+
(github.com/polkascan/py-substrate-interface). Imported lazily — the ETH-only
|
|
66
|
+
`ShieldedClient` works without it, and `ReviveShieldedClient` raises a clear
|
|
67
|
+
error if a compatible version isn't installed.
|
|
68
|
+
|
|
69
|
+
## Two transaction styles
|
|
70
|
+
|
|
71
|
+
The SDK can submit shielded deposits/withdrawals two ways:
|
|
72
|
+
|
|
73
|
+
| Style | Class | Account type | Tx mechanism |
|
|
74
|
+
|-------|-------|--------------|--------------|
|
|
75
|
+
| **ETH** | `ShieldedClient` | ECDSA (`0x...` privkey) | `eth_sendRawTransaction` |
|
|
76
|
+
| **Polkadot / Substrate** | `ReviveShieldedClient` | sr25519 (seed / mnemonic) | `revive.call` extrinsic |
|
|
77
|
+
|
|
78
|
+
Both share the same EVM-based Merkle tree building (`build_tree` / `eth_getLogs`,
|
|
79
|
+
or fetch from the Kusama Shield Flask proxy via `/tree-leaves`). Only the
|
|
80
|
+
transaction submission differs.
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
### Polkadot AssetHub (Mainnet)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from shielded_transfers import ShieldedClient, POLKADOT_ASSET_HUB
|
|
88
|
+
import json
|
|
89
|
+
|
|
90
|
+
client = ShieldedClient(
|
|
91
|
+
rpc_url=POLKADOT_ASSET_HUB["rpc"],
|
|
92
|
+
pool_address=POLKADOT_ASSET_HUB["pool"],
|
|
93
|
+
private_key="0x_your_private_key",
|
|
94
|
+
deployment_block=POLKADOT_ASSET_HUB["deployment_block"],
|
|
95
|
+
native_token="DOT",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Check balances
|
|
99
|
+
wallet_bal, _ = client.get_balance()
|
|
100
|
+
pool_bal, _ = client.get_pool_balance()
|
|
101
|
+
print(f"Wallet: {wallet_bal} wei, Pool: {pool_bal} wei")
|
|
102
|
+
|
|
103
|
+
# Deposit 1 DOT
|
|
104
|
+
note = client.deposit(1 * 10**18)
|
|
105
|
+
|
|
106
|
+
with open("deposit_note.json", "w") as f:
|
|
107
|
+
json.dump(note, f)
|
|
108
|
+
|
|
109
|
+
# ... later ...
|
|
110
|
+
|
|
111
|
+
with open("deposit_note.json") as f:
|
|
112
|
+
note = json.load(f)
|
|
113
|
+
|
|
114
|
+
tx_hash = client.withdraw(note)
|
|
115
|
+
print(f"Withdraw TX: {tx_hash}")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Paseo AssetHub (Testnet)
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from shielded_transfers import ShieldedClient, PASEO_ASSET_HUB
|
|
122
|
+
import json
|
|
123
|
+
|
|
124
|
+
client = ShieldedClient(
|
|
125
|
+
rpc_url=PASEO_ASSET_HUB["rpc"],
|
|
126
|
+
pool_address=PASEO_ASSET_HUB["pool"],
|
|
127
|
+
private_key="0x_your_private_key",
|
|
128
|
+
deployment_block=PASEO_ASSET_HUB["deployment_block"],
|
|
129
|
+
native_token="DOT",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Deposit 10 PAS (testnet)
|
|
133
|
+
note = client.deposit(10 * 10**18)
|
|
134
|
+
|
|
135
|
+
with open("deposit_note.json", "w") as f:
|
|
136
|
+
json.dump(note, f)
|
|
137
|
+
|
|
138
|
+
# Withdraw
|
|
139
|
+
with open("deposit_note.json") as f:
|
|
140
|
+
note = json.load(f)
|
|
141
|
+
|
|
142
|
+
tx_hash = client.withdraw(note, recipient="0x_recipient_address")
|
|
143
|
+
print(f"Withdraw TX: {tx_hash}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Polkadot / Substrate (revive.call) — `ReviveShieldedClient`
|
|
147
|
+
|
|
148
|
+
For sr25519 (Substrate) accounts — e.g. a polkadot.js browser wallet or Nova
|
|
149
|
+
account that cannot sign Ethereum transactions directly.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from shielded_transfers import ReviveShieldedClient, POLKADOT_ASSET_HUB
|
|
153
|
+
|
|
154
|
+
client = ReviveShieldedClient(
|
|
155
|
+
rpc_url=POLKADOT_ASSET_HUB["rpc"], # EVM JSON-RPC (reads/tree)
|
|
156
|
+
ws_url="wss://asset-hub-polkadot-rpc.n.dwellir.com", # Substrate WS (revive)
|
|
157
|
+
pool_address=POLKADOT_ASSET_HUB["pool"],
|
|
158
|
+
substrate_uri="0x_your_sr25519_seed", # or a mnemonic / "//Alice"
|
|
159
|
+
deployment_block=POLKADOT_ASSET_HUB["deployment_block"],
|
|
160
|
+
native_token="DOT",
|
|
161
|
+
native_decimals=10, # DOT = 10, Paseo PAS = 12
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# One-time mapping of the sr25519 account to its H160 (only needed once)
|
|
165
|
+
client.ensure_mapped()
|
|
166
|
+
|
|
167
|
+
# Deposit 0.01 DOT via revive.call
|
|
168
|
+
note = client.deposit_revive(amount_dot=0.01)
|
|
169
|
+
print("Deposit TX:", note["tx_hash"])
|
|
170
|
+
print("Secret: ", note["secret"]) # keep private for withdrawal
|
|
171
|
+
|
|
172
|
+
# Withdraw back via revive.call, fetching the current tree from the Flask proxy
|
|
173
|
+
tx_hash = client.withdraw_revive(
|
|
174
|
+
note,
|
|
175
|
+
recipient=client.h160,
|
|
176
|
+
proxy_base_url="https://proxyswap.laissez-faire.trade",
|
|
177
|
+
)
|
|
178
|
+
print("Withdraw TX:", tx_hash)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Important notes for `revive.call`:**
|
|
182
|
+
- The `value` is in **native plancks**, not wei (`1 DOT = 1e10`, Paseo `1 PAS = 1e12`).
|
|
183
|
+
- Requires a large weight limit (handled internally) and a non-zero storage
|
|
184
|
+
deposit (~0.1 native units) — the SDK sets these automatically.
|
|
185
|
+
- `revive.call` EVM logs are **not** indexed by `eth_getLogs` on AssetHub, so
|
|
186
|
+
the tree is best fetched from the Kusama Shield Flask proxy
|
|
187
|
+
(`proxy_base_url=...` → `GET /tree-leaves/<network>`).
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
### Constructor Parameters
|
|
192
|
+
|
|
193
|
+
| Parameter | Type | Required | Description |
|
|
194
|
+
|-----------|------|----------|-------------|
|
|
195
|
+
| `rpc_url` | str | Yes | RPC endpoint URL |
|
|
196
|
+
| `pool_address` | str | Yes | Shielded pool contract address |
|
|
197
|
+
| `private_key` | str | Yes | Account private key |
|
|
198
|
+
| `deployment_block` | int | Yes | Block number when pool was deployed |
|
|
199
|
+
| `circuit_dir` | Path | No | Directory containing circuit files |
|
|
200
|
+
| `poseidon_helper` | Path | No | Path to poseidon_helper.mjs |
|
|
201
|
+
|
|
202
|
+
### Active Deployments
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# Polkadot AssetHub (Mainnet)
|
|
206
|
+
POLKADOT_ASSET_HUB = {
|
|
207
|
+
"rpc": "https://polkadot-assethub-rpc.laissez-faire.trade",
|
|
208
|
+
"pool": "0x0D694Da746e73D1e255c1894F90e38170db45809",
|
|
209
|
+
"verifier": "0x6A13781E43AEA21918120CD0E7a2ed8614c01e14",
|
|
210
|
+
"poseidon": "0xB8F0C6679D6Cc56450470522Bd96573C3D615052",
|
|
211
|
+
"deployment_block": 18697500,
|
|
212
|
+
"chain_id": 420420419,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Paseo AssetHub (Testnet)
|
|
216
|
+
PASEO_ASSET_HUB = {
|
|
217
|
+
"rpc": "https://paseo-assethub-rpc.laissez-faire.trade",
|
|
218
|
+
"pool": "0xbcE09D4De052b2816df1285663ac89528DF45380",
|
|
219
|
+
"verifier": "0xcA4cBc5d31eccd08d393C43aF492F729FF30b685",
|
|
220
|
+
"poseidon": "0x1d165f6fE5A30422E0E2140e91C8A9B800380637",
|
|
221
|
+
"deployment_block": 11273491,
|
|
222
|
+
"chain_id": 420420421,
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## API Reference
|
|
227
|
+
|
|
228
|
+
### ShieldedClient
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
client = ShieldedClient(rpc_url, pool_address, private_key, deployment_block)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Properties
|
|
235
|
+
|
|
236
|
+
- `client.address` - Account address
|
|
237
|
+
- `client.chain_id` - Chain ID
|
|
238
|
+
- `client.pool_address` - Pool contract address
|
|
239
|
+
|
|
240
|
+
#### Methods
|
|
241
|
+
|
|
242
|
+
##### get_balance()
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
wei, formatted = client.get_balance()
|
|
246
|
+
```
|
|
247
|
+
Returns wallet balance in wei and formatted string.
|
|
248
|
+
|
|
249
|
+
##### get_pool_balance()
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
wei, formatted = client.get_pool_balance()
|
|
253
|
+
```
|
|
254
|
+
Returns pool balance in wei and formatted string.
|
|
255
|
+
|
|
256
|
+
##### get_tree_size()
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
size = client.get_tree_size()
|
|
260
|
+
```
|
|
261
|
+
Returns the current Merkle tree size from the contract.
|
|
262
|
+
|
|
263
|
+
##### get_root()
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
root = client.get_root()
|
|
267
|
+
```
|
|
268
|
+
Returns the current Merkle tree root.
|
|
269
|
+
|
|
270
|
+
##### is_known_root(root)
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
known = client.is_known_root(root)
|
|
274
|
+
```
|
|
275
|
+
Checks if a root is in the 16-slot known-roots window.
|
|
276
|
+
|
|
277
|
+
##### deposit(amount_wei, asset_id=0)
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
note = client.deposit(amount_wei, asset_id=0)
|
|
281
|
+
```
|
|
282
|
+
Creates a shielded deposit.
|
|
283
|
+
|
|
284
|
+
**Parameters:**
|
|
285
|
+
- `amount_wei` (int): Amount in wei
|
|
286
|
+
- `asset_id` (int): Asset ID (0 for native PAS)
|
|
287
|
+
|
|
288
|
+
**Returns:**
|
|
289
|
+
```python
|
|
290
|
+
{
|
|
291
|
+
"secret": "0x...", # Secret key (keep private!)
|
|
292
|
+
"nullifier": 123..., # Nullifier for proving
|
|
293
|
+
"nullifier_hash": 456..., # Hash for double-spend prevention
|
|
294
|
+
"commitment": 789..., # Public commitment
|
|
295
|
+
"amount_wei": 10000000000000000000,
|
|
296
|
+
"asset_id": 0,
|
|
297
|
+
"tx_hash": "0x...",
|
|
298
|
+
"block_number": 11000000,
|
|
299
|
+
"deposit_block": 11085793,
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
##### build_tree(start_block=None)
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
tree = client.build_tree(start_block=11085793)
|
|
307
|
+
```
|
|
308
|
+
Builds the Merkle tree from on-chain events.
|
|
309
|
+
|
|
310
|
+
**Returns:** `LeanIMT` instance
|
|
311
|
+
|
|
312
|
+
##### withdraw(note, recipient=None)
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
tx_hash = client.withdraw(note, recipient=None)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Parameters:**
|
|
319
|
+
- `note` (dict): Deposit note from `deposit()`
|
|
320
|
+
- `recipient` (str): Recipient address (default: self)
|
|
321
|
+
|
|
322
|
+
**Returns:** Transaction hash
|
|
323
|
+
|
|
324
|
+
### ReviveShieldedClient
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
client = ReviveShieldedClient(rpc_url, ws_url, pool_address, substrate_uri, deployment_block)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Subclasses `ShieldedClient` and adds Substrate (`revive.call`) tx submission.
|
|
331
|
+
Additional constructor parameters:
|
|
332
|
+
|
|
333
|
+
| Parameter | Type | Required | Description |
|
|
334
|
+
|-----------|------|----------|-------------|
|
|
335
|
+
| `ws_url` | str | Yes | Substrate WebSocket RPC URL |
|
|
336
|
+
| `substrate_uri` | str | Yes | sr25519 seed (`0x...`), mnemonic, or SURI (`//Alice`) |
|
|
337
|
+
| `native_decimals` | int | No | Native token decimals (DOT=10, Paseo=12). Defaults to a heuristic |
|
|
338
|
+
| `ss58_format` | int | No | SS58 format (default 42 for AssetHub) |
|
|
339
|
+
|
|
340
|
+
#### Properties
|
|
341
|
+
|
|
342
|
+
- `client.ss58_address` - Substrate (SS58) address
|
|
343
|
+
- `client.h160` - Derived EVM H160 address
|
|
344
|
+
- `client.keypair` - substrateinterface Keypair
|
|
345
|
+
|
|
346
|
+
#### Methods
|
|
347
|
+
|
|
348
|
+
##### is_mapped()
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
mapped = client.is_mapped()
|
|
352
|
+
```
|
|
353
|
+
Returns whether the sr25519 account is mapped to its H160 for revive.
|
|
354
|
+
|
|
355
|
+
##### ensure_mapped()
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
client.ensure_mapped() # or tx_hash = client.ensure_mapped()
|
|
359
|
+
```
|
|
360
|
+
Maps the account (one-time, via `revive.mapAccount`) if not already mapped.
|
|
361
|
+
|
|
362
|
+
##### deposit_revive(amount_dot, wait_for_inclusion=True)
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
note = client.deposit_revive(0.01)
|
|
366
|
+
```
|
|
367
|
+
Deposits native tokens via `revive.call`. `amount_dot` is in native units.
|
|
368
|
+
|
|
369
|
+
##### withdraw_revive(note, recipient, wait_for_inclusion=True, proxy_base_url=None)
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
tx_hash = client.withdraw_revive(note, recipient=client.h160,
|
|
373
|
+
proxy_base_url="https://proxyswap.laissez-faire.trade")
|
|
374
|
+
```
|
|
375
|
+
Withdraws via `revive.call`. `recipient` is an EVM H160. If `proxy_base_url` is
|
|
376
|
+
set, the Merkle tree is fetched from the proxy's `/tree-leaves/<network>`;
|
|
377
|
+
otherwise it is built locally via `eth_getLogs` (`build_tree`).
|
|
378
|
+
|
|
379
|
+
##### fetch_tree_from_proxy(base_url="https://proxyswap.laissez-faire.trade", network=None)
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
tree = client.fetch_tree_from_proxy(network="polkadot")
|
|
383
|
+
```
|
|
384
|
+
Fetches the current tree leaves from the Kusama Shield Flask proxy and returns
|
|
385
|
+
a `LeanIMT`.
|
|
386
|
+
|
|
387
|
+
### Commitment Generation
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
from shielded_transfers import generate_commitment
|
|
391
|
+
|
|
392
|
+
note = generate_commitment(secret_hex, amount_wei, asset_id)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### LeanIMT
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from shielded_transfers import LeanIMT
|
|
399
|
+
|
|
400
|
+
tree = LeanIMT()
|
|
401
|
+
tree.insert(leaf)
|
|
402
|
+
tree.get_proof(leaf_index)
|
|
403
|
+
tree.find_leaf_index(leaf)
|
|
404
|
+
tree.root
|
|
405
|
+
tree.size
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## CLI Usage
|
|
409
|
+
|
|
410
|
+
### Deposit
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
shielded-deposit \
|
|
414
|
+
--amount 10 \
|
|
415
|
+
--rpc-url https://paseo-assethub-rpc.laissez-faire.trade \
|
|
416
|
+
--private-key 0x... \
|
|
417
|
+
--output deposit_note.json
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Withdraw
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
shielded-withdraw \
|
|
424
|
+
--note deposit_note.json \
|
|
425
|
+
--rpc-url https://paseo-assethub-rpc.laissez-faire.trade \
|
|
426
|
+
--private-key 0x... \
|
|
427
|
+
--recipient 0x...
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Environment Variables
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
export PASEO_RPC_URL="https://paseo-assethub-rpc.laissez-faire.trade"
|
|
434
|
+
export PRIVATE_KEY="0x_your_private_key"
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Architecture
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
shielded_transfers/
|
|
441
|
+
├── __init__.py # Package exports
|
|
442
|
+
├── client.py # ShieldedClient (ETH / eth_sendRawTransaction)
|
|
443
|
+
├── revive.py # ReviveShieldedClient (Substrate / revive.call)
|
|
444
|
+
├── commitment.py # Commitment generation (Poseidon)
|
|
445
|
+
├── tree.py # LeanIMT Merkle tree implementation
|
|
446
|
+
├── constants.py # Selectors, BN254 parameters
|
|
447
|
+
├── exceptions.py # Custom exceptions
|
|
448
|
+
├── cli.py # Command-line interface
|
|
449
|
+
└── poseidon_helper.mjs # Node.js Poseidon hash helper
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Key Components
|
|
453
|
+
|
|
454
|
+
1. **Commitment Generation**: Uses Poseidon hash (via Node.js) to compute:
|
|
455
|
+
- `nullifier = poseidon2(secret, 1)`
|
|
456
|
+
- `nullifier_hash = poseidon1(nullifier)`
|
|
457
|
+
- `precommitment = poseidon2(nullifier, secret)`
|
|
458
|
+
- `value_asset_hash = poseidon2(amount, asset_id)`
|
|
459
|
+
- `commitment = poseidon2(value_asset_hash, precommitment)`
|
|
460
|
+
|
|
461
|
+
2. **Merkle Tree**: LeanIMT with 128 levels, matching the Solidity contract
|
|
462
|
+
|
|
463
|
+
3. **ZK Proof**: Generated using snarkjs with the v7 circuit
|
|
464
|
+
|
|
465
|
+
## Known Issues
|
|
466
|
+
|
|
467
|
+
1. **Withdraw event scanning**: The tree building from events may occasionally miss deposits due to RPC event indexing. The script includes recovery logic to handle this.
|
|
468
|
+
|
|
469
|
+
2. **Gas estimation**: Some RPCs may fail gas estimation. The SDK uses a default of 500k gas as fallback.
|
|
470
|
+
|
|
471
|
+
3. **Poseidon dependency**: Requires Node.js process to be running for Poseidon hashing.
|
|
472
|
+
|
|
473
|
+
## Error Handling
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
from shielded_transfers import (
|
|
477
|
+
ShieldedTransfersError,
|
|
478
|
+
DepositError,
|
|
479
|
+
WithdrawError,
|
|
480
|
+
ProofError,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
note = client.deposit(amount)
|
|
485
|
+
except DepositError as e:
|
|
486
|
+
print(f"Deposit failed: {e}")
|
|
487
|
+
except ProofError as e:
|
|
488
|
+
print(f"ZK proof failed: {e}")
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Testing
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
# Run the original roundtrip script
|
|
495
|
+
cd /home/pi/zk/rust_tx_gen
|
|
496
|
+
python3 paseo_v7_roundtrip.py --deposit-only
|
|
497
|
+
python3 paseo_v7_roundtrip.py --withdraw deposit_note_*.json
|
|
498
|
+
|
|
499
|
+
# Or use the library
|
|
500
|
+
cd /home/pi/zk/shielded-transfers-python
|
|
501
|
+
python3 -c "
|
|
502
|
+
from shielded_transfers import ShieldedClient, PASEO_ASSET_HUB
|
|
503
|
+
client = ShieldedClient(
|
|
504
|
+
rpc_url=PASEO_ASSET_HUB['rpc'],
|
|
505
|
+
pool_address=PASEO_ASSET_HUB['pool'],
|
|
506
|
+
private_key='0x...',
|
|
507
|
+
deployment_block=PASEO_ASSET_HUB['deployment_block'],
|
|
508
|
+
)
|
|
509
|
+
note = client.deposit(10**18)
|
|
510
|
+
print(f'Deposit: {note[\"tx_hash\"]}')
|
|
511
|
+
"
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Files
|
|
515
|
+
|
|
516
|
+
| File | Description |
|
|
517
|
+
|------|-------------|
|
|
518
|
+
| `client.py` | Main SDK client with ETH deposit/withdraw (`eth_sendRawTransaction`) |
|
|
519
|
+
| `revive.py` | Substrate `revive.call` deposit/withdraw (`ReviveShieldedClient`) |
|
|
520
|
+
| `commitment.py` | Commitment and Poseidon hashing |
|
|
521
|
+
| `tree.py` | LeanIMT Merkle tree |
|
|
522
|
+
| `constants.py` | Contract addresses, selectors |
|
|
523
|
+
| `exceptions.py` | Custom exception classes |
|
|
524
|
+
| `cli.py` | Command-line tools |
|
|
525
|
+
|
|
526
|
+
## Related
|
|
527
|
+
|
|
528
|
+
- [Original roundtrip script](../rust_tx_gen/paseo_v7_roundtrip.py)
|
|
529
|
+
- [Deployment notes](../rust_tx_gen/DEPLOYMENT_NOTES.md)
|
|
530
|
+
- [Circuit files](../shielded-transfers/public/)
|