Merge branch 'main' into schouhy/implement-pda-for-public-accounts

This commit is contained in:
Sergio Chouhy 2025-12-04 16:00:20 -03:00
commit 35bf943244
46 changed files with 3569 additions and 2061 deletions

627
README.md
View File

@ -1,15 +1,84 @@
# 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 programs 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 Bobs 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.
- Bobs 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 Charlies public account as recipient.
- A ZKP of correct execution is generated.
- Bobs 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 accounts 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 validators perspective, public transactions are processed as quickly as any RISC-Vbased 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
Ubuntu / Debian
```sh
apt install build-essential clang libssl-dev pkg-config
```
Fedora
```sh
sudo dnf install clang openssl-devel pkgconf llvm
```
> **Note for Fedora 41+ users:** GCC 14+ has stricter C++ standard library headers that cause build failures with the bundled RocksDB. You must set `CXXFLAGS="-include cstdint"` when running cargo commands. See the [Run tests](#run-tests) section for examples.
- On Mac
```sh
xcode-select --install
@ -31,3 +100,553 @@ 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
# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build:
CXXFLAGS="-include cstdint" 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
# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build:
CXXFLAGS="-include cstdint" 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
# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build:
CXXFLAGS="-include cstdint" 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
# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build:
CXXFLAGS="-include cstdint" 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 accounts 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 lets 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.
Lets 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 recipients private account must be uninitialized.
#### Sending tokens from the public account to a private account owned by someone else
For this tutorial, well simulate that scenario by creating a new private account that we own, but well 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
Weve 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, weve 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 tokens 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 tokens 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
Lets create a new token, but this time using a public definition account and a private holding account to store the entire supply.
Since we cant 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
```

View File

@ -36,9 +36,9 @@ path = "../wallet"
[dependencies.common]
path = "../common"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.nssa]
path = "../nssa"
features = ["no_docker"]
[dependencies.key_protocol]
path = "../key_protocol"

View File

@ -56,6 +56,8 @@ 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::cli::execute_setup("test_pass".to_owned()).await?;
let home_dir_sequencer = home_dir.join("sequencer");
let mut sequencer_config =

View File

@ -8,6 +8,7 @@ use std::{
use actix_web::dev::ServerHandle;
use anyhow::Result;
use common::{PINATA_BASE58, sequencer_client::SequencerClient};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{AccountId, ProgramDeploymentTransaction, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
@ -15,15 +16,17 @@ 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::{PersistentAccountData, PersistentStorage},
config::PersistentStorage,
helperfunctions::{fetch_config, fetch_persistent_storage},
};
@ -56,7 +59,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;
@ -83,13 +86,15 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_success_move_to_another_account() {
info!("########## test_success_move_to_another_account ##########");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: ChainIndex::root(),
}));
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();
let PersistentStorage {
accounts: persistent_accounts,
@ -120,7 +125,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;
@ -159,7 +164,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());
@ -200,7 +205,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;
@ -231,7 +236,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;
@ -284,51 +289,44 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let wallet_config = fetch_config().await.unwrap();
// Create new account for the token definition
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for the token supply holder
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for receiving a token transaction
wallet::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {},
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap();
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block: _,
} = fetch_persistent_storage().await.unwrap();
let mut new_persistent_accounts_account_id = Vec::new();
for per_acc in persistent_accounts {
match per_acc {
PersistentAccountData::Public(per_acc) => {
if (per_acc.account_id.to_string() != ACC_RECEIVER)
&& (per_acc.account_id.to_string() != ACC_SENDER)
{
new_persistent_accounts_account_id.push(per_acc.account_id);
}
}
_ => continue,
}
}
let [
definition_account_id,
supply_account_id,
recipient_account_id,
] = new_persistent_accounts_account_id
.try_into()
.expect("Failed to produce new account, not present in persistent accounts");
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
@ -339,7 +337,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");
@ -398,7 +396,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");
@ -453,8 +451,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -464,8 +464,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -475,8 +477,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -494,7 +498,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();
@ -541,7 +545,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();
@ -575,7 +579,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();
@ -608,8 +612,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -619,8 +625,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -630,8 +638,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -649,7 +659,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();
@ -703,7 +713,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 {
@ -715,7 +725,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)
@ -744,8 +754,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -755,8 +767,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -766,8 +780,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -785,7 +801,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();
@ -822,7 +838,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();
@ -851,7 +867,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();
@ -880,8 +896,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -891,8 +909,10 @@ 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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -902,8 +922,10 @@ 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(
NewSubcommand::Public {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -921,7 +943,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();
@ -968,7 +990,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();
@ -997,7 +1019,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();
@ -1029,7 +1051,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;
@ -1068,7 +1090,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");
};
@ -1104,9 +1126,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
);
let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap();
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
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
@ -1123,8 +1147,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
let (to_keys, _) = wallet_storage
.storage
.user_data
.user_private_accounts
.get(&to_account_id)
.get_private_account(&to_account_id)
.cloned()
.unwrap();
@ -1136,7 +1159,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");
};
@ -1144,7 +1167,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();
@ -1171,13 +1194,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
@ -1207,7 +1230,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");
// };
@ -1258,7 +1281,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;
@ -1301,7 +1324,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;
@ -1347,7 +1370,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");
};
@ -1377,10 +1400,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();
@ -1393,7 +1414,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;
@ -1468,9 +1489,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_authenticated_transfer_initialize_function() {
info!("########## test initialize account for authenticated transfer ##########");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {}));
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
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");
};
@ -1478,7 +1501,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();
@ -1506,11 +1529,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();
@ -1524,7 +1545,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");
};
@ -1540,7 +1561,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();
@ -1560,16 +1581,17 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
#[nssa_integration_test]
pub async fn test_pinata_private_receiver_new_account() {
info!("########## test_pinata_private_receiver ##########");
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(
NewSubcommand::Private {},
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
@ -1578,8 +1600,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();
@ -1592,7 +1613,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;
@ -1632,7 +1653,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();
@ -1643,7 +1664,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!");
}

View File

@ -14,6 +14,7 @@ hex = "0.4.3"
aes-gcm.workspace = true
bip39.workspace = true
hmac-sha512.workspace = true
thiserror.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.common]

View File

@ -0,0 +1,148 @@
use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct ChainIndex(Vec<u32>);
#[derive(thiserror::Error, Debug)]
pub enum ChainIndexError {
#[error("No root found")]
NoRootFound,
#[error("Failed to parse segment into a number")]
ParseIntError(#[from] std::num::ParseIntError),
}
impl FromStr for ChainIndex {
type Err = ChainIndexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.starts_with('/') {
return Err(ChainIndexError::NoRootFound);
}
if s == "/" {
return Ok(ChainIndex(vec![]));
}
let uprooted_substring = s.strip_prefix("/").unwrap();
let splitted_chain: Vec<&str> = uprooted_substring.split("/").collect();
let mut res = vec![];
for split_ch in splitted_chain {
let cci = split_ch.parse()?;
res.push(cci);
}
Ok(Self(res))
}
}
impl Display for ChainIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "/")?;
for cci in &self.0[..(self.0.len().saturating_sub(1))] {
write!(f, "{cci}/")?;
}
if let Some(last) = self.0.last() {
write!(f, "{}", last)?;
}
Ok(())
}
}
impl Default for ChainIndex {
fn default() -> Self {
ChainIndex::from_str("/").expect("Root parsing failure")
}
}
impl ChainIndex {
pub fn root() -> Self {
ChainIndex::default()
}
pub fn chain(&self) -> &[u32] {
&self.0
}
pub fn next_in_line(&self) -> ChainIndex {
let mut chain = self.0.clone();
// ToDo: Add overflow check
if let Some(last_p) = chain.last_mut() {
*last_p += 1
}
ChainIndex(chain)
}
pub fn nth_child(&self, child_id: u32) -> ChainIndex {
let mut chain = self.0.clone();
chain.push(child_id);
ChainIndex(chain)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_id_root_correct() {
let chain_id = ChainIndex::root();
let chain_id_2 = ChainIndex::from_str("/").unwrap();
assert_eq!(chain_id, chain_id_2);
}
#[test]
fn test_chain_id_deser_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
assert_eq!(chain_id.chain(), &[257]);
}
#[test]
fn test_chain_id_deser_failure_no_root() {
let chain_index_error = ChainIndex::from_str("257").err().unwrap();
assert!(matches!(chain_index_error, ChainIndexError::NoRootFound));
}
#[test]
fn test_chain_id_deser_failure_int_parsing_failure() {
let chain_index_error = ChainIndex::from_str("/hello").err().unwrap();
assert!(matches!(
chain_index_error,
ChainIndexError::ParseIntError(_)
));
}
#[test]
fn test_chain_id_next_in_line_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
let next_in_line = chain_id.next_in_line();
assert_eq!(next_in_line, ChainIndex::from_str("/258").unwrap());
}
#[test]
fn test_chain_id_child_correct() {
let chain_id = ChainIndex::from_str("/257").unwrap();
let child = chain_id.nth_child(3);
assert_eq!(child, ChainIndex::from_str("/257/3").unwrap());
}
#[test]
fn test_correct_display() {
let chainid = ChainIndex(vec![5, 7, 8]);
let string_index = format!("{chainid}");
assert_eq!(string_index, "/5/7/8".to_string());
}
}

View File

@ -0,0 +1,263 @@
use k256::{Scalar, elliptic_curve::PrimeField};
use nssa_core::encryption::IncomingViewingPublicKey;
use serde::{Deserialize, Serialize};
use crate::key_management::{
KeyChain,
key_tree::traits::KeyNode,
secret_holders::{PrivateKeyHolder, SecretSpendingKey},
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPrivate {
pub value: (KeyChain, nssa::Account),
pub ccc: [u8; 32],
/// Can be [`None`] if root
pub cci: Option<u32>,
}
impl KeyNode for ChildKeysPrivate {
fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_priv");
let ssk = SecretSpendingKey(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
);
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let nsk = ssk.generate_nullifier_secret_key();
let isk = ssk.generate_incoming_viewing_secret_key();
let ovk = ssk.generate_outgoing_viewing_secret_key();
let npk = (&nsk).into();
let ipk = IncomingViewingPublicKey::from_scalar(isk);
Self {
value: (
KeyChain {
secret_spending_key: ssk,
nullifer_public_key: npk,
incoming_viewing_public_key: ipk,
private_key_holder: PrivateKeyHolder {
nullifier_secret_key: nsk,
incoming_viewing_secret_key: isk,
outgoing_viewing_secret_key: ovk,
},
},
nssa::Account::default(),
),
ccc,
cci: None,
}
}
fn nth_child(&self, cci: u32) -> Self {
let parent_pt = Scalar::from_repr(
self.value
.0
.private_key_holder
.outgoing_viewing_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation")
+ Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into())
.expect("Key generated as scalar, must be valid representation")
* Scalar::from_repr(
self.value
.0
.private_key_holder
.incoming_viewing_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation");
let mut input = vec![];
input.extend_from_slice(b"NSSA_seed_priv");
input.extend_from_slice(&parent_pt.to_bytes());
input.extend_from_slice(&cci.to_le_bytes());
let hash_value = hmac_sha512::HMAC::mac(input, self.ccc);
let ssk = SecretSpendingKey(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
);
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let nsk = ssk.generate_nullifier_secret_key();
let isk = ssk.generate_incoming_viewing_secret_key();
let ovk = ssk.generate_outgoing_viewing_secret_key();
let npk = (&nsk).into();
let ipk = IncomingViewingPublicKey::from_scalar(isk);
Self {
value: (
KeyChain {
secret_spending_key: ssk,
nullifer_public_key: npk,
incoming_viewing_public_key: ipk,
private_key_holder: PrivateKeyHolder {
nullifier_secret_key: nsk,
incoming_viewing_secret_key: isk,
outgoing_viewing_secret_key: ovk,
},
},
nssa::Account::default(),
),
ccc,
cci: Some(cci),
}
}
fn chain_code(&self) -> &[u8; 32] {
&self.ccc
}
fn child_index(&self) -> Option<u32> {
self.cci
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.value.0.nullifer_public_key)
}
}
impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) {
fn from(value: &'a ChildKeysPrivate) -> Self {
&value.value
}
}
impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) {
fn from(value: &'a mut ChildKeysPrivate) -> Self {
&mut value.value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keys_deterministic_generation() {
let root_keys = ChildKeysPrivate::root([42; 64]);
let child_keys = root_keys.nth_child(5);
assert_eq!(root_keys.cci, None);
assert_eq!(child_keys.cci, Some(5));
assert_eq!(
root_keys.value.0.secret_spending_key.0,
[
249, 83, 253, 32, 174, 204, 185, 44, 253, 167, 61, 92, 128, 5, 152, 4, 220, 21, 88,
84, 167, 180, 154, 249, 44, 77, 33, 136, 59, 131, 203, 152
]
);
assert_eq!(
child_keys.value.0.secret_spending_key.0,
[
16, 242, 229, 242, 252, 158, 153, 210, 234, 120, 70, 85, 83, 196, 5, 53, 28, 26,
187, 230, 22, 193, 146, 232, 237, 3, 166, 184, 122, 1, 233, 93
]
);
assert_eq!(
root_keys.value.0.private_key_holder.nullifier_secret_key,
[
38, 195, 52, 182, 16, 66, 167, 156, 9, 14, 65, 100, 17, 93, 166, 71, 27, 148, 93,
85, 116, 109, 130, 8, 195, 222, 159, 214, 141, 41, 124, 57
]
);
assert_eq!(
child_keys.value.0.private_key_holder.nullifier_secret_key,
[
215, 46, 2, 151, 174, 60, 86, 154, 5, 3, 175, 245, 12, 176, 220, 58, 250, 118, 236,
49, 254, 221, 229, 58, 40, 1, 170, 145, 175, 108, 23, 170
]
);
assert_eq!(
root_keys
.value
.0
.private_key_holder
.incoming_viewing_secret_key,
[
153, 161, 15, 34, 96, 184, 165, 165, 27, 244, 155, 40, 70, 5, 241, 133, 78, 40, 61,
118, 48, 148, 226, 5, 97, 18, 201, 128, 82, 248, 163, 72
]
);
assert_eq!(
child_keys
.value
.0
.private_key_holder
.incoming_viewing_secret_key,
[
192, 155, 55, 43, 164, 115, 71, 145, 227, 225, 21, 57, 55, 12, 226, 44, 10, 103,
39, 73, 230, 173, 60, 69, 69, 122, 110, 241, 164, 3, 192, 57
]
);
assert_eq!(
root_keys
.value
.0
.private_key_holder
.outgoing_viewing_secret_key,
[
205, 87, 71, 129, 90, 242, 217, 200, 140, 252, 124, 46, 207, 7, 33, 156, 83, 166,
150, 81, 98, 131, 182, 156, 110, 92, 78, 140, 125, 218, 152, 154
]
);
assert_eq!(
child_keys
.value
.0
.private_key_holder
.outgoing_viewing_secret_key,
[
131, 202, 219, 172, 219, 29, 48, 120, 226, 209, 209, 10, 216, 173, 48, 167, 233,
17, 35, 155, 30, 217, 176, 120, 72, 146, 250, 226, 165, 178, 255, 90
]
);
assert_eq!(
root_keys.value.0.nullifer_public_key.0,
[
65, 176, 149, 243, 192, 45, 216, 177, 169, 56, 229, 7, 28, 66, 204, 87, 109, 83,
152, 64, 14, 188, 179, 210, 147, 60, 22, 251, 203, 70, 89, 215
]
);
assert_eq!(
child_keys.value.0.nullifer_public_key.0,
[
69, 104, 130, 115, 48, 134, 19, 188, 67, 148, 163, 54, 155, 237, 57, 27, 136, 228,
111, 233, 205, 158, 149, 31, 84, 11, 241, 176, 243, 12, 138, 249
]
);
assert_eq!(
root_keys.value.0.incoming_viewing_public_key.0,
&[
3, 174, 56, 136, 244, 179, 18, 122, 38, 220, 36, 50, 200, 41, 104, 167, 70, 18, 60,
202, 93, 193, 29, 16, 125, 252, 96, 51, 199, 152, 47, 233, 178
]
);
assert_eq!(
child_keys.value.0.incoming_viewing_public_key.0,
&[
3, 18, 202, 246, 79, 141, 169, 51, 55, 202, 120, 169, 244, 201, 156, 162, 216, 115,
126, 53, 46, 94, 235, 125, 114, 178, 215, 81, 171, 93, 93, 88, 117
]
);
}
}

View File

@ -0,0 +1,132 @@
use serde::{Deserialize, Serialize};
use crate::key_management::key_tree::traits::KeyNode;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPublic {
pub csk: nssa::PrivateKey,
pub cpk: nssa::PublicKey,
pub ccc: [u8; 32],
/// Can be [`None`] if root
pub cci: Option<u32>,
}
impl KeyNode for ChildKeysPublic {
fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_pub");
let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap();
let ccc = *hash_value.last_chunk::<32>().unwrap();
let cpk = nssa::PublicKey::new_from_private_key(&csk);
Self {
csk,
cpk,
ccc,
cci: None,
}
}
fn nth_child(&self, cci: u32) -> Self {
let mut hash_input = vec![];
hash_input.extend_from_slice(self.csk.value());
hash_input.extend_from_slice(&cci.to_le_bytes());
let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc);
let csk = nssa::PrivateKey::try_new(
*hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32"),
)
.unwrap();
let ccc = *hash_value
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let cpk = nssa::PublicKey::new_from_private_key(&csk);
Self {
csk,
cpk,
ccc,
cci: Some(cci),
}
}
fn chain_code(&self) -> &[u8; 32] {
&self.ccc
}
fn child_index(&self) -> Option<u32> {
self.cci
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.cpk)
}
}
impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey {
fn from(value: &'a ChildKeysPublic) -> Self {
&value.csk
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keys_deterministic_generation() {
let root_keys = ChildKeysPublic::root([42; 64]);
let child_keys = root_keys.nth_child(5);
assert_eq!(root_keys.cci, None);
assert_eq!(child_keys.cci, Some(5));
assert_eq!(
root_keys.ccc,
[
61, 30, 91, 26, 133, 91, 236, 192, 231, 53, 186, 139, 11, 221, 202, 11, 178, 215,
254, 103, 191, 60, 117, 112, 1, 226, 31, 156, 83, 104, 150, 224
]
);
assert_eq!(
child_keys.ccc,
[
67, 26, 102, 68, 189, 155, 102, 80, 199, 188, 112, 142, 207, 157, 36, 210, 48, 224,
35, 6, 112, 180, 11, 190, 135, 218, 9, 14, 84, 231, 58, 98
]
);
assert_eq!(
root_keys.csk.value(),
&[
241, 82, 246, 237, 62, 130, 116, 47, 189, 112, 99, 67, 178, 40, 115, 245, 141, 193,
77, 164, 243, 76, 222, 64, 50, 146, 23, 145, 91, 164, 92, 116
]
);
assert_eq!(
child_keys.csk.value(),
&[
11, 151, 27, 212, 167, 26, 77, 234, 103, 145, 53, 191, 184, 25, 240, 191, 156, 25,
60, 144, 65, 22, 193, 163, 246, 227, 212, 81, 49, 170, 33, 158
]
);
assert_eq!(
root_keys.cpk.value(),
&[
220, 170, 95, 177, 121, 37, 86, 166, 56, 238, 232, 72, 21, 106, 107, 217, 158, 74,
133, 91, 143, 244, 155, 15, 2, 230, 223, 169, 13, 20, 163, 138
]
);
assert_eq!(
child_keys.cpk.value(),
&[
152, 249, 236, 111, 132, 96, 184, 122, 21, 179, 240, 15, 234, 155, 164, 144, 108,
110, 120, 74, 176, 147, 196, 168, 243, 186, 203, 79, 97, 17, 194, 52
]
);
}
}

View File

@ -0,0 +1,324 @@
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use crate::key_management::{
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
traits::KeyNode,
},
secret_holders::SeedHolder,
};
pub mod chain_index;
pub mod keys_private;
pub mod keys_public;
pub mod traits;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KeyTree<N: KeyNode> {
pub key_map: BTreeMap<ChainIndex, N>,
pub account_id_map: HashMap<nssa::AccountId, ChainIndex>,
}
pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
pub type KeyTreePrivate = KeyTree<ChildKeysPrivate>;
impl<N: KeyNode> KeyTree<N> {
pub fn new(seed: &SeedHolder) -> Self {
let seed_fit: [u8; 64] = seed
.seed
.clone()
.try_into()
.expect("SeedHolder seed is 64 bytes long");
let root_keys = N::root(seed_fit);
let account_id = root_keys.account_id();
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]);
let account_id_map = HashMap::from_iter([(account_id, ChainIndex::root())]);
Self {
key_map,
account_id_map,
}
}
pub fn new_from_root(root: N) -> Self {
let account_id_map = HashMap::from_iter([(root.account_id(), ChainIndex::root())]);
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]);
Self {
key_map,
account_id_map,
}
}
// ToDo: Add function to create a tree from list of nodes with consistency check.
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
if !self.key_map.contains_key(parent_id) {
return None;
}
let leftmost_child = parent_id.nth_child(u32::MIN);
if !self.key_map.contains_key(&leftmost_child) {
return Some(0);
}
let mut right = u32::MAX - 1;
let mut left_border = u32::MIN;
let mut right_border = u32::MAX;
loop {
let rightmost_child = parent_id.nth_child(right);
let rightmost_ref = self.key_map.get(&rightmost_child);
let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line());
match (&rightmost_ref, &rightmost_ref_next) {
(Some(_), Some(_)) => {
left_border = right;
right = (right + right_border) / 2;
}
(Some(_), None) => {
break Some(right + 1);
}
(None, None) => {
right_border = right;
right = (left_border + right) / 2;
}
(None, Some(_)) => {
unreachable!();
}
}
}
}
pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option<nssa::AccountId> {
let father_keys = self.key_map.get(&parent_cci)?;
let next_child_id = self
.find_next_last_child_of_id(&parent_cci)
.expect("Can be None only if parent is not present");
let next_cci = parent_cci.nth_child(next_child_id);
let child_keys = father_keys.nth_child(next_child_id);
let account_id = child_keys.account_id();
self.key_map.insert(next_cci.clone(), child_keys);
self.account_id_map.insert(account_id, next_cci);
Some(account_id)
}
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get(chain_id))
}
pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get_mut(chain_id))
}
pub fn insert(&mut self, account_id: nssa::AccountId, chain_index: ChainIndex, node: N) {
self.account_id_map.insert(account_id, chain_index.clone());
self.key_map.insert(chain_index, node);
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use nssa::AccountId;
use super::*;
fn seed_holder_for_tests() -> SeedHolder {
SeedHolder {
seed: [42; 64].to_vec(),
}
}
#[test]
fn test_simple_key_tree() {
let seed_holder = seed_holder_for_tests();
let tree = KeyTreePublic::new(&seed_holder);
assert!(tree.key_map.contains_key(&ChainIndex::root()));
assert!(tree.account_id_map.contains_key(&AccountId::new([
46, 223, 229, 177, 59, 18, 189, 219, 153, 31, 249, 90, 112, 230, 180, 164, 80, 25, 106,
159, 14, 238, 1, 192, 91, 8, 210, 165, 199, 41, 60, 104,
])));
}
#[test]
fn test_small_key_tree() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
tree.generate_new_node(ChainIndex::root()).unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 7);
}
#[test]
fn test_key_tree_can_not_make_child_keys() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
let key_opt = tree.generate_new_node(ChainIndex::from_str("/3").unwrap());
assert_eq!(key_opt, None);
}
#[test]
fn test_key_tree_complex_structure() {
let seed_holder = seed_holder_for_tests();
let mut tree = KeyTreePublic::new(&seed_holder);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 0);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
tree.generate_new_node(ChainIndex::root()).unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/1").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::root())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 2);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/0").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 2);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/1").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0").unwrap())
.unwrap();
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 3);
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/2").unwrap())
);
tree.generate_new_node(ChainIndex::from_str("/0/1").unwrap())
.unwrap();
assert!(
tree.key_map
.contains_key(&ChainIndex::from_str("/0/1/0").unwrap())
);
let next_last_child_for_parent_id = tree
.find_next_last_child_of_id(&ChainIndex::from_str("/0/1").unwrap())
.unwrap();
assert_eq!(next_last_child_for_parent_id, 1);
}
}

View File

@ -0,0 +1,14 @@
/// Trait, that reperesents a Node in hierarchical key tree
pub trait KeyNode {
/// Tree root node
fn root(seed: [u8; 64]) -> Self;
/// `cci`'s child of node
fn nth_child(&self, cci: u32) -> Self;
fn chain_code(&self) -> &[u8; 32];
fn child_index(&self) -> Option<u32>;
fn account_id(&self) -> nssa::AccountId;
}

View File

@ -8,12 +8,13 @@ use serde::{Deserialize, Serialize};
pub type PublicAccountSigningKey = [u8; 32];
pub mod ephemeral_key_holder;
pub mod key_tree;
pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone, Debug)]
/// Entrypoint to key management
pub struct KeyChain {
secret_spending_key: SecretSpendingKey,
pub secret_spending_key: SecretSpendingKey,
pub private_key_holder: PrivateKeyHolder,
pub nullifer_public_key: NullifierPublicKey,
pub incoming_viewing_public_key: IncomingViewingPublicKey,
@ -39,6 +40,25 @@ impl KeyChain {
}
}
pub fn new_mnemonic(passphrase: String) -> Self {
// Currently dropping SeedHolder at the end of initialization.
// Not entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_mnemonic(passphrase);
let secret_spending_key = seed_holder.produce_top_secret_key_holder();
let private_key_holder = secret_spending_key.produce_private_key_holder();
let nullifer_public_key = private_key_holder.generate_nullifier_public_key();
let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key();
Self {
secret_spending_key,
private_key_holder,
nullifer_public_key,
incoming_viewing_public_key,
}
}
pub fn calculate_shared_secret_receiver(
&self,
ephemeral_public_key_sender: EphemeralPublicKey,

View File

@ -8,6 +8,8 @@ use rand::{RngCore, rngs::OsRng};
use serde::{Deserialize, Serialize};
use sha2::{Digest, digest::FixedOutput};
const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32];
#[derive(Debug)]
/// Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects.
@ -37,7 +39,8 @@ impl SeedHolder {
let mut enthopy_bytes: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut enthopy_bytes);
let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap();
let mnemonic = Mnemonic::from_entropy(&enthopy_bytes)
.expect("Enthropy must be a multiple of 32 bytes");
let seed_wide = mnemonic.to_seed("mnemonic");
Self {
@ -45,6 +48,16 @@ impl SeedHolder {
}
}
pub fn new_mnemonic(passphrase: String) -> Self {
let mnemonic = Mnemonic::from_entropy(&NSSA_ENTROPY_BYTES)
.expect("Enthropy must be a multiple of 32 bytes");
let seed_wide = mnemonic.to_seed(passphrase);
Self {
seed: seed_wide.to_vec(),
}
}
pub fn generate_secret_spending_key_hash(&self) -> HashType {
let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed");
@ -155,4 +168,14 @@ mod tests {
let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key();
}
#[test]
fn two_seeds_generated_same_from_same_mnemonic() {
let mnemonic = "test_pass";
let seed_holder1 = SeedHolder::new_mnemonic(mnemonic.to_string());
let seed_holder2 = SeedHolder::new_mnemonic(mnemonic.to_string());
assert_eq!(seed_holder1.seed, seed_holder2.seed);
}
}

View File

@ -4,16 +4,25 @@ use anyhow::Result;
use k256::AffinePoint;
use serde::{Deserialize, Serialize};
use crate::key_management::KeyChain;
use crate::key_management::{
KeyChain,
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
};
pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
/// Map for all user public accounts
pub pub_account_signing_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
/// Map for all user private accounts
pub user_private_accounts: HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Default public accounts
pub default_pub_account_signing_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
/// Default private accounts
pub default_user_private_accounts:
HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Tree of public keys
pub public_key_tree: KeyTreePublic,
/// Tree of private keys
pub private_key_tree: KeyTreePrivate,
}
impl NSSAUserData {
@ -47,39 +56,42 @@ impl NSSAUserData {
}
pub fn new_with_accounts(
accounts_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
accounts_key_chains: HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
default_accounts_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
default_accounts_key_chains: HashMap<
nssa::AccountId,
(KeyChain, nssa_core::account::Account),
>,
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
) -> Result<Self> {
if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) {
if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
Ok(Self {
pub_account_signing_keys: accounts_keys,
user_private_accounts: accounts_key_chains,
default_pub_account_signing_keys: default_accounts_keys,
default_user_private_accounts: default_accounts_key_chains,
public_key_tree,
private_key_tree,
})
}
/// Generated new private key for public transaction signatures
///
/// Returns the account_id of new account
pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::AccountId {
let private_key = nssa::PrivateKey::new_os_random();
let account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
self.pub_account_signing_keys
.insert(account_id, private_key);
account_id
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: ChainIndex,
) -> nssa::AccountId {
self.public_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
@ -87,22 +99,23 @@ impl NSSAUserData {
&self,
account_id: &nssa::AccountId,
) -> Option<&nssa::PrivateKey> {
self.pub_account_signing_keys.get(account_id)
// First seek in defaults
if let Some(key) = self.default_pub_account_signing_keys.get(account_id) {
Some(key)
// Then seek in tree
} else {
self.public_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Generated new private key for privacy preserving transactions
///
/// Returns the account_id of new account
pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::AccountId {
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from(&key_chain.nullifer_public_key);
self.user_private_accounts.insert(
account_id,
(key_chain, nssa_core::account::Account::default()),
);
account_id
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: ChainIndex,
) -> nssa::AccountId {
self.private_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
@ -110,7 +123,13 @@ impl NSSAUserData {
&self,
account_id: &nssa::AccountId,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get(account_id)
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Returns the signing key for public transaction signatures
@ -118,14 +137,27 @@ impl NSSAUserData {
&mut self,
account_id: &nssa::AccountId,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
self.user_private_accounts.get_mut(account_id)
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get_mut(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree
.get_node_mut(*account_id)
.map(Into::into)
}
}
}
impl Default for NSSAUserData {
fn default() -> Self {
// Safe unwrap as maps are empty
Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap()
Self::new_with_accounts(
HashMap::new(),
HashMap::new(),
KeyTreePublic::new(&SeedHolder::new_mnemonic("default".to_string())),
KeyTreePrivate::new(&SeedHolder::new_mnemonic("default".to_string())),
)
.unwrap()
}
}
@ -137,20 +169,27 @@ mod tests {
fn test_new_account() {
let mut user_data = NSSAUserData::default();
let addr_pub = user_data.generate_new_public_transaction_private_key();
let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain();
let account_id_pub =
user_data.generate_new_public_transaction_private_key(ChainIndex::root());
let account_id_private =
user_data.generate_new_privacy_preserving_transaction_key_chain(ChainIndex::root());
let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some();
let is_private_key_generated = user_data
.get_pub_account_signing_key(&account_id_pub)
.is_some();
assert!(is_private_key_generated);
let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some();
let is_key_chain_generated = user_data.get_private_account(&account_id_private).is_some();
assert!(is_key_chain_generated);
let addr_private_str = addr_private.to_string();
println!("{addr_private_str:#?}");
let key_chain = &user_data.get_private_account(&addr_private).unwrap().0;
let account_id_private_str = account_id_private.to_string();
println!("{account_id_private_str:#?}");
let key_chain = &user_data
.get_private_account(&account_id_private)
.unwrap()
.0;
println!("{key_chain:#?}");
}
}

View File

@ -1,7 +1,10 @@
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
use serde::{Deserialize, Serialize};
use crate::account::{Account, AccountId, AccountWithMetadata};
use crate::account::{Account, AccountWithMetadata};
#[cfg(feature = "host")]
use crate::account::AccountId;
pub type ProgramId = [u32; 8];
pub type InstructionData = Vec<u32>;

View File

@ -1,10 +1,11 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::account::AccountId;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::{PrivateKey, error::NssaError};
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, Serialize, Deserialize)]
pub struct PublicKey([u8; 32]);
impl BorshDeserialize for PublicKey {

View File

@ -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"

294
wallet/src/chain_storage.rs Normal file
View File

@ -0,0 +1,294 @@
use std::collections::{HashMap, hash_map::Entry};
use anyhow::Result;
use key_protocol::{
key_management::{
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
},
key_protocol_core::NSSAUserData,
};
use nssa::program::Program;
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
pub wallet_config: WalletConfig,
}
impl WalletChainStore {
pub fn new(
config: WalletConfig,
persistent_accounts: Vec<PersistentAccountData>,
) -> Result<Self> {
if persistent_accounts.is_empty() {
anyhow::bail!("Roots not found; please run setup beforehand");
}
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
let public_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Public(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have public root");
let private_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Private(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have private root");
let mut public_tree = KeyTreePublic::new_from_root(match public_root {
PersistentAccountData::Public(data) => data.data,
_ => unreachable!(),
});
let mut private_tree = KeyTreePrivate::new_from_root(match private_root {
PersistentAccountData::Private(data) => data.data,
_ => unreachable!(),
});
for pers_acc_data in persistent_accounts {
match pers_acc_data {
PersistentAccountData::Public(data) => {
public_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Private(data) => {
private_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Preconfigured(acc_data) => match acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, data.account));
}
},
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
})
}
pub fn new_storage(config: WalletConfig, password: String) -> Result<Self> {
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
for init_acc_data in config.initial_accounts.clone() {
match init_acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
let mut account = data.account;
// TODO: Program owner is only known after code is compiled and can't be set in
// the config. Therefore we overwrite it here on startup. Fix this when program
// id can be fetched from the node and queried from the wallet.
account.program_owner = Program::authenticated_transfer_program().id();
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, account));
}
}
}
let public_tree = KeyTreePublic::new(&SeedHolder::new_mnemonic(password.clone()));
let private_tree = KeyTreePrivate::new(&SeedHolder::new_mnemonic(password));
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
})
}
pub fn insert_private_account_data(
&mut self,
account_id: nssa::AccountId,
account: nssa_core::account::Account,
) {
println!("inserting at address {account_id}, this account {account:?}");
let entry = self
.user_data
.default_user_private_accounts
.entry(account_id)
.and_modify(|data| data.1 = account.clone());
if matches!(entry, Entry::Vacant(_)) {
self.user_data
.private_key_tree
.account_id_map
.get(&account_id)
.map(|chain_index| {
self.user_data
.private_key_tree
.key_map
.entry(chain_index.clone())
.and_modify(|data| data.value.1 = account)
});
}
}
}
#[cfg(test)]
mod tests {
use key_protocol::key_management::key_tree::{
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode,
};
use super::*;
use crate::config::{
InitialAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
};
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(
r#"{
"Public": {
"account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
}
}"#,
)
.unwrap();
let initial_acc2 = serde_json::from_str(
r#"{
"Public": {
"account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
}
}"#,
)
.unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
initial_accounts
}
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_poll_retry_delay_millis: 500,
initial_accounts: create_initial_accounts(),
}
}
fn create_sample_persistent_accounts() -> Vec<PersistentAccountData> {
let public_data = ChildKeysPublic::root([42; 64]);
let private_data = ChildKeysPrivate::root([47; 64]);
vec![
PersistentAccountData::Public(PersistentAccountDataPublic {
account_id: public_data.account_id(),
chain_index: ChainIndex::root(),
data: public_data,
}),
PersistentAccountData::Private(PersistentAccountDataPrivate {
account_id: private_data.account_id(),
chain_index: ChainIndex::root(),
data: private_data,
}),
]
}
#[test]
fn test_new_initializes_correctly() {
let config = create_sample_wallet_config();
let accs = create_sample_persistent_accounts();
let _ = WalletChainStore::new(config.clone(), accs).unwrap();
}
}

View File

@ -1,188 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData;
use nssa::program::Program;
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
pub wallet_config: WalletConfig,
}
impl WalletChainStore {
pub fn new(config: WalletConfig) -> Result<Self> {
let mut public_init_acc_map = HashMap::new();
let mut private_init_acc_map = HashMap::new();
for init_acc_data in config.initial_accounts.clone() {
match init_acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id.parse()?, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
let mut account = data.account;
// TODO: Program owner is only known after code is compiled and can't be set in
// the config. Therefore we overwrite it here on startup. Fix this when program
// id can be fetched from the node and queried from the wallet.
account.program_owner = Program::authenticated_transfer_program().id();
private_init_acc_map
.insert(data.account_id.parse()?, (data.key_chain, account));
}
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(public_init_acc_map, private_init_acc_map)?,
wallet_config: config,
})
}
pub fn insert_private_account_data(
&mut self,
account_id: nssa::AccountId,
account: nssa_core::account::Account,
) {
println!(
"inserting at addres {}, this account {:?}",
account_id, account
);
self.user_data
.user_private_accounts
.entry(account_id)
.and_modify(|(_, acc)| *acc = account);
}
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
match acc_data {
PersistentAccountData::Public(acc_data) => {
self.user_data
.pub_account_signing_keys
.insert(acc_data.account_id, acc_data.pub_sign_key);
}
PersistentAccountData::Private(acc_data) => {
self.user_data
.user_private_accounts
.insert(acc_data.account_id, (acc_data.key_chain, acc_data.account));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::InitialAccountData;
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(
r#"{
"Public": {
"account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
}
}"#,
)
.unwrap();
let initial_acc2 = serde_json::from_str(
r#"{
"Public": {
"account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
}
}"#,
)
.unwrap();
let initial_accounts = vec![initial_acc1, initial_acc2];
initial_accounts
}
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_poll_retry_delay_millis: 500,
initial_accounts: create_initial_accounts(),
}
}
#[test]
fn test_new_initializes_correctly() {
let config = create_sample_wallet_config();
let _ = WalletChainStore::new(config.clone()).unwrap();
}
}

View File

@ -2,14 +2,16 @@ use anyhow::Result;
use base58::ToBase58;
use clap::Subcommand;
use itertools::Itertools as _;
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
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;
@ -93,9 +95,17 @@ pub enum AccountSubcommand {
#[derive(Subcommand, Debug, Clone)]
pub enum NewSubcommand {
/// Register new public account
Public {},
Public {
#[arg(long)]
/// Chain index of a parent node
cci: ChainIndex,
},
/// Register new private account
Private {},
Private {
#[arg(long)]
/// Chain index of a parent node
cci: ChainIndex,
},
}
impl WalletSubcommand for NewSubcommand {
@ -104,8 +114,8 @@ impl WalletSubcommand for NewSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
NewSubcommand::Public {} => {
let account_id = wallet_core.create_new_account_public();
NewSubcommand::Public { cci } => {
let account_id = wallet_core.create_new_account_public(cci);
println!("Generated new account with account_id Public/{account_id}");
@ -115,8 +125,8 @@ impl WalletSubcommand for NewSubcommand {
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
NewSubcommand::Private {} => {
let account_id = wallet_core.create_new_account_private();
NewSubcommand::Private { cci } => {
let account_id = wallet_core.create_new_account_private(cci);
let (key, _) = wallet_core
.storage
@ -275,12 +285,19 @@ impl WalletSubcommand for AccountSubcommand {
.await?
.last_block;
if !wallet_core
if wallet_core
.storage
.user_data
.user_private_accounts
.private_key_tree
.account_id_map
.is_empty()
{
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
} else {
parse_block_range(
last_synced_block + 1,
curr_last_block,
@ -288,12 +305,6 @@ impl WalletSubcommand for AccountSubcommand {
wallet_core,
)
.await?;
} else {
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
}
Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block))
@ -301,14 +312,28 @@ impl WalletSubcommand for AccountSubcommand {
AccountSubcommand::List {} => {
let user_data = &wallet_core.storage.user_data;
let accounts = user_data
.pub_account_signing_keys
.default_pub_account_signing_keys
.keys()
.map(|id| format!("Public/{id}"))
.map(|id| format!("Preconfigured Public/{id}"))
.chain(
user_data
.user_private_accounts
.default_user_private_accounts
.keys()
.map(|id| format!("Private/{id}")),
.map(|id| format!("Preconfigured Private/{id}")),
)
.chain(
user_data
.public_key_tree
.account_id_map
.iter()
.map(|(id, chain_index)| format!("{chain_index} Public/{id}")),
)
.chain(
user_data
.private_key_tree
.account_id_map
.iter()
.map(|(id, chain_index)| format!("{chain_index} Private/{id}")),
)
.format(",\n");

View File

@ -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)]

View File

@ -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)]

View File

@ -1,15 +1,200 @@
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,
},
}
/// 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(())
}

View File

@ -0,0 +1,3 @@
pub mod native_token_transfer;
pub mod pinata;
pub mod token;

View File

@ -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?;

View File

@ -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)
}

View File

@ -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

View File

@ -1,4 +1,9 @@
use key_protocol::key_management::KeyChain;
use key_protocol::key_management::{
KeyChain,
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -10,7 +15,8 @@ pub struct InitialAccountDataPublic {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPublic {
pub account_id: nssa::AccountId,
pub pub_sign_key: nssa::PrivateKey,
pub chain_index: ChainIndex,
pub data: ChildKeysPublic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -23,8 +29,8 @@ pub struct InitialAccountDataPrivate {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub account_id: nssa::AccountId,
pub account: nssa_core::account::Account,
pub key_chain: KeyChain,
pub chain_index: ChainIndex,
pub data: ChildKeysPrivate,
}
// Big difference in enum variants sizes
@ -45,6 +51,7 @@ pub enum InitialAccountData {
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
Private(PersistentAccountDataPrivate),
Preconfigured(InitialAccountData),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -67,6 +74,7 @@ impl PersistentAccountData {
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
Self::Preconfigured(acc) => acc.account_id(),
}
}
}
@ -95,6 +103,12 @@ impl From<PersistentAccountDataPrivate> for PersistentAccountData {
}
}
impl From<InitialAccountData> for PersistentAccountData {
fn from(value: InitialAccountData) -> Self {
Self::Preconfigured(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
/// Gas spent per deploying one byte of data

View File

@ -1,17 +1,23 @@
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,
},
};
@ -90,7 +96,7 @@ pub async fn fetch_config() -> Result<WalletConfig> {
/// Fetch data stored at home
///
/// If file not present, it is considered as empty list of persistent accounts
/// File must be created through setup beforehand.
pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
let home = get_home()?;
let accs_path = home.join("storage.json");
@ -102,10 +108,9 @@ pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
Ok(serde_json::from_slice(&storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => Ok(PersistentStorage {
accounts: vec![],
last_synced_block: 0,
}),
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
@ -120,25 +125,51 @@ pub fn produce_data_for_storage(
) -> PersistentStorage {
let mut vec_for_storage = vec![];
for (account_id, key) in &user_data.pub_account_signing_keys {
vec_for_storage.push(
PersistentAccountDataPublic {
account_id: *account_id,
pub_sign_key: key.clone(),
}
.into(),
);
for (account_id, key) in &user_data.public_key_tree.account_id_map {
if let Some(data) = user_data.public_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPublic {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, (key, acc)) in &user_data.user_private_accounts {
for (account_id, key) in &user_data.private_key_tree.account_id_map {
if let Some(data) = user_data.private_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPrivate {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, key) in &user_data.default_pub_account_signing_keys {
vec_for_storage.push(
PersistentAccountDataPrivate {
account_id: *account_id,
account: acc.clone(),
key_chain: key.clone(),
}
InitialAccountData::Public(InitialAccountDataPublic {
account_id: account_id.to_string(),
pub_sign_key: key.clone(),
})
.into(),
);
)
}
for (account_id, (key_chain, account)) in &user_data.default_user_private_accounts {
vec_for_storage.push(
InitialAccountData::Private(InitialAccountDataPrivate {
account_id: account_id.to_string(),
account: account.clone(),
key_chain: key_chain.clone(),
})
.into(),
)
}
PersistentStorage {
@ -199,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::*;

View File

@ -3,30 +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;
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,
};
@ -36,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,
@ -54,15 +46,12 @@ impl WalletCore {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
let mut storage = WalletChainStore::new(config)?;
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block,
} = fetch_persistent_storage().await?;
for pers_acc_data in persistent_accounts {
storage.insert_account_data(pers_acc_data);
}
let storage = WalletChainStore::new(config, persistent_accounts)?;
Ok(Self {
storage,
@ -72,6 +61,23 @@ impl WalletCore {
})
}
pub async fn start_from_config_new_storage(
config: WalletConfig,
password: String,
) -> Result<Self> {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
let storage = WalletChainStore::new_storage(config, password)?;
Ok(Self {
storage,
poller: tx_poller,
sequencer_client: client.clone(),
last_synced_block: 0,
})
}
/// Store persistent data at home
pub async fn store_persistent_data(&self) -> Result<PathBuf> {
let home = get_home()?;
@ -102,16 +108,16 @@ impl WalletCore {
Ok(config_path)
}
pub fn create_new_account_public(&mut self) -> AccountId {
pub fn create_new_account_public(&mut self, chain_index: ChainIndex) -> AccountId {
self.storage
.user_data
.generate_new_public_transaction_private_key()
.generate_new_public_transaction_private_key(chain_index)
}
pub fn create_new_account_private(&mut self) -> AccountId {
pub fn create_new_account_private(&mut self, chain_index: ChainIndex) -> AccountId {
self.storage
.user_data
.generate_new_privacy_preserving_transaction_key_chain()
.generate_new_privacy_preserving_transaction_key_chain(chain_index)
}
/// Get account balance
@ -141,20 +147,24 @@ 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
.user_private_accounts
.get(account_id)
.get_private_account(account_id)
.map(|value| value.1.clone())
}
pub fn get_private_account_commitment(&self, account_id: &AccountId) -> Option<Commitment> {
let (keys, account) = self
.storage
.user_data
.user_private_accounts
.get(account_id)?;
let (keys, account) = self.storage.user_data.get_private_account(account_id)?;
Some(Commitment::new(&keys.nullifer_public_key, account))
}
@ -208,225 +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),
}
/// 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<Command>,
}
#[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.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 (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;
}
}

View File

@ -1,14 +1,16 @@
use anyhow::Result;
use clap::{CommandFactory, Parser};
use clap::{CommandFactory as _, Parser as _};
use tokio::runtime::Builder;
use wallet::{Args, execute_continious_run, execute_subcommand};
use wallet::cli::{Args, OverCommand, execute_continuous_run, execute_setup, execute_subcommand};
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)
@ -21,16 +23,20 @@ fn main() -> Result<()> {
env_logger::init();
runtime.block_on(async move {
if let Some(command) = args.command {
// TODO: It should return error, not panic
execute_subcommand(command).await.unwrap();
} else if args.continious_run {
execute_continious_run().await.unwrap();
if let Some(over_command) = args.command {
match over_command {
OverCommand::Command(command) => {
let _output = execute_subcommand(command).await?;
Ok(())
}
OverCommand::Setup { password } => execute_setup(password).await,
}
} else if args.continuous_run {
execute_continuous_run().await
} else {
let help = Args::command().render_long_help();
println!("{help}");
Ok(())
}
});
Ok(())
})
}

View File

@ -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],
))
}
}

View 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,
})
}

View 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;

View File

@ -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)
})
}
}

View 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)
}

View 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])
})
}
}

View File

@ -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?)
}
}

View 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)
})
}
}

View 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)
})
}
}

View 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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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],
))
}
}