mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 21:33:09 +00:00
Merge branch 'main' into Pravdyvy/keys-restoration-from-mnemonic
This commit is contained in:
commit
33264ba59a
605
README.md
605
README.md
@ -1,10 +1,69 @@
|
||||
# nescience-testnet
|
||||
This repo serves for Nescience testnet
|
||||
# Nescience
|
||||
|
||||
Nescience State Separation Architecture (NSSA) is a programmable blockchain system that introduces a clean separation between public and private states, while keeping them fully interoperable. It lets developers build apps that can operate across both transparent and privacy-preserving accounts. Privacy is handled automatically by the protocol through zero-knowledge proofs (ZKPs). The result is a programmable blockchain where privacy comes built-in.
|
||||
|
||||
## Background
|
||||
|
||||
Typically, public blockchains maintain a fully transparent state, where the mapping from addresses to account values is entirely visible. In NSSA, we introduce a parallel *private state*, a new layer of accounts that coexists with the public one. The public and private states can be viewed as a partition of the address space: accounts with public addresses are openly visible, while private accounts are accessible only to holders of the corresponding viewing keys. Consistency across both states is enforced through zero-knowledge proofs (ZKPs).
|
||||
|
||||
Public accounts are represented on-chain as a visible map from addresses to account states and are modified in-place when their values change. Private accounts, by contrast, are never stored in raw form on-chain. Each update creates a new commitment, which cryptographically binds the current value of the account while preserving privacy. Commitments of previous valid versions remain on-chain, but a nullifier set is maintained to mark old versions as spent, ensuring that only the most up-to-date version of each private account can be used in any execution.
|
||||
|
||||
### Programmability and selective privacy
|
||||
|
||||
Our goal is to enable full programmability within this hybrid model, matching the flexibility and composability of public blockchains. Developers write and deploy programs in NSSA just as they would on any other blockchain. Privacy, along with the ability to execute programs involving any combination of public and private accounts, is handled entirely at the protocol level and available out of the box for all programs. From the program’s perspective, all accounts are indistinguishable. This abstraction allows developers to focus purely on business logic, while the system transparently enforces privacy and consistency guarantees.
|
||||
|
||||
To the best of our knowledge, this approach is unique to Nescience. Other programmable blockchains with a focus on privacy typically adopt a developer-driven model for private execution, meaning that dApp logic must explicitly handle private inputs correctly. In contrast, Nescience handles privacy at the protocol level, so developers do not need to modify their programs—private and public accounts are treated uniformly, and privacy-preserving execution is available out of the box.
|
||||
|
||||
### Example: creating and transferring tokens across states
|
||||
|
||||
1. Token creation (public execution):
|
||||
- Alice submits a transaction to execute the token program `New` function on-chain.
|
||||
- A new public token account is created, representing the token.
|
||||
- The minted tokens are recorded on-chain and fully visible on Alice's public account.
|
||||
2. Transfer from public to private (local / privacy-preserving execution)
|
||||
- Alice executes the token program `Transfer` function locally, specifying a Bob’s private account as recipient.
|
||||
- A ZKP of correct execution is generated.
|
||||
- The proof is submitted to the blockchain, and validator nodes verify it.
|
||||
- Alice's public account balance is modified accordingly.
|
||||
- Bob’s private account and balance remain hidden, while the transfer is provably valid.
|
||||
3. Transferring private to public (local / privacy-preserving execution)
|
||||
- Bob executes the token program `Transfer` function locally, specifying a Charlie’s public account as recipient.
|
||||
- A ZKP of correct execution is generated.
|
||||
- Bob’s private account and balance still remain hidden.
|
||||
- Charlie's public account is modified with the new tokens added.
|
||||
4. Transferring public to public (public execution):
|
||||
- Alice submits a transaction to execute the token program `Transfer` function on-chain, specifying Charlie's public account as recipient.
|
||||
- The execution is handled on-chain without ZKPs involved.
|
||||
- Alice's and Charlie's accounts are modified according to the transaction.
|
||||
|
||||
#### Key points:
|
||||
- The same token program is used in all executions.
|
||||
- The difference lies in execution mode: public executions update visible accounts on-chain, while private executions rely on ZKPs.
|
||||
- Validators only need to verify proofs for privacy-preserving transactions, keeping processing efficient.
|
||||
|
||||
### The account’s model
|
||||
|
||||
To achieve both state separation and full programmability, NSSA adopts a stateless program model. Programs do not hold internal state. Instead, all persistent data resides in accounts explicitly passed to the program during execution. This design enables fine-grained control over access and visibility while maintaining composability across public and private states.
|
||||
|
||||
### Execution types
|
||||
|
||||
Execution is divided into two fundamentally distinct types based on how they are processed: public execution, which is executed transparently on-chain, and private execution, which occurs off-chain. For private execution, the blockchain relies on ZKPs to verify the correctness of execution and ensure that all system invariants are preserved.
|
||||
|
||||
Both public and private executions of the same program are enforced to use the same Risc0 VM bytecode. For public transactions, programs are executed directly on-chain like any standard RISC-V VM execution, without generating or verifying proofs. For privacy-preserving transactions, users generate Risc0 ZKPs of correct execution, and validator nodes only verify these proofs rather than re-executing the program. This design ensures that from a validator’s perspective, public transactions are processed as quickly as any RISC-V–based VM, while verification of ZKPs keeps privacy-preserving transactions efficient as well. Additionally, the system naturally supports parallel execution similar to Solana, further increasing throughput. The main computational bottleneck for privacy-preserving transactions lies on the user side, in generating zk proofs.
|
||||
|
||||
### Resources
|
||||
- [IFT Research call](https://forum.vac.dev/t/ift-research-call-september-10th-2025-updates-on-the-development-of-nescience/566)
|
||||
- [NSSA v0.2 specs](https://www.notion.so/NSSA-v0-2-specifications-2848f96fb65c800c9818e6f66d9be8f2)
|
||||
- [Choice of VM/zkVM](https://www.notion.so/Conclusion-on-the-chosen-VM-and-zkVM-for-NSSA-2318f96fb65c806a810ed1300f56992d)
|
||||
- [NSSA vs other privacy projects](https://www.notion.so/Privacy-projects-comparison-2688f96fb65c8096b694ecf7e4deca30)
|
||||
- [NSSA state model](https://www.notion.so/Public-state-model-decision-2388f96fb65c80758b20c76de07b1fcc)
|
||||
- [NSSA sequencer specs](https://www.notion.so/Sequencer-specs-2428f96fb65c802da2bfea7b0b214ecb)
|
||||
- [NSSA sequencer code](https://www.notion.so/NSSA-sequencer-pseudocode-2508f96fb65c805e8859e047dffd6785)
|
||||
- [NSSA Token program desing](https://www.notion.so/Token-program-design-2538f96fb65c80a1b4bdc4fd9dd162d7)
|
||||
- [NSSA cross program calls](https://www.notion.so/NSSA-cross-program-calls-Tail-call-model-proposal-extended-version-2838f96fb65c8096b3a2d390444193b6)
|
||||
|
||||
For more details you can read [here](https://notes.status.im/Ya2wDpIyQquoiRiuEIM8hQ?view).
|
||||
|
||||
# Install dependencies
|
||||
|
||||
Install build dependencies
|
||||
- On Linux
|
||||
```sh
|
||||
@ -31,3 +90,541 @@ Then restart your shell and run
|
||||
```sh
|
||||
rzup install
|
||||
```
|
||||
|
||||
# Run tests
|
||||
|
||||
The NSSA repository includes both unit and integration test suites.
|
||||
|
||||
### Unit tests
|
||||
|
||||
```bash
|
||||
# RISC0_DEV_MODE=1 is used to skip proof generation and reduce test runtime overhead
|
||||
RISC0_DEV_MODE=1 cargo test --release
|
||||
```
|
||||
|
||||
### Integration tests
|
||||
|
||||
```bash
|
||||
export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
|
||||
cd integration_tests
|
||||
# RISC0_DEV_MODE=1 skips proof generation; RUST_LOG=info enables runtime logs
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all
|
||||
```
|
||||
|
||||
# Run the sequencer
|
||||
|
||||
The sequencer can be run locally:
|
||||
|
||||
```bash
|
||||
cd sequencer_runner
|
||||
RUST_LOG=info cargo run --release configs/debug
|
||||
```
|
||||
|
||||
If everything went well you should see an output similar to this:
|
||||
```bash
|
||||
[2025-11-13T19:50:29Z INFO sequencer_runner] Sequencer core set up
|
||||
[2025-11-13T19:50:29Z INFO network] Starting http server at 0.0.0.0:3040
|
||||
[2025-11-13T19:50:29Z INFO actix_server::builder] starting 8 workers
|
||||
[2025-11-13T19:50:29Z INFO sequencer_runner] HTTP server started
|
||||
[2025-11-13T19:50:29Z INFO sequencer_runner] Starting main sequencer loop
|
||||
[2025-11-13T19:50:29Z INFO actix_server::server] Tokio runtime found; starting in existing Tokio runtime
|
||||
[2025-11-13T19:50:29Z INFO actix_server::server] starting service: "actix-web-service-0.0.0.0:3040", workers: 8, listening on: 0.0.0.0:3040
|
||||
[2025-11-13T19:50:39Z INFO sequencer_runner] Collecting transactions from mempool, block creation
|
||||
[2025-11-13T19:50:39Z INFO sequencer_core] Created block with 0 transactions in 0 seconds
|
||||
[2025-11-13T19:50:39Z INFO sequencer_runner] Block with id 2 created
|
||||
[2025-11-13T19:50:39Z INFO sequencer_runner] Waiting for new transactions
|
||||
```
|
||||
|
||||
# Try the Wallet CLI
|
||||
|
||||
## Install
|
||||
This repository includes a CLI for interacting with the Nescience sequencer. To install it, run the following command from the root of the repository:
|
||||
|
||||
```bash
|
||||
cargo install --path wallet --force
|
||||
```
|
||||
Before using the CLI, set the environment variable `NSSA_WALLET_HOME_DIR` to the directory containing the wallet configuration file. A sample configuration is available at `integration_tests/configs/debug/wallet/`. To use it, run:
|
||||
|
||||
```bash
|
||||
export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
|
||||
```
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Health-check
|
||||
|
||||
Verify that the node is running and that the wallet can connect to it:
|
||||
|
||||
```bash
|
||||
wallet check-health
|
||||
```
|
||||
|
||||
You should see `✅ All looks good!`.
|
||||
|
||||
### The commands
|
||||
|
||||
The wallet provides several commands to interact with the node and query state. To see the full list, run `wallet help`:
|
||||
|
||||
```bash
|
||||
Commands:
|
||||
auth-transfer Authenticated transfer subcommand
|
||||
chain-info Generic chain info subcommand
|
||||
account Account view and sync subcommand
|
||||
pinata Pinata program interaction subcommand
|
||||
token Token program interaction subcommand
|
||||
check-health Check the wallet can connect to the node and builtin local programs match the remote versions
|
||||
```
|
||||
|
||||
### Accounts
|
||||
|
||||
Every piece of state in NSSA is stored in an account. The CLI provides commands to manage accounts. Run `wallet account` to see the options available:
|
||||
```bash
|
||||
Commands:
|
||||
get Get account data
|
||||
new Produce new public or private account
|
||||
sync-private Sync private accounts
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
#### Create a new public account
|
||||
|
||||
You can create both public and private accounts through the CLI. For example:
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
```
|
||||
|
||||
This address is required when executing any program that interacts with the account.
|
||||
|
||||
#### Account initialization
|
||||
|
||||
To query the account’s current status, run:
|
||||
|
||||
```bash
|
||||
# Replace the address with yours
|
||||
wallet account get --addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account is Uninitialized
|
||||
```
|
||||
|
||||
New accounts start as uninitialized, meaning no program owns them yet. Programs can claim uninitialized accounts; once claimed, the account becomes permanently owned by that program.
|
||||
|
||||
In this example, we will initialize the account for the Authenticated transfer program, which securely manages native token transfers by requiring authentication for debits.
|
||||
|
||||
Initialize the account by running:
|
||||
|
||||
```bash
|
||||
# This command submits a public transaction executing the `init` function of the
|
||||
# Authenticated-transfer program. The wallet polls the sequencer until the
|
||||
# transaction is included in a block, which may take several seconds.
|
||||
wallet auth-transfer init --addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
```
|
||||
|
||||
After it completes, check the updated account status:
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":0}
|
||||
```
|
||||
|
||||
### Funding the account: executing the Piñata program
|
||||
|
||||
Now that we have a public account initialized by the authenticated transfer program, we need to fund it. For that, the testnet provides the Piñata program. See the [Pinata](#piñata-program) section for instructions on how to use it.
|
||||
|
||||
```bash
|
||||
# Complete with your address and the correct solution for your case
|
||||
wallet pinata claim --to-addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ --solution 989106
|
||||
```
|
||||
|
||||
After the claim succeeds, the account will be funded with some tokens:
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":150}
|
||||
```
|
||||
|
||||
### Native token transfers: executing the Authenticated transfers program
|
||||
|
||||
NSSA comes with a program for managing and transferring native tokens. Run `wallet auth-transfer` to see the options available:
|
||||
```bash
|
||||
Commands:
|
||||
init Initialize account under authenticated transfer program
|
||||
send Send native tokens from one account to another with variable privacy
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
We have already used the `init` command. The `send` command is used to execute the `Transfer` function of the authenticated program.
|
||||
Let's try it. For that we need to create another account for the recipient of the transfer.
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
```
|
||||
|
||||
The new account is uninitialized. The authenticated transfers program will claim any uninitialized account used in a transfer. So we don't need to manually initialize the recipient account.
|
||||
|
||||
Let's send 37 tokens to the new account.
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ \
|
||||
--to Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--amount 37
|
||||
```
|
||||
|
||||
Once that succeeds we can check the states.
|
||||
|
||||
```bash
|
||||
# Sender account
|
||||
wallet account get --addr Public/HrA8TVjBS8UVf9akV7LRhyh6k4c7F6PS7PvqgtPmKAT8
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":113}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Recipient account
|
||||
wallet account get --addr Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":37}
|
||||
```
|
||||
|
||||
#### Create a new private account
|
||||
|
||||
Now let’s switch to the private state and create a private account.
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
With npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951
|
||||
With ipk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
|
||||
```
|
||||
|
||||
For now, focus only on the account address. Ignore the `npk` and `ipk` values. These are stored locally in the wallet and are used internally to build privacy-preserving transactions.
|
||||
Also, the account id for private accounts is derived from the `npk` and `ipk` values. But we won't need them now.
|
||||
|
||||
Just like public accounts, new private accounts start out uninitialized:
|
||||
|
||||
```bash
|
||||
wallet account get --addr Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
|
||||
# Output:
|
||||
Account is Uninitialized
|
||||
```
|
||||
Unlike public accounts, private accounts are never visible to the network. They exist only in your local wallet storage.
|
||||
|
||||
#### Sending tokens from the public account to the private account
|
||||
|
||||
Sending tokens to an uninitialized private account causes the Authenticated-Transfers program to claim it. Just like with public accounts.
|
||||
This happens because program execution logic does not depend on whether the involved accounts are public or private.
|
||||
|
||||
Let’s send 17 tokens to the new private account.
|
||||
|
||||
The syntax is identical to the public-to-public transfer; just set the private address as the recipient.
|
||||
|
||||
This command will run the Authenticated-Transfer program locally, generate a proof, and submit it to the sequencer. Depending on your machine, this can take from 30 seconds to 4 minutes.
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--to Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL \
|
||||
--amount 17
|
||||
```
|
||||
|
||||
After it succeeds, check both accounts:
|
||||
|
||||
```bash
|
||||
# Public sender account
|
||||
wallet account get --addr Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":20}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Private recipient account
|
||||
wallet account get --addr Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated transfer program
|
||||
{"balance":17}
|
||||
```
|
||||
|
||||
Note: the last command does not query the network.
|
||||
It works even offline because private account data lives only in your wallet storage. Other users cannot read your private balances.
|
||||
|
||||
#### Digression: modifying private accounts
|
||||
|
||||
As a general rule, private accounts can only be modified through a program execution performed by their owner. That is, the person who holds the private key for that account. There is one exception: an uninitialized private account may be initialized by any user, without requiring the private key. After initialization, only the owner can modify it.
|
||||
|
||||
This mechanism enables a common use case: transferring funds from any account (public or private) to a private account owned by someone else. For such transfers, the recipient’s private account must be uninitialized.
|
||||
|
||||
|
||||
#### Sending tokens from the public account to a private account owned by someone else
|
||||
|
||||
For this tutorial, we’ll simulate that scenario by creating a new private account that we own, but we’ll treat it as if it belonged to someone else.
|
||||
|
||||
Let's create a new (uninitialized) private account like before:
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Private/AukXPRBmrYVqoqEW2HTs7N3hvTn3qdNFDcxDHVr5hMm5
|
||||
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
|
||||
With ipk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
|
||||
```
|
||||
|
||||
Now we'll ignore the private account address and focus on the `npk` and `ipk` values. We'll need this to send tokens to a foreign private account. Syntax is very similar.
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--to-npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e \
|
||||
--to-ipk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72 \
|
||||
--amount 3
|
||||
```
|
||||
|
||||
The command above produces a privacy-preserving transaction, which may take a few minutes to complete. The updated values of the private account are encrypted and included in the transaction.
|
||||
|
||||
Once the transaction is accepted, the recipient must run `wallet account sync-private`. This command scans the chain for encrypted values that belong to their private accounts and updates the local versions accordingly.
|
||||
|
||||
|
||||
#### Transfers in other combinations of public and private accounts
|
||||
|
||||
We’ve shown how to use the authenticated-transfers program for transfers between two public accounts, and for transfers from a public sender to a private recipient. Sending tokens from a private account (whether to a public account or to another private account) works in essentially the same way.
|
||||
|
||||
### The token program
|
||||
|
||||
So far, we’ve made transfers using the authenticated-transfers program, which handles native token transfers. The Token program, on the other hand, is used for creating and managing custom tokens.
|
||||
The CLI provides commands to execute the token program. To see the options available run `wallet token`:
|
||||
|
||||
```bash
|
||||
Commands:
|
||||
new Produce a new token
|
||||
send Send tokens from one account to another with variable privacy
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
The Token program manages its accounts in two categories. Meaning, all accounts owned by the Token program fall into one of these types.
|
||||
- Token definition accounts: these accounts store metadata about a token, such as its name, total supply, and other identifying properties. They act as the token’s unique identifier.
|
||||
- Token holding accounts: these accounts hold actual token balances. In addition to the balance, they also record which token definition they belong to.
|
||||
|
||||
#### Creating a new token
|
||||
|
||||
To create a new token, simply run `wallet token new`. This will create a transaction to execute the `New` function of the token program.
|
||||
The command expects a name, the desired total supply, and two uninitialized accounts:
|
||||
- One that will be initialized as the token definition account for the new token.
|
||||
- Another that will be initialized as a token holding account and receive the token’s entire initial supply.
|
||||
|
||||
|
||||
##### New token with both definition and supply accounts set as public
|
||||
|
||||
For example, let's create two new (uninitialized) public accounts and then use them to create a new token.
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
```
|
||||
|
||||
Now we use them to create a new token. Let's call it the "Token A"
|
||||
|
||||
```bash
|
||||
wallet token new \
|
||||
--name TOKENA \
|
||||
--total-supply 1337 \
|
||||
--definition-addr Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7 \
|
||||
--supply-addr Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
```
|
||||
|
||||
After it succeeds, we can inspect the two accounts to see how they were initialized.
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
|
||||
# Output:
|
||||
Definition account owned by token program
|
||||
{"account_type":"Token definition","name":"TOKENA","total_supply":1337}
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1337}
|
||||
```
|
||||
|
||||
##### New token with public account definition but private holding account for initial supply
|
||||
|
||||
Let’s create a new token, but this time using a public definition account and a private holding account to store the entire supply.
|
||||
|
||||
Since we can’t reuse the accounts from the previous example, we need to create fresh ones for this case.
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
With npk 6a2dfe433cf28e525aa0196d719be3c16146f7ee358ca39595323f94fde38f93
|
||||
With ipk 03d59abf4bee974cc12ddb44641c19f0b5441fef39191f047c988c29a77252a577
|
||||
```
|
||||
|
||||
And we use them to create the token.
|
||||
|
||||
Now we use them to create a new token. Let's call it "Token B".
|
||||
|
||||
```bash
|
||||
wallet token new \
|
||||
--name TOKENB \
|
||||
--total-supply 7331 \
|
||||
--definition-addr Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii \
|
||||
--supply-addr Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
```
|
||||
|
||||
After it succeeds, we can check their values
|
||||
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii
|
||||
|
||||
# Output:
|
||||
Definition account owned by token program
|
||||
{"account_type":"Token definition","name":"TOKENB","total_supply":7331}
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account get --addr Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":7331}
|
||||
```
|
||||
|
||||
Like any other private account owned by us, it cannot be seen by other users.
|
||||
|
||||
#### Custom token transfers
|
||||
|
||||
The Token program has a function to move funds from one token holding account to another one. If executed with an uninitialized account as the recipient, this will be automatically claimed by the token program.
|
||||
|
||||
The transfer function can be executed with the `wallet token send` command.
|
||||
|
||||
Let's create a new public account for the recipient.
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with addr Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6
|
||||
```
|
||||
|
||||
Let's send 10 B tokens to this new account. We'll debit this from the supply account used in the creation of the token.
|
||||
|
||||
```bash
|
||||
wallet token send \
|
||||
--from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \
|
||||
--to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--amount 10
|
||||
```
|
||||
|
||||
Let's inspect the public account:
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":10}
|
||||
```
|
||||
|
||||
### Piñata program
|
||||
|
||||
The testnet comes with a program that serves as a faucet for native tokens. We call it the Piñata. Use the command `wallet pinata claim` to get native tokens from it. This requires two parameters:
|
||||
- `--to-addr` is the address of the account that will receive the tokens. **Use only initialized accounts here.**
|
||||
- `--solution` a solution to the Pinata challenge. This will change every time the Pinata is successfully claimed.
|
||||
|
||||
To find the solution to the challenge, first query the Pinata account. This is always at the address: `Public/EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7`.
|
||||
|
||||
```bash
|
||||
wallet account get --addr Public/EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7
|
||||
|
||||
# Output:
|
||||
{"balance":750,"program_owner_b64":"/SQ9PX+NYQgXm7YMP7VMUBRwvU/Bq4pHTTZcCpTC5FM=","data_b64":"A939OBnG9OhvzOocqfCAJKSYvtcuV15OHDIVNg34MC8i","nonce":0}
|
||||
```
|
||||
|
||||
Copy the `data_b64` value and run the following python script:
|
||||
|
||||
```python
|
||||
import base64, hashlib
|
||||
|
||||
def find_16byte_prefix(data: str, max_attempts: int) -> bytes:
|
||||
data = base64.b64decode(data_b64)[1:]
|
||||
for attempt in range(max_attempts):
|
||||
suffix = attempt.to_bytes(16, 'little')
|
||||
h = hashlib.sha256(data + suffix).digest()
|
||||
if h[:3] == b"\x00\x00\x00":
|
||||
solution = int.from_bytes(suffix, 'little')
|
||||
return f"Solution: {solution}"
|
||||
raise RuntimeError(f"No suffix found in {max_attempts} attempts")
|
||||
|
||||
|
||||
data_b64 = "A939OBnG9OhvzOocqfCAJKSYvtcuV15OHDIVNg34MC8i" # <- Change with the value from the Piñata account
|
||||
print(find_16byte_prefix(data_b64, 50000000))
|
||||
```
|
||||
|
||||
### Chain information
|
||||
|
||||
The wallet provides some commands to query information about the chain. These are under the `wallet chain-info` command.
|
||||
|
||||
```bash
|
||||
Commands:
|
||||
current-block-id Get current block id from sequencer
|
||||
block Get block at id from sequencer
|
||||
transaction Get transaction at hash from sequencer
|
||||
```
|
||||
|
||||
For example, run this to find the current block id.
|
||||
|
||||
```bash
|
||||
wallet chain-info current-block-id
|
||||
|
||||
# Output:
|
||||
Last block id is 65537
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ fn make_private_account_input_from_str(account_id: &str) -> String {
|
||||
pub async fn pre_test(
|
||||
home_dir: PathBuf,
|
||||
) -> Result<(ServerHandle, JoinHandle<Result<()>>, TempDir)> {
|
||||
wallet::execute_setup("test_pass".to_owned()).await?;
|
||||
wallet::cli::execute_setup("test_pass".to_owned()).await?;
|
||||
|
||||
let home_dir_sequencer = home_dir.join("sequencer");
|
||||
|
||||
|
||||
@ -17,13 +17,15 @@ use sequencer_runner::startup_sequencer;
|
||||
use tempfile::TempDir;
|
||||
use tokio::task::JoinHandle;
|
||||
use wallet::{
|
||||
Command, SubcommandReturnValue, WalletCore,
|
||||
WalletCore,
|
||||
cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
account::{AccountSubcommand, NewSubcommand},
|
||||
config::ConfigSubcommand,
|
||||
native_token_transfer_program::AuthTransferSubcommand,
|
||||
pinata_program::PinataProgramAgnosticSubcommand,
|
||||
token_program::TokenProgramAgnosticSubcommand,
|
||||
programs::{
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
},
|
||||
config::PersistentStorage,
|
||||
helperfunctions::{fetch_config, fetch_persistent_storage},
|
||||
@ -58,7 +60,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -93,7 +95,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let PersistentStorage {
|
||||
accounts: persistent_accounts,
|
||||
@ -124,7 +126,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -163,7 +165,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
let failed_send = wallet::execute_subcommand(command).await;
|
||||
let failed_send = wallet::cli::execute_subcommand(command).await;
|
||||
|
||||
assert!(failed_send.is_err());
|
||||
|
||||
@ -204,7 +206,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -235,7 +237,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -290,7 +292,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token definition
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -303,7 +305,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token supply holder
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -316,7 +318,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -336,7 +338,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
name: "A NAME".to_string(),
|
||||
total_supply: 37,
|
||||
};
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
@ -395,7 +397,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Waiting for next block creation");
|
||||
@ -450,7 +452,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token definition (public)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -463,7 +465,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token supply holder (private)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -476,7 +478,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -497,7 +499,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
total_supply: 37,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -544,7 +546,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -578,7 +580,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -611,7 +613,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token definition (public)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -624,7 +626,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token supply holder (private)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -637,7 +639,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -658,7 +660,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
total_supply: 37,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -712,7 +714,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
};
|
||||
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } =
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
@ -724,7 +726,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
|
||||
let command = Command::Account(AccountSubcommand::SyncPrivate {});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
|
||||
@ -753,7 +755,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token definition (public)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -766,7 +768,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token supply holder (public)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -779,7 +781,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -800,7 +802,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
total_supply: 37,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -837,7 +839,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -866,7 +868,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -895,7 +897,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token definition (public)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: definition_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -908,7 +910,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for the token supply holder (private)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: supply_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -921,7 +923,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// Create new account for receiving a token transaction
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: recipient_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Public {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -942,7 +944,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
total_supply: 37,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -989,7 +991,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -1018,7 +1020,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
wallet::execute_subcommand(Command::Token(subcommand))
|
||||
wallet::cli::execute_subcommand(Command::Token(subcommand))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -1050,7 +1052,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -1089,7 +1091,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
});
|
||||
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } =
|
||||
wallet::execute_subcommand(command).await.unwrap()
|
||||
wallet::cli::execute_subcommand(command).await.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
@ -1129,7 +1131,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::root(),
|
||||
}));
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: to_account_id,
|
||||
} = sub_ret
|
||||
@ -1158,7 +1160,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
|
||||
panic!("FAILED TO SEND TX");
|
||||
};
|
||||
@ -1166,7 +1168,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await;
|
||||
|
||||
let command = Command::Account(AccountSubcommand::SyncPrivate {});
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -1193,13 +1195,13 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// info!(
|
||||
// "########## test_success_private_transfer_to_another_owned_account_cont_run_path
|
||||
// ##########" );
|
||||
// let continious_run_handle = tokio::spawn(wallet::execute_continious_run());
|
||||
// let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run());
|
||||
|
||||
// let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap();
|
||||
|
||||
// let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {}));
|
||||
|
||||
// let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
// let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
// let SubcommandReturnValue::RegisterAccount {
|
||||
// account_id: to_account_id,
|
||||
// } = sub_ret
|
||||
@ -1229,7 +1231,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
// amount: 100,
|
||||
// });
|
||||
|
||||
// let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
// let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
// let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
|
||||
// panic!("FAILED TO SEND TX");
|
||||
// };
|
||||
@ -1280,7 +1282,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let from_acc = wallet_storage.get_account_private(&from).unwrap();
|
||||
assert_eq!(from_acc.balance, 10000);
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -1323,7 +1325,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -1369,7 +1371,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } =
|
||||
wallet::execute_subcommand(command).await.unwrap()
|
||||
wallet::cli::execute_subcommand(command).await.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
@ -1399,10 +1401,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let pinata_account_id = PINATA_BASE58;
|
||||
|
||||
let pinata_prize = 150;
|
||||
let solution = 989106;
|
||||
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
|
||||
to_account_id: make_public_account_input_from_str(ACC_SENDER),
|
||||
solution,
|
||||
to: make_public_account_input_from_str(ACC_SENDER),
|
||||
});
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
@ -1415,7 +1415,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.unwrap()
|
||||
.balance;
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -1494,7 +1494,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::root(),
|
||||
}));
|
||||
let SubcommandReturnValue::RegisterAccount { account_id } =
|
||||
wallet::execute_subcommand(command).await.unwrap()
|
||||
wallet::cli::execute_subcommand(command).await.unwrap()
|
||||
else {
|
||||
panic!("Error creating account");
|
||||
};
|
||||
@ -1502,7 +1502,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
|
||||
account_id: make_public_account_input_from_str(&account_id.to_string()),
|
||||
});
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Checking correct execution");
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
@ -1530,11 +1530,9 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
info!("########## test_pinata_private_receiver ##########");
|
||||
let pinata_account_id = PINATA_BASE58;
|
||||
let pinata_prize = 150;
|
||||
let solution = 989106;
|
||||
|
||||
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
|
||||
to_account_id: make_private_account_input_from_str(ACC_SENDER_PRIVATE),
|
||||
solution,
|
||||
to: make_private_account_input_from_str(ACC_SENDER_PRIVATE),
|
||||
});
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
@ -1548,7 +1546,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.balance;
|
||||
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } =
|
||||
wallet::execute_subcommand(command).await.unwrap()
|
||||
wallet::cli::execute_subcommand(command).await.unwrap()
|
||||
else {
|
||||
panic!("invalid subcommand return value");
|
||||
};
|
||||
@ -1564,7 +1562,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.balance;
|
||||
|
||||
let command = Command::Account(AccountSubcommand::SyncPrivate {});
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
@ -1587,12 +1585,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
info!("########## test_pinata_private_receiver_new_account ##########");
|
||||
let pinata_account_id = PINATA_BASE58;
|
||||
let pinata_prize = 150;
|
||||
let solution = 989106;
|
||||
|
||||
// Create new account for the token supply holder (private)
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: winner_account_id,
|
||||
} = wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
|
||||
NewSubcommand::Private {
|
||||
cci: ChainIndex::root(),
|
||||
},
|
||||
@ -1604,8 +1601,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
};
|
||||
|
||||
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
|
||||
to_account_id: make_private_account_input_from_str(&winner_account_id.to_string()),
|
||||
solution,
|
||||
to: make_private_account_input_from_str(&winner_account_id.to_string()),
|
||||
});
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
@ -1618,7 +1614,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
.unwrap()
|
||||
.balance;
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
@ -1658,7 +1654,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
key: "seq_poll_retry_delay_millis".to_string(),
|
||||
value: "1000".to_string(),
|
||||
});
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
|
||||
@ -1669,7 +1665,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
key: "seq_poll_retry_delay_millis".to_string(),
|
||||
value: old_seq_poll_retry_delay_millis.to_string(),
|
||||
});
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Success!");
|
||||
}
|
||||
@ -1683,7 +1679,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::root(),
|
||||
}));
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: to_account_id1,
|
||||
} = sub_ret
|
||||
@ -1695,7 +1691,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::from_str("/0").unwrap(),
|
||||
}));
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: to_account_id2,
|
||||
} = sub_ret
|
||||
@ -1713,7 +1709,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: make_private_account_input_from_str(&from.to_string()),
|
||||
@ -1725,7 +1721,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 101,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let from: AccountId = ACC_SENDER.parse().unwrap();
|
||||
|
||||
@ -1733,7 +1729,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::root(),
|
||||
}));
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: to_account_id3,
|
||||
} = sub_ret
|
||||
@ -1745,7 +1741,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
cci: ChainIndex::from_str("/0").unwrap(),
|
||||
}));
|
||||
|
||||
let sub_ret = wallet::execute_subcommand(command).await.unwrap();
|
||||
let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: to_account_id4,
|
||||
} = sub_ret
|
||||
@ -1763,7 +1759,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 102,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: make_public_account_input_from_str(&from.to_string()),
|
||||
@ -1775,11 +1771,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 103,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("########## PREPARATION END ##########");
|
||||
|
||||
wallet::execute_keys_restoration("test_pass".to_string(), 10)
|
||||
wallet::cli::execute_keys_restoration("test_pass".to_string(), 10)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -1840,7 +1836,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 10,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: make_public_account_input_from_str(&to_account_id3.to_string()),
|
||||
@ -1852,7 +1848,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
|
||||
amount: 11,
|
||||
});
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
wallet::cli::execute_subcommand(command).await.unwrap();
|
||||
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
@ -3,7 +3,7 @@ use nssa_core::{
|
||||
program::{ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
||||
};
|
||||
|
||||
// The token program has two functions:
|
||||
// The token program has three functions:
|
||||
// 1. New token definition.
|
||||
// Arguments to this function are:
|
||||
// * Two **default** accounts: [definition_account, holding_account].
|
||||
@ -18,6 +18,11 @@ use nssa_core::{
|
||||
// * Two accounts: [sender_account, recipient_account].
|
||||
// * An instruction data byte string of length 23, indicating the total supply with the following layout
|
||||
// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00].
|
||||
// 3. Initialize account with zero balance
|
||||
// Arguments to this function are:
|
||||
// * Two accounts: [definition_account, account_to_initialize].
|
||||
// * An dummy byte string of length 23, with the following layout
|
||||
// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00].
|
||||
|
||||
const TOKEN_DEFINITION_TYPE: u8 = 0;
|
||||
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
|
||||
@ -45,6 +50,25 @@ impl TokenDefinition {
|
||||
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
|
||||
bytes.into()
|
||||
}
|
||||
|
||||
fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let name = data[1..7].try_into().unwrap();
|
||||
let total_supply = u128::from_le_bytes(
|
||||
data[7..]
|
||||
.try_into()
|
||||
.expect("Total supply must be 16 bytes little-endian"),
|
||||
);
|
||||
Some(Self {
|
||||
account_type,
|
||||
name,
|
||||
total_supply,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenHolding {
|
||||
@ -61,8 +85,16 @@ impl TokenHolding {
|
||||
None
|
||||
} else {
|
||||
let account_type = data[0];
|
||||
let definition_id = AccountId::new(data[1..33].try_into().unwrap());
|
||||
let balance = u128::from_le_bytes(data[33..].try_into().unwrap());
|
||||
let definition_id = AccountId::new(
|
||||
data[1..33]
|
||||
.try_into()
|
||||
.expect("Defintion ID must be 32 bytes long"),
|
||||
);
|
||||
let balance = u128::from_le_bytes(
|
||||
data[33..]
|
||||
.try_into()
|
||||
.expect("balance must be 16 bytes little-endian"),
|
||||
);
|
||||
Some(Self {
|
||||
definition_id,
|
||||
balance,
|
||||
@ -167,6 +199,33 @@ fn new_definition(
|
||||
vec![definition_target_account_post, holding_target_account_post]
|
||||
}
|
||||
|
||||
fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<Account> {
|
||||
if pre_states.len() != 2 {
|
||||
panic!("Invalid number of accounts");
|
||||
}
|
||||
|
||||
let definition = &pre_states[0];
|
||||
let account_to_initialize = &pre_states[1];
|
||||
|
||||
if account_to_initialize.account != Account::default() {
|
||||
panic!("Only uninitialized accounts can be initialized");
|
||||
}
|
||||
|
||||
// TODO: We should check that this is an account owned by the token program.
|
||||
// This check can't be done here since the ID of the program is known only after compiling it
|
||||
//
|
||||
// Check definition account is valid
|
||||
let _definition_values =
|
||||
TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid");
|
||||
let holding_values = TokenHolding::new(&definition.account_id);
|
||||
|
||||
let definition_post = definition.account.clone();
|
||||
let mut account_to_initialize_post = account_to_initialize.account.clone();
|
||||
account_to_initialize_post.data = holding_values.into_data();
|
||||
|
||||
vec![definition_post, account_to_initialize_post]
|
||||
}
|
||||
|
||||
type Instruction = [u8; 23];
|
||||
|
||||
fn main() {
|
||||
@ -175,36 +234,59 @@ fn main() {
|
||||
instruction,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
match instruction[0] {
|
||||
let (pre_states, post_states) = match instruction[0] {
|
||||
0 => {
|
||||
// Parse instruction
|
||||
let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
|
||||
let name: [u8; 6] = instruction[17..].try_into().unwrap();
|
||||
let total_supply = u128::from_le_bytes(
|
||||
instruction[1..17]
|
||||
.try_into()
|
||||
.expect("Total supply must be 16 bytes little-endian"),
|
||||
);
|
||||
let name: [u8; 6] = instruction[17..]
|
||||
.try_into()
|
||||
.expect("Name must be 6 bytes long");
|
||||
assert_ne!(name, [0; 6]);
|
||||
|
||||
// Execute
|
||||
let post_states = new_definition(&pre_states, name, total_supply);
|
||||
write_nssa_outputs(pre_states, post_states);
|
||||
(pre_states, post_states)
|
||||
}
|
||||
1 => {
|
||||
// Parse instruction
|
||||
let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap());
|
||||
let name: [u8; 6] = instruction[17..].try_into().unwrap();
|
||||
let balance_to_move = u128::from_le_bytes(
|
||||
instruction[1..17]
|
||||
.try_into()
|
||||
.expect("Balance to move must be 16 bytes little-endian"),
|
||||
);
|
||||
let name: [u8; 6] = instruction[17..]
|
||||
.try_into()
|
||||
.expect("Name must be 6 bytes long");
|
||||
assert_eq!(name, [0; 6]);
|
||||
|
||||
// Execute
|
||||
let post_states = transfer(&pre_states, balance_to_move);
|
||||
write_nssa_outputs(pre_states, post_states);
|
||||
(pre_states, post_states)
|
||||
}
|
||||
2 => {
|
||||
// Initialize account
|
||||
assert_eq!(instruction[1..], [0; 22]);
|
||||
let post_states = initialize_account(&pre_states);
|
||||
(pre_states, post_states)
|
||||
}
|
||||
_ => panic!("Invalid instruction"),
|
||||
};
|
||||
|
||||
write_nssa_outputs(pre_states, post_states);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
|
||||
|
||||
use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE};
|
||||
use crate::{
|
||||
TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE,
|
||||
initialize_account, new_definition, transfer,
|
||||
};
|
||||
|
||||
#[should_panic(expected = "Invalid number of input accounts")]
|
||||
#[test]
|
||||
@ -551,4 +633,37 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_initialize_account_succeeds() {
|
||||
let pre_states = vec![
|
||||
AccountWithMetadata {
|
||||
account: Account {
|
||||
// Definition ID with
|
||||
data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16]
|
||||
.into_iter()
|
||||
.chain(u128::to_le_bytes(1000))
|
||||
.collect(),
|
||||
..Account::default()
|
||||
},
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([1; 32]),
|
||||
},
|
||||
AccountWithMetadata {
|
||||
account: Account::default(),
|
||||
is_authorized: false,
|
||||
account_id: AccountId::new([2; 32]),
|
||||
},
|
||||
];
|
||||
let post_states = initialize_account(&pre_states);
|
||||
let [definition, holding] = post_states.try_into().ok().unwrap();
|
||||
assert_eq!(definition.data, pre_states[0].account.data);
|
||||
assert_eq!(
|
||||
holding.data,
|
||||
vec![
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ base58.workspace = true
|
||||
hex = "0.4.3"
|
||||
rand.workspace = true
|
||||
itertools = "0.14.0"
|
||||
sha2.workspace = true
|
||||
|
||||
[dependencies.key_protocol]
|
||||
path = "../key_protocol"
|
||||
|
||||
@ -7,10 +7,11 @@ use nssa::{Account, AccountId, program::Program};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
SubcommandReturnValue, WalletCore,
|
||||
cli::WalletSubcommand,
|
||||
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
|
||||
parse_block_range,
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{
|
||||
AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix, parse_block_range,
|
||||
},
|
||||
};
|
||||
|
||||
const TOKEN_DEFINITION_TYPE: u8 = 0;
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
};
|
||||
|
||||
/// Represents generic chain CLI subcommand
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
};
|
||||
|
||||
/// Represents generic config CLI subcommand
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
|
||||
@ -1,15 +1,268 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{SubcommandReturnValue, WalletCore};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use common::sequencer_client::SequencerClient;
|
||||
use nssa::program::Program;
|
||||
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{
|
||||
account::AccountSubcommand,
|
||||
chain::ChainSubcommand,
|
||||
config::ConfigSubcommand,
|
||||
programs::{
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
token::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
},
|
||||
helperfunctions::{fetch_config, parse_block_range},
|
||||
};
|
||||
|
||||
pub mod account;
|
||||
pub mod chain;
|
||||
pub mod config;
|
||||
pub mod native_token_transfer_program;
|
||||
pub mod pinata_program;
|
||||
pub mod token_program;
|
||||
pub mod programs;
|
||||
|
||||
pub(crate) trait WalletSubcommand {
|
||||
async fn handle_subcommand(self, wallet_core: &mut WalletCore)
|
||||
-> Result<SubcommandReturnValue>;
|
||||
}
|
||||
|
||||
/// Represents CLI command for a wallet
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
#[clap(about)]
|
||||
pub enum Command {
|
||||
/// Authenticated transfer subcommand
|
||||
#[command(subcommand)]
|
||||
AuthTransfer(AuthTransferSubcommand),
|
||||
/// Generic chain info subcommand
|
||||
#[command(subcommand)]
|
||||
ChainInfo(ChainSubcommand),
|
||||
/// Account view and sync subcommand
|
||||
#[command(subcommand)]
|
||||
Account(AccountSubcommand),
|
||||
/// Pinata program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
Pinata(PinataProgramAgnosticSubcommand),
|
||||
/// Token program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
Token(TokenProgramAgnosticSubcommand),
|
||||
/// Check the wallet can connect to the node and builtin local programs
|
||||
/// match the remote versions
|
||||
CheckHealth {},
|
||||
/// Command to setup config, get and set config fields
|
||||
#[command(subcommand)]
|
||||
Config(ConfigSubcommand),
|
||||
}
|
||||
|
||||
/// Represents overarching CLI command for a wallet with setup included
|
||||
#[derive(Debug, Subcommand, Clone)]
|
||||
#[clap(about)]
|
||||
pub enum OverCommand {
|
||||
/// Represents CLI command for a wallet
|
||||
#[command(subcommand)]
|
||||
Command(Command),
|
||||
/// Setup of a storage. Initializes rots for public and private trees from `password`.
|
||||
Setup {
|
||||
#[arg(short, long)]
|
||||
password: String,
|
||||
},
|
||||
/// !!!WARNING!!! will rewrite current storage
|
||||
RestoreKeys {
|
||||
#[arg(short, long)]
|
||||
password: String,
|
||||
#[arg(short, long)]
|
||||
depth: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||
///
|
||||
/// All account adresses must be valid 32 byte base58 strings.
|
||||
///
|
||||
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
|
||||
/// where valid options for `privacy_prefix` is `Public` and `Private`
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about)]
|
||||
pub struct Args {
|
||||
/// Continious run flag
|
||||
#[arg(short, long)]
|
||||
pub continuous_run: bool,
|
||||
/// Wallet command
|
||||
#[command(subcommand)]
|
||||
pub command: Option<OverCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SubcommandReturnValue {
|
||||
PrivacyPreservingTransfer { tx_hash: String },
|
||||
RegisterAccount { account_id: nssa::AccountId },
|
||||
Account(nssa::Account),
|
||||
Empty,
|
||||
SyncedToBlock(u64),
|
||||
}
|
||||
|
||||
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
|
||||
let wallet_config = fetch_config().await?;
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
|
||||
|
||||
let subcommand_ret = match command {
|
||||
Command::AuthTransfer(transfer_subcommand) => {
|
||||
transfer_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::ChainInfo(chain_subcommand) => {
|
||||
chain_subcommand.handle_subcommand(&mut wallet_core).await?
|
||||
}
|
||||
Command::Account(account_subcommand) => {
|
||||
account_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::Pinata(pinata_subcommand) => {
|
||||
pinata_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::CheckHealth {} => {
|
||||
let remote_program_ids = wallet_core
|
||||
.sequencer_client
|
||||
.get_program_ids()
|
||||
.await
|
||||
.expect("Error fetching program ids");
|
||||
let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer")
|
||||
else {
|
||||
panic!("Missing authenticated transfer ID from remote");
|
||||
};
|
||||
if authenticated_transfer_id != &Program::authenticated_transfer_program().id() {
|
||||
panic!("Local ID for authenticated transfer program is different from remote");
|
||||
}
|
||||
let Some(token_id) = remote_program_ids.get("token") else {
|
||||
panic!("Missing token program ID from remote");
|
||||
};
|
||||
if token_id != &Program::token().id() {
|
||||
panic!("Local ID for token program is different from remote");
|
||||
}
|
||||
let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else {
|
||||
panic!("Missing privacy preserving circuit ID from remote");
|
||||
};
|
||||
if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID {
|
||||
panic!("Local ID for privacy preserving circuit is different from remote");
|
||||
}
|
||||
|
||||
println!("✅All looks good!");
|
||||
|
||||
SubcommandReturnValue::Empty
|
||||
}
|
||||
Command::Token(token_subcommand) => {
|
||||
token_subcommand.handle_subcommand(&mut wallet_core).await?
|
||||
}
|
||||
Command::Config(config_subcommand) => {
|
||||
config_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(subcommand_ret)
|
||||
}
|
||||
|
||||
pub async fn execute_continuous_run() -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
|
||||
|
||||
let mut latest_block_num = seq_client.get_last_block().await?.last_block;
|
||||
let mut curr_last_block = latest_block_num;
|
||||
|
||||
loop {
|
||||
parse_block_range(
|
||||
curr_last_block,
|
||||
latest_block_num,
|
||||
seq_client.clone(),
|
||||
&mut wallet_core,
|
||||
)
|
||||
.await?;
|
||||
|
||||
curr_last_block = latest_block_num + 1;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(
|
||||
config.seq_poll_timeout_millis,
|
||||
))
|
||||
.await;
|
||||
|
||||
latest_block_num = seq_client.get_last_block().await?.last_block;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute_setup(password: String) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let mut wallet_core =
|
||||
WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?;
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.public_key_tree
|
||||
.generate_tree_for_depth(depth);
|
||||
|
||||
println!("Public tree generated");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.generate_tree_for_depth(depth);
|
||||
|
||||
println!("Private tree generated");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.public_key_tree
|
||||
.cleanup_tree_for_depth(depth, wallet_core.sequencer_client.clone())
|
||||
.await?;
|
||||
|
||||
println!("Public tree cleaned up");
|
||||
|
||||
let last_block = wallet_core
|
||||
.sequencer_client
|
||||
.get_last_block()
|
||||
.await?
|
||||
.last_block;
|
||||
|
||||
println!("Last block is {last_block}");
|
||||
|
||||
parse_block_range(
|
||||
1,
|
||||
last_block,
|
||||
wallet_core.sequencer_client.clone(),
|
||||
&mut wallet_core,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Private tree clean up start");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.cleanup_tree_for_depth(depth);
|
||||
|
||||
println!("Private tree cleaned up");
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
3
wallet/src/cli/programs/mod.rs
Normal file
3
wallet/src/cli/programs/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
@ -4,9 +4,10 @@ use common::transaction::NSSATransaction;
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::{
|
||||
SubcommandReturnValue, WalletCore,
|
||||
cli::WalletSubcommand,
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
|
||||
program_facades::native_token_transfer::NativeTokenTransfer,
|
||||
};
|
||||
|
||||
/// Represents generic CLI subcommand for a wallet working with native token transfer program
|
||||
@ -56,11 +57,11 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
||||
AccountPrivacyKind::Public => {
|
||||
let account_id = account_id.parse()?;
|
||||
|
||||
let res = wallet_core
|
||||
.register_account_under_authenticated_transfers_programs(account_id)
|
||||
let res = NativeTokenTransfer(wallet_core)
|
||||
.register_account(account_id)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let transfer_tx =
|
||||
wallet_core.poll_native_token_transfer(res.tx_hash).await?;
|
||||
@ -74,13 +75,11 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
||||
AccountPrivacyKind::Private => {
|
||||
let account_id = account_id.parse()?;
|
||||
|
||||
let (res, [secret]) = wallet_core
|
||||
.register_account_under_authenticated_transfers_programs_private(
|
||||
account_id,
|
||||
)
|
||||
let (res, secret) = NativeTokenTransfer(wallet_core)
|
||||
.register_account_private(account_id)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -317,23 +316,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
let to: AccountId = to.parse().unwrap();
|
||||
|
||||
let to_initialization = wallet_core.check_private_account_initialized(&to).await?;
|
||||
let (res, [secret_from, secret_to]) = NativeTokenTransfer(wallet_core)
|
||||
.send_private_transfer_to_owned_account(from, to, amount)
|
||||
.await?;
|
||||
|
||||
let (res, [secret_from, secret_to]) = if let Some(to_proof) = to_initialization {
|
||||
wallet_core
|
||||
.send_private_native_token_transfer_owned_account_already_initialized(
|
||||
from, to, amount, to_proof,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
wallet_core
|
||||
.send_private_native_token_transfer_owned_account_not_initialized(
|
||||
from, to, amount,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -373,11 +360,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
|
||||
let to_ipk =
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
|
||||
|
||||
let (res, [secret_from, _]) = wallet_core
|
||||
.send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount)
|
||||
let (res, [secret_from, _]) = NativeTokenTransfer(wallet_core)
|
||||
.send_private_transfer_to_outer_account(from, to_npk, to_ipk, amount)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -413,21 +400,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
let to: AccountId = to.parse().unwrap();
|
||||
|
||||
let to_initialization = wallet_core.check_private_account_initialized(&to).await?;
|
||||
let (res, secret) = NativeTokenTransfer(wallet_core)
|
||||
.send_shielded_transfer(from, to, amount)
|
||||
.await?;
|
||||
|
||||
let (res, [secret]) = if let Some(to_proof) = to_initialization {
|
||||
wallet_core
|
||||
.send_shielded_native_token_transfer_already_initialized(
|
||||
from, to, amount, to_proof,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
wallet_core
|
||||
.send_shielded_native_token_transfer_not_initialized(from, to, amount)
|
||||
.await?
|
||||
};
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -468,11 +445,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
||||
let to_ipk =
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec());
|
||||
|
||||
let res = wallet_core
|
||||
.send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount)
|
||||
let (res, _) = NativeTokenTransfer(wallet_core)
|
||||
.send_shielded_transfer_to_outer_account(from, to_npk, to_ipk, amount)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
|
||||
@ -502,11 +479,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
let to: AccountId = to.parse().unwrap();
|
||||
|
||||
let (res, [secret]) = wallet_core
|
||||
.send_deshielded_native_token_transfer(from, to, amount)
|
||||
let (res, secret) = NativeTokenTransfer(wallet_core)
|
||||
.send_deshielded_transfer(from, to, amount)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -532,11 +509,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
let to: AccountId = to.parse().unwrap();
|
||||
|
||||
let res = wallet_core
|
||||
.send_public_native_token_transfer(from, to, amount)
|
||||
let res = NativeTokenTransfer(wallet_core)
|
||||
.send_public_transfer(from, to, amount)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Subcommand;
|
||||
use common::{PINATA_BASE58, transaction::NSSATransaction};
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
SubcommandReturnValue, WalletCore,
|
||||
cli::WalletSubcommand,
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
|
||||
program_facades::pinata::Pinata,
|
||||
};
|
||||
|
||||
/// Represents generic CLI subcommand for a wallet working with pinata program
|
||||
@ -14,12 +14,9 @@ use crate::{
|
||||
pub enum PinataProgramAgnosticSubcommand {
|
||||
/// Claim pinata
|
||||
Claim {
|
||||
/// to_account_id - valid 32 byte base58 string with privacy prefix
|
||||
/// to - valid 32 byte base58 string with privacy prefix
|
||||
#[arg(long)]
|
||||
to_account_id: String,
|
||||
/// solution - solution to pinata challenge
|
||||
#[arg(long)]
|
||||
solution: u128,
|
||||
to: String,
|
||||
},
|
||||
}
|
||||
|
||||
@ -29,26 +26,20 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand {
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
let underlying_subcommand = match self {
|
||||
PinataProgramAgnosticSubcommand::Claim {
|
||||
to_account_id,
|
||||
solution,
|
||||
} => {
|
||||
let (to_account_id, to_addr_privacy) =
|
||||
parse_addr_with_privacy_prefix(&to_account_id)?;
|
||||
PinataProgramAgnosticSubcommand::Claim { to } => {
|
||||
let (to, to_addr_privacy) = parse_addr_with_privacy_prefix(&to)?;
|
||||
|
||||
match to_addr_privacy {
|
||||
AccountPrivacyKind::Public => {
|
||||
PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim {
|
||||
pinata_account_id: PINATA_BASE58.to_string(),
|
||||
winner_account_id: to_account_id,
|
||||
solution,
|
||||
winner_account_id: to,
|
||||
})
|
||||
}
|
||||
AccountPrivacyKind::Private => PinataProgramSubcommand::Private(
|
||||
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
|
||||
pinata_account_id: PINATA_BASE58.to_string(),
|
||||
winner_account_id: to_account_id,
|
||||
solution,
|
||||
winner_account_id: to,
|
||||
},
|
||||
),
|
||||
}
|
||||
@ -82,9 +73,6 @@ pub enum PinataProgramSubcommandPublic {
|
||||
/// winner_account_id - valid 32 byte hex string
|
||||
#[arg(long)]
|
||||
winner_account_id: String,
|
||||
/// solution - solution to pinata challenge
|
||||
#[arg(long)]
|
||||
solution: u128,
|
||||
},
|
||||
}
|
||||
|
||||
@ -100,9 +88,6 @@ pub enum PinataProgramSubcommandPrivate {
|
||||
/// winner_account_id - valid 32 byte hex string
|
||||
#[arg(long)]
|
||||
winner_account_id: String,
|
||||
/// solution - solution to pinata challenge
|
||||
#[arg(long)]
|
||||
solution: u128,
|
||||
},
|
||||
}
|
||||
|
||||
@ -115,16 +100,28 @@ impl WalletSubcommand for PinataProgramSubcommandPublic {
|
||||
PinataProgramSubcommandPublic::Claim {
|
||||
pinata_account_id,
|
||||
winner_account_id,
|
||||
solution,
|
||||
} => {
|
||||
let res = wallet_core
|
||||
.claim_pinata(
|
||||
pinata_account_id.parse().unwrap(),
|
||||
let pinata_account_id = pinata_account_id.parse().unwrap();
|
||||
let solution = find_solution(wallet_core, pinata_account_id)
|
||||
.await
|
||||
.context("failed to compute solution")?;
|
||||
|
||||
let res = Pinata(wallet_core)
|
||||
.claim(
|
||||
pinata_account_id,
|
||||
winner_account_id.parse().unwrap(),
|
||||
solution,
|
||||
)
|
||||
.await?;
|
||||
info!("Results of tx send is {res:#?}");
|
||||
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
.poll_native_token_transfer(tx_hash.clone())
|
||||
.await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
@ -141,41 +138,26 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
|
||||
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
|
||||
pinata_account_id,
|
||||
winner_account_id,
|
||||
solution,
|
||||
} => {
|
||||
let pinata_account_id = pinata_account_id.parse().unwrap();
|
||||
let winner_account_id = winner_account_id.parse().unwrap();
|
||||
let solution = find_solution(wallet_core, pinata_account_id)
|
||||
.await
|
||||
.context("failed to compute solution")?;
|
||||
|
||||
let winner_initialization = wallet_core
|
||||
.check_private_account_initialized(&winner_account_id)
|
||||
let (res, secret_winner) = Pinata(wallet_core)
|
||||
.claim_private_owned_account(pinata_account_id, winner_account_id, solution)
|
||||
.await?;
|
||||
|
||||
let (res, [secret_winner]) = if let Some(winner_proof) = winner_initialization {
|
||||
wallet_core
|
||||
.claim_pinata_private_owned_account_already_initialized(
|
||||
pinata_account_id,
|
||||
winner_account_id,
|
||||
solution,
|
||||
winner_proof,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
wallet_core
|
||||
.claim_pinata_private_owned_account_not_initialized(
|
||||
pinata_account_id,
|
||||
winner_account_id,
|
||||
solution,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
info!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
.poll_native_token_transfer(tx_hash.clone())
|
||||
.await?;
|
||||
|
||||
println!("Transaction data is {transfer_tx:?}");
|
||||
|
||||
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
|
||||
let acc_decode_data = vec![(secret_winner, winner_account_id)];
|
||||
|
||||
@ -210,3 +192,46 @@ impl WalletSubcommand for PinataProgramSubcommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId) -> Result<u128> {
|
||||
let account = wallet.get_account_public(pinata_account_id).await?;
|
||||
let data: [u8; 33] = account
|
||||
.data
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::Error::msg("invalid pinata account data"))?;
|
||||
|
||||
println!("Computing solution for pinata...");
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let solution = compute_solution(data);
|
||||
|
||||
println!("Found solution {solution} in {:?}", now.elapsed());
|
||||
Ok(solution)
|
||||
}
|
||||
|
||||
fn compute_solution(data: [u8; 33]) -> u128 {
|
||||
let difficulty = data[0];
|
||||
let seed = &data[1..];
|
||||
|
||||
let mut solution = 0u128;
|
||||
while !validate_solution(difficulty, seed, solution) {
|
||||
solution = solution.checked_add(1).expect("solution overflowed u128");
|
||||
}
|
||||
|
||||
solution
|
||||
}
|
||||
|
||||
fn validate_solution(difficulty: u8, seed: &[u8], solution: u128) -> bool {
|
||||
use sha2::{Digest as _, digest::FixedOutput as _};
|
||||
|
||||
let mut bytes = [0; 32 + 16];
|
||||
bytes[..32].copy_from_slice(seed);
|
||||
bytes[32..].copy_from_slice(&solution.to_le_bytes());
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(bytes);
|
||||
let digest: [u8; 32] = hasher.finalize_fixed().into();
|
||||
|
||||
let difficulty = difficulty as usize;
|
||||
digest[..difficulty].iter().all(|&b| b == 0)
|
||||
}
|
||||
@ -4,9 +4,10 @@ use common::transaction::NSSATransaction;
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::{
|
||||
SubcommandReturnValue, WalletCore,
|
||||
cli::WalletSubcommand,
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
|
||||
program_facades::token::Token,
|
||||
};
|
||||
|
||||
/// Represents generic CLI subcommand for a wallet working with token program
|
||||
@ -338,8 +339,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
|
||||
}
|
||||
let mut name_bytes = [0; 6];
|
||||
name_bytes[..name.len()].copy_from_slice(name);
|
||||
wallet_core
|
||||
.send_new_token_definition(
|
||||
Token(wallet_core)
|
||||
.send_new_definition(
|
||||
definition_account_id.parse().unwrap(),
|
||||
supply_account_id.parse().unwrap(),
|
||||
name_bytes,
|
||||
@ -353,8 +354,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
} => {
|
||||
wallet_core
|
||||
.send_transfer_token_transaction(
|
||||
Token(wallet_core)
|
||||
.send_transfer_transaction(
|
||||
sender_account_id.parse().unwrap(),
|
||||
recipient_account_id.parse().unwrap(),
|
||||
balance_to_move,
|
||||
@ -389,8 +390,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
|
||||
|
||||
let (res, [secret_supply]) = wallet_core
|
||||
.send_new_token_definition_private_owned(
|
||||
let (res, secret_supply) = Token(wallet_core)
|
||||
.send_new_definition_private_owned(
|
||||
definition_account_id,
|
||||
supply_account_id,
|
||||
name_bytes,
|
||||
@ -398,7 +399,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -428,31 +429,15 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
|
||||
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
|
||||
|
||||
let recipient_initialization = wallet_core
|
||||
.check_private_account_initialized(&recipient_account_id)
|
||||
let (res, [secret_sender, secret_recipient]) = Token(wallet_core)
|
||||
.send_transfer_transaction_private_owned_account(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (res, [secret_sender, secret_recipient]) =
|
||||
if let Some(recipient_proof) = recipient_initialization {
|
||||
wallet_core
|
||||
.send_transfer_token_transaction_private_owned_account_already_initialized(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
recipient_proof,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
wallet_core
|
||||
.send_transfer_token_transaction_private_owned_account_not_initialized(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -496,8 +481,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
recipient_ipk.to_vec(),
|
||||
);
|
||||
|
||||
let (res, [secret_sender, _]) = wallet_core
|
||||
.send_transfer_token_transaction_private_foreign_account(
|
||||
let (res, [secret_sender, _]) = Token(wallet_core)
|
||||
.send_transfer_transaction_private_foreign_account(
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_ipk,
|
||||
@ -505,7 +490,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -545,15 +530,15 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
|
||||
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
|
||||
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
|
||||
|
||||
let (res, [secret_sender]) = wallet_core
|
||||
.send_transfer_token_transaction_deshielded(
|
||||
let (res, secret_sender) = Token(wallet_core)
|
||||
.send_transfer_transaction_deshielded(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -604,8 +589,8 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
recipient_ipk.to_vec(),
|
||||
);
|
||||
|
||||
let res = wallet_core
|
||||
.send_transfer_token_transaction_shielded_foreign_account(
|
||||
let (res, _) = Token(wallet_core)
|
||||
.send_transfer_transaction_shielded_foreign_account(
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_ipk,
|
||||
@ -613,7 +598,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -638,31 +623,15 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
|
||||
let recipient_account_id: AccountId = recipient_account_id.parse().unwrap();
|
||||
|
||||
let recipient_initialization = wallet_core
|
||||
.check_private_account_initialized(&recipient_account_id)
|
||||
let (res, secret_recipient) = Token(wallet_core)
|
||||
.send_transfer_transaction_shielded_owned_account(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (res, [secret_recipient]) =
|
||||
if let Some(recipient_proof) = recipient_initialization {
|
||||
wallet_core
|
||||
.send_transfer_token_transaction_shielded_owned_account_already_initialized(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
recipient_proof,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
wallet_core
|
||||
.send_transfer_token_transaction_shielded_owned_account_not_initialized(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
balance_to_move,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
println!("Results of tx send is {res:#?}");
|
||||
println!("Results of tx send are {res:#?}");
|
||||
|
||||
let tx_hash = res.tx_hash;
|
||||
let transfer_tx = wallet_core
|
||||
@ -1,16 +1,21 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||
use key_protocol::key_protocol_core::NSSAUserData;
|
||||
use nssa::Account;
|
||||
use common::{
|
||||
block::HashableBlockData, sequencer_client::SequencerClient, transaction::NSSATransaction,
|
||||
};
|
||||
use key_protocol::{
|
||||
key_management::key_tree::traits::KeyNode as _, key_protocol_core::NSSAUserData,
|
||||
};
|
||||
use nssa::{Account, privacy_preserving_transaction::message::EncryptedAccountData};
|
||||
use nssa_core::account::Nonce;
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use serde::Serialize;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
use crate::{
|
||||
HOME_DIR_ENV_VAR,
|
||||
HOME_DIR_ENV_VAR, WalletCore,
|
||||
config::{
|
||||
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
||||
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
|
||||
@ -225,6 +230,125 @@ impl From<Account> for HumanReadableAccount {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn parse_block_range(
|
||||
start: u64,
|
||||
stop: u64,
|
||||
seq_client: Arc<SequencerClient>,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<()> {
|
||||
for block_id in start..(stop + 1) {
|
||||
let block =
|
||||
borsh::from_slice::<HashableBlockData>(&seq_client.get_block(block_id).await?.block)?;
|
||||
|
||||
for tx in block.transactions {
|
||||
let nssa_tx = NSSATransaction::try_from(&tx)?;
|
||||
|
||||
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
|
||||
let mut affected_accounts = vec![];
|
||||
|
||||
for (acc_account_id, (key_chain, _)) in
|
||||
&wallet_core.storage.user_data.default_user_private_accounts
|
||||
{
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(
|
||||
key_chain.nullifer_public_key.clone(),
|
||||
key_chain.incoming_viewing_public_key.clone(),
|
||||
);
|
||||
|
||||
for (ciph_id, encrypted_data) in tx
|
||||
.message()
|
||||
.encrypted_private_post_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if encrypted_data.view_tag == view_tag {
|
||||
let ciphertext = &encrypted_data.ciphertext;
|
||||
let commitment = &tx.message.new_commitments[ciph_id];
|
||||
let shared_secret = key_chain
|
||||
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
|
||||
|
||||
let res_acc = nssa_core::EncryptionScheme::decrypt(
|
||||
ciphertext,
|
||||
&shared_secret,
|
||||
commitment,
|
||||
ciph_id as u32,
|
||||
);
|
||||
|
||||
if let Some(res_acc) = res_acc {
|
||||
println!(
|
||||
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
|
||||
);
|
||||
|
||||
affected_accounts.push((*acc_account_id, res_acc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for keys_node in wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.values()
|
||||
{
|
||||
let acc_account_id = keys_node.account_id();
|
||||
let key_chain = &keys_node.value.0;
|
||||
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(
|
||||
key_chain.nullifer_public_key.clone(),
|
||||
key_chain.incoming_viewing_public_key.clone(),
|
||||
);
|
||||
|
||||
for (ciph_id, encrypted_data) in tx
|
||||
.message()
|
||||
.encrypted_private_post_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if encrypted_data.view_tag == view_tag {
|
||||
let ciphertext = &encrypted_data.ciphertext;
|
||||
let commitment = &tx.message.new_commitments[ciph_id];
|
||||
let shared_secret = key_chain
|
||||
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
|
||||
|
||||
let res_acc = nssa_core::EncryptionScheme::decrypt(
|
||||
ciphertext,
|
||||
&shared_secret,
|
||||
commitment,
|
||||
ciph_id as u32,
|
||||
);
|
||||
|
||||
if let Some(res_acc) = res_acc {
|
||||
println!(
|
||||
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
|
||||
);
|
||||
|
||||
affected_accounts.push((acc_account_id, res_acc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (affected_account_id, new_acc) in affected_accounts {
|
||||
wallet_core
|
||||
.storage
|
||||
.insert_private_account_data(affected_account_id, new_acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wallet_core.last_synced_block = block_id;
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!(
|
||||
"Block at id {block_id} with timestamp {} parsed",
|
||||
block.timestamp
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -3,31 +3,24 @@ use std::{path::PathBuf, sync::Arc};
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||
use chain_storage::WalletChainStore;
|
||||
use clap::{Parser, Subcommand};
|
||||
use common::{
|
||||
block::HashableBlockData,
|
||||
sequencer_client::SequencerClient,
|
||||
error::ExecutionFailureKind,
|
||||
sequencer_client::{SequencerClient, json::SendTxResponse},
|
||||
transaction::{EncodedTransaction, NSSATransaction},
|
||||
};
|
||||
use config::WalletConfig;
|
||||
use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode};
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use log::info;
|
||||
use nssa::{
|
||||
Account, AccountId, privacy_preserving_transaction::message::EncryptedAccountData,
|
||||
program::Program,
|
||||
};
|
||||
use nssa_core::{Commitment, MembershipProof};
|
||||
use nssa::{Account, AccountId, PrivacyPreservingTransaction, program::Program};
|
||||
use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData};
|
||||
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
|
||||
config::ConfigSubcommand, native_token_transfer_program::AuthTransferSubcommand,
|
||||
pinata_program::PinataProgramAgnosticSubcommand,
|
||||
token_program::TokenProgramAgnosticSubcommand,
|
||||
},
|
||||
config::PersistentStorage,
|
||||
helperfunctions::{fetch_config, fetch_persistent_storage, get_home, produce_data_for_storage},
|
||||
helperfunctions::{
|
||||
fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces,
|
||||
},
|
||||
poller::TxPoller,
|
||||
};
|
||||
|
||||
@ -37,11 +30,9 @@ pub mod chain_storage;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod helperfunctions;
|
||||
pub mod pinata_interactions;
|
||||
pub mod poller;
|
||||
pub mod token_program_interactions;
|
||||
pub mod token_transfers;
|
||||
pub mod transaction_utils;
|
||||
mod privacy_preserving_tx;
|
||||
pub mod program_facades;
|
||||
|
||||
pub struct WalletCore {
|
||||
pub storage: WalletChainStore,
|
||||
@ -156,6 +147,15 @@ impl WalletCore {
|
||||
Ok(response.account)
|
||||
}
|
||||
|
||||
pub fn get_account_public_signing_key(
|
||||
&self,
|
||||
account_id: &AccountId,
|
||||
) -> Option<&nssa::PrivateKey> {
|
||||
self.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(account_id)
|
||||
}
|
||||
|
||||
pub fn get_account_private(&self, account_id: &AccountId) -> Option<Account> {
|
||||
self.storage
|
||||
.user_data
|
||||
@ -218,361 +218,79 @@ impl WalletCore {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents CLI command for a wallet
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
#[clap(about)]
|
||||
pub enum Command {
|
||||
/// Authenticated transfer subcommand
|
||||
#[command(subcommand)]
|
||||
AuthTransfer(AuthTransferSubcommand),
|
||||
/// Generic chain info subcommand
|
||||
#[command(subcommand)]
|
||||
ChainInfo(ChainSubcommand),
|
||||
/// Account view and sync subcommand
|
||||
#[command(subcommand)]
|
||||
Account(AccountSubcommand),
|
||||
/// Pinata program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
Pinata(PinataProgramAgnosticSubcommand),
|
||||
/// Token program interaction subcommand
|
||||
#[command(subcommand)]
|
||||
Token(TokenProgramAgnosticSubcommand),
|
||||
/// Check the wallet can connect to the node and builtin local programs
|
||||
/// match the remote versions
|
||||
CheckHealth {},
|
||||
/// Command to setup config, get and set config fields
|
||||
#[command(subcommand)]
|
||||
Config(ConfigSubcommand),
|
||||
}
|
||||
|
||||
/// Represents overarching CLI command for a wallet with setup included
|
||||
#[derive(Debug, Subcommand, Clone)]
|
||||
#[clap(about)]
|
||||
pub enum OverCommand {
|
||||
/// Represents CLI command for a wallet
|
||||
#[command(subcommand)]
|
||||
Command(Command),
|
||||
/// Setup of a storage. Initializes rots for public and private trees from `password`.
|
||||
Setup {
|
||||
#[arg(short, long)]
|
||||
password: String,
|
||||
},
|
||||
/// !!!WARNING!!! will rewrite current storage
|
||||
RestoreKeys {
|
||||
#[arg(short, long)]
|
||||
password: String,
|
||||
#[arg(short, long)]
|
||||
depth: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||
///
|
||||
/// All account adresses must be valid 32 byte base58 strings.
|
||||
///
|
||||
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
|
||||
/// where valid options for `privacy_prefix` is `Public` and `Private`
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about)]
|
||||
pub struct Args {
|
||||
/// Continious run flag
|
||||
#[arg(short, long)]
|
||||
pub continious_run: bool,
|
||||
/// Wallet command
|
||||
#[command(subcommand)]
|
||||
pub command: Option<OverCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SubcommandReturnValue {
|
||||
PrivacyPreservingTransfer { tx_hash: String },
|
||||
RegisterAccount { account_id: nssa::AccountId },
|
||||
Account(nssa::Account),
|
||||
Empty,
|
||||
SyncedToBlock(u64),
|
||||
}
|
||||
|
||||
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
|
||||
let wallet_config = fetch_config().await?;
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
|
||||
|
||||
let subcommand_ret = match command {
|
||||
Command::AuthTransfer(transfer_subcommand) => {
|
||||
transfer_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::ChainInfo(chain_subcommand) => {
|
||||
chain_subcommand.handle_subcommand(&mut wallet_core).await?
|
||||
}
|
||||
Command::Account(account_subcommand) => {
|
||||
account_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::Pinata(pinata_subcommand) => {
|
||||
pinata_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
Command::CheckHealth {} => {
|
||||
let remote_program_ids = wallet_core
|
||||
.sequencer_client
|
||||
.get_program_ids()
|
||||
.await
|
||||
.expect("Error fetching program ids");
|
||||
let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer")
|
||||
else {
|
||||
panic!("Missing authenticated transfer ID from remote");
|
||||
};
|
||||
if authenticated_transfer_id != &Program::authenticated_transfer_program().id() {
|
||||
panic!("Local ID for authenticated transfer program is different from remote");
|
||||
}
|
||||
let Some(token_id) = remote_program_ids.get("token") else {
|
||||
panic!("Missing token program ID from remote");
|
||||
};
|
||||
if token_id != &Program::token().id() {
|
||||
panic!("Local ID for token program is different from remote");
|
||||
}
|
||||
let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else {
|
||||
panic!("Missing privacy preserving circuit ID from remote");
|
||||
};
|
||||
if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID {
|
||||
panic!("Local ID for privacy preserving circuit is different from remote");
|
||||
}
|
||||
|
||||
println!("✅All looks good!");
|
||||
|
||||
SubcommandReturnValue::Empty
|
||||
}
|
||||
Command::Token(token_subcommand) => {
|
||||
token_subcommand.handle_subcommand(&mut wallet_core).await?
|
||||
}
|
||||
Command::Config(config_subcommand) => {
|
||||
config_subcommand
|
||||
.handle_subcommand(&mut wallet_core)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(subcommand_ret)
|
||||
}
|
||||
|
||||
pub async fn parse_block_range(
|
||||
start: u64,
|
||||
stop: u64,
|
||||
seq_client: Arc<SequencerClient>,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<()> {
|
||||
for block_id in start..(stop + 1) {
|
||||
let block =
|
||||
borsh::from_slice::<HashableBlockData>(&seq_client.get_block(block_id).await?.block)?;
|
||||
|
||||
for tx in block.transactions {
|
||||
let nssa_tx = NSSATransaction::try_from(&tx)?;
|
||||
|
||||
if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx {
|
||||
let mut affected_accounts = vec![];
|
||||
|
||||
for (acc_account_id, (key_chain, _)) in
|
||||
&wallet_core.storage.user_data.default_user_private_accounts
|
||||
{
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(
|
||||
key_chain.nullifer_public_key.clone(),
|
||||
key_chain.incoming_viewing_public_key.clone(),
|
||||
);
|
||||
|
||||
for (ciph_id, encrypted_data) in tx
|
||||
.message()
|
||||
.encrypted_private_post_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if encrypted_data.view_tag == view_tag {
|
||||
let ciphertext = &encrypted_data.ciphertext;
|
||||
let commitment = &tx.message.new_commitments[ciph_id];
|
||||
let shared_secret = key_chain
|
||||
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
|
||||
|
||||
let res_acc = nssa_core::EncryptionScheme::decrypt(
|
||||
ciphertext,
|
||||
&shared_secret,
|
||||
commitment,
|
||||
ciph_id as u32,
|
||||
);
|
||||
|
||||
if let Some(res_acc) = res_acc {
|
||||
println!(
|
||||
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
|
||||
);
|
||||
|
||||
affected_accounts.push((*acc_account_id, res_acc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for keys_node in wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.values()
|
||||
{
|
||||
let acc_account_id = keys_node.account_id();
|
||||
let key_chain = &keys_node.value.0;
|
||||
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(
|
||||
key_chain.nullifer_public_key.clone(),
|
||||
key_chain.incoming_viewing_public_key.clone(),
|
||||
);
|
||||
|
||||
for (ciph_id, encrypted_data) in tx
|
||||
.message()
|
||||
.encrypted_private_post_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if encrypted_data.view_tag == view_tag {
|
||||
let ciphertext = &encrypted_data.ciphertext;
|
||||
let commitment = &tx.message.new_commitments[ciph_id];
|
||||
let shared_secret = key_chain
|
||||
.calculate_shared_secret_receiver(encrypted_data.epk.clone());
|
||||
|
||||
let res_acc = nssa_core::EncryptionScheme::decrypt(
|
||||
ciphertext,
|
||||
&shared_secret,
|
||||
commitment,
|
||||
ciph_id as u32,
|
||||
);
|
||||
|
||||
if let Some(res_acc) = res_acc {
|
||||
println!(
|
||||
"Received new account for account_id {acc_account_id:#?} with account object {res_acc:#?}"
|
||||
);
|
||||
|
||||
affected_accounts.push((acc_account_id, res_acc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (affected_account_id, new_acc) in affected_accounts {
|
||||
wallet_core
|
||||
.storage
|
||||
.insert_private_account_data(affected_account_id, new_acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wallet_core.last_synced_block = block_id;
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!(
|
||||
"Block at id {block_id} with timestamp {} parsed",
|
||||
block.timestamp
|
||||
);
|
||||
pub async fn send_privacy_preserving_tx(
|
||||
&self,
|
||||
accounts: Vec<PrivacyPreservingAccount>,
|
||||
instruction_data: &InstructionData,
|
||||
program: &Program,
|
||||
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send_privacy_preserving_tx_with_pre_check(
|
||||
&self,
|
||||
accounts: Vec<PrivacyPreservingAccount>,
|
||||
instruction_data: &InstructionData,
|
||||
program: &Program,
|
||||
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
||||
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||
let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?;
|
||||
|
||||
pub async fn execute_continious_run() -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
||||
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
|
||||
let pre_states = acc_manager.pre_states();
|
||||
tx_pre_check(
|
||||
&pre_states
|
||||
.iter()
|
||||
.map(|pre| &pre.account)
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
let mut latest_block_num = seq_client.get_last_block().await?.last_block;
|
||||
let mut curr_last_block = latest_block_num;
|
||||
|
||||
loop {
|
||||
parse_block_range(
|
||||
curr_last_block,
|
||||
latest_block_num,
|
||||
seq_client.clone(),
|
||||
&mut wallet_core,
|
||||
let private_account_keys = acc_manager.private_account_keys();
|
||||
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
|
||||
&pre_states,
|
||||
instruction_data,
|
||||
acc_manager.visibility_mask(),
|
||||
&produce_random_nonces(private_account_keys.len()),
|
||||
&private_account_keys
|
||||
.iter()
|
||||
.map(|keys| (keys.npk.clone(), keys.ssk.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
&acc_manager.private_account_auth(),
|
||||
program,
|
||||
)
|
||||
.await?;
|
||||
.unwrap();
|
||||
|
||||
curr_last_block = latest_block_num + 1;
|
||||
let message =
|
||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||
acc_manager.public_account_ids(),
|
||||
Vec::from_iter(acc_manager.public_account_nonces()),
|
||||
private_account_keys
|
||||
.iter()
|
||||
.map(|keys| (keys.npk.clone(), keys.ipk.clone(), keys.epk.clone()))
|
||||
.collect(),
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(
|
||||
config.seq_poll_timeout_millis,
|
||||
let witness_set =
|
||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&acc_manager.witness_signing_keys(),
|
||||
);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
let shared_secrets = private_account_keys
|
||||
.into_iter()
|
||||
.map(|keys| keys.ssk)
|
||||
.collect();
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
shared_secrets,
|
||||
))
|
||||
.await;
|
||||
|
||||
latest_block_num = seq_client.get_last_block().await?.last_block;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute_setup(password: String) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> {
|
||||
let config = fetch_config().await?;
|
||||
let mut wallet_core =
|
||||
WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?;
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.public_key_tree
|
||||
.generate_tree_for_depth(depth);
|
||||
|
||||
println!("Public tree generated");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.generate_tree_for_depth(depth);
|
||||
|
||||
println!("Private tree generated");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.public_key_tree
|
||||
.cleanup_tree_for_depth(depth, wallet_core.sequencer_client.clone())
|
||||
.await?;
|
||||
|
||||
println!("Public tree cleaned up");
|
||||
|
||||
let last_block = wallet_core
|
||||
.sequencer_client
|
||||
.get_last_block()
|
||||
.await?
|
||||
.last_block;
|
||||
|
||||
println!("Last block is {last_block}");
|
||||
|
||||
parse_block_range(
|
||||
1,
|
||||
last_block,
|
||||
wallet_core.sequencer_client.clone(),
|
||||
&mut wallet_core,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Private tree clean up start");
|
||||
|
||||
wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.cleanup_tree_for_depth(depth);
|
||||
|
||||
println!("Private tree cleaned up");
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use clap::{CommandFactory as _, Parser as _};
|
||||
use tokio::runtime::Builder;
|
||||
use wallet::{
|
||||
Args, OverCommand, execute_continious_run, execute_keys_restoration, execute_setup,
|
||||
use wallet::cli::{
|
||||
Args, OverCommand, execute_continuous_run, execute_keys_restoration, execute_setup,
|
||||
execute_subcommand,
|
||||
};
|
||||
|
||||
@ -10,8 +10,10 @@ pub const NUM_THREADS: usize = 2;
|
||||
|
||||
// TODO #169: We have sample configs for sequencer, but not for wallet
|
||||
// TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config
|
||||
// file path? TODO #172: Why it requires config as env var while sequencer_runner accepts as
|
||||
// argument? TODO #171: Running pinata doesn't give output about transaction hash and etc.
|
||||
// file path?
|
||||
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
|
||||
// argument?
|
||||
// TODO #171: Running pinata doesn't give output about transaction hash and etc.
|
||||
fn main() -> Result<()> {
|
||||
let runtime = Builder::new_multi_thread()
|
||||
.worker_threads(NUM_THREADS)
|
||||
@ -24,25 +26,23 @@ fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
runtime.block_on(async move {
|
||||
if let Some(overcommand) = args.command {
|
||||
match overcommand {
|
||||
if let Some(over_command) = args.command {
|
||||
match over_command {
|
||||
OverCommand::Command(command) => {
|
||||
execute_subcommand(command).await.unwrap();
|
||||
}
|
||||
OverCommand::Setup { password } => {
|
||||
execute_setup(password).await.unwrap();
|
||||
let _output = execute_subcommand(command).await?;
|
||||
Ok(())
|
||||
}
|
||||
OverCommand::RestoreKeys { password, depth } => {
|
||||
execute_keys_restoration(password, depth).await.unwrap();
|
||||
execute_keys_restoration(password, depth).await
|
||||
}
|
||||
OverCommand::Setup { password } => execute_setup(password).await,
|
||||
}
|
||||
} else if args.continious_run {
|
||||
execute_continious_run().await.unwrap();
|
||||
} else if args.continuous_run {
|
||||
execute_continuous_run().await
|
||||
} else {
|
||||
let help = Args::command().render_long_help();
|
||||
println!("{help}");
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,161 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{AccountId, privacy_preserving_transaction::circuit};
|
||||
use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata};
|
||||
|
||||
use crate::{
|
||||
WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData,
|
||||
};
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn claim_pinata(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![pinata_account_id, winner_account_id];
|
||||
let program_id = nssa::program::Program::pinata().id();
|
||||
let message =
|
||||
nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn claim_pinata_private_owned_account_already_initialized(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
winner_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: winner_nsk,
|
||||
npk: winner_npk,
|
||||
ipk: winner_ipk,
|
||||
auth_acc: winner_pre,
|
||||
proof: _,
|
||||
} = self
|
||||
.private_acc_preparation(winner_account_id, true, false)
|
||||
.await?;
|
||||
|
||||
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
|
||||
|
||||
let program = nssa::program::Program::pinata();
|
||||
|
||||
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
|
||||
|
||||
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
|
||||
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[pinata_pre, winner_pre],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&[0, 1],
|
||||
&produce_random_nonces(1),
|
||||
&[(winner_npk.clone(), shared_secret_winner.clone())],
|
||||
&[(winner_nsk.unwrap(), winner_proof)],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message =
|
||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||
vec![pinata_account_id],
|
||||
vec![],
|
||||
vec![(
|
||||
winner_npk.clone(),
|
||||
winner_ipk.clone(),
|
||||
eph_holder_winner.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&[],
|
||||
);
|
||||
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_winner],
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn claim_pinata_private_owned_account_not_initialized(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: winner_npk,
|
||||
ipk: winner_ipk,
|
||||
auth_acc: winner_pre,
|
||||
proof: _,
|
||||
} = self
|
||||
.private_acc_preparation(winner_account_id, false, false)
|
||||
.await?;
|
||||
|
||||
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
|
||||
|
||||
let program = nssa::program::Program::pinata();
|
||||
|
||||
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
|
||||
|
||||
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
|
||||
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[pinata_pre, winner_pre],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(winner_npk.clone(), shared_secret_winner.clone())],
|
||||
&[],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message =
|
||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||
vec![pinata_account_id],
|
||||
vec![],
|
||||
vec![(
|
||||
winner_npk.clone(),
|
||||
winner_ipk.clone(),
|
||||
eph_holder_winner.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&[],
|
||||
);
|
||||
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_winner],
|
||||
))
|
||||
}
|
||||
}
|
||||
212
wallet/src/privacy_preserving_tx.rs
Normal file
212
wallet/src/privacy_preserving_tx.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use common::error::ExecutionFailureKind;
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{AccountId, PrivateKey};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
|
||||
};
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
pub enum PrivacyPreservingAccount {
|
||||
Public(AccountId),
|
||||
PrivateOwned(AccountId),
|
||||
PrivateForeign {
|
||||
npk: NullifierPublicKey,
|
||||
ipk: IncomingViewingPublicKey,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PrivateAccountKeys {
|
||||
pub npk: NullifierPublicKey,
|
||||
pub ssk: SharedSecretKey,
|
||||
pub ipk: IncomingViewingPublicKey,
|
||||
pub epk: EphemeralPublicKey,
|
||||
}
|
||||
|
||||
enum State {
|
||||
Public {
|
||||
account: AccountWithMetadata,
|
||||
sk: Option<PrivateKey>,
|
||||
},
|
||||
Private(AccountPreparedData),
|
||||
}
|
||||
|
||||
pub struct AccountManager {
|
||||
states: Vec<State>,
|
||||
visibility_mask: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AccountManager {
|
||||
pub async fn new(
|
||||
wallet: &WalletCore,
|
||||
accounts: Vec<PrivacyPreservingAccount>,
|
||||
) -> Result<Self, ExecutionFailureKind> {
|
||||
let mut pre_states = Vec::with_capacity(accounts.len());
|
||||
let mut visibility_mask = Vec::with_capacity(accounts.len());
|
||||
|
||||
for account in accounts {
|
||||
let (state, mask) = match account {
|
||||
PrivacyPreservingAccount::Public(account_id) => {
|
||||
let acc = wallet
|
||||
.get_account_public(account_id)
|
||||
.await
|
||||
.map_err(|_| ExecutionFailureKind::KeyNotFoundError)?;
|
||||
|
||||
let sk = wallet.get_account_public_signing_key(&account_id).cloned();
|
||||
let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id);
|
||||
|
||||
(State::Public { account, sk }, 0)
|
||||
}
|
||||
PrivacyPreservingAccount::PrivateOwned(account_id) => {
|
||||
let pre = private_acc_preparation(wallet, account_id).await?;
|
||||
let mask = if pre.auth_acc.is_authorized { 1 } else { 2 };
|
||||
|
||||
(State::Private(pre), mask)
|
||||
}
|
||||
PrivacyPreservingAccount::PrivateForeign { npk, ipk } => {
|
||||
let acc = nssa_core::account::Account::default();
|
||||
let auth_acc = AccountWithMetadata::new(acc, false, &npk);
|
||||
let pre = AccountPreparedData {
|
||||
nsk: None,
|
||||
npk,
|
||||
ipk,
|
||||
auth_acc,
|
||||
proof: None,
|
||||
};
|
||||
|
||||
(State::Private(pre), 2)
|
||||
}
|
||||
};
|
||||
|
||||
pre_states.push(state);
|
||||
visibility_mask.push(mask);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
states: pre_states,
|
||||
visibility_mask,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pre_states(&self) -> Vec<AccountWithMetadata> {
|
||||
self.states
|
||||
.iter()
|
||||
.map(|state| match state {
|
||||
State::Public { account, .. } => account.clone(),
|
||||
State::Private(pre) => pre.auth_acc.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn visibility_mask(&self) -> &[u8] {
|
||||
&self.visibility_mask
|
||||
}
|
||||
|
||||
pub fn public_account_nonces(&self) -> Vec<Nonce> {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Public { account, sk } => sk.as_ref().map(|_| account.account.nonce),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn private_account_keys(&self) -> Vec<PrivateAccountKeys> {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Private(pre) => {
|
||||
let eph_holder = EphemeralKeyHolder::new(&pre.npk);
|
||||
|
||||
Some(PrivateAccountKeys {
|
||||
npk: pre.npk.clone(),
|
||||
ssk: eph_holder.calculate_shared_secret_sender(&pre.ipk),
|
||||
ipk: pre.ipk.clone(),
|
||||
epk: eph_holder.generate_ephemeral_public_key(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn private_account_auth(&self) -> Vec<(NullifierSecretKey, MembershipProof)> {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Private(pre) => Some((pre.nsk?, pre.proof.clone()?)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn public_account_ids(&self) -> Vec<AccountId> {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Public { account, .. } => Some(account.account_id),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn witness_signing_keys(&self) -> Vec<&PrivateKey> {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Public { sk, .. } => sk.as_ref(),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
struct AccountPreparedData {
|
||||
nsk: Option<NullifierSecretKey>,
|
||||
npk: NullifierPublicKey,
|
||||
ipk: IncomingViewingPublicKey,
|
||||
auth_acc: AccountWithMetadata,
|
||||
proof: Option<MembershipProof>,
|
||||
}
|
||||
|
||||
async fn private_acc_preparation(
|
||||
wallet: &WalletCore,
|
||||
account_id: AccountId,
|
||||
) -> Result<AccountPreparedData, ExecutionFailureKind> {
|
||||
let Some((from_keys, from_acc)) = wallet
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(&account_id)
|
||||
.cloned()
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let mut nsk = Some(from_keys.private_key_holder.nullifier_secret_key);
|
||||
|
||||
let from_npk = from_keys.nullifer_public_key;
|
||||
let from_ipk = from_keys.incoming_viewing_public_key;
|
||||
|
||||
// TODO: Remove this unwrap, error types must be compatible
|
||||
let proof = wallet
|
||||
.check_private_account_initialized(&account_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if proof.is_none() {
|
||||
nsk = None;
|
||||
}
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), proof.is_some(), &from_npk);
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof,
|
||||
})
|
||||
}
|
||||
6
wallet/src/program_facades/mod.rs
Normal file
6
wallet/src/program_facades/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
//! This module contains [`WalletCore`](crate::WalletCore) facades for interacting with various
|
||||
//! on-chain programs.
|
||||
|
||||
pub mod native_token_transfer;
|
||||
pub mod pinata;
|
||||
pub mod token;
|
||||
@ -0,0 +1,35 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
|
||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||
use crate::PrivacyPreservingAccount;
|
||||
|
||||
impl NativeTokenTransfer<'_> {
|
||||
pub async fn send_deshielded_transfer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(from),
|
||||
PrivacyPreservingAccount::Public(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected sender's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
}
|
||||
33
wallet/src/program_facades/native_token_transfer/mod.rs
Normal file
33
wallet/src/program_facades/native_token_transfer/mod.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use common::error::ExecutionFailureKind;
|
||||
use nssa::{Account, program::Program};
|
||||
use nssa_core::program::InstructionData;
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
pub mod deshielded;
|
||||
pub mod private;
|
||||
pub mod public;
|
||||
pub mod shielded;
|
||||
|
||||
pub struct NativeTokenTransfer<'w>(pub &'w WalletCore);
|
||||
|
||||
fn auth_transfer_preparation(
|
||||
balance_to_move: u128,
|
||||
) -> (
|
||||
InstructionData,
|
||||
Program,
|
||||
impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
||||
) {
|
||||
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let tx_pre_check = move |accounts: &[&Account]| {
|
||||
let from = accounts[0];
|
||||
if from.balance >= balance_to_move {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExecutionFailureKind::InsufficientFundsError)
|
||||
}
|
||||
};
|
||||
|
||||
(instruction_data, program, tx_pre_check)
|
||||
}
|
||||
89
wallet/src/program_facades/native_token_transfer/private.rs
Normal file
89
wallet/src/program_facades/native_token_transfer/private.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use std::vec;
|
||||
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::{AccountId, program::Program};
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
|
||||
|
||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||
use crate::PrivacyPreservingAccount;
|
||||
|
||||
impl NativeTokenTransfer<'_> {
|
||||
pub async fn register_account_private(
|
||||
&self,
|
||||
from: AccountId,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction: u128 = 0;
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![PrivacyPreservingAccount::PrivateOwned(from)],
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&Program::authenticated_transfer_program(),
|
||||
|_| Ok(()),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let mut secrets_iter = secrets.into_iter();
|
||||
let first = secrets_iter.next().expect("expected sender's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_private_transfer_to_outer_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(from),
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let mut secrets_iter = secrets.into_iter();
|
||||
let first = secrets_iter.next().expect("expected sender's secret");
|
||||
let second = secrets_iter.next().expect("expected receiver's secret");
|
||||
(resp, [first, second])
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_private_transfer_to_owned_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(from),
|
||||
PrivacyPreservingAccount::PrivateOwned(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let mut secrets_iter = secrets.into_iter();
|
||||
let first = secrets_iter.next().expect("expected sender's secret");
|
||||
let second = secrets_iter.next().expect("expected receiver's secret");
|
||||
(resp, [first, second])
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -5,21 +5,21 @@ use nssa::{
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
|
||||
use crate::WalletCore;
|
||||
use super::NativeTokenTransfer;
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn send_public_native_token_transfer(
|
||||
impl NativeTokenTransfer<'_> {
|
||||
pub async fn send_public_transfer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let Ok(balance) = self.get_account_balance(from).await else {
|
||||
let Ok(balance) = self.0.get_account_balance(from).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
|
||||
if balance >= balance_to_move {
|
||||
let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else {
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
|
||||
@ -28,7 +28,7 @@ impl WalletCore {
|
||||
let message =
|
||||
Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
@ -38,17 +38,17 @@ impl WalletCore {
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
} else {
|
||||
Err(ExecutionFailureKind::InsufficientFundsError)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_account_under_authenticated_transfers_programs(
|
||||
pub async fn register_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else {
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
|
||||
@ -57,7 +57,7 @@ impl WalletCore {
|
||||
let program_id = Program::authenticated_transfer_program().id();
|
||||
let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
@ -67,6 +67,6 @@ impl WalletCore {
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
}
|
||||
68
wallet/src/program_facades/native_token_transfer/shielded.rs
Normal file
68
wallet/src/program_facades/native_token_transfer/shielded.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey};
|
||||
|
||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||
use crate::PrivacyPreservingAccount;
|
||||
|
||||
impl NativeTokenTransfer<'_> {
|
||||
pub async fn send_shielded_transfer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(from),
|
||||
PrivacyPreservingAccount::PrivateOwned(to),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected sender's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_shielded_transfer_to_outer_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx_with_pre_check(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(from),
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
tx_pre_check,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected sender's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
}
|
||||
52
wallet/src/program_facades/pinata.rs
Normal file
52
wallet/src/program_facades/pinata.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
use nssa_core::SharedSecretKey;
|
||||
|
||||
use crate::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
pub struct Pinata<'w>(pub &'w WalletCore);
|
||||
|
||||
impl Pinata<'_> {
|
||||
pub async fn claim(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![pinata_account_id, winner_account_id];
|
||||
let program_id = nssa::program::Program::pinata().id();
|
||||
let message =
|
||||
nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn claim_private_owned_account(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(pinata_account_id),
|
||||
PrivacyPreservingAccount::PrivateOwned(winner_account_id),
|
||||
],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&nssa::program::Program::pinata(),
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected recipient's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
}
|
||||
275
wallet/src/program_facades/token.rs
Normal file
275
wallet/src/program_facades/token.rs
Normal file
@ -0,0 +1,275 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::{AccountId, program::Program};
|
||||
use nssa_core::{
|
||||
NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
|
||||
program::InstructionData,
|
||||
};
|
||||
|
||||
use crate::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
pub struct Token<'w>(pub &'w WalletCore);
|
||||
|
||||
impl Token<'_> {
|
||||
pub async fn send_new_definition(
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![definition_account_id, supply_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = [0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_new_definition_private_owned(
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_definition(name, total_supply);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(definition_account_id),
|
||||
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected recipient's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![sender_account_id, recipient_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = [0; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Some(signing_key) = self
|
||||
.0
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&sender_account_id)
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
let witness_set =
|
||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction_private_owned_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_transfer(amount);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
|
||||
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let mut iter = secrets.into_iter();
|
||||
let first = iter.next().expect("expected sender's secret");
|
||||
let second = iter.next().expect("expected recipient's secret");
|
||||
(resp, [first, second])
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction_private_foreign_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_transfer(amount);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: recipient_npk,
|
||||
ipk: recipient_ipk,
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let mut iter = secrets.into_iter();
|
||||
let first = iter.next().expect("expected sender's secret");
|
||||
let second = iter.next().expect("expected recipient's secret");
|
||||
(resp, [first, second])
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction_deshielded(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_transfer(amount);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
|
||||
PrivacyPreservingAccount::Public(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected sender's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction_shielded_owned_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_transfer(amount);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(sender_account_id),
|
||||
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected recipient's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_transfer_transaction_shielded_foreign_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program) = token_program_preparation_transfer(amount);
|
||||
|
||||
self.0
|
||||
.send_privacy_preserving_tx(
|
||||
vec![
|
||||
PrivacyPreservingAccount::Public(sender_account_id),
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: recipient_npk,
|
||||
ipk: recipient_ipk,
|
||||
},
|
||||
],
|
||||
&instruction_data,
|
||||
&program,
|
||||
)
|
||||
.await
|
||||
.map(|(resp, secrets)| {
|
||||
let first = secrets
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("expected recipient's secret");
|
||||
(resp, first)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) {
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = [0; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let instruction_data = Program::serialize_instruction(instruction).unwrap();
|
||||
let program = Program::token();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
|
||||
fn token_program_preparation_definition(
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> (InstructionData, Program) {
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = [0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let instruction_data = Program::serialize_instruction(instruction).unwrap();
|
||||
let program = Program::token();
|
||||
|
||||
(instruction_data, program)
|
||||
}
|
||||
@ -1,278 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::{Account, AccountId, program::Program};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
|
||||
program::InstructionData,
|
||||
};
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
impl WalletCore {
|
||||
pub fn token_program_preparation_transfer(
|
||||
amount: u128,
|
||||
) -> (
|
||||
InstructionData,
|
||||
Program,
|
||||
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
) {
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = [0; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let instruction_data = Program::serialize_instruction(instruction).unwrap();
|
||||
let program = Program::token();
|
||||
let tx_pre_check = |_: &Account, _: &Account| Ok(());
|
||||
|
||||
(instruction_data, program, tx_pre_check)
|
||||
}
|
||||
|
||||
pub fn token_program_preparation_definition(
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> (
|
||||
InstructionData,
|
||||
Program,
|
||||
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
) {
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = [0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let instruction_data = Program::serialize_instruction(instruction).unwrap();
|
||||
let program = Program::token();
|
||||
let tx_pre_check = |_: &Account, _: &Account| Ok(());
|
||||
|
||||
(instruction_data, program, tx_pre_check)
|
||||
}
|
||||
|
||||
pub async fn send_new_token_definition(
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![definition_account_id, supply_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)]
|
||||
let mut instruction = [0; 23];
|
||||
instruction[1..17].copy_from_slice(&total_supply.to_le_bytes());
|
||||
instruction[17..].copy_from_slice(&name);
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
vec![],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_new_token_definition_private_owned(
|
||||
&self,
|
||||
definition_account_id: AccountId,
|
||||
supply_account_id: AccountId,
|
||||
name: [u8; 6],
|
||||
total_supply: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_definition(name, total_supply);
|
||||
|
||||
// Kind of non-obvious naming
|
||||
// Basically this funtion is called because authentication mask is [0, 2]
|
||||
self.shielded_two_accs_receiver_uninit(
|
||||
definition_account_id,
|
||||
supply_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![sender_account_id, recipient_account_id];
|
||||
let program_id = nssa::program::Program::token().id();
|
||||
// Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 ||
|
||||
// 0x00 || 0x00 || 0x00].
|
||||
let mut instruction = [0; 23];
|
||||
instruction[0] = 0x01;
|
||||
instruction[1..17].copy_from_slice(&amount.to_le_bytes());
|
||||
let Ok(nonces) = self.get_accounts_nonces(vec![sender_account_id]).await else {
|
||||
return Err(ExecutionFailureKind::SequencerError);
|
||||
};
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Some(signing_key) = self
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&sender_account_id)
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
let witness_set =
|
||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_private_owned_account_already_initialized(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
recipient_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.private_tx_two_accs_all_init(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
recipient_proof,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_private_owned_account_not_initialized(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.private_tx_two_accs_receiver_uninit(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_private_foreign_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.private_tx_two_accs_receiver_outer(
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_ipk,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_deshielded(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.deshielded_tx_two_accs(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_shielded_owned_account_already_initialized(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
recipient_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.shielded_two_accs_all_init(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
recipient_proof,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_shielded_owned_account_not_initialized(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_account_id: AccountId,
|
||||
amount: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.shielded_two_accs_receiver_uninit(
|
||||
sender_account_id,
|
||||
recipient_account_id,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_transfer_token_transaction_shielded_foreign_account(
|
||||
&self,
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_ipk: IncomingViewingPublicKey,
|
||||
amount: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::token_program_preparation_transfer(amount);
|
||||
|
||||
self.shielded_two_accs_receiver_outer(
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_ipk,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn send_deshielded_native_token_transfer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.deshielded_tx_two_accs(from, to, instruction_data, tx_pre_check, program)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
use common::error::ExecutionFailureKind;
|
||||
use nssa::{Account, program::Program};
|
||||
use nssa_core::program::InstructionData;
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
pub mod deshielded;
|
||||
pub mod private;
|
||||
pub mod public;
|
||||
pub mod shielded;
|
||||
|
||||
impl WalletCore {
|
||||
pub fn auth_transfer_preparation(
|
||||
balance_to_move: u128,
|
||||
) -> (
|
||||
InstructionData,
|
||||
Program,
|
||||
impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
) {
|
||||
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let tx_pre_check = move |from: &Account, _: &Account| {
|
||||
if from.balance >= balance_to_move {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExecutionFailureKind::InsufficientFundsError)
|
||||
}
|
||||
};
|
||||
|
||||
(instruction_data, program, tx_pre_check)
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
|
||||
};
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn send_private_native_token_transfer_outer_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.private_tx_two_accs_receiver_outer(
|
||||
from,
|
||||
to_npk,
|
||||
to_ipk,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_private_native_token_transfer_owned_account_not_initialized(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.private_tx_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_private_native_token_transfer_owned_account_already_initialized(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.private_tx_two_accs_all_init(
|
||||
from,
|
||||
to,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
to_proof,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use nssa::AccountId;
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey,
|
||||
};
|
||||
|
||||
use crate::WalletCore;
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn send_shielded_native_token_transfer_already_initialized(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.shielded_two_accs_all_init(from, to, instruction_data, tx_pre_check, program, to_proof)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_shielded_native_token_transfer_not_initialized(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.shielded_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_shielded_native_token_transfer_outer_account(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
balance_to_move: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) =
|
||||
WalletCore::auth_transfer_preparation(balance_to_move);
|
||||
|
||||
self.shielded_two_accs_receiver_outer(
|
||||
from,
|
||||
to_npk,
|
||||
to_ipk,
|
||||
instruction_data,
|
||||
tx_pre_check,
|
||||
program,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1,592 +0,0 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{
|
||||
Account, AccountId, PrivacyPreservingTransaction,
|
||||
privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet},
|
||||
program::Program,
|
||||
};
|
||||
use nssa_core::{
|
||||
Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData,
|
||||
};
|
||||
|
||||
use crate::{WalletCore, helperfunctions::produce_random_nonces};
|
||||
|
||||
pub(crate) struct AccountPreparedData {
|
||||
pub nsk: Option<NullifierSecretKey>,
|
||||
pub npk: NullifierPublicKey,
|
||||
pub ipk: IncomingViewingPublicKey,
|
||||
pub auth_acc: AccountWithMetadata,
|
||||
pub proof: Option<MembershipProof>,
|
||||
}
|
||||
|
||||
impl WalletCore {
|
||||
pub(crate) async fn private_acc_preparation(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
is_authorized: bool,
|
||||
needs_proof: bool,
|
||||
) -> Result<AccountPreparedData, ExecutionFailureKind> {
|
||||
let Some((from_keys, from_acc)) = self
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(&account_id)
|
||||
.cloned()
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let mut nsk = None;
|
||||
let mut proof = None;
|
||||
|
||||
let from_npk = from_keys.nullifer_public_key;
|
||||
let from_ipk = from_keys.incoming_viewing_public_key;
|
||||
|
||||
let sender_commitment = Commitment::new(&from_npk, &from_acc);
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk);
|
||||
|
||||
if is_authorized {
|
||||
nsk = Some(from_keys.private_key_holder.nullifier_secret_key);
|
||||
}
|
||||
|
||||
if needs_proof {
|
||||
proof = self
|
||||
.sequencer_client
|
||||
.get_proof_for_commitment(sender_commitment)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_all_init(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: to_nsk,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, true, false).await?;
|
||||
|
||||
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 1],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[
|
||||
(from_nsk.unwrap(), from_proof.unwrap()),
|
||||
(to_nsk.unwrap(), to_proof),
|
||||
],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder_to.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_receiver_uninit(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, false, false).await?;
|
||||
|
||||
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 2],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder_to.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_receiver_outer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let to_acc = nssa_core::account::Account::default();
|
||||
|
||||
tx_pre_check(&sender_pre.account, &to_acc)?;
|
||||
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
|
||||
let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk);
|
||||
let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 2],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn deshielded_tx_two_accs(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let Ok(to_acc) = self.get_account_public(to).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
tx_pre_check(&sender_pre.account, &to_acc)?;
|
||||
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 0],
|
||||
&produce_random_nonces(1),
|
||||
&[(from_npk.clone(), shared_secret.clone())],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![to],
|
||||
vec![],
|
||||
vec![(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_all_init(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: to_nsk,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, true, false).await?;
|
||||
|
||||
tx_pre_check(&from_acc, &recipient_pre.account)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 1],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[(to_nsk.unwrap(), to_proof)],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_receiver_uninit(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, false, false).await?;
|
||||
|
||||
tx_pre_check(&from_acc, &recipient_pre.account)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_receiver_outer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let to_acc = Account::default();
|
||||
|
||||
tx_pre_check(&from_acc, &to_acc)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[],
|
||||
&program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_private(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn register_account_under_authenticated_transfers_programs_private(
|
||||
&self,
|
||||
from: AccountId,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(from, false, false).await?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let instruction: u128 = 0;
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre],
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&[2],
|
||||
&produce_random_nonces(1),
|
||||
&[(from_npk.clone(), shared_secret_from.clone())],
|
||||
&[],
|
||||
&Program::authenticated_transfer_program(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from],
|
||||
))
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user