mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-08 09:09:31 +00:00
fix: resolve merge conflicts
This commit is contained in:
commit
4c28133448
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -2109,7 +2109,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2410,7 +2410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7105,7 +7105,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8038,7 +8038,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -9321,7 +9321,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -93,6 +93,12 @@ Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completio
|
||||
exec zsh
|
||||
```
|
||||
|
||||
> **Note:** After updating the completion script, re-run step 1 to copy the new file, then rebuild the cache:
|
||||
> ```sh
|
||||
> cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/
|
||||
> rm -rf ~/.zcompdump* && exec zsh
|
||||
> ```
|
||||
|
||||
### Requirements
|
||||
|
||||
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
|
||||
@ -197,8 +203,7 @@ wallet account get --account-id <TAB>
|
||||
2. Rebuild the completion cache:
|
||||
|
||||
```sh
|
||||
rm -f ~/.zcompdump*
|
||||
exec zsh
|
||||
rm -rf ~/.zcompdump* && exec zsh
|
||||
```
|
||||
|
||||
### Account IDs not completing
|
||||
|
||||
@ -46,7 +46,7 @@ _wallet() {
|
||||
cword=$COMP_CWORD
|
||||
}
|
||||
|
||||
local commands="auth-transfer chain-info account pinata token amm check-health config restore-keys deploy-program help"
|
||||
local commands="auth-transfer chain-info account pinata token amm ata check-health config restore-keys deploy-program help"
|
||||
|
||||
# Find the main command and subcommand by scanning words before the cursor.
|
||||
# Global options that take a value are skipped along with their argument.
|
||||
@ -127,10 +127,10 @@ _wallet() {
|
||||
--to-label)
|
||||
_wallet_complete_account_label "$cur"
|
||||
;;
|
||||
--to-npk | --to-vpk | --amount)
|
||||
--to-npk | --to-vpk | --to-identifier | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --amount" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@ -187,11 +187,11 @@ _wallet() {
|
||||
sync-private)
|
||||
;; # no options
|
||||
new)
|
||||
# `account new` is itself a subcommand: public | private
|
||||
# `account new` is itself a subcommand: public | private-accounts-key
|
||||
local new_subcmd=""
|
||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
||||
case "${words[$i]}" in
|
||||
public | private)
|
||||
public | private-accounts-key)
|
||||
new_subcmd="${words[$i]}"
|
||||
break
|
||||
;;
|
||||
@ -199,13 +199,26 @@ _wallet() {
|
||||
done
|
||||
|
||||
if [[ -z "$new_subcmd" ]]; then
|
||||
COMPREPLY=($(compgen -W "public private" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "public private-accounts-key" -- "$cur"))
|
||||
else
|
||||
case "$prev" in
|
||||
--cci | -l | --label)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--cci -l --label" -- "$cur"))
|
||||
case "$new_subcmd" in
|
||||
public)
|
||||
case "$prev" in
|
||||
--cci | -l | --label)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--cci -l --label" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
private-accounts-key)
|
||||
case "$prev" in
|
||||
--cci)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--cci" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@ -289,10 +302,10 @@ _wallet() {
|
||||
--to-label)
|
||||
_wallet_complete_account_label "$cur"
|
||||
;;
|
||||
--to-npk | --to-vpk | --amount)
|
||||
--to-npk | --to-vpk | --to-identifier | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --amount" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@ -331,10 +344,10 @@ _wallet() {
|
||||
--holder-label)
|
||||
_wallet_complete_account_label "$cur"
|
||||
;;
|
||||
--holder-npk | --holder-vpk | --amount)
|
||||
--holder-npk | --holder-vpk | --holder-identifier | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --holder-npk --holder-vpk --amount" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --holder-npk --holder-vpk --holder-identifier --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@ -344,7 +357,7 @@ _wallet() {
|
||||
amm)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "new swap add-liquidity remove-liquidity help" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "new swap-exact-input swap-exact-output add-liquidity remove-liquidity help" -- "$cur"))
|
||||
;;
|
||||
new)
|
||||
case "$prev" in
|
||||
@ -373,7 +386,7 @@ _wallet() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
swap)
|
||||
swap-exact-input)
|
||||
case "$prev" in
|
||||
--user-holding-a)
|
||||
_wallet_complete_account_id "$cur"
|
||||
@ -394,6 +407,15 @@ _wallet() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
swap-exact-output)
|
||||
case "$prev" in
|
||||
--user-holding-a | --user-holding-b | --exact-amount-out | --max-amount-in | --token-definition)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --exact-amount-out --max-amount-in --token-definition" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
add-liquidity)
|
||||
case "$prev" in
|
||||
--user-holding-a)
|
||||
@ -451,6 +473,68 @@ _wallet() {
|
||||
esac
|
||||
;;
|
||||
|
||||
ata)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "address create send burn list help" -- "$cur"))
|
||||
;;
|
||||
address)
|
||||
case "$prev" in
|
||||
--owner | --token-definition)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
create)
|
||||
case "$prev" in
|
||||
--owner)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--token-definition)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
send)
|
||||
case "$prev" in
|
||||
--from)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--to | --token-definition | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --token-definition --to --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
burn)
|
||||
case "$prev" in
|
||||
--holder)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--token-definition | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--holder --token-definition --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
list)
|
||||
case "$prev" in
|
||||
--owner | --token-definition)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
config)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
|
||||
@ -24,6 +24,7 @@ _wallet() {
|
||||
'pinata:Pinata program interaction subcommand'
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'ata:Associated Token Account program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
@ -52,6 +53,9 @@ _wallet() {
|
||||
amm)
|
||||
_wallet_amm
|
||||
;;
|
||||
ata)
|
||||
_wallet_ata
|
||||
;;
|
||||
config)
|
||||
_wallet_config
|
||||
;;
|
||||
@ -72,7 +76,7 @@ _wallet() {
|
||||
# auth-transfer subcommand
|
||||
_wallet_auth_transfer() {
|
||||
local -a subcommands
|
||||
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
@ -91,16 +95,17 @@ _wallet_auth_transfer() {
|
||||
init)
|
||||
_arguments \
|
||||
'--account-id[Account ID to initialize]:account_id:_wallet_account_ids' \
|
||||
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels'
|
||||
'--account-label[Account label (alternative to --account-id)]:label:'
|
||||
;;
|
||||
send)
|
||||
_arguments \
|
||||
'--from[Source account ID]:from_account:_wallet_account_ids' \
|
||||
'--from-label[Source account label (alternative to --from)]:label:_wallet_account_labels' \
|
||||
'--from-label[From account label (alternative to --from)]:label:' \
|
||||
'--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels' \
|
||||
'--to-label[To account label (alternative to --to)]:label:' \
|
||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--to-identifier[Identifier for the recipient private account]:identifier:' \
|
||||
'--amount[Amount of native tokens to send]:amount:'
|
||||
;;
|
||||
esac
|
||||
@ -111,7 +116,7 @@ _wallet_auth_transfer() {
|
||||
# chain-info subcommand
|
||||
_wallet_chain_info() {
|
||||
local -a subcommands
|
||||
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
@ -144,7 +149,7 @@ _wallet_chain_info() {
|
||||
# account subcommand
|
||||
_wallet_account() {
|
||||
local -a subcommands
|
||||
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
@ -169,7 +174,7 @@ _wallet_account() {
|
||||
'(-r --raw)'{-r,--raw}'[Get raw account data]' \
|
||||
'(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/vpk for private accounts)]' \
|
||||
'(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids' \
|
||||
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels'
|
||||
'--account-label[Account label (alternative to --account-id)]:label:'
|
||||
;;
|
||||
list|ls)
|
||||
_arguments \
|
||||
@ -181,19 +186,27 @@ _wallet_account() {
|
||||
'*:: :->new_args'
|
||||
case $state in
|
||||
account_type)
|
||||
compadd public private
|
||||
compadd public private-accounts-key
|
||||
;;
|
||||
new_args)
|
||||
_arguments \
|
||||
'--cci[Chain index of a parent node]:chain_index:' \
|
||||
'(-l --label)'{-l,--label}'[Label to assign to the new account]:label:'
|
||||
case $line[1] in
|
||||
public)
|
||||
_arguments \
|
||||
'--cci[Chain index of a parent node]:chain_index:' \
|
||||
'(-l --label)'{-l,--label}'[Label to assign to the new account]:label:'
|
||||
;;
|
||||
private-accounts-key)
|
||||
_arguments \
|
||||
'--cci[Chain index of a parent node]:chain_index:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
label)
|
||||
_arguments \
|
||||
'(-a --account-id)'{-a,--account-id}'[Account ID to label]:account_id:_wallet_account_ids' \
|
||||
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels' \
|
||||
'--account-label[Account label (alternative to --account-id)]:label:' \
|
||||
'(-l --label)'{-l,--label}'[The label to assign to the account]:label:'
|
||||
;;
|
||||
esac
|
||||
@ -204,7 +217,7 @@ _wallet_account() {
|
||||
# pinata subcommand
|
||||
_wallet_pinata() {
|
||||
local -a subcommands
|
||||
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
@ -222,7 +235,7 @@ _wallet_pinata() {
|
||||
claim)
|
||||
_arguments \
|
||||
'--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' \
|
||||
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels'
|
||||
'--to-label[To account label (alternative to --to)]:label:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@ -255,36 +268,38 @@ _wallet_token() {
|
||||
'--name[Token name]:name:' \
|
||||
'--total-supply[Total supply of tokens to mint]:total_supply:' \
|
||||
'--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \
|
||||
'--definition-account-label[Definition account label (alternative to --definition-account-id)]:label:_wallet_account_labels' \
|
||||
'--definition-account-label[Definition account label (alternative to --definition-account-id)]:label:' \
|
||||
'--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' \
|
||||
'--supply-account-label[Supply account label (alternative to --supply-account-id)]:label:_wallet_account_labels'
|
||||
'--supply-account-label[Supply account label (alternative to --supply-account-id)]:label:'
|
||||
;;
|
||||
send)
|
||||
_arguments \
|
||||
'--from[Source holding account ID]:from_account:_wallet_account_ids' \
|
||||
'--from-label[Source account label (alternative to --from)]:label:_wallet_account_labels' \
|
||||
'--from-label[From account label (alternative to --from)]:label:' \
|
||||
'--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels' \
|
||||
'--to-label[To account label (alternative to --to)]:label:' \
|
||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--to-identifier[Identifier for the recipient private account]:identifier:' \
|
||||
'--amount[Amount of tokens to send]:amount:'
|
||||
;;
|
||||
burn)
|
||||
_arguments \
|
||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||
'--definition-label[Definition account label (alternative to --definition)]:label:_wallet_account_labels' \
|
||||
'--definition-label[Definition account label (alternative to --definition)]:label:' \
|
||||
'--holder[Holder account ID]:holder_account:_wallet_account_ids' \
|
||||
'--holder-label[Holder account label (alternative to --holder)]:label:_wallet_account_labels' \
|
||||
'--holder-label[Holder account label (alternative to --holder)]:label:' \
|
||||
'--amount[Amount of tokens to burn]:amount:'
|
||||
;;
|
||||
mint)
|
||||
_arguments \
|
||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||
'--definition-label[Definition account label (alternative to --definition)]:label:_wallet_account_labels' \
|
||||
'--definition-label[Definition account label (alternative to --definition)]:label:' \
|
||||
'--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \
|
||||
'--holder-label[Holder account label (alternative to --holder)]:label:_wallet_account_labels' \
|
||||
'--holder-label[Holder account label (alternative to --holder)]:label:' \
|
||||
'--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--holder-vpk[Holder viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--holder-identifier[Identifier for the holder private account]:identifier:' \
|
||||
'--amount[Amount of tokens to mint]:amount:'
|
||||
;;
|
||||
esac
|
||||
@ -295,7 +310,7 @@ _wallet_token() {
|
||||
# amm subcommand
|
||||
_wallet_amm() {
|
||||
local -a subcommands
|
||||
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
@ -304,7 +319,8 @@ _wallet_amm() {
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'new:Create a new liquidity pool'
|
||||
'swap:Swap tokens using the AMM'
|
||||
'swap-exact-input:Swap specifying exact input amount'
|
||||
'swap-exact-output:Swap specifying exact output amount'
|
||||
'add-liquidity:Add liquidity to an existing pool'
|
||||
'remove-liquidity:Remove liquidity from a pool'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
@ -316,32 +332,40 @@ _wallet_amm() {
|
||||
new)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
|
||||
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
|
||||
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
|
||||
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
|
||||
'--balance-a[Amount of token A to deposit]:balance_a:' \
|
||||
'--balance-b[Amount of token B to deposit]:balance_b:'
|
||||
;;
|
||||
swap)
|
||||
swap-exact-input)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
|
||||
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
|
||||
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
|
||||
'--amount-in[Amount of tokens to swap]:amount_in:' \
|
||||
'--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \
|
||||
'--token-definition[Definition ID of the token being provided]:token_def:'
|
||||
;;
|
||||
swap-exact-output)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:' \
|
||||
'--exact-amount-out[Exact amount of tokens expected out]:exact_amount_out:' \
|
||||
'--max-amount-in[Maximum tokens to spend]:max_amount_in:' \
|
||||
'--token-definition[Definition ID of the token being provided]:token_def:'
|
||||
;;
|
||||
add-liquidity)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
|
||||
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
|
||||
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
|
||||
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
|
||||
'--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \
|
||||
'--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \
|
||||
'--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:'
|
||||
@ -349,11 +373,11 @@ _wallet_amm() {
|
||||
remove-liquidity)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
|
||||
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
|
||||
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
|
||||
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
|
||||
'--balance-lp[Amount of LP tokens to burn]:balance_lp:' \
|
||||
'--min-amount-a[Minimum token A to receive]:min_amount_a:' \
|
||||
'--min-amount-b[Minimum token B to receive]:min_amount_b:'
|
||||
@ -363,6 +387,61 @@ _wallet_amm() {
|
||||
esac
|
||||
}
|
||||
|
||||
# ata subcommand
|
||||
_wallet_ata() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'address:Derive and print the Associated Token Account address (local only)'
|
||||
'create:Create (or idempotently no-op) the Associated Token Account'
|
||||
'send:Send tokens from owner ATA to a recipient token holding account'
|
||||
'burn:Burn tokens from holder ATA'
|
||||
'list:List all ATAs for a given owner across multiple token definitions'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'ata subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
address)
|
||||
_arguments \
|
||||
'--owner[Owner account (no privacy prefix)]:owner:' \
|
||||
'--token-definition[Token definition account (no privacy prefix)]:token_def:'
|
||||
;;
|
||||
create)
|
||||
_arguments \
|
||||
'--owner[Owner account with privacy prefix]:owner:_wallet_account_ids' \
|
||||
'--token-definition[Token definition account (no privacy prefix)]:token_def:'
|
||||
;;
|
||||
send)
|
||||
_arguments \
|
||||
'--from[Sender account with privacy prefix]:from:_wallet_account_ids' \
|
||||
'--token-definition[Token definition account (no privacy prefix)]:token_def:' \
|
||||
'--to[Recipient account (no privacy prefix)]:to:' \
|
||||
'--amount[Amount of tokens to send]:amount:'
|
||||
;;
|
||||
burn)
|
||||
_arguments \
|
||||
'--holder[Holder account with privacy prefix]:holder:_wallet_account_ids' \
|
||||
'--token-definition[Token definition account (no privacy prefix)]:token_def:' \
|
||||
'--amount[Amount of tokens to burn]:amount:'
|
||||
;;
|
||||
list)
|
||||
_arguments \
|
||||
'--owner[Owner account (no privacy prefix)]:owner:' \
|
||||
'--token-definition[Token definition accounts (no privacy prefix)]:token_def:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# config subcommand
|
||||
_wallet_config() {
|
||||
local -a subcommands
|
||||
@ -435,6 +514,7 @@ _wallet_help() {
|
||||
'pinata:Pinata program interaction subcommand'
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'ata:Associated Token Account program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
@ -468,25 +548,4 @@ _wallet_account_ids() {
|
||||
_multi_parts / accounts
|
||||
}
|
||||
|
||||
# Helper function to complete account labels
|
||||
# Uses `wallet account list` to get available labels
|
||||
_wallet_account_labels() {
|
||||
local -a labels
|
||||
local line
|
||||
|
||||
if command -v wallet &>/dev/null; then
|
||||
while IFS= read -r line; do
|
||||
local label
|
||||
# Extract label from [...] at end of line
|
||||
label="${line##*\[}"
|
||||
label="${label%\]}"
|
||||
[[ -n "$label" && "$label" != "$line" ]] && labels+=("$label")
|
||||
done < <(wallet account list 2>/dev/null)
|
||||
fi
|
||||
|
||||
if (( ${#labels} > 0 )); then
|
||||
compadd -a labels
|
||||
fi
|
||||
}
|
||||
|
||||
_wallet "$@"
|
||||
|
||||
@ -5,6 +5,7 @@ This tutorial walks through native token transfers between public and private ac
|
||||
4. Private account creation.
|
||||
5. Native token transfer from a public account to a private account.
|
||||
6. Native token transfer from a public account to a private account owned by someone else.
|
||||
7. Sending to a private accounts key from multiple independent senders.
|
||||
|
||||
---
|
||||
|
||||
@ -142,7 +143,7 @@ Account owned by authenticated-transfer program
|
||||
> Private accounts are structurally identical to public accounts, but their values are stored off-chain. On-chain, only a 32-byte commitment is recorded.
|
||||
> Transactions include encrypted private values so the owner can recover them, and the decryption keys are never shared.
|
||||
> Private accounts use two keypairs: nullifier keys for privacy-preserving executions and viewing keys for encrypting and decrypting values.
|
||||
> The private account ID is derived from the nullifier public key.
|
||||
> The private account ID is derived from the nullifier public key and a numeric identifier: `SHA256(prefix || npk || identifier)`. The same `npk` paired with different identifiers yields different, independent account IDs.
|
||||
> Private accounts can be initialized by anyone, but once initialized they can only be modified by the owner’s keys.
|
||||
> Updates include a new commitment and a nullifier for the old state, which prevents linkage between versions.
|
||||
|
||||
@ -158,7 +159,9 @@ With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> Focus on the account ID for now. The `npk` and `vpk` values are stored locally and used to build privacy-preserving transactions. The private account ID is derived from `npk`.
|
||||
> Save this account ID. You will use it in later commands.
|
||||
|
||||
### b. Check the account status
|
||||
|
||||
Just like public accounts, new private accounts start out uninitialized:
|
||||
|
||||
@ -218,21 +221,23 @@ Account owned by authenticated-transfer program
|
||||
## 6. Native token transfer from a public account to a private account owned by someone else
|
||||
|
||||
> [!Important]
|
||||
> We’ll simulate transferring to someone else by creating a new private account we own and treating it as if it belonged to another user.
|
||||
> We’ll simulate transferring to someone else by creating a new private accounts key and treating it as if it belonged to another user. When the recipient is someone else, you only have their `npk` and `vpk` — not an account ID.
|
||||
|
||||
### a. Create a new uninitialized private account
|
||||
### a. Create a new private accounts key to simulate a foreign recipient
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
wallet account new private-accounts-key
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Private/AukXPRBmrYVqoqEW2HTs7N3hvTn3qdNFDcxDHVr5hMm5
|
||||
Generated new private accounts key at path /1
|
||||
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
|
||||
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> Ignore the private account ID here and use the `npk` and `vpk` values to send to a foreign private account.
|
||||
> Ignore the account ID here and use the `npk` and `vpk` values to send to a foreign private account.
|
||||
|
||||
### b. Send 3 tokens using the recipient’s npk and vpk
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
@ -242,9 +247,74 @@ wallet auth-transfer send \
|
||||
--amount 3
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> `--to-identifier` is omitted here. When omitted, the wallet picks a random identifier, which is usually fine. Use the flag explicitly when a specific identifier is required.
|
||||
|
||||
> [!Warning]
|
||||
> This command creates a privacy-preserving transaction, which may take a few minutes. The updated values are encrypted and included in the transaction.
|
||||
> Once accepted, the recipient must run `wallet account sync-private` to scan the chain for their encrypted updates and refresh local state.
|
||||
|
||||
> [!Note]
|
||||
> You have seen transfers between two public accounts and from a public sender to a private recipient. Transfers from a private sender, whether to a public account or to another private account, follow the same pattern.
|
||||
|
||||
## 7. Sending to a private accounts key from multiple independent senders
|
||||
|
||||
> [!Important]
|
||||
> A private accounts key (`npk` + `vpk`) can be shared with multiple senders. Each sender independently chooses an identifier; the recipient's account ID is derived from `(npk, identifier)`. Two senders using different identifiers produce two separate private accounts under the same key.
|
||||
|
||||
### a. Alice creates a private accounts key
|
||||
|
||||
```bash
|
||||
wallet account new private-accounts-key
|
||||
|
||||
# Output:
|
||||
Generated new private accounts key at path /2
|
||||
With npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345
|
||||
With vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c
|
||||
```
|
||||
|
||||
Alice shares the `npk` and `vpk` values with Bob and Charlie out of band.
|
||||
|
||||
### b. Bob sends 10 tokens to Alice using identifier 1
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/BobXqJprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPA \
|
||||
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
|
||||
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
|
||||
--to-identifier 1 \
|
||||
--amount 10
|
||||
```
|
||||
|
||||
### c. Charlie sends 5 tokens to Alice using identifier 2
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/CharlieYrP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPB \
|
||||
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
|
||||
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
|
||||
--to-identifier 2 \
|
||||
--amount 5
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> Bob and Charlie each chose a different identifier. They do not need to coordinate — any two distinct values work.
|
||||
|
||||
### d. Alice syncs to discover the new accounts
|
||||
|
||||
```bash
|
||||
wallet account sync-private
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account list
|
||||
|
||||
# Output (private account entries under key /2):
|
||||
/2 Private/AliceBobAcctXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
/2 Private/AliceCharlieAcctXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
Alice now has two separate private accounts, one funded by Bob and one by Charlie, both controlled by the same key at path `/2`.
|
||||
|
||||
> [!Tip]
|
||||
> Alice can check each account balance with `wallet account get --account-id Private/...`. Neither balance is visible on-chain.
|
||||
|
||||
@ -63,6 +63,7 @@ impl IndexerCore {
|
||||
.iter()
|
||||
.map(|init_comm_data| {
|
||||
let npk = &init_comm_data.npk;
|
||||
let account_id = nssa::AccountId::from((npk, 0));
|
||||
|
||||
let mut acc = init_comm_data.account.clone();
|
||||
|
||||
@ -70,8 +71,8 @@ impl IndexerCore {
|
||||
nssa::program::Program::authenticated_transfer_program().id();
|
||||
|
||||
(
|
||||
nssa_core::Commitment::new(npk, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(npk),
|
||||
nssa_core::Commitment::new(&account_id, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(&account_id),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
||||
@ -60,11 +60,11 @@ impl InitialData {
|
||||
|
||||
let mut private_charlie_key_chain = KeyChain::new_os_random();
|
||||
let mut private_charlie_account_id =
|
||||
AccountId::from(&private_charlie_key_chain.nullifier_public_key);
|
||||
AccountId::from((&private_charlie_key_chain.nullifier_public_key, 0));
|
||||
|
||||
let mut private_david_key_chain = KeyChain::new_os_random();
|
||||
let mut private_david_account_id =
|
||||
AccountId::from(&private_david_key_chain.nullifier_public_key);
|
||||
AccountId::from((&private_david_key_chain.nullifier_public_key, 0));
|
||||
|
||||
// Ensure consistent ordering
|
||||
if private_charlie_account_id > private_david_account_id {
|
||||
@ -139,11 +139,10 @@ impl InitialData {
|
||||
})
|
||||
})
|
||||
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
|
||||
let account_id = AccountId::from(&key_chain.nullifier_public_key);
|
||||
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
|
||||
account_id,
|
||||
account: account.clone(),
|
||||
key_chain: key_chain.clone(),
|
||||
identifier: 0,
|
||||
}))
|
||||
}))
|
||||
.collect()
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
)]
|
||||
|
||||
use anyhow::Result;
|
||||
use integration_tests::TestContext;
|
||||
use integration_tests::{TestContext, format_private_account_id};
|
||||
use log::info;
|
||||
use nssa::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
@ -70,34 +70,29 @@ async fn new_public_account_with_label() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn new_private_account_with_label() -> Result<()> {
|
||||
async fn add_label_to_existing_account() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
let account_id = ctx.existing_private_accounts()[0];
|
||||
let label = "my-test-private-account".to_owned();
|
||||
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
|
||||
cci: None,
|
||||
label: Some(label.clone()),
|
||||
}));
|
||||
let command = Command::Account(AccountSubcommand::Label {
|
||||
account_id: Some(format_private_account_id(account_id)),
|
||||
account_label: None,
|
||||
label: label.clone(),
|
||||
});
|
||||
|
||||
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Extract the account_id from the result
|
||||
|
||||
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||
panic!("Expected RegisterAccount return value")
|
||||
};
|
||||
|
||||
// Verify the label was stored
|
||||
let stored_label = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.labels
|
||||
.get(&account_id.to_string())
|
||||
.expect("Label should be stored for the new account");
|
||||
.expect("Label should be stored for the account");
|
||||
|
||||
assert_eq!(stored_label.to_string(), label);
|
||||
|
||||
info!("Successfully created private account with label");
|
||||
info!("Successfully set label on existing private account");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -133,6 +133,7 @@ async fn amm_public() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
@ -162,6 +163,7 @@ async fn amm_public() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 7,
|
||||
};
|
||||
|
||||
@ -550,6 +552,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 5,
|
||||
};
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
@ -574,6 +577,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 5,
|
||||
};
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
|
||||
@ -268,6 +268,7 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: fund_amount,
|
||||
}),
|
||||
)
|
||||
@ -500,6 +501,7 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: fund_amount,
|
||||
}),
|
||||
)
|
||||
@ -614,6 +616,7 @@ async fn burn_via_ata_private_owner() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: fund_amount,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -30,6 +30,7 @@ async fn private_transfer_to_owned_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -71,6 +72,7 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: Some(to_npk_string),
|
||||
to_vpk: Some(hex::encode(to_vpk.0)),
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -121,6 +123,7 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -170,12 +173,11 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
|
||||
};
|
||||
|
||||
// Get the keys for the newly created account
|
||||
let (to_keys, _) = ctx
|
||||
let (to_keys, _, to_identifier) = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(to_account_id)
|
||||
.cloned()
|
||||
.context("Failed to get private account")?;
|
||||
|
||||
// Send to this account using claiming path (using npk and vpk instead of account ID)
|
||||
@ -186,6 +188,7 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
|
||||
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
|
||||
to_identifier: Some(to_identifier),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -236,6 +239,7 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -280,6 +284,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: Some(to_npk_string),
|
||||
to_vpk: Some(hex::encode(to_vpk.0)),
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -336,12 +341,11 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
|
||||
};
|
||||
|
||||
// Get the newly created account's keys
|
||||
let (to_keys, _) = ctx
|
||||
let (to_keys, _, to_identifier) = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(to_account_id)
|
||||
.cloned()
|
||||
.context("Failed to get private account")?;
|
||||
|
||||
// Send transfer using nullifier and viewing public keys
|
||||
@ -352,6 +356,7 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
|
||||
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
|
||||
to_identifier: Some(to_identifier),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -455,6 +460,7 @@ async fn private_transfer_using_from_label() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -527,3 +533,112 @@ async fn initialize_private_account_using_label() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
// Both transfers below will target this same node with distinct identifiers.
|
||||
let chain_index = ctx.wallet_mut().create_private_accounts_key(None);
|
||||
let (npk, vpk) = {
|
||||
let node = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("node was just inserted");
|
||||
let key_chain = &node.value.0;
|
||||
(
|
||||
key_chain.nullifier_public_key,
|
||||
key_chain.viewing_public_key.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let npk_hex = hex::encode(npk.0);
|
||||
let vpk_hex = hex::encode(vpk.0);
|
||||
|
||||
let identifier_1 = 1_u128;
|
||||
let identifier_2 = 2_u128;
|
||||
|
||||
let sender_0: AccountId = ctx.existing_public_accounts()[0];
|
||||
let sender_1: AccountId = ctx.existing_public_accounts()[1];
|
||||
|
||||
wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: Some(format_public_account_id(sender_0)),
|
||||
from_label: None,
|
||||
to: None,
|
||||
to_label: None,
|
||||
to_npk: Some(npk_hex.clone()),
|
||||
to_vpk: Some(vpk_hex.clone()),
|
||||
to_identifier: Some(identifier_1),
|
||||
amount: 100,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: Some(format_public_account_id(sender_1)),
|
||||
from_label: None,
|
||||
to: None,
|
||||
to_label: None,
|
||||
to_npk: Some(npk_hex),
|
||||
to_vpk: Some(vpk_hex),
|
||||
to_identifier: Some(identifier_2),
|
||||
amount: 200,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
Command::Account(AccountSubcommand::SyncPrivate {}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Both accounts must be discovered with the correct balances.
|
||||
let account_id_1 = AccountId::from((&npk, identifier_1));
|
||||
let acc_1 = ctx
|
||||
.wallet()
|
||||
.get_account_private(account_id_1)
|
||||
.context("account for identifier 1 not found after sync")?;
|
||||
assert_eq!(acc_1.balance, 100);
|
||||
|
||||
let account_id_2 = AccountId::from((&npk, identifier_2));
|
||||
let acc_2 = ctx
|
||||
.wallet()
|
||||
.get_account_private(account_id_2)
|
||||
.context("account for identifier 2 not found after sync")?;
|
||||
assert_eq!(acc_2.balance, 200);
|
||||
|
||||
// Both account ids must resolve to the same key node.
|
||||
let tree = &ctx.wallet().storage().user_data.private_key_tree;
|
||||
let ci_1 = tree
|
||||
.account_id_map
|
||||
.get(&account_id_1)
|
||||
.context("account_id_1 missing from private_key_tree.account_id_map")?;
|
||||
let ci_2 = tree
|
||||
.account_id_map
|
||||
.get(&account_id_2)
|
||||
.context("account_id_2 missing from private_key_tree.account_id_map")?;
|
||||
assert_eq!(
|
||||
ci_1, ci_2,
|
||||
"identifiers 1 and 2 under the same NPK must share a single chain_index"
|
||||
);
|
||||
assert_eq!(
|
||||
ci_1, &chain_index,
|
||||
"both accounts must resolve to the key node created at the start of the test"
|
||||
);
|
||||
|
||||
info!("Successfully transferred to two distinct identifiers under the same NPK");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -81,6 +82,7 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -119,6 +121,7 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 1_000_000,
|
||||
});
|
||||
|
||||
@ -159,6 +162,7 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -193,6 +197,7 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -278,6 +283,7 @@ async fn successful_transfer_using_from_label() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -325,6 +331,7 @@ async fn successful_transfer_using_to_label() -> Result<()> {
|
||||
to_label: Some(label),
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
|
||||
@ -111,6 +111,7 @@ async fn indexer_state_consistency() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -147,6 +148,7 @@ async fn indexer_state_consistency() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -233,6 +235,7 @@ async fn indexer_state_consistency_with_labels() -> Result<()> {
|
||||
to_label: Some(to_label_str),
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
|
||||
@ -92,6 +92,7 @@ fn indexer_ffi_state_consistency() -> Result<()> {
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
amount: 100,
|
||||
to_identifier: Some(0),
|
||||
});
|
||||
|
||||
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
|
||||
@ -133,6 +134,7 @@ fn indexer_ffi_state_consistency() -> Result<()> {
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
amount: 100,
|
||||
to_identifier: Some(0),
|
||||
});
|
||||
|
||||
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
|
||||
@ -235,6 +237,7 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> {
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
amount: 100,
|
||||
to_identifier: Some(0),
|
||||
});
|
||||
|
||||
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
|
||||
|
||||
@ -59,12 +59,11 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
|
||||
};
|
||||
|
||||
// Get the keys for the newly created account
|
||||
let (to_keys, _) = ctx
|
||||
let (to_keys, _, to_identifier) = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(to_account_id)
|
||||
.cloned()
|
||||
.context("Failed to get private account")?;
|
||||
|
||||
// Send to this account using claiming path (using npk and vpk instead of account ID)
|
||||
@ -75,6 +74,7 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
|
||||
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
|
||||
to_identifier: Some(to_identifier),
|
||||
amount: 100,
|
||||
});
|
||||
|
||||
@ -151,6 +151,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 100,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
@ -163,6 +164,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 101,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
@ -203,6 +205,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 102,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
@ -215,6 +218,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 103,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
@ -259,16 +263,16 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
.expect("Acc 4 should be restored");
|
||||
|
||||
assert_eq!(
|
||||
acc1.value.1.program_owner,
|
||||
acc1.value.1[0].1.program_owner,
|
||||
Program::authenticated_transfer_program().id()
|
||||
);
|
||||
assert_eq!(
|
||||
acc2.value.1.program_owner,
|
||||
acc2.value.1[0].1.program_owner,
|
||||
Program::authenticated_transfer_program().id()
|
||||
);
|
||||
|
||||
assert_eq!(acc1.value.1.balance, 100);
|
||||
assert_eq!(acc2.value.1.balance, 101);
|
||||
assert_eq!(acc1.value.1[0].1.balance, 100);
|
||||
assert_eq!(acc2.value.1[0].1.balance, 101);
|
||||
|
||||
info!("Tree checks passed, testing restored accounts can transact");
|
||||
|
||||
@ -280,6 +284,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 10,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
@ -291,6 +296,7 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: 11,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
@ -134,6 +134,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
@ -227,6 +228,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
holder_label: None,
|
||||
holder_npk: None,
|
||||
holder_vpk: None,
|
||||
holder_identifier: None,
|
||||
amount: mint_amount,
|
||||
};
|
||||
|
||||
@ -372,6 +374,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
@ -566,6 +569,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
holder_label: None,
|
||||
holder_npk: None,
|
||||
holder_vpk: None,
|
||||
holder_identifier: None,
|
||||
amount: mint_amount_public,
|
||||
};
|
||||
|
||||
@ -614,6 +618,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
holder_label: None,
|
||||
holder_npk: None,
|
||||
holder_vpk: None,
|
||||
holder_identifier: None,
|
||||
amount: mint_amount_private,
|
||||
};
|
||||
|
||||
@ -756,6 +761,7 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
@ -887,6 +893,7 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
@ -1013,6 +1020,7 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
|
||||
@ -1131,12 +1139,11 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
|
||||
};
|
||||
|
||||
// Get keys for foreign mint (claiming path)
|
||||
let (holder_keys, _) = ctx
|
||||
let (holder_keys, _, holder_identifier) = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(recipient_account_id)
|
||||
.cloned()
|
||||
.context("Failed to get private account keys")?;
|
||||
|
||||
// Mint using claiming path (foreign account)
|
||||
@ -1148,6 +1155,7 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
|
||||
holder_label: None,
|
||||
holder_npk: Some(hex::encode(holder_keys.nullifier_public_key.0)),
|
||||
holder_vpk: Some(hex::encode(holder_keys.viewing_public_key.0)),
|
||||
holder_identifier: Some(holder_identifier),
|
||||
amount: mint_amount,
|
||||
};
|
||||
|
||||
@ -1351,6 +1359,7 @@ async fn transfer_token_using_from_label() -> Result<()> {
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: Some(0),
|
||||
amount: transfer_amount,
|
||||
};
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
|
||||
|
||||
@ -220,14 +220,17 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
data: Data::default(),
|
||||
},
|
||||
true,
|
||||
AccountId::from(&sender_npk),
|
||||
AccountId::from((&sender_npk, 0)),
|
||||
);
|
||||
let recipient_nsk = [2; 32];
|
||||
let recipient_vsk = [99; 32];
|
||||
let recipient_vpk = ViewingPublicKey::from_scalar(recipient_vsk);
|
||||
let recipient_npk = NullifierPublicKey::from(&recipient_nsk);
|
||||
let recipient_pre =
|
||||
AccountWithMetadata::new(Account::default(), false, AccountId::from(&recipient_npk));
|
||||
let recipient_pre = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::from((&recipient_npk, 0)),
|
||||
);
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&sender_npk);
|
||||
let sender_ss = eph_holder_from.calculate_shared_secret_sender(&sender_vpk);
|
||||
@ -249,7 +252,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![(sender_npk, sender_ss), (recipient_npk, recipient_ss)],
|
||||
vec![(sender_npk, 0, sender_ss), (recipient_npk, 0, recipient_ss)],
|
||||
vec![sender_nsk],
|
||||
vec![Some(proof)],
|
||||
&program.into(),
|
||||
|
||||
@ -26,7 +26,7 @@ use nssa_core::program::DEFAULT_PROGRAM_ID;
|
||||
use tempfile::tempdir;
|
||||
use wallet_ffi::{
|
||||
FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey,
|
||||
FfiTransferResult, WalletHandle, error,
|
||||
FfiTransferResult, FfiU128, WalletHandle, error,
|
||||
};
|
||||
|
||||
unsafe extern "C" {
|
||||
@ -53,6 +53,11 @@ unsafe extern "C" {
|
||||
out_account_id: *mut FfiBytes32,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_create_private_accounts_key(
|
||||
handle: *mut WalletHandle,
|
||||
out_keys: *mut FfiPrivateAccountKeys,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_list_accounts(
|
||||
handle: *mut WalletHandle,
|
||||
out_list: *mut FfiAccountList,
|
||||
@ -116,6 +121,7 @@ unsafe extern "C" {
|
||||
handle: *mut WalletHandle,
|
||||
from: *const FfiBytes32,
|
||||
to_keys: *const FfiPrivateAccountKeys,
|
||||
to_identifier: *const FfiU128,
|
||||
amount: *const [u8; 16],
|
||||
out_result: *mut FfiTransferResult,
|
||||
) -> error::WalletFfiError;
|
||||
@ -132,6 +138,7 @@ unsafe extern "C" {
|
||||
handle: *mut WalletHandle,
|
||||
from: *const FfiBytes32,
|
||||
to_keys: *const FfiPrivateAccountKeys,
|
||||
to_identifier: *const FfiU128,
|
||||
amount: *const [u8; 16],
|
||||
out_result: *mut FfiTransferResult,
|
||||
) -> error::WalletFfiError;
|
||||
@ -260,33 +267,28 @@ fn wallet_ffi_create_public_accounts() -> Result<()> {
|
||||
fn wallet_ffi_create_private_accounts() -> Result<()> {
|
||||
let password = "password_for_tests";
|
||||
let n_accounts = 10;
|
||||
// Create `n_accounts` private accounts with wallet FFI
|
||||
let new_private_account_ids_ffi = unsafe {
|
||||
let mut account_ids = Vec::new();
|
||||
// Create `n_accounts` receiving keys with wallet FFI
|
||||
let new_npks_ffi = unsafe {
|
||||
let mut npks = Vec::new();
|
||||
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
|
||||
for _ in 0..n_accounts {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
account_ids.push(out_account_id.data);
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
|
||||
npks.push(out_keys.nullifier_public_key.data);
|
||||
wallet_ffi_free_private_account_keys(&raw mut out_keys);
|
||||
}
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
account_ids
|
||||
npks
|
||||
};
|
||||
|
||||
// All returned IDs must be unique and non-zero
|
||||
assert_eq!(new_private_account_ids_ffi.len(), n_accounts);
|
||||
let unique: HashSet<_> = new_private_account_ids_ffi.iter().collect();
|
||||
assert_eq!(
|
||||
unique.len(),
|
||||
n_accounts,
|
||||
"Duplicate private account IDs returned"
|
||||
);
|
||||
// All returned NPKs must be unique and non-zero
|
||||
assert_eq!(new_npks_ffi.len(), n_accounts);
|
||||
let unique: HashSet<_> = new_npks_ffi.iter().collect();
|
||||
assert_eq!(unique.len(), n_accounts, "Duplicate NPKs returned");
|
||||
assert!(
|
||||
new_private_account_ids_ffi
|
||||
.iter()
|
||||
.all(|id| *id != [0_u8; 32]),
|
||||
"Zero account ID returned"
|
||||
new_npks_ffi.iter().all(|id| *id != [0_u8; 32]),
|
||||
"Zero NPK returned"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@ -294,46 +296,35 @@ fn wallet_ffi_create_private_accounts() -> Result<()> {
|
||||
#[test]
|
||||
fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let mut out_private_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
let home = tempfile::tempdir()?;
|
||||
|
||||
// Create a private account with the wallet FFI and save it
|
||||
unsafe {
|
||||
// Create a receiving key and save
|
||||
let first_npk = unsafe {
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_private_account_id);
|
||||
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
|
||||
let npk = out_keys.nullifier_public_key.data;
|
||||
wallet_ffi_free_private_account_keys(&raw mut out_keys);
|
||||
wallet_ffi_save(wallet_ffi_handle);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
let private_account_keys = unsafe {
|
||||
let wallet_ffi_handle = load_existing_ffi_wallet(home.path())?;
|
||||
|
||||
let mut private_account = FfiAccount::default();
|
||||
|
||||
let result = wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
&raw const out_private_account_id,
|
||||
&raw mut private_account,
|
||||
);
|
||||
assert_eq!(result, error::WalletFfiError::Success);
|
||||
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
let result = wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
&raw const out_private_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
assert_eq!(result, error::WalletFfiError::Success);
|
||||
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
|
||||
out_keys
|
||||
npk
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
nssa::AccountId::from(&private_account_keys.npk()),
|
||||
out_private_account_id.into()
|
||||
// After loading, creating a new key should yield a different NPK (state was persisted)
|
||||
let second_npk = unsafe {
|
||||
let wallet_ffi_handle = load_existing_ffi_wallet(home.path())?;
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
|
||||
let npk = out_keys.nullifier_public_key.data;
|
||||
wallet_ffi_free_private_account_keys(&raw mut out_keys);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
npk
|
||||
};
|
||||
|
||||
assert_ne!(first_npk, [0_u8; 32], "First NPK should be non-zero");
|
||||
assert_ne!(second_npk, [0_u8; 32], "Second NPK should be non-zero");
|
||||
assert_ne!(
|
||||
first_npk, second_npk,
|
||||
"Keys should differ after state was persisted"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@ -344,22 +335,22 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
|
||||
let password = "password_for_tests";
|
||||
|
||||
// Create the wallet FFI and track which account IDs were created as public/private
|
||||
let (wallet_ffi_handle, created_public_ids, created_private_ids) = unsafe {
|
||||
let (wallet_ffi_handle, created_public_ids) = unsafe {
|
||||
let handle = new_wallet_ffi_with_default_config(password)?;
|
||||
let mut public_ids: Vec<[u8; 32]> = Vec::new();
|
||||
let mut private_ids: Vec<[u8; 32]> = Vec::new();
|
||||
|
||||
// Create 5 public accounts and 5 private accounts, recording their IDs
|
||||
// Create 5 public accounts and 5 receiving keys
|
||||
for _ in 0..5 {
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
wallet_ffi_create_account_public(handle, &raw mut out_account_id);
|
||||
public_ids.push(out_account_id.data);
|
||||
|
||||
wallet_ffi_create_account_private(handle, &raw mut out_account_id);
|
||||
private_ids.push(out_account_id.data);
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_private_accounts_key(handle, &raw mut out_keys);
|
||||
wallet_ffi_free_private_account_keys(&raw mut out_keys);
|
||||
}
|
||||
|
||||
(handle, public_ids, private_ids)
|
||||
(handle, public_ids)
|
||||
};
|
||||
|
||||
// Get the account list with FFI method
|
||||
@ -382,31 +373,19 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
|
||||
.filter(|e| e.is_public)
|
||||
.map(|e| e.account_id.data)
|
||||
.collect();
|
||||
let listed_private_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
|
||||
.iter()
|
||||
.filter(|e| !e.is_public)
|
||||
.map(|e| e.account_id.data)
|
||||
.collect();
|
||||
|
||||
for id in &created_public_ids {
|
||||
assert!(
|
||||
listed_public_ids.contains(id),
|
||||
"Created public account not found in list with is_public=true"
|
||||
);
|
||||
}
|
||||
for id in &created_private_ids {
|
||||
assert!(
|
||||
listed_private_ids.contains(id),
|
||||
"Created private account not found in list with is_public=false"
|
||||
);
|
||||
}
|
||||
|
||||
// Total listed accounts must be at least the number we created
|
||||
// Total listed accounts must be at least the number of public accounts created
|
||||
// (receiving keys without synced accounts don't appear in the list)
|
||||
assert!(
|
||||
wallet_ffi_account_list.count >= created_public_ids.len() + created_private_ids.len(),
|
||||
"Listed account count ({}) is less than the number of created accounts ({})",
|
||||
wallet_ffi_account_list.count >= created_public_ids.len(),
|
||||
"Listed account count ({}) is less than the number of created public accounts ({})",
|
||||
wallet_ffi_account_list.count,
|
||||
created_public_ids.len() + created_private_ids.len()
|
||||
created_public_ids.len()
|
||||
);
|
||||
|
||||
unsafe {
|
||||
@ -710,25 +689,13 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
|
||||
// Create a new uninitialized public account
|
||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||
// Create a new private account
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
unsafe {
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
}
|
||||
|
||||
// Check its program owner is the default program id
|
||||
let account: Account = unsafe {
|
||||
let mut out_account = FfiAccount::default();
|
||||
wallet_ffi_get_account_private(
|
||||
wallet_ffi_handle,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_account,
|
||||
);
|
||||
(&out_account).try_into().unwrap()
|
||||
};
|
||||
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
|
||||
|
||||
// Call the init funciton
|
||||
// Call the init function
|
||||
let mut transfer_result = FfiTransferResult::default();
|
||||
unsafe {
|
||||
wallet_ffi_register_private_account(
|
||||
@ -832,24 +799,24 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
(out_account_id, out_keys)
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
|
||||
let account_id = nssa::AccountId::from((&out_keys.npk(), 0_u128));
|
||||
let to: FfiBytes32 = (&account_id).into();
|
||||
(to, out_keys)
|
||||
};
|
||||
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result = FfiTransferResult::default();
|
||||
unsafe {
|
||||
let to_identifier = FfiU128 {
|
||||
data: 0_u128.to_le_bytes(),
|
||||
};
|
||||
wallet_ffi_transfer_shielded(
|
||||
wallet_ffi_handle,
|
||||
&raw const from,
|
||||
&raw const to_keys,
|
||||
&raw const to_identifier,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
@ -966,25 +933,25 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
|
||||
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_account_id = FfiBytes32::default();
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
||||
wallet_ffi_get_private_account_keys(
|
||||
wallet_ffi_handle,
|
||||
&raw const out_account_id,
|
||||
&raw mut out_keys,
|
||||
);
|
||||
(out_account_id, out_keys)
|
||||
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
|
||||
let account_id = nssa::AccountId::from((&out_keys.npk(), 0_u128));
|
||||
let to: FfiBytes32 = (&account_id).into();
|
||||
(to, out_keys)
|
||||
};
|
||||
|
||||
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
||||
|
||||
let mut transfer_result = FfiTransferResult::default();
|
||||
unsafe {
|
||||
let to_identifier = FfiU128 {
|
||||
data: 0_u128.to_le_bytes(),
|
||||
};
|
||||
wallet_ffi_transfer_private(
|
||||
wallet_ffi_handle,
|
||||
&raw const from,
|
||||
&raw const to_keys,
|
||||
&raw const to_identifier,
|
||||
&raw const amount,
|
||||
&raw mut transfer_result,
|
||||
);
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
use k256::{Scalar, elliptic_curve::PrimeField as _};
|
||||
use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey};
|
||||
use nssa_core::{Identifier, NullifierPublicKey, encryption::ViewingPublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key_management::{
|
||||
KeyChain,
|
||||
key_tree::traits::KeyNode,
|
||||
key_tree::traits::KeyTreeNode,
|
||||
secret_holders::{PrivateKeyHolder, SecretSpendingKey},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ChildKeysPrivate {
|
||||
pub value: (KeyChain, nssa::Account),
|
||||
pub value: (KeyChain, Vec<(Identifier, 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 {
|
||||
impl ChildKeysPrivate {
|
||||
#[must_use]
|
||||
pub fn root(seed: [u8; 64]) -> Self {
|
||||
let hash_value = hmac_sha512::HMAC::mac(seed, b"LEE_master_priv");
|
||||
|
||||
let ssk = SecretSpendingKey(
|
||||
@ -46,14 +47,15 @@ impl KeyNode for ChildKeysPrivate {
|
||||
viewing_secret_key: vsk,
|
||||
},
|
||||
},
|
||||
nssa::Account::default(),
|
||||
vec![],
|
||||
),
|
||||
ccc,
|
||||
cci: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn nth_child(&self, cci: u32) -> Self {
|
||||
#[must_use]
|
||||
pub fn nth_child(&self, cci: u32) -> Self {
|
||||
#[expect(clippy::arithmetic_side_effects, reason = "TODO: fix later")]
|
||||
let parent_pt =
|
||||
Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into())
|
||||
@ -95,43 +97,27 @@ impl KeyNode for ChildKeysPrivate {
|
||||
viewing_secret_key: vsk,
|
||||
},
|
||||
},
|
||||
nssa::Account::default(),
|
||||
vec![],
|
||||
),
|
||||
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.nullifier_public_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::single_char_lifetime_names,
|
||||
reason = "TODO add meaningful name"
|
||||
)]
|
||||
impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) {
|
||||
fn from(value: &'a ChildKeysPrivate) -> Self {
|
||||
&value.value
|
||||
impl KeyTreeNode for ChildKeysPrivate {
|
||||
fn from_seed(seed: [u8; 64]) -> Self {
|
||||
Self::root(seed)
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::single_char_lifetime_names,
|
||||
reason = "TODO add meaningful name"
|
||||
)]
|
||||
impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) {
|
||||
fn from(value: &'a mut ChildKeysPrivate) -> Self {
|
||||
&mut value.value
|
||||
fn derive_child(&self, cci: u32) -> Self {
|
||||
self.nth_child(cci)
|
||||
}
|
||||
|
||||
fn account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
|
||||
self.value.1.iter().map(|(identifier, _)| {
|
||||
nssa::AccountId::from((&self.value.0.nullifier_public_key, *identifier))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use k256::elliptic_curve::{PrimeField as _, sec1::ToEncodedPoint as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key_management::key_tree::traits::KeyNode;
|
||||
use crate::key_management::key_tree::traits::KeyTreeNode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ChildKeysPublic {
|
||||
@ -13,32 +13,8 @@ pub struct ChildKeysPublic {
|
||||
}
|
||||
|
||||
impl ChildKeysPublic {
|
||||
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
|
||||
let mut hash_input = vec![];
|
||||
|
||||
if ((2_u32).pow(31)).cmp(&cci) == std::cmp::Ordering::Greater {
|
||||
// Non-harden.
|
||||
// BIP-032 compatibility requires 1-byte header from the public_key;
|
||||
// Not stored in `self.cpk.value()`.
|
||||
let sk = k256::SecretKey::from_bytes(self.csk.value().into())
|
||||
.expect("32 bytes, within curve order");
|
||||
let pk = sk.public_key();
|
||||
hash_input.extend_from_slice(pk.to_encoded_point(true).as_bytes());
|
||||
} else {
|
||||
// Harden.
|
||||
hash_input.extend_from_slice(&[0_u8]);
|
||||
hash_input.extend_from_slice(self.csk.value());
|
||||
}
|
||||
|
||||
#[expect(clippy::big_endian_bytes, reason = "BIP-032 uses big endian")]
|
||||
hash_input.extend_from_slice(&cci.to_be_bytes());
|
||||
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyNode for ChildKeysPublic {
|
||||
fn root(seed: [u8; 64]) -> Self {
|
||||
#[must_use]
|
||||
pub fn root(seed: [u8; 64]) -> Self {
|
||||
let hash_value = hmac_sha512::HMAC::mac(seed, "LEE_master_pub");
|
||||
|
||||
let csk = nssa::PrivateKey::try_new(
|
||||
@ -58,7 +34,8 @@ impl KeyNode for ChildKeysPublic {
|
||||
}
|
||||
}
|
||||
|
||||
fn nth_child(&self, cci: u32) -> Self {
|
||||
#[must_use]
|
||||
pub fn nth_child(&self, cci: u32) -> Self {
|
||||
let hash_value = self.compute_hash_value(cci);
|
||||
|
||||
let csk = nssa::PrivateKey::try_new({
|
||||
@ -90,17 +67,33 @@ impl KeyNode for ChildKeysPublic {
|
||||
}
|
||||
}
|
||||
|
||||
fn chain_code(&self) -> &[u8; 32] {
|
||||
&self.ccc
|
||||
}
|
||||
|
||||
fn child_index(&self) -> Option<u32> {
|
||||
self.cci
|
||||
}
|
||||
|
||||
fn account_id(&self) -> nssa::AccountId {
|
||||
#[must_use]
|
||||
pub fn account_id(&self) -> nssa::AccountId {
|
||||
nssa::AccountId::from(&self.cpk)
|
||||
}
|
||||
|
||||
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
|
||||
let mut hash_input = vec![];
|
||||
|
||||
if ((2_u32).pow(31)).cmp(&cci) == std::cmp::Ordering::Greater {
|
||||
// Non-harden.
|
||||
// BIP-032 compatibility requires 1-byte header from the public_key;
|
||||
// Not stored in `self.cpk.value()`.
|
||||
let sk = k256::SecretKey::from_bytes(self.csk.value().into())
|
||||
.expect("32 bytes, within curve order");
|
||||
let pk = sk.public_key();
|
||||
hash_input.extend_from_slice(pk.to_encoded_point(true).as_bytes());
|
||||
} else {
|
||||
// Harden.
|
||||
hash_input.extend_from_slice(&[0_u8]);
|
||||
hash_input.extend_from_slice(self.csk.value());
|
||||
}
|
||||
|
||||
#[expect(clippy::big_endian_bytes, reason = "BIP-032 uses big endian")]
|
||||
hash_input.extend_from_slice(&cci.to_be_bytes());
|
||||
|
||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
@ -113,6 +106,20 @@ impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyTreeNode for ChildKeysPublic {
|
||||
fn from_seed(seed: [u8; 64]) -> Self {
|
||||
Self::root(seed)
|
||||
}
|
||||
|
||||
fn derive_child(&self, cci: u32) -> Self {
|
||||
self.nth_child(cci)
|
||||
}
|
||||
|
||||
fn account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
|
||||
std::iter::once(self.account_id())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa::{PrivateKey, PublicKey};
|
||||
|
||||
@ -2,12 +2,13 @@ use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use nssa::{Account, AccountId};
|
||||
use nssa_core::Identifier;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key_management::{
|
||||
key_tree::{
|
||||
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
|
||||
traits::KeyNode,
|
||||
traits::KeyTreeNode,
|
||||
},
|
||||
secret_holders::SeedHolder,
|
||||
};
|
||||
@ -20,7 +21,7 @@ pub mod traits;
|
||||
pub const DEPTH_SOFT_CAP: u32 = 20;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct KeyTree<N: KeyNode> {
|
||||
pub struct KeyTree<N: KeyTreeNode> {
|
||||
pub key_map: BTreeMap<ChainIndex, N>,
|
||||
pub account_id_map: BTreeMap<nssa::AccountId, ChainIndex>,
|
||||
}
|
||||
@ -28,7 +29,7 @@ pub struct KeyTree<N: KeyNode> {
|
||||
pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
|
||||
pub type KeyTreePrivate = KeyTree<ChildKeysPrivate>;
|
||||
|
||||
impl<N: KeyNode> KeyTree<N> {
|
||||
impl<N: KeyTreeNode> KeyTree<N> {
|
||||
#[must_use]
|
||||
pub fn new(seed: &SeedHolder) -> Self {
|
||||
let seed_fit: [u8; 64] = seed
|
||||
@ -37,29 +38,62 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
.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 = BTreeMap::from_iter([(account_id, ChainIndex::root())]);
|
||||
let root_keys = N::from_seed(seed_fit);
|
||||
let account_id_map = root_keys
|
||||
.account_ids()
|
||||
.map(|id| (id, ChainIndex::root()))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
key_map,
|
||||
key_map: BTreeMap::from_iter([(ChainIndex::root(), root_keys)]),
|
||||
account_id_map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_root(root: N) -> Self {
|
||||
let account_id_map = BTreeMap::from_iter([(root.account_id(), ChainIndex::root())]);
|
||||
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]);
|
||||
let account_id_map = root
|
||||
.account_ids()
|
||||
.map(|id| (id, ChainIndex::root()))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
key_map,
|
||||
key_map: BTreeMap::from_iter([(ChainIndex::root(), root)]),
|
||||
account_id_map,
|
||||
}
|
||||
}
|
||||
|
||||
// ToDo: Add function to create a tree from list of nodes with consistency check.
|
||||
pub fn generate_new_node(&mut self, parent_cci: &ChainIndex) -> Option<ChainIndex> {
|
||||
let parent_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 = parent_keys.derive_child(next_child_id);
|
||||
let account_ids = child_keys.account_ids();
|
||||
|
||||
for account_id in account_ids {
|
||||
self.account_id_map.insert(account_id, next_cci.clone());
|
||||
}
|
||||
self.key_map.insert(next_cci.clone(), child_keys);
|
||||
|
||||
Some(next_cci)
|
||||
}
|
||||
|
||||
pub fn fill_node(&mut self, chain_index: &ChainIndex) -> Option<ChainIndex> {
|
||||
let parent_keys = self.key_map.get(&chain_index.parent()?)?;
|
||||
let child_id = *chain_index.chain().last()?;
|
||||
|
||||
let child_keys = parent_keys.derive_child(child_id);
|
||||
let account_ids = child_keys.account_ids();
|
||||
|
||||
for account_id in account_ids {
|
||||
self.account_id_map.insert(account_id, chain_index.clone());
|
||||
}
|
||||
self.key_map.insert(chain_index.clone(), child_keys);
|
||||
|
||||
Some(chain_index.clone())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
|
||||
@ -102,25 +136,6 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_node(
|
||||
&mut self,
|
||||
parent_cci: &ChainIndex,
|
||||
) -> Option<(nssa::AccountId, ChainIndex)> {
|
||||
let parent_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 = parent_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.clone());
|
||||
|
||||
Some((account_id, next_cci))
|
||||
}
|
||||
|
||||
fn find_next_slot_layered(&self) -> ChainIndex {
|
||||
let mut depth = 1;
|
||||
|
||||
@ -134,44 +149,10 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_node(&mut self, chain_index: &ChainIndex) -> Option<(nssa::AccountId, ChainIndex)> {
|
||||
let parent_keys = self.key_map.get(&chain_index.parent()?)?;
|
||||
let child_id = *chain_index.chain().last()?;
|
||||
|
||||
let child_keys = parent_keys.nth_child(child_id);
|
||||
let account_id = child_keys.account_id();
|
||||
|
||||
self.key_map.insert(chain_index.clone(), child_keys);
|
||||
self.account_id_map.insert(account_id, chain_index.clone());
|
||||
|
||||
Some((account_id, chain_index.clone()))
|
||||
}
|
||||
|
||||
pub fn generate_new_node_layered(&mut self) -> Option<(nssa::AccountId, ChainIndex)> {
|
||||
pub fn generate_new_node_layered(&mut self) -> Option<ChainIndex> {
|
||||
self.fill_node(&self.find_next_slot_layered())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
|
||||
let chain_id = self.account_id_map.get(&account_id)?;
|
||||
self.key_map.get(chain_id)
|
||||
}
|
||||
|
||||
pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> {
|
||||
let chain_id = self.account_id_map.get(&account_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);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, addr: nssa::AccountId) -> Option<N> {
|
||||
let chain_index = self.account_id_map.remove(&addr)?;
|
||||
self.key_map.remove(&chain_index)
|
||||
}
|
||||
|
||||
/// Populates tree with children.
|
||||
///
|
||||
/// For given `depth` adds children to a tree such that their `ChainIndex::depth(&self) <
|
||||
@ -194,37 +175,50 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyTree<ChildKeysPrivate> {
|
||||
/// Cleanup of non-initialized accounts in a private tree.
|
||||
///
|
||||
/// If account is default, removes them, stops at first non-default account.
|
||||
///
|
||||
/// Walks through tree in lairs of same depth using `ChainIndex::chain_ids_at_depth()`.
|
||||
///
|
||||
/// Chain must be parsed for accounts beforehand.
|
||||
///
|
||||
/// Slow, maintains tree consistency.
|
||||
pub fn cleanup_tree_remove_uninit_layered(&mut self, depth: u32) {
|
||||
let depth = usize::try_from(depth).expect("Depth is expected to fit in usize");
|
||||
'outer: for i in (1..depth).rev() {
|
||||
println!("Cleanup of tree at depth {i}");
|
||||
for id in ChainIndex::chain_ids_at_depth(i) {
|
||||
if let Some(node) = self.key_map.get(&id) {
|
||||
if node.value.1 == nssa::Account::default() {
|
||||
let addr = node.account_id();
|
||||
self.remove(addr);
|
||||
} else {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
|
||||
let chain_id = self.account_id_map.get(&account_id)?;
|
||||
self.key_map.get(chain_id)
|
||||
}
|
||||
|
||||
pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> {
|
||||
let chain_id = self.account_id_map.get(&account_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);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, addr: nssa::AccountId) -> Option<N> {
|
||||
let chain_index = self.account_id_map.remove(&addr)?;
|
||||
self.key_map.remove(&chain_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyTree<ChildKeysPublic> {
|
||||
/// Generate a new public key node, returning the account ID and chain index.
|
||||
pub fn generate_new_public_node(
|
||||
&mut self,
|
||||
parent_cci: &ChainIndex,
|
||||
) -> Option<(nssa::AccountId, ChainIndex)> {
|
||||
let cci = self.generate_new_node(parent_cci)?;
|
||||
let node = self.key_map.get(&cci)?;
|
||||
let account_id = node.account_ids().next()?;
|
||||
Some((account_id, cci))
|
||||
}
|
||||
|
||||
/// Generate a new public key node using layered placement, returning the account ID and chain
|
||||
/// index.
|
||||
pub fn generate_new_public_node_layered(&mut self) -> Option<(nssa::AccountId, ChainIndex)> {
|
||||
let cci = self.generate_new_node_layered()?;
|
||||
let node = self.key_map.get(&cci)?;
|
||||
let account_id = node.account_ids().next()?;
|
||||
Some((account_id, cci))
|
||||
}
|
||||
|
||||
/// Cleanup of non-initialized accounts in a public tree.
|
||||
///
|
||||
/// If account is default, removes them, stops at first non-default account.
|
||||
@ -259,6 +253,65 @@ impl KeyTree<ChildKeysPublic> {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyTree<ChildKeysPrivate> {
|
||||
pub fn create_private_accounts_key_node(
|
||||
&mut self,
|
||||
parent_cci: &ChainIndex,
|
||||
) -> Option<ChainIndex> {
|
||||
self.generate_new_node(parent_cci)
|
||||
}
|
||||
|
||||
pub fn create_private_accounts_key_node_layered(&mut self) -> Option<ChainIndex> {
|
||||
self.generate_new_node_layered()
|
||||
}
|
||||
|
||||
/// Register an additional identifier on an existing private key node, inserting the derived
|
||||
/// `AccountId` into `account_id_map`. Returns `None` if the node does not exist or the
|
||||
/// `AccountId` is already registered.
|
||||
pub fn register_identifier_on_node(
|
||||
&mut self,
|
||||
cci: &ChainIndex,
|
||||
identifier: Identifier,
|
||||
) -> Option<nssa::AccountId> {
|
||||
let node = self.key_map.get(cci)?;
|
||||
let account_id = nssa::AccountId::from((&node.value.0.nullifier_public_key, identifier));
|
||||
if self.account_id_map.contains_key(&account_id) {
|
||||
return None;
|
||||
}
|
||||
self.account_id_map.insert(account_id, cci.clone());
|
||||
Some(account_id)
|
||||
}
|
||||
|
||||
/// Cleanup of non-initialized accounts in a private tree.
|
||||
///
|
||||
/// If account has no synced entries, removes it, stops at first initialized account.
|
||||
///
|
||||
/// Walks through tree in layers of same depth using `ChainIndex::chain_ids_at_depth()`.
|
||||
///
|
||||
/// Chain must be parsed for accounts beforehand.
|
||||
///
|
||||
/// Slow, maintains tree consistency.
|
||||
pub fn cleanup_tree_remove_uninit_layered(&mut self, depth: u32) {
|
||||
let depth = usize::try_from(depth).expect("Depth is expected to fit in usize");
|
||||
'outer: for i in (1..depth).rev() {
|
||||
println!("Cleanup of tree at depth {i}");
|
||||
for id in ChainIndex::chain_ids_at_depth(i) {
|
||||
if let Some(node) = self.key_map.get(&id).cloned() {
|
||||
if node.value.1.is_empty() {
|
||||
let account_ids = node.account_ids();
|
||||
self.key_map.remove(&id);
|
||||
for addr in account_ids {
|
||||
self.account_id_map.remove(&addr);
|
||||
}
|
||||
} else {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![expect(clippy::shadow_unrelated, reason = "We don't care about this in tests")]
|
||||
@ -478,25 +531,59 @@ mod tests {
|
||||
.key_map
|
||||
.get_mut(&ChainIndex::from_str("/1").unwrap())
|
||||
.unwrap();
|
||||
acc.value.1.balance = 2;
|
||||
acc.value.1.push((
|
||||
0,
|
||||
nssa::Account {
|
||||
balance: 2,
|
||||
..nssa::Account::default()
|
||||
},
|
||||
));
|
||||
|
||||
let acc = tree
|
||||
.key_map
|
||||
.get_mut(&ChainIndex::from_str("/2").unwrap())
|
||||
.unwrap();
|
||||
acc.value.1.balance = 3;
|
||||
acc.value.1.push((
|
||||
0,
|
||||
nssa::Account {
|
||||
balance: 3,
|
||||
..nssa::Account::default()
|
||||
},
|
||||
));
|
||||
|
||||
let acc = tree
|
||||
.key_map
|
||||
.get_mut(&ChainIndex::from_str("/0/1").unwrap())
|
||||
.unwrap();
|
||||
acc.value.1.balance = 5;
|
||||
acc.value.1.push((
|
||||
0,
|
||||
nssa::Account {
|
||||
balance: 5,
|
||||
..nssa::Account::default()
|
||||
},
|
||||
));
|
||||
|
||||
let acc = tree
|
||||
.key_map
|
||||
.get_mut(&ChainIndex::from_str("/1/0").unwrap())
|
||||
.unwrap();
|
||||
acc.value.1.balance = 6;
|
||||
acc.value.1.push((
|
||||
0,
|
||||
nssa::Account {
|
||||
balance: 6,
|
||||
..nssa::Account::default()
|
||||
},
|
||||
));
|
||||
|
||||
// Update account_id_map for nodes that now have entries
|
||||
for chain_index_str in ["/1", "/2", "/0/1", "/1/0"] {
|
||||
let id = ChainIndex::from_str(chain_index_str).unwrap();
|
||||
if let Some(node) = tree.key_map.get(&id) {
|
||||
for account_id in node.account_ids() {
|
||||
tree.account_id_map.insert(account_id, id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree.cleanup_tree_remove_uninit_layered(10);
|
||||
|
||||
@ -518,15 +605,15 @@ mod tests {
|
||||
assert_eq!(key_set, key_set_res);
|
||||
|
||||
let acc = &tree.key_map[&ChainIndex::from_str("/1").unwrap()];
|
||||
assert_eq!(acc.value.1.balance, 2);
|
||||
assert_eq!(acc.value.1[0].1.balance, 2);
|
||||
|
||||
let acc = &tree.key_map[&ChainIndex::from_str("/2").unwrap()];
|
||||
assert_eq!(acc.value.1.balance, 3);
|
||||
assert_eq!(acc.value.1[0].1.balance, 3);
|
||||
|
||||
let acc = &tree.key_map[&ChainIndex::from_str("/0/1").unwrap()];
|
||||
assert_eq!(acc.value.1.balance, 5);
|
||||
assert_eq!(acc.value.1[0].1.balance, 5);
|
||||
|
||||
let acc = &tree.key_map[&ChainIndex::from_str("/1/0").unwrap()];
|
||||
assert_eq!(acc.value.1.balance, 6);
|
||||
assert_eq!(acc.value.1[0].1.balance, 6);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,8 @@
|
||||
/// 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.
|
||||
pub trait KeyTreeNode: Sized {
|
||||
#[must_use]
|
||||
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;
|
||||
fn from_seed(seed: [u8; 64]) -> Self;
|
||||
#[must_use]
|
||||
fn derive_child(&self, cci: u32) -> Self;
|
||||
#[must_use]
|
||||
fn account_ids(&self) -> impl Iterator<Item = nssa::AccountId>;
|
||||
}
|
||||
|
||||
@ -173,11 +173,12 @@ mod tests {
|
||||
// /0/0
|
||||
key_tree_private.generate_new_node_layered().unwrap();
|
||||
// /2
|
||||
let (second_child_id, _) = key_tree_private.generate_new_node_layered().unwrap();
|
||||
let second_chain_index = key_tree_private.generate_new_node_layered().unwrap();
|
||||
|
||||
key_tree_private
|
||||
.get_node(second_child_id)
|
||||
.unwrap()
|
||||
.key_map
|
||||
.get(&second_chain_index)
|
||||
.expect("Node was just inserted")
|
||||
.value
|
||||
.0
|
||||
.clone()
|
||||
|
||||
@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use k256::AffinePoint;
|
||||
use nssa::{Account, AccountId};
|
||||
use nssa_core::Identifier;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key_management::{
|
||||
@ -13,13 +15,18 @@ use crate::key_management::{
|
||||
|
||||
pub type PublicKey = AffinePoint;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UserPrivateAccountData {
|
||||
pub key_chain: KeyChain,
|
||||
pub accounts: Vec<(Identifier, Account)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NSSAUserData {
|
||||
/// Default public accounts.
|
||||
pub default_pub_account_signing_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
|
||||
/// Default private accounts.
|
||||
pub default_user_private_accounts:
|
||||
BTreeMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
|
||||
pub default_user_private_accounts: BTreeMap<AccountId, UserPrivateAccountData>,
|
||||
/// Tree of public keys.
|
||||
pub public_key_tree: KeyTreePublic,
|
||||
/// Tree of private keys.
|
||||
@ -54,13 +61,16 @@ impl NSSAUserData {
|
||||
}
|
||||
|
||||
fn valid_private_key_transaction_pairing_check(
|
||||
accounts_keys_map: &BTreeMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
|
||||
accounts_keys_map: &BTreeMap<AccountId, UserPrivateAccountData>,
|
||||
) -> bool {
|
||||
let mut check_res = true;
|
||||
for (account_id, (key, _)) in accounts_keys_map {
|
||||
let expected_account_id = nssa::AccountId::from(&key.nullifier_public_key);
|
||||
if expected_account_id != *account_id {
|
||||
println!("{expected_account_id}, {account_id}");
|
||||
for (account_id, entry) in accounts_keys_map {
|
||||
let any_match = entry.accounts.iter().any(|(identifier, _)| {
|
||||
nssa::AccountId::from((&entry.key_chain.nullifier_public_key, *identifier))
|
||||
== *account_id
|
||||
});
|
||||
if !any_match {
|
||||
println!("No matching entry found for account_id {account_id}");
|
||||
check_res = false;
|
||||
}
|
||||
}
|
||||
@ -69,10 +79,7 @@ impl NSSAUserData {
|
||||
|
||||
pub fn new_with_accounts(
|
||||
default_accounts_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
|
||||
default_accounts_key_chains: BTreeMap<
|
||||
nssa::AccountId,
|
||||
(KeyChain, nssa_core::account::Account),
|
||||
>,
|
||||
default_accounts_key_chains: BTreeMap<AccountId, UserPrivateAccountData>,
|
||||
public_key_tree: KeyTreePublic,
|
||||
private_key_tree: KeyTreePrivate,
|
||||
) -> Result<Self> {
|
||||
@ -108,11 +115,11 @@ impl NSSAUserData {
|
||||
match parent_cci {
|
||||
Some(parent_cci) => self
|
||||
.public_key_tree
|
||||
.generate_new_node(&parent_cci)
|
||||
.generate_new_public_node(&parent_cci)
|
||||
.expect("Parent must be present in a tree"),
|
||||
None => self
|
||||
.public_key_tree
|
||||
.generate_new_node_layered()
|
||||
.generate_new_public_node_layered()
|
||||
.expect("Search for new node slot failed"),
|
||||
}
|
||||
}
|
||||
@ -128,50 +135,61 @@ impl NSSAUserData {
|
||||
.or_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,
|
||||
parent_cci: Option<ChainIndex>,
|
||||
) -> (nssa::AccountId, ChainIndex) {
|
||||
/// Creates a new receiving key node and returns its `ChainIndex`.
|
||||
pub fn create_private_accounts_key(&mut self, parent_cci: Option<ChainIndex>) -> ChainIndex {
|
||||
match parent_cci {
|
||||
Some(parent_cci) => self
|
||||
.private_key_tree
|
||||
.generate_new_node(&parent_cci)
|
||||
.create_private_accounts_key_node(&parent_cci)
|
||||
.expect("Parent must be present in a tree"),
|
||||
None => self
|
||||
.private_key_tree
|
||||
.generate_new_node_layered()
|
||||
.create_private_accounts_key_node_layered()
|
||||
.expect("Search for new node slot failed"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the signing key for public transaction signatures.
|
||||
/// Registers an additional identifier on an existing private key node, deriving and recording
|
||||
/// the corresponding `AccountId`. Returns `None` if the node does not exist or the identifier
|
||||
/// is already registered.
|
||||
pub fn register_identifier_on_private_key_chain(
|
||||
&mut self,
|
||||
cci: &ChainIndex,
|
||||
identifier: Identifier,
|
||||
) -> Option<nssa::AccountId> {
|
||||
self.private_key_tree
|
||||
.register_identifier_on_node(cci, identifier)
|
||||
}
|
||||
|
||||
/// Returns the key chain and account data for the given private account ID.
|
||||
#[must_use]
|
||||
pub fn get_private_account(
|
||||
&self,
|
||||
account_id: nssa::AccountId,
|
||||
) -> Option<&(KeyChain, nssa_core::account::Account)> {
|
||||
self.default_user_private_accounts
|
||||
.get(&account_id)
|
||||
.or_else(|| self.private_key_tree.get_node(account_id).map(Into::into))
|
||||
}
|
||||
|
||||
/// Returns the signing key for public transaction signatures.
|
||||
pub fn get_private_account_mut(
|
||||
&mut self,
|
||||
account_id: &nssa::AccountId,
|
||||
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
|
||||
// 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)
|
||||
) -> Option<(KeyChain, nssa_core::account::Account, Identifier)> {
|
||||
// Check default accounts
|
||||
if let Some(entry) = self.default_user_private_accounts.get(&account_id) {
|
||||
for (identifier, account) in &entry.accounts {
|
||||
let expected_id =
|
||||
nssa::AccountId::from((&entry.key_chain.nullifier_public_key, *identifier));
|
||||
if expected_id == account_id {
|
||||
return Some((entry.key_chain.clone(), account.clone(), *identifier));
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
// Check tree
|
||||
if let Some(node) = self.private_key_tree.get_node(account_id) {
|
||||
let key_chain = &node.value.0;
|
||||
for (identifier, account) in &node.value.1 {
|
||||
let expected_id =
|
||||
nssa::AccountId::from((&key_chain.nullifier_public_key, *identifier));
|
||||
if expected_id == account_id {
|
||||
return Some((key_chain.clone(), account.clone(), *identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
|
||||
@ -249,16 +267,15 @@ mod tests {
|
||||
fn new_account() {
|
||||
let mut user_data = NSSAUserData::default();
|
||||
|
||||
let (account_id_private, _) = user_data
|
||||
.generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root()));
|
||||
|
||||
let is_key_chain_generated = user_data.get_private_account(account_id_private).is_some();
|
||||
let chain_index = user_data.create_private_accounts_key(Some(ChainIndex::root()));
|
||||
|
||||
let is_key_chain_generated = user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.contains_key(&chain_index);
|
||||
assert!(is_key_chain_generated);
|
||||
|
||||
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;
|
||||
let key_chain = &user_data.private_key_tree.key_map[&chain_index].value.0;
|
||||
println!("{key_chain:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
use crate::{NullifierPublicKey, NullifierSecretKey, program::ProgramId};
|
||||
use crate::{NullifierSecretKey, program::ProgramId};
|
||||
|
||||
pub mod data;
|
||||
|
||||
@ -26,9 +26,9 @@ impl Nonce {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn private_account_nonce_init(npk: &NullifierPublicKey) -> Self {
|
||||
pub fn private_account_nonce_init(account_id: &AccountId) -> Self {
|
||||
let mut bytes: [u8; 64] = [0_u8; 64];
|
||||
bytes[..32].copy_from_slice(&npk.0);
|
||||
bytes[..32].copy_from_slice(account_id.value());
|
||||
let result: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap();
|
||||
let result = result.first_chunk::<16>().unwrap();
|
||||
|
||||
@ -306,8 +306,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn initialize_private_nonce() {
|
||||
let npk = NullifierPublicKey([42; 32]);
|
||||
let nonce = Nonce::private_account_nonce_init(&npk);
|
||||
let account_id = AccountId::new([42; 32]);
|
||||
let nonce = Nonce::private_account_nonce_init(&account_id);
|
||||
let expected_nonce = Nonce(37_937_661_125_547_691_021_612_781_941_709_513_486);
|
||||
assert_eq!(nonce, expected_nonce);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Commitment, CommitmentSetDigest, MembershipProof, Nullifier, NullifierPublicKey,
|
||||
Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey,
|
||||
NullifierSecretKey, SharedSecretKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
encryption::Ciphertext,
|
||||
@ -19,8 +19,8 @@ pub struct PrivacyPreservingCircuitInput {
|
||||
/// - `2` - private account without authentication
|
||||
/// - `3` - private PDA account
|
||||
pub visibility_mask: Vec<u8>,
|
||||
/// Public keys of private accounts.
|
||||
pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,
|
||||
/// Public keys and identifiers of private accounts.
|
||||
pub private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>,
|
||||
/// Nullifier secret keys for authorized private accounts.
|
||||
pub private_account_nsks: Vec<NullifierSecretKey>,
|
||||
/// Membership proofs for private accounts. Can be [`None`] for uninitialized accounts.
|
||||
@ -57,7 +57,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
Commitment, Nullifier, NullifierPublicKey,
|
||||
Commitment, Nullifier,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
};
|
||||
|
||||
@ -94,12 +94,12 @@ mod tests {
|
||||
}],
|
||||
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],
|
||||
new_commitments: vec![Commitment::new(
|
||||
&NullifierPublicKey::from(&[1; 32]),
|
||||
&AccountId::new([1; 32]),
|
||||
&Account::default(),
|
||||
)],
|
||||
new_nullifiers: vec![(
|
||||
Nullifier::for_account_update(
|
||||
&Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()),
|
||||
&Commitment::new(&AccountId::new([2; 32]), &Account::default()),
|
||||
&[1; 32],
|
||||
),
|
||||
[0xab; 32],
|
||||
|
||||
@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{NullifierPublicKey, account::Account};
|
||||
use crate::account::{Account, AccountId};
|
||||
|
||||
/// A commitment to all zero data.
|
||||
/// ```python
|
||||
@ -49,16 +49,16 @@ impl std::fmt::Debug for Commitment {
|
||||
}
|
||||
|
||||
impl Commitment {
|
||||
/// Generates the commitment to a private account owned by user for npk:
|
||||
/// SHA256( `Comm_DS` || npk || `program_owner` || balance || nonce || SHA256(data)).
|
||||
/// Generates the commitment to a private account owned by user for `account_id`:
|
||||
/// SHA256( `Comm_DS` || `account_id` || `program_owner` || balance || nonce || SHA256(data)).
|
||||
#[must_use]
|
||||
pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self {
|
||||
pub fn new(account_id: &AccountId, account: &Account) -> Self {
|
||||
const COMMITMENT_PREFIX: &[u8; 32] =
|
||||
b"/LEE/v0.3/Commitment/\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(COMMITMENT_PREFIX);
|
||||
bytes.extend_from_slice(&npk.to_byte_array());
|
||||
bytes.extend_from_slice(account_id.value());
|
||||
let account_bytes_with_hashed_data = {
|
||||
let mut this = Vec::new();
|
||||
for word in &account.program_owner {
|
||||
@ -115,14 +115,15 @@ mod tests {
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
|
||||
use crate::{
|
||||
Commitment, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, NullifierPublicKey, account::Account,
|
||||
Commitment, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH,
|
||||
account::{Account, AccountId},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn nothing_up_my_sleeve_dummy_commitment() {
|
||||
let default_account = Account::default();
|
||||
let npk_null = NullifierPublicKey([0; 32]);
|
||||
let expected_dummy_commitment = Commitment::new(&npk_null, &default_account);
|
||||
let account_id_null = AccountId::new([0; 32]);
|
||||
let expected_dummy_commitment = Commitment::new(&account_id_null, &default_account);
|
||||
assert_eq!(DUMMY_COMMITMENT, expected_dummy_commitment);
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "host")]
|
||||
pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey};
|
||||
|
||||
use crate::{Commitment, account::Account};
|
||||
use crate::{Commitment, Identifier, account::Account};
|
||||
#[cfg(feature = "host")]
|
||||
pub mod shared_key_derivation;
|
||||
|
||||
@ -40,11 +40,14 @@ impl EncryptionScheme {
|
||||
#[must_use]
|
||||
pub fn encrypt(
|
||||
account: &Account,
|
||||
identifier: Identifier,
|
||||
shared_secret: &SharedSecretKey,
|
||||
commitment: &Commitment,
|
||||
output_index: u32,
|
||||
) -> Ciphertext {
|
||||
let mut buffer = account.to_bytes();
|
||||
// Plaintext: identifier (16 bytes, little-endian) || account bytes
|
||||
let mut buffer = identifier.to_le_bytes().to_vec();
|
||||
buffer.extend_from_slice(&account.to_bytes());
|
||||
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
|
||||
Ciphertext(buffer)
|
||||
}
|
||||
@ -86,12 +89,17 @@ impl EncryptionScheme {
|
||||
shared_secret: &SharedSecretKey,
|
||||
commitment: &Commitment,
|
||||
output_index: u32,
|
||||
) -> Option<Account> {
|
||||
) -> Option<(Identifier, Account)> {
|
||||
use std::io::Cursor;
|
||||
let mut buffer = ciphertext.0.clone();
|
||||
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
|
||||
|
||||
let mut cursor = Cursor::new(buffer.as_slice());
|
||||
if buffer.len() < 16 {
|
||||
return None;
|
||||
}
|
||||
let identifier = Identifier::from_le_bytes(buffer[..16].try_into().unwrap());
|
||||
|
||||
let mut cursor = Cursor::new(&buffer[16..]);
|
||||
Account::from_cursor(&mut cursor)
|
||||
.inspect_err(|err| {
|
||||
println!(
|
||||
@ -104,5 +112,6 @@ impl EncryptionScheme {
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
.map(|account| (identifier, account))
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ pub use commitment::{
|
||||
compute_digest_for_path,
|
||||
};
|
||||
pub use encryption::{EncryptionScheme, SharedSecretKey};
|
||||
pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey};
|
||||
pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey};
|
||||
|
||||
pub mod account;
|
||||
mod circuit_io;
|
||||
|
||||
@ -4,18 +4,24 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Commitment, account::AccountId};
|
||||
|
||||
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
|
||||
|
||||
pub type Identifier = u128;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Hash))]
|
||||
pub struct NullifierPublicKey(pub [u8; 32]);
|
||||
|
||||
impl From<&NullifierPublicKey> for AccountId {
|
||||
fn from(value: &NullifierPublicKey) -> Self {
|
||||
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] =
|
||||
b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
|
||||
impl From<(&NullifierPublicKey, Identifier)> for AccountId {
|
||||
fn from(value: (&NullifierPublicKey, Identifier)) -> Self {
|
||||
let (npk, identifier) = value;
|
||||
|
||||
let mut bytes = [0; 64];
|
||||
// 32 bytes prefix || 32 bytes npk || 16 bytes identifier
|
||||
let mut bytes = [0; 80];
|
||||
bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
|
||||
bytes[32..].copy_from_slice(&value.0);
|
||||
bytes[32..64].copy_from_slice(&npk.0);
|
||||
bytes[64..80].copy_from_slice(&identifier.to_le_bytes());
|
||||
|
||||
Self::new(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
@ -85,10 +91,10 @@ impl Nullifier {
|
||||
|
||||
/// Computes a nullifier for an account initialization.
|
||||
#[must_use]
|
||||
pub fn for_account_initialization(npk: &NullifierPublicKey) -> Self {
|
||||
pub fn for_account_initialization(account_id: &AccountId) -> Self {
|
||||
const INIT_PREFIX: &[u8; 32] = b"/LEE/v0.3/Nullifier/Initialize/\x00";
|
||||
let mut bytes = INIT_PREFIX.to_vec();
|
||||
bytes.extend_from_slice(&npk.to_byte_array());
|
||||
bytes.extend_from_slice(account_id.value());
|
||||
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
|
||||
}
|
||||
}
|
||||
@ -111,7 +117,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn constructor_for_account_initialization() {
|
||||
let npk = NullifierPublicKey([
|
||||
let account_id = AccountId::new([
|
||||
112, 188, 193, 129, 150, 55, 228, 67, 88, 168, 29, 151, 5, 92, 23, 190, 17, 162, 164,
|
||||
255, 29, 105, 42, 186, 43, 11, 157, 168, 132, 225, 17, 163,
|
||||
]);
|
||||
@ -119,7 +125,7 @@ mod tests {
|
||||
149, 59, 95, 181, 2, 194, 20, 143, 72, 233, 104, 243, 59, 70, 67, 243, 110, 77, 109,
|
||||
132, 139, 111, 51, 125, 128, 92, 107, 46, 252, 4, 20, 149,
|
||||
]);
|
||||
let nullifier = Nullifier::for_account_initialization(&npk);
|
||||
let nullifier = Nullifier::for_account_initialization(&account_id);
|
||||
assert_eq!(nullifier, expected_nullifier);
|
||||
}
|
||||
|
||||
@ -145,11 +151,46 @@ mod tests {
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let expected_account_id = AccountId::new([
|
||||
139, 72, 194, 222, 215, 187, 147, 56, 55, 35, 222, 205, 156, 12, 204, 227, 166, 44, 30,
|
||||
81, 186, 14, 167, 234, 28, 236, 32, 213, 125, 251, 193, 233,
|
||||
165, 52, 40, 32, 231, 171, 113, 10, 65, 241, 156, 72, 154, 207, 122, 192, 15, 46, 50,
|
||||
253, 105, 164, 89, 84, 40, 191, 182, 119, 64, 255, 67, 142,
|
||||
]);
|
||||
|
||||
let account_id = AccountId::from(&npk);
|
||||
let account_id = AccountId::from((&npk, 0));
|
||||
|
||||
assert_eq!(account_id, expected_account_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_id_from_nullifier_public_key_identifier_1() {
|
||||
let nsk = [
|
||||
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
|
||||
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let expected_account_id = AccountId::new([
|
||||
203, 201, 109, 245, 40, 54, 195, 12, 55, 33, 0, 86, 245, 65, 70, 156, 24, 249, 26, 95,
|
||||
56, 247, 99, 121, 165, 182, 234, 255, 19, 127, 191, 72,
|
||||
]);
|
||||
|
||||
let account_id = AccountId::from((&npk, 1));
|
||||
|
||||
assert_eq!(account_id, expected_account_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_id_from_nullifier_public_key_byte_asymmetric_identifier() {
|
||||
let identifier: u128 = 0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210;
|
||||
let nsk = [
|
||||
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
|
||||
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let expected_account_id = AccountId::new([
|
||||
178, 16, 226, 206, 217, 38, 38, 45, 155, 240, 226, 253, 168, 87, 146, 70, 72, 32, 174,
|
||||
19, 245, 25, 214, 162, 209, 135, 252, 82, 27, 2, 174, 196,
|
||||
]);
|
||||
|
||||
let account_id = AccountId::from((&npk, identifier));
|
||||
|
||||
assert_eq!(account_id, expected_account_id);
|
||||
}
|
||||
|
||||
@ -919,18 +919,6 @@ mod tests {
|
||||
assert_ne!(private_id, public_id);
|
||||
}
|
||||
|
||||
/// A private PDA address differs from a standard private account address at the same `npk`,
|
||||
/// because the private PDA formula includes `program_id` and `seed`.
|
||||
#[test]
|
||||
fn for_private_pda_differs_from_standard_private() {
|
||||
let program_id: ProgramId = [1; 8];
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let private_pda_id = AccountId::for_private_pda(&program_id, &seed, &npk);
|
||||
let standard_private_id = AccountId::from(&npk);
|
||||
assert_ne!(private_pda_id, standard_private_id);
|
||||
}
|
||||
|
||||
// ---- compute_public_authorized_pdas tests ----
|
||||
|
||||
/// `compute_public_authorized_pdas` returns the public PDA addresses for the caller's seeds.
|
||||
|
||||
@ -2,8 +2,8 @@ use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
|
||||
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey,
|
||||
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
account::AccountWithMetadata,
|
||||
program::{ChainedCall, InstructionData, ProgramId, ProgramOutput},
|
||||
};
|
||||
@ -68,7 +68,7 @@ pub fn execute_and_prove(
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
instruction_data: InstructionData,
|
||||
visibility_mask: Vec<u8>,
|
||||
private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,
|
||||
private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>,
|
||||
private_account_nsks: Vec<NullifierSecretKey>,
|
||||
private_account_membership_proofs: Vec<Option<MembershipProof>>,
|
||||
program_with_dependencies: &ProgramWithDependencies,
|
||||
@ -214,11 +214,8 @@ mod tests {
|
||||
AccountId::new([0; 32]),
|
||||
);
|
||||
|
||||
let recipient = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::from(&recipient_keys.npk()),
|
||||
);
|
||||
let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0));
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
|
||||
|
||||
let balance_to_move: u128 = 37;
|
||||
|
||||
@ -232,7 +229,7 @@ mod tests {
|
||||
let expected_recipient_post = Account {
|
||||
program_owner: program.id(),
|
||||
balance: balance_to_move,
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_keys.npk()),
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
|
||||
data: Data::default(),
|
||||
};
|
||||
|
||||
@ -245,7 +242,7 @@ mod tests {
|
||||
vec![sender, recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![0, 2],
|
||||
vec![(recipient_keys.npk(), shared_secret)],
|
||||
vec![(recipient_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
@ -262,7 +259,7 @@ mod tests {
|
||||
assert_eq!(output.new_nullifiers.len(), 1);
|
||||
assert_eq!(output.ciphertexts.len(), 1);
|
||||
|
||||
let recipient_post = EncryptionScheme::decrypt(
|
||||
let (_identifier, recipient_post) = EncryptionScheme::decrypt(
|
||||
&output.ciphertexts[0],
|
||||
&shared_secret,
|
||||
&output.new_commitments[0],
|
||||
@ -287,27 +284,24 @@ mod tests {
|
||||
data: Data::default(),
|
||||
},
|
||||
true,
|
||||
AccountId::from(&sender_keys.npk()),
|
||||
AccountId::from((&sender_keys.npk(), 0)),
|
||||
);
|
||||
let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account);
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let commitment_sender = Commitment::new(&sender_account_id, &sender_pre.account);
|
||||
|
||||
let recipient = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::from(&recipient_keys.npk()),
|
||||
);
|
||||
let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0));
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
|
||||
let balance_to_move: u128 = 37;
|
||||
|
||||
let mut commitment_set = CommitmentSet::with_capacity(2);
|
||||
commitment_set.extend(std::slice::from_ref(&commitment_sender));
|
||||
|
||||
let expected_new_nullifiers = vec![
|
||||
(
|
||||
Nullifier::for_account_update(&commitment_sender, &sender_keys.nsk),
|
||||
commitment_set.digest(),
|
||||
),
|
||||
(
|
||||
Nullifier::for_account_initialization(&recipient_keys.npk()),
|
||||
Nullifier::for_account_initialization(&recipient_account_id),
|
||||
DUMMY_COMMITMENT_HASH,
|
||||
),
|
||||
];
|
||||
@ -323,12 +317,12 @@ mod tests {
|
||||
let expected_private_account_2 = Account {
|
||||
program_owner: program.id(),
|
||||
balance: balance_to_move,
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_keys.npk()),
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
|
||||
..Default::default()
|
||||
};
|
||||
let expected_new_commitments = vec![
|
||||
Commitment::new(&sender_keys.npk(), &expected_private_account_1),
|
||||
Commitment::new(&recipient_keys.npk(), &expected_private_account_2),
|
||||
Commitment::new(&sender_account_id, &expected_private_account_1),
|
||||
Commitment::new(&recipient_account_id, &expected_private_account_2),
|
||||
];
|
||||
|
||||
let esk_1 = [3; 32];
|
||||
@ -342,8 +336,8 @@ mod tests {
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![
|
||||
(sender_keys.npk(), shared_secret_1),
|
||||
(recipient_keys.npk(), shared_secret_2),
|
||||
(sender_keys.npk(), 0, shared_secret_1),
|
||||
(recipient_keys.npk(), 0, shared_secret_2),
|
||||
],
|
||||
vec![sender_keys.nsk],
|
||||
vec![commitment_set.get_proof_for(&commitment_sender), None],
|
||||
@ -358,7 +352,7 @@ mod tests {
|
||||
assert_eq!(output.new_nullifiers, expected_new_nullifiers);
|
||||
assert_eq!(output.ciphertexts.len(), 2);
|
||||
|
||||
let sender_post = EncryptionScheme::decrypt(
|
||||
let (_identifier, sender_post) = EncryptionScheme::decrypt(
|
||||
&output.ciphertexts[0],
|
||||
&shared_secret_1,
|
||||
&expected_new_commitments[0],
|
||||
@ -367,7 +361,7 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(sender_post, expected_private_account_1);
|
||||
|
||||
let recipient_post = EncryptionScheme::decrypt(
|
||||
let (_identifier, recipient_post) = EncryptionScheme::decrypt(
|
||||
&output.ciphertexts[1],
|
||||
&shared_secret_2,
|
||||
&expected_new_commitments[1],
|
||||
@ -383,7 +377,7 @@ mod tests {
|
||||
let pre = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
AccountId::from(&account_keys.npk()),
|
||||
AccountId::from((&account_keys.npk(), 0)),
|
||||
);
|
||||
|
||||
let validity_window_chain_caller = Program::validity_window_chain_caller();
|
||||
@ -410,7 +404,7 @@ mod tests {
|
||||
vec![pre],
|
||||
instruction,
|
||||
vec![2],
|
||||
vec![(account_keys.npk(), shared_secret)],
|
||||
vec![(account_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
@ -459,7 +453,7 @@ mod tests {
|
||||
vec![pda_pre, sender_pre],
|
||||
instruction,
|
||||
vec![3, 0],
|
||||
vec![(npk, shared_secret_pda)],
|
||||
vec![(npk, 0, shared_secret_pda)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
@ -505,7 +499,7 @@ mod tests {
|
||||
vec![pda_pre, bob_pre],
|
||||
instruction,
|
||||
vec![3, 0],
|
||||
vec![(npk, shared_secret_pda)],
|
||||
vec![(npk, 0, shared_secret_pda)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
|
||||
@ -154,9 +154,11 @@ pub mod tests {
|
||||
|
||||
let encrypted_private_post_states = Vec::new();
|
||||
|
||||
let new_commitments = vec![Commitment::new(&npk2, &account2)];
|
||||
let account_id2 = nssa_core::account::AccountId::from((&npk2, 0));
|
||||
let new_commitments = vec![Commitment::new(&account_id2, &account2)];
|
||||
|
||||
let old_commitment = Commitment::new(&npk1, &account1);
|
||||
let account_id1 = nssa_core::account::AccountId::from((&npk1, 0));
|
||||
let old_commitment = Commitment::new(&account_id1, &account1);
|
||||
let new_nullifiers = vec![(
|
||||
Nullifier::for_account_update(&old_commitment, &nsk1),
|
||||
[0; 32],
|
||||
@ -179,11 +181,12 @@ pub mod tests {
|
||||
let npk = NullifierPublicKey::from(&[1; 32]);
|
||||
let vpk = ViewingPublicKey::from_scalar([2; 32]);
|
||||
let account = Account::default();
|
||||
let commitment = Commitment::new(&npk, &account);
|
||||
let account_id = nssa_core::account::AccountId::from((&npk, 0));
|
||||
let commitment = Commitment::new(&account_id, &account);
|
||||
let esk = [3; 32];
|
||||
let shared_secret = SharedSecretKey::new(&esk, &vpk);
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2);
|
||||
let ciphertext = EncryptionScheme::encrypt(&account, 0, &shared_secret, &commitment, 2);
|
||||
let encrypted_account_data =
|
||||
EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone());
|
||||
|
||||
|
||||
@ -459,7 +459,8 @@ pub mod tests {
|
||||
|
||||
#[must_use]
|
||||
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
|
||||
let commitment = Commitment::new(&keys.npk(), account);
|
||||
let account_id = AccountId::from((&keys.npk(), 0));
|
||||
let commitment = Commitment::new(&account_id, account);
|
||||
self.private_state.0.extend(&[commitment]);
|
||||
self
|
||||
}
|
||||
@ -617,13 +618,13 @@ pub mod tests {
|
||||
..Account::default()
|
||||
};
|
||||
|
||||
let npk1 = keys1.npk();
|
||||
let npk2 = keys2.npk();
|
||||
let account_id1 = AccountId::from((&keys1.npk(), 0));
|
||||
let account_id2 = AccountId::from((&keys2.npk(), 0));
|
||||
|
||||
let init_commitment1 = Commitment::new(&npk1, &account);
|
||||
let init_commitment2 = Commitment::new(&npk2, &account);
|
||||
let init_nullifier1 = Nullifier::for_account_initialization(&npk1);
|
||||
let init_nullifier2 = Nullifier::for_account_initialization(&npk2);
|
||||
let init_commitment1 = Commitment::new(&account_id1, &account);
|
||||
let init_commitment2 = Commitment::new(&account_id2, &account);
|
||||
let init_nullifier1 = Nullifier::for_account_initialization(&account_id1);
|
||||
let init_nullifier2 = Nullifier::for_account_initialization(&account_id2);
|
||||
|
||||
let initial_private_accounts = vec![
|
||||
(init_commitment1, init_nullifier1),
|
||||
@ -1283,7 +1284,8 @@ pub mod tests {
|
||||
|
||||
let sender_nonce = sender.account.nonce;
|
||||
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
let recipient =
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let esk = [3; 32];
|
||||
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.vpk());
|
||||
@ -1293,7 +1295,7 @@ pub mod tests {
|
||||
vec![sender, recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![0, 2],
|
||||
vec![(recipient_keys.npk(), shared_secret)],
|
||||
vec![(recipient_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
@ -1320,11 +1322,15 @@ pub mod tests {
|
||||
state: &V03State,
|
||||
) -> PrivacyPreservingTransaction {
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
|
||||
let sender_pre =
|
||||
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let sender_commitment = Commitment::new(&sender_account_id, sender_private_account);
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
sender_private_account.clone(),
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let recipient_pre =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let esk_1 = [3; 32];
|
||||
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.vpk());
|
||||
@ -1339,8 +1345,8 @@ pub mod tests {
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![
|
||||
(sender_keys.npk(), shared_secret_1),
|
||||
(recipient_keys.npk(), shared_secret_2),
|
||||
(sender_keys.npk(), 0, shared_secret_1),
|
||||
(recipient_keys.npk(), 0, shared_secret_2),
|
||||
],
|
||||
vec![sender_keys.nsk],
|
||||
vec![state.get_proof_for_commitment(&sender_commitment), None],
|
||||
@ -1372,9 +1378,13 @@ pub mod tests {
|
||||
state: &V03State,
|
||||
) -> PrivacyPreservingTransaction {
|
||||
let program = Program::authenticated_transfer_program();
|
||||
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
|
||||
let sender_pre =
|
||||
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let sender_commitment = Commitment::new(&sender_account_id, sender_private_account);
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
sender_private_account.clone(),
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let recipient_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(*recipient_account_id),
|
||||
false,
|
||||
@ -1389,7 +1399,7 @@ pub mod tests {
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![1, 0],
|
||||
vec![(sender_keys.npk(), shared_secret)],
|
||||
vec![(sender_keys.npk(), 0, shared_secret)],
|
||||
vec![sender_keys.nsk],
|
||||
vec![state.get_proof_for_commitment(&sender_commitment)],
|
||||
&program.into(),
|
||||
@ -1476,8 +1486,10 @@ pub mod tests {
|
||||
&state,
|
||||
);
|
||||
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0));
|
||||
let expected_new_commitment_1 = Commitment::new(
|
||||
&sender_keys.npk(),
|
||||
&sender_account_id,
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
|
||||
@ -1486,15 +1498,15 @@ pub mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
|
||||
let sender_pre_commitment = Commitment::new(&sender_account_id, &sender_private_account);
|
||||
let expected_new_nullifier =
|
||||
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
|
||||
|
||||
let expected_new_commitment_2 = Commitment::new(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_account_id,
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_keys.npk()),
|
||||
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
|
||||
balance: balance_to_move,
|
||||
..Account::default()
|
||||
},
|
||||
@ -1553,8 +1565,9 @@ pub mod tests {
|
||||
&state,
|
||||
);
|
||||
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let expected_new_commitment = Commitment::new(
|
||||
&sender_keys.npk(),
|
||||
&sender_account_id,
|
||||
&Account {
|
||||
program_owner: Program::authenticated_transfer_program().id(),
|
||||
nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
|
||||
@ -1563,7 +1576,7 @@ pub mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
|
||||
let sender_pre_commitment = Commitment::new(&sender_account_id, &sender_private_account);
|
||||
let expected_new_nullifier =
|
||||
Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk);
|
||||
|
||||
@ -1895,10 +1908,10 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let result = execute_and_prove(
|
||||
vec![private_account_1, private_account_2],
|
||||
@ -1907,10 +1920,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -1933,7 +1948,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||
@ -1941,6 +1956,7 @@ pub mod tests {
|
||||
// Setting only one key for an execution with two private accounts.
|
||||
let private_account_keys = [(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
)];
|
||||
let result = execute_and_prove(
|
||||
@ -1968,10 +1984,10 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
// Setting no second commitment proof.
|
||||
let private_account_membership_proofs = [Some((0, vec![]))];
|
||||
@ -1982,10 +1998,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2009,10 +2027,10 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
// Setting no auth key for an execution with one non default private accounts.
|
||||
let private_account_nsks = [];
|
||||
@ -2023,10 +2041,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2050,20 +2070,22 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let private_account_keys = [
|
||||
// First private account is the sender
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
// Second private account is the recipient
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
];
|
||||
@ -2098,7 +2120,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -2107,7 +2129,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
&recipient_keys.npk(),
|
||||
(&recipient_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let result = execute_and_prove(
|
||||
@ -2117,10 +2139,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2144,7 +2168,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -2153,7 +2177,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
&recipient_keys.npk(),
|
||||
(&recipient_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let result = execute_and_prove(
|
||||
@ -2163,10 +2187,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2190,7 +2216,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -2199,7 +2225,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
&recipient_keys.npk(),
|
||||
(&recipient_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let result = execute_and_prove(
|
||||
@ -2209,10 +2235,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2236,7 +2264,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -2245,7 +2273,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
&recipient_keys.npk(),
|
||||
(&recipient_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let result = execute_and_prove(
|
||||
@ -2255,10 +2283,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2283,13 +2313,13 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
// This should be set to false in normal circumstances
|
||||
true,
|
||||
&recipient_keys.npk(),
|
||||
(&recipient_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let result = execute_and_prove(
|
||||
@ -2299,10 +2329,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2341,7 +2373,7 @@ pub mod tests {
|
||||
vec![public_account_1, private_pda_account],
|
||||
Program::serialize_instruction(10_u128).unwrap(),
|
||||
visibility_mask.to_vec(),
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -2370,7 +2402,7 @@ pub mod tests {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, u128::MAX, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -2408,7 +2440,7 @@ pub mod tests {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk_b, shared_secret)],
|
||||
vec![(npk_b, 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -2442,7 +2474,7 @@ pub mod tests {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction((seed, seed, callee_id)).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, u128::MAX, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
@ -2479,7 +2511,7 @@ pub mod tests {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program_with_deps,
|
||||
@ -2515,7 +2547,7 @@ pub mod tests {
|
||||
vec![pre_a, pre_b],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![3, 3],
|
||||
vec![(keys_a.npk(), shared_a), (keys_b.npk(), shared_b)],
|
||||
vec![(keys_a.npk(), 0, shared_a), (keys_b.npk(), 0, shared_b)],
|
||||
vec![],
|
||||
vec![None, None],
|
||||
&program.into(),
|
||||
@ -2559,7 +2591,7 @@ pub mod tests {
|
||||
vec![owned_pre_state],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![3],
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -2579,10 +2611,10 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let result = execute_and_prove(
|
||||
vec![private_account_1, private_account_2],
|
||||
@ -2591,10 +2623,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2618,24 +2652,27 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
// Setting three private account keys for a circuit execution with only two private
|
||||
// accounts.
|
||||
let private_account_keys = [
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[57; 32], &sender_keys.vpk()),
|
||||
),
|
||||
];
|
||||
@ -2664,10 +2701,10 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
let private_account_2 =
|
||||
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
// Setting two private account keys for a circuit execution with only one non default
|
||||
// private account (visibility mask equal to 1 means that auth keys are expected).
|
||||
@ -2681,10 +2718,12 @@ pub mod tests {
|
||||
vec![
|
||||
(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
|
||||
),
|
||||
(
|
||||
recipient_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
|
||||
),
|
||||
],
|
||||
@ -2763,7 +2802,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&sender_keys.npk(),
|
||||
(&sender_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let visibility_mask = [1, 1];
|
||||
@ -2775,8 +2814,8 @@ pub mod tests {
|
||||
Program::serialize_instruction(100_u128).unwrap(),
|
||||
visibility_mask.to_vec(),
|
||||
vec![
|
||||
(sender_keys.npk(), shared_secret),
|
||||
(sender_keys.npk(), shared_secret),
|
||||
(sender_keys.npk(), 0, shared_secret),
|
||||
(sender_keys.npk(), 0, shared_secret),
|
||||
],
|
||||
private_account_nsks.to_vec(),
|
||||
private_account_membership_proofs.to_vec(),
|
||||
@ -3090,14 +3129,16 @@ pub mod tests {
|
||||
balance: 100,
|
||||
..Account::default()
|
||||
};
|
||||
let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
|
||||
let sender_init_nullifier = Nullifier::for_account_initialization(&sender_keys.npk());
|
||||
let sender_account_id = AccountId::from((&sender_keys.npk(), 0));
|
||||
let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account);
|
||||
let sender_init_nullifier = Nullifier::for_account_initialization(&sender_account_id);
|
||||
let mut state = V03State::new_with_genesis_accounts(
|
||||
&[],
|
||||
vec![(sender_commitment.clone(), sender_init_nullifier)],
|
||||
0,
|
||||
);
|
||||
let sender_pre = AccountWithMetadata::new(sender_private_account, true, &sender_keys.npk());
|
||||
let sender_pre =
|
||||
AccountWithMetadata::new(sender_private_account, true, (&sender_keys.npk(), 0));
|
||||
let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||
let recipient_account_id =
|
||||
AccountId::from(&PublicKey::new_from_private_key(&recipient_private_key));
|
||||
@ -3111,7 +3152,7 @@ pub mod tests {
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(37_u128).unwrap(),
|
||||
vec![1, 0],
|
||||
vec![(sender_keys.npk(), shared_secret)],
|
||||
vec![(sender_keys.npk(), 0, shared_secret)],
|
||||
vec![sender_keys.nsk],
|
||||
vec![state.get_proof_for_commitment(&sender_commitment)],
|
||||
&program.into(),
|
||||
@ -3163,7 +3204,7 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&from_keys.npk(),
|
||||
(&from_keys.npk(), 0),
|
||||
);
|
||||
let to_account = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -3171,13 +3212,15 @@ pub mod tests {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&to_keys.npk(),
|
||||
(&to_keys.npk(), 0),
|
||||
);
|
||||
|
||||
let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account);
|
||||
let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account);
|
||||
let from_init_nullifier = Nullifier::for_account_initialization(&from_keys.npk());
|
||||
let to_init_nullifier = Nullifier::for_account_initialization(&to_keys.npk());
|
||||
let from_account_id = AccountId::from((&from_keys.npk(), 0));
|
||||
let to_account_id = AccountId::from((&to_keys.npk(), 0));
|
||||
let from_commitment = Commitment::new(&from_account_id, &from_account.account);
|
||||
let to_commitment = Commitment::new(&to_account_id, &to_account.account);
|
||||
let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id);
|
||||
let to_init_nullifier = Nullifier::for_account_initialization(&to_account_id);
|
||||
let mut state = V03State::new_with_genesis_accounts(
|
||||
&[],
|
||||
vec![
|
||||
@ -3216,21 +3259,21 @@ pub mod tests {
|
||||
nonce: from_new_nonce,
|
||||
..from_account.account.clone()
|
||||
};
|
||||
let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post);
|
||||
let from_expected_commitment = Commitment::new(&from_account_id, &from_expected_post);
|
||||
|
||||
let to_expected_post = Account {
|
||||
balance: u128::from(number_of_calls) * amount,
|
||||
nonce: to_new_nonce,
|
||||
..to_account.account.clone()
|
||||
};
|
||||
let to_expected_commitment = Commitment::new(&to_keys.npk(), &to_expected_post);
|
||||
let to_expected_commitment = Commitment::new(&to_account_id, &to_expected_post);
|
||||
|
||||
// Act
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![to_account, from_account],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![1, 1],
|
||||
vec![(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)],
|
||||
vec![(from_keys.npk(), 0, to_ss), (to_keys.npk(), 0, from_ss)],
|
||||
vec![from_keys.nsk, to_keys.nsk],
|
||||
vec![
|
||||
state.get_proof_for_commitment(&from_commitment),
|
||||
@ -3482,7 +3525,7 @@ pub mod tests {
|
||||
|
||||
// Create an authorized private account with default values (new account being initialized)
|
||||
let authorized_account =
|
||||
AccountWithMetadata::new(Account::default(), true, &private_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0));
|
||||
|
||||
let program = Program::authenticated_transfer_program();
|
||||
|
||||
@ -3499,7 +3542,7 @@ pub mod tests {
|
||||
vec![authorized_account],
|
||||
Program::serialize_instruction(balance).unwrap(),
|
||||
vec![1],
|
||||
vec![(private_keys.npk(), shared_secret)],
|
||||
vec![(private_keys.npk(), 0, shared_secret)],
|
||||
vec![private_keys.nsk],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -3521,7 +3564,8 @@ pub mod tests {
|
||||
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||
let account_id = AccountId::from((&private_keys.npk(), 0));
|
||||
let nullifier = Nullifier::for_account_initialization(&account_id);
|
||||
assert!(state.private_state.1.contains(&nullifier));
|
||||
}
|
||||
|
||||
@ -3535,7 +3579,7 @@ pub mod tests {
|
||||
// operate them without the corresponding private keys, so unauthorized private claiming
|
||||
// remains allowed.
|
||||
let unauthorized_account =
|
||||
AccountWithMetadata::new(Account::default(), false, &private_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), false, (&private_keys.npk(), 0));
|
||||
|
||||
let program = Program::claimer();
|
||||
let esk = [5; 32];
|
||||
@ -3546,7 +3590,7 @@ pub mod tests {
|
||||
vec![unauthorized_account],
|
||||
Program::serialize_instruction(0_u128).unwrap(),
|
||||
vec![2],
|
||||
vec![(private_keys.npk(), shared_secret)],
|
||||
vec![(private_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&program.into(),
|
||||
@ -3568,7 +3612,8 @@ pub mod tests {
|
||||
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||
.unwrap();
|
||||
|
||||
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||
let account_id = AccountId::from((&private_keys.npk(), 0));
|
||||
let nullifier = Nullifier::for_account_initialization(&account_id);
|
||||
assert!(state.private_state.1.contains(&nullifier));
|
||||
}
|
||||
|
||||
@ -3581,7 +3626,7 @@ pub mod tests {
|
||||
|
||||
// Step 1: Create a new private account with authorization
|
||||
let authorized_account =
|
||||
AccountWithMetadata::new(Account::default(), true, &private_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0));
|
||||
|
||||
let claimer_program = Program::claimer();
|
||||
|
||||
@ -3597,7 +3642,7 @@ pub mod tests {
|
||||
vec![authorized_account.clone()],
|
||||
Program::serialize_instruction(balance).unwrap(),
|
||||
vec![1],
|
||||
vec![(private_keys.npk(), shared_secret)],
|
||||
vec![(private_keys.npk(), 0, shared_secret)],
|
||||
vec![private_keys.nsk],
|
||||
vec![None],
|
||||
&claimer_program.into(),
|
||||
@ -3623,7 +3668,8 @@ pub mod tests {
|
||||
);
|
||||
|
||||
// Verify the account is now initialized (nullifier exists)
|
||||
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||
let account_id = AccountId::from((&private_keys.npk(), 0));
|
||||
let nullifier = Nullifier::for_account_initialization(&account_id);
|
||||
assert!(state.private_state.1.contains(&nullifier));
|
||||
|
||||
// Prepare new state of account
|
||||
@ -3642,7 +3688,7 @@ pub mod tests {
|
||||
vec![account_metadata],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![1],
|
||||
vec![(private_keys.npk(), shared_secret2)],
|
||||
vec![(private_keys.npk(), 0, shared_secret2)],
|
||||
vec![private_keys.nsk],
|
||||
vec![None],
|
||||
&noop_program.into(),
|
||||
@ -3710,7 +3756,7 @@ pub mod tests {
|
||||
let program = Program::changer_claimer();
|
||||
let sender_keys = test_private_account_keys_1();
|
||||
let private_account =
|
||||
AccountWithMetadata::new(Account::default(), true, &sender_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0));
|
||||
// Don't change data (None) and don't claim (false)
|
||||
let instruction: (Option<Vec<u8>>, bool) = (None, false);
|
||||
|
||||
@ -3720,6 +3766,7 @@ pub mod tests {
|
||||
vec![1],
|
||||
vec![(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[3; 32], &sender_keys.vpk()),
|
||||
)],
|
||||
vec![sender_keys.nsk],
|
||||
@ -3736,7 +3783,7 @@ pub mod tests {
|
||||
let program = Program::changer_claimer();
|
||||
let sender_keys = test_private_account_keys_1();
|
||||
let private_account =
|
||||
AccountWithMetadata::new(Account::default(), true, &sender_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0));
|
||||
// Change data but don't claim (false) - should fail
|
||||
let new_data = vec![1, 2, 3, 4, 5];
|
||||
let instruction: (Option<Vec<u8>>, bool) = (Some(new_data), false);
|
||||
@ -3747,6 +3794,7 @@ pub mod tests {
|
||||
vec![1],
|
||||
vec![(
|
||||
sender_keys.npk(),
|
||||
0,
|
||||
SharedSecretKey::new(&[3; 32], &sender_keys.vpk()),
|
||||
)],
|
||||
vec![sender_keys.nsk],
|
||||
@ -3776,11 +3824,12 @@ pub mod tests {
|
||||
sender_keys.account_id(),
|
||||
);
|
||||
let recipient_account =
|
||||
AccountWithMetadata::new(Account::default(), true, &recipient_keys.npk());
|
||||
AccountWithMetadata::new(Account::default(), true, (&recipient_keys.npk(), 0));
|
||||
|
||||
let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0));
|
||||
let recipient_commitment =
|
||||
Commitment::new(&recipient_keys.npk(), &recipient_account.account);
|
||||
let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_keys.npk());
|
||||
Commitment::new(&recipient_account_id, &recipient_account.account);
|
||||
let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id);
|
||||
let state = V03State::new_with_genesis_accounts(
|
||||
&[(sender_account.account_id, sender_account.account.balance)],
|
||||
vec![(recipient_commitment.clone(), recipient_init_nullifier)],
|
||||
@ -3803,7 +3852,7 @@ pub mod tests {
|
||||
vec![sender_account, recipient_account],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![0, 1],
|
||||
vec![(recipient_keys.npk(), recipient)],
|
||||
vec![(recipient_keys.npk(), 0, recipient)],
|
||||
vec![recipient_keys.nsk],
|
||||
vec![state.get_proof_for_commitment(&recipient_commitment)],
|
||||
&program_with_deps,
|
||||
@ -3938,7 +3987,7 @@ pub mod tests {
|
||||
let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap();
|
||||
let validity_window_program = Program::validity_window();
|
||||
let account_keys = test_private_account_keys_1();
|
||||
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
||||
let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0));
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs();
|
||||
let tx = {
|
||||
let esk = [3; 32];
|
||||
@ -3953,7 +4002,7 @@ pub mod tests {
|
||||
vec![pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![2],
|
||||
vec![(account_keys.npk(), shared_secret)],
|
||||
vec![(account_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&validity_window_program.into(),
|
||||
@ -4007,7 +4056,7 @@ pub mod tests {
|
||||
validity_window.try_into().unwrap();
|
||||
let validity_window_program = Program::validity_window();
|
||||
let account_keys = test_private_account_keys_1();
|
||||
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
||||
let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0));
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs();
|
||||
let tx = {
|
||||
let esk = [3; 32];
|
||||
@ -4022,7 +4071,7 @@ pub mod tests {
|
||||
vec![pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![2],
|
||||
vec![(account_keys.npk(), shared_secret)],
|
||||
vec![(account_keys.npk(), 0, shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&validity_window_program.into(),
|
||||
|
||||
@ -4,9 +4,9 @@ use std::{
|
||||
};
|
||||
|
||||
use nssa_core::{
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof,
|
||||
Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
|
||||
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Identifier,
|
||||
MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
|
||||
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
compute_digest_for_path,
|
||||
program::{
|
||||
@ -17,6 +17,8 @@ use nssa_core::{
|
||||
};
|
||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||
|
||||
const PRIVATE_PDA_FIXED_IDENTIFIER: u128 = u128::MAX;
|
||||
|
||||
/// State of the involved accounts before and after program execution.
|
||||
struct ExecutionState {
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
@ -54,7 +56,7 @@ impl ExecutionState {
|
||||
/// Validate program outputs and derive the overall execution state.
|
||||
pub fn derive_from_outputs(
|
||||
visibility_mask: &[u8],
|
||||
private_account_keys: &[(NullifierPublicKey, SharedSecretKey)],
|
||||
private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)],
|
||||
program_id: ProgramId,
|
||||
program_outputs: Vec<ProgramOutput>,
|
||||
) -> Self {
|
||||
@ -67,7 +69,7 @@ impl ExecutionState {
|
||||
let mut keys_iter = private_account_keys.iter();
|
||||
for (pos, &mask) in visibility_mask.iter().enumerate() {
|
||||
if matches!(mask, 1..=3) {
|
||||
let (npk, _) = keys_iter.next().unwrap_or_else(|| {
|
||||
let (npk, _, _) = keys_iter.next().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"private_account_keys shorter than visibility_mask demands: no key for masked position {pos} (mask {mask})"
|
||||
)
|
||||
@ -487,7 +489,7 @@ fn resolve_authorization_and_record_bindings(
|
||||
fn compute_circuit_output(
|
||||
execution_state: ExecutionState,
|
||||
visibility_mask: &[u8],
|
||||
private_account_keys: &[(NullifierPublicKey, SharedSecretKey)],
|
||||
private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)],
|
||||
private_account_nsks: &[NullifierSecretKey],
|
||||
private_account_membership_proofs: &[Option<MembershipProof>],
|
||||
) -> PrivacyPreservingCircuitOutput {
|
||||
@ -523,16 +525,18 @@ fn compute_circuit_output(
|
||||
output.public_post_states.push(post_state);
|
||||
}
|
||||
1 | 2 => {
|
||||
let Some((npk, shared_secret)) = private_keys_iter.next() else {
|
||||
let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else {
|
||||
panic!("Missing private account key");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
AccountId::from(npk),
|
||||
pre_state.account_id,
|
||||
"AccountId mismatch"
|
||||
assert_ne!(
|
||||
*identifier, PRIVATE_PDA_FIXED_IDENTIFIER,
|
||||
"Identifier must be different from {PRIVATE_PDA_FIXED_IDENTIFIER}. This is reserved for private PDA."
|
||||
);
|
||||
|
||||
let account_id = AccountId::from((npk, *identifier));
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
|
||||
let (new_nullifier, new_nonce) = if account_visibility_mask == 1 {
|
||||
// Private account with authentication
|
||||
|
||||
@ -560,7 +564,7 @@ fn compute_circuit_output(
|
||||
let new_nullifier = compute_nullifier_and_set_digest(
|
||||
membership_proof_opt.as_ref(),
|
||||
&pre_state.account,
|
||||
npk,
|
||||
&account_id,
|
||||
nsk,
|
||||
);
|
||||
|
||||
@ -590,9 +594,9 @@ fn compute_circuit_output(
|
||||
"Membership proof must be None for unauthorized accounts"
|
||||
);
|
||||
|
||||
let nullifier = Nullifier::for_account_initialization(npk);
|
||||
let nullifier = Nullifier::for_account_initialization(&account_id);
|
||||
|
||||
let new_nonce = Nonce::private_account_nonce_init(npk);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&account_id);
|
||||
|
||||
((nullifier, DUMMY_COMMITMENT_HASH), new_nonce)
|
||||
};
|
||||
@ -603,11 +607,12 @@ fn compute_circuit_output(
|
||||
post_with_updated_nonce.nonce = new_nonce;
|
||||
|
||||
// Compute commitment
|
||||
let commitment_post = Commitment::new(npk, &post_with_updated_nonce);
|
||||
let commitment_post = Commitment::new(&account_id, &post_with_updated_nonce);
|
||||
|
||||
// Encrypt and push post state
|
||||
let encrypted_account = EncryptionScheme::encrypt(
|
||||
&post_with_updated_nonce,
|
||||
*identifier,
|
||||
shared_secret,
|
||||
&commitment_post,
|
||||
output_index,
|
||||
@ -628,10 +633,15 @@ fn compute_circuit_output(
|
||||
// `private_pda_bound_positions` check) guarantees that every mask-3
|
||||
// position has been through at least one such binding, so this
|
||||
// branch can safely use the wallet npk without re-verifying.
|
||||
let Some((npk, shared_secret)) = private_keys_iter.next() else {
|
||||
let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else {
|
||||
panic!("Missing private account key");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
*identifier, PRIVATE_PDA_FIXED_IDENTIFIER,
|
||||
"Identifier for private PDAs must be {PRIVATE_PDA_FIXED_IDENTIFIER}."
|
||||
);
|
||||
|
||||
let (new_nullifier, new_nonce) = if pre_state.is_authorized {
|
||||
// Existing private PDA with authentication (like mask 1)
|
||||
let Some(nsk) = private_nsks_iter.next() else {
|
||||
@ -650,7 +660,7 @@ fn compute_circuit_output(
|
||||
let new_nullifier = compute_nullifier_and_set_digest(
|
||||
membership_proof_opt.as_ref(),
|
||||
&pre_state.account,
|
||||
npk,
|
||||
&pre_state.account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
|
||||
@ -677,8 +687,8 @@ fn compute_circuit_output(
|
||||
"Membership proof must be None for new accounts"
|
||||
);
|
||||
|
||||
let nullifier = Nullifier::for_account_initialization(npk);
|
||||
let new_nonce = Nonce::private_account_nonce_init(npk);
|
||||
let nullifier = Nullifier::for_account_initialization(&pre_state.account_id);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id);
|
||||
((nullifier, DUMMY_COMMITMENT_HASH), new_nonce)
|
||||
};
|
||||
output.new_nullifiers.push(new_nullifier);
|
||||
@ -686,10 +696,12 @@ fn compute_circuit_output(
|
||||
let mut post_with_updated_nonce = post_state;
|
||||
post_with_updated_nonce.nonce = new_nonce;
|
||||
|
||||
let commitment_post = Commitment::new(npk, &post_with_updated_nonce);
|
||||
let commitment_post =
|
||||
Commitment::new(&pre_state.account_id, &post_with_updated_nonce);
|
||||
|
||||
let encrypted_account = EncryptionScheme::encrypt(
|
||||
&post_with_updated_nonce,
|
||||
PRIVATE_PDA_FIXED_IDENTIFIER,
|
||||
shared_secret,
|
||||
&commitment_post,
|
||||
output_index,
|
||||
@ -726,7 +738,7 @@ fn compute_circuit_output(
|
||||
fn compute_nullifier_and_set_digest(
|
||||
membership_proof_opt: Option<&MembershipProof>,
|
||||
pre_account: &Account,
|
||||
npk: &NullifierPublicKey,
|
||||
account_id: &AccountId,
|
||||
nsk: &NullifierSecretKey,
|
||||
) -> (Nullifier, CommitmentSetDigest) {
|
||||
membership_proof_opt.as_ref().map_or_else(
|
||||
@ -738,12 +750,12 @@ fn compute_nullifier_and_set_digest(
|
||||
);
|
||||
|
||||
// Compute initialization nullifier
|
||||
let nullifier = Nullifier::for_account_initialization(npk);
|
||||
let nullifier = Nullifier::for_account_initialization(account_id);
|
||||
(nullifier, DUMMY_COMMITMENT_HASH)
|
||||
},
|
||||
|membership_proof| {
|
||||
// Compute commitment set digest associated with provided auth path
|
||||
let commitment_pre = Commitment::new(npk, pre_account);
|
||||
let commitment_pre = Commitment::new(account_id, pre_account);
|
||||
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
|
||||
|
||||
// Compute update nullifier
|
||||
|
||||
@ -110,6 +110,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
.iter()
|
||||
.map(|init_comm_data| {
|
||||
let npk = &init_comm_data.npk;
|
||||
let account_id = nssa::AccountId::from((npk, 0));
|
||||
|
||||
let mut acc = init_comm_data.account.clone();
|
||||
|
||||
@ -117,8 +118,8 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
nssa::program::Program::authenticated_transfer_program().id();
|
||||
|
||||
(
|
||||
nssa_core::Commitment::new(npk, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(npk),
|
||||
nssa_core::Commitment::new(&account_id, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(&account_id),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@ -1107,10 +1108,14 @@ mod tests {
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![AccountWithMetadata::new(Account::default(), true, &npk)],
|
||||
vec![AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
true,
|
||||
(&npk, 0),
|
||||
)],
|
||||
Program::serialize_instruction(0_u128).unwrap(),
|
||||
vec![1],
|
||||
vec![(npk, shared_secret)],
|
||||
vec![(npk, 0, shared_secret)],
|
||||
vec![nsk],
|
||||
vec![None],
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
|
||||
@ -95,9 +95,16 @@ pub struct PublicAccountPrivateInitialData {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PrivateAccountPrivateInitialData {
|
||||
pub account_id: nssa::AccountId,
|
||||
pub account: nssa_core::account::Account,
|
||||
pub key_chain: KeyChain,
|
||||
pub identifier: nssa_core::Identifier,
|
||||
}
|
||||
|
||||
impl PrivateAccountPrivateInitialData {
|
||||
#[must_use]
|
||||
pub fn account_id(&self) -> nssa::AccountId {
|
||||
nssa::AccountId::from((&self.key_chain.nullifier_public_key, self.identifier))
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@ -142,7 +149,6 @@ pub fn initial_priv_accounts_private_keys() -> Vec<PrivateAccountPrivateInitialD
|
||||
|
||||
vec![
|
||||
PrivateAccountPrivateInitialData {
|
||||
account_id: AccountId::from(&key_chain_1.nullifier_public_key),
|
||||
account: Account {
|
||||
program_owner: DEFAULT_PROGRAM_OWNER,
|
||||
balance: PRIV_ACC_A_INITIAL_BALANCE,
|
||||
@ -150,9 +156,9 @@ pub fn initial_priv_accounts_private_keys() -> Vec<PrivateAccountPrivateInitialD
|
||||
nonce: 0.into(),
|
||||
},
|
||||
key_chain: key_chain_1,
|
||||
identifier: 0,
|
||||
},
|
||||
PrivateAccountPrivateInitialData {
|
||||
account_id: AccountId::from(&key_chain_2.nullifier_public_key),
|
||||
account: Account {
|
||||
program_owner: DEFAULT_PROGRAM_OWNER,
|
||||
balance: PRIV_ACC_B_INITIAL_BALANCE,
|
||||
@ -160,6 +166,7 @@ pub fn initial_priv_accounts_private_keys() -> Vec<PrivateAccountPrivateInitialD
|
||||
nonce: 0.into(),
|
||||
},
|
||||
key_chain: key_chain_2,
|
||||
identifier: 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -201,14 +208,15 @@ pub fn initial_state() -> V03State {
|
||||
.iter()
|
||||
.map(|init_comm_data| {
|
||||
let npk = &init_comm_data.npk;
|
||||
let account_id = nssa::AccountId::from((npk, 0));
|
||||
|
||||
let mut acc = init_comm_data.account.clone();
|
||||
|
||||
acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
|
||||
|
||||
(
|
||||
nssa_core::Commitment::new(npk, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(npk),
|
||||
nssa_core::Commitment::new(&account_id, &acc),
|
||||
nssa_core::Nullifier::for_account_initialization(&account_id),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@ -239,8 +247,8 @@ mod tests {
|
||||
const PUB_ACC_A_TEXT_ADDR: &str = "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV";
|
||||
const PUB_ACC_B_TEXT_ADDR: &str = "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo";
|
||||
|
||||
const PRIV_ACC_A_TEXT_ADDR: &str = "5ya25h4Xc9GAmrGB2WrTEnEWtQKJwRwQx3Xfo2tucNcE";
|
||||
const PRIV_ACC_B_TEXT_ADDR: &str = "E8HwiTyQe4H9HK7icTvn95HQMnzx49mP9A2ddtMLpNaN";
|
||||
const PRIV_ACC_A_TEXT_ADDR: &str = "4eGX3M3rgjHsme8n3sSp89af8JRZtYVTesbJjLqaX1VQ";
|
||||
const PRIV_ACC_B_TEXT_ADDR: &str = "3m6HQmCgmAvsxZtxAHPqqEqoBG4335fCG8TzxigyW7rE";
|
||||
|
||||
#[test]
|
||||
fn pub_state_consistency() {
|
||||
@ -354,11 +362,11 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
init_private_accs_keys[0].account_id.to_string(),
|
||||
init_private_accs_keys[0].account_id().to_string(),
|
||||
PRIV_ACC_A_TEXT_ADDR
|
||||
);
|
||||
assert_eq!(
|
||||
init_private_accs_keys[1].account_id.to_string(),
|
||||
init_private_accs_keys[1].account_id().to_string(),
|
||||
PRIV_ACC_B_TEXT_ADDR
|
||||
);
|
||||
|
||||
|
||||
@ -7,7 +7,10 @@ use nssa::AccountId;
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::{FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, WalletHandle},
|
||||
types::{
|
||||
FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiPrivateAccountKeys,
|
||||
WalletHandle,
|
||||
},
|
||||
wallet::get_wallet,
|
||||
};
|
||||
|
||||
@ -59,10 +62,18 @@ pub unsafe extern "C" fn wallet_ffi_create_account_public(
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Create a new private account.
|
||||
/// Create a new private account, storing a default account entry in local storage.
|
||||
///
|
||||
/// Private accounts use privacy-preserving transactions with nullifiers
|
||||
/// and commitments.
|
||||
/// This is the private-account equivalent of `wallet_ffi_create_account_public`.
|
||||
/// It generates a key node, assigns a random identifier, and inserts a default
|
||||
/// account record so the account can immediately be used with
|
||||
/// `wallet_ffi_register_private_account`.
|
||||
///
|
||||
/// The identifier is chosen at random and is not encoded in the mnemonic seed.
|
||||
/// Once the account is initialized, the identifier is embedded in the encrypted
|
||||
/// transaction payload and can be recovered by running `sync-private` from the
|
||||
/// same mnemonic. An account that was created locally but has never been initialized
|
||||
/// cannot be recovered from the seed alone.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
@ -107,6 +118,78 @@ pub unsafe extern "C" fn wallet_ffi_create_account_private(
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Create a new private key node.
|
||||
///
|
||||
/// Returns the nullifier public key (npk) and viewing public key (vpk) to share with
|
||||
/// senders. Account IDs are discovered later via sync when senders initialize accounts
|
||||
/// under this key.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `out_keys`: Output pointer for the key data (npk + vpk)
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful creation
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Memory
|
||||
/// The keys structure must be freed with `wallet_ffi_free_private_account_keys()`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
/// - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_create_private_accounts_key(
|
||||
handle: *mut WalletHandle,
|
||||
out_keys: *mut FfiPrivateAccountKeys,
|
||||
) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if out_keys.is_null() {
|
||||
print_error("Null output pointer for keys");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let mut wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let chain_index = wallet.create_private_accounts_key(None);
|
||||
|
||||
let node = wallet
|
||||
.storage()
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("Node was just inserted");
|
||||
|
||||
let key_chain = &node.value.0;
|
||||
let npk_bytes = key_chain.nullifier_public_key.0;
|
||||
let vpk_bytes = key_chain.viewing_public_key.to_bytes();
|
||||
let vpk_len = vpk_bytes.len();
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
reason = "We need to convert the boxed slice into a raw pointer for FFI"
|
||||
)]
|
||||
let vpk_ptr = Box::into_raw(vpk_bytes.to_vec().into_boxed_slice()) as *const u8;
|
||||
|
||||
unsafe {
|
||||
(*out_keys).nullifier_public_key.data = npk_bytes;
|
||||
(*out_keys).viewing_public_key = vpk_ptr;
|
||||
(*out_keys).viewing_public_key_len = vpk_len;
|
||||
}
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// List all accounts in the wallet.
|
||||
///
|
||||
/// Returns both public and private accounts managed by this wallet.
|
||||
|
||||
@ -116,7 +116,8 @@ pub unsafe extern "C" fn wallet_ffi_get_private_account_keys(
|
||||
|
||||
let account_id = AccountId::new(unsafe { (*account_id).data });
|
||||
|
||||
let Some((key_chain, _account)) = wallet.storage().user_data.get_private_account(account_id)
|
||||
let Some((key_chain, _account, _identifier)) =
|
||||
wallet.storage().user_data.get_private_account(account_id)
|
||||
else {
|
||||
print_error("Private account not found in wallet");
|
||||
return WalletFfiError::AccountNotFound;
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
map_execution_error,
|
||||
types::{FfiBytes32, FfiTransferResult, WalletHandle},
|
||||
types::{FfiBytes32, FfiTransferResult, FfiU128, WalletHandle},
|
||||
wallet::get_wallet,
|
||||
FfiPrivateAccountKeys,
|
||||
};
|
||||
@ -102,6 +102,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_public(
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `from`: Source account ID (must be owned by this wallet)
|
||||
/// - `to_keys`: Destination account keys
|
||||
/// - `to_identifier`: Identifier for the recipient's private account
|
||||
/// - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||
/// - `out_result`: Output pointer for transfer result
|
||||
///
|
||||
@ -125,6 +126,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
||||
handle: *mut WalletHandle,
|
||||
from: *const FfiBytes32,
|
||||
to_keys: *const FfiPrivateAccountKeys,
|
||||
to_identifier: *const FfiU128,
|
||||
amount: *const [u8; 16],
|
||||
out_result: *mut FfiTransferResult,
|
||||
) -> WalletFfiError {
|
||||
@ -133,7 +135,12 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if from.is_null() || to_keys.is_null() || amount.is_null() || out_result.is_null() {
|
||||
if from.is_null()
|
||||
|| to_keys.is_null()
|
||||
|| to_identifier.is_null()
|
||||
|| amount.is_null()
|
||||
|| out_result.is_null()
|
||||
{
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
@ -155,13 +162,18 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
||||
return e;
|
||||
}
|
||||
};
|
||||
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(
|
||||
transfer.send_shielded_transfer_to_outer_account(from_id, to_npk, to_vpk, amount),
|
||||
) {
|
||||
match block_on(transfer.send_shielded_transfer_to_outer_account(
|
||||
from_id,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
)) {
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);
|
||||
@ -271,6 +283,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_deshielded(
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `from`: Source account ID (must be owned by this wallet)
|
||||
/// - `to_keys`: Destination account keys
|
||||
/// - `to_identifier`: Identifier for the recipient's private account
|
||||
/// - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||
/// - `out_result`: Output pointer for transfer result
|
||||
///
|
||||
@ -294,6 +307,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private(
|
||||
handle: *mut WalletHandle,
|
||||
from: *const FfiBytes32,
|
||||
to_keys: *const FfiPrivateAccountKeys,
|
||||
to_identifier: *const FfiU128,
|
||||
amount: *const [u8; 16],
|
||||
out_result: *mut FfiTransferResult,
|
||||
) -> WalletFfiError {
|
||||
@ -302,7 +316,12 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private(
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if from.is_null() || to_keys.is_null() || amount.is_null() || out_result.is_null() {
|
||||
if from.is_null()
|
||||
|| to_keys.is_null()
|
||||
|| to_identifier.is_null()
|
||||
|| amount.is_null()
|
||||
|| out_result.is_null()
|
||||
{
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
@ -324,12 +343,18 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private(
|
||||
return e;
|
||||
}
|
||||
};
|
||||
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.send_private_transfer_to_outer_account(from_id, to_npk, to_vpk, amount))
|
||||
{
|
||||
match block_on(transfer.send_private_transfer_to_outer_account(
|
||||
from_id,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
)) {
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);
|
||||
|
||||
@ -126,6 +126,24 @@ typedef struct FfiBytes32 {
|
||||
uint8_t data[32];
|
||||
} FfiBytes32;
|
||||
|
||||
/**
|
||||
* Public keys for a private account (safe to expose).
|
||||
*/
|
||||
typedef struct FfiPrivateAccountKeys {
|
||||
/**
|
||||
* Nullifier public key (32 bytes).
|
||||
*/
|
||||
struct FfiBytes32 nullifier_public_key;
|
||||
/**
|
||||
* viewing public key (compressed secp256k1 point).
|
||||
*/
|
||||
const uint8_t *viewing_public_key;
|
||||
/**
|
||||
* Length of viewing public key (typically 33 bytes).
|
||||
*/
|
||||
uintptr_t viewing_public_key_len;
|
||||
} FfiPrivateAccountKeys;
|
||||
|
||||
/**
|
||||
* Single entry in the account list.
|
||||
*/
|
||||
@ -189,24 +207,6 @@ typedef struct FfiPublicAccountKey {
|
||||
struct FfiBytes32 public_key;
|
||||
} FfiPublicAccountKey;
|
||||
|
||||
/**
|
||||
* Public keys for a private account (safe to expose).
|
||||
*/
|
||||
typedef struct FfiPrivateAccountKeys {
|
||||
/**
|
||||
* Nullifier public key (32 bytes).
|
||||
*/
|
||||
struct FfiBytes32 nullifier_public_key;
|
||||
/**
|
||||
* viewing public key (compressed secp256k1 point).
|
||||
*/
|
||||
const uint8_t *viewing_public_key;
|
||||
/**
|
||||
* Length of viewing public key (typically 33 bytes).
|
||||
*/
|
||||
uintptr_t viewing_public_key_len;
|
||||
} FfiPrivateAccountKeys;
|
||||
|
||||
/**
|
||||
* Result of a transfer operation.
|
||||
*/
|
||||
@ -243,10 +243,18 @@ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle
|
||||
struct FfiBytes32 *out_account_id);
|
||||
|
||||
/**
|
||||
* Create a new private account.
|
||||
* Create a new private account, storing a default account entry in local storage.
|
||||
*
|
||||
* Private accounts use privacy-preserving transactions with nullifiers
|
||||
* and commitments.
|
||||
* This is the private-account equivalent of `wallet_ffi_create_account_public`.
|
||||
* It generates a key node, assigns a random identifier, and inserts a default
|
||||
* account record so the account can immediately be used with
|
||||
* `wallet_ffi_register_private_account`.
|
||||
*
|
||||
* The identifier is chosen at random and is not encoded in the mnemonic seed.
|
||||
* Once the account is initialized, the identifier is embedded in the encrypted
|
||||
* transaction payload and can be recovered by running `sync-private` from the
|
||||
* same mnemonic. An account that was created locally but has never been initialized
|
||||
* cannot be recovered from the seed alone.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
@ -263,6 +271,31 @@ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle
|
||||
enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handle,
|
||||
struct FfiBytes32 *out_account_id);
|
||||
|
||||
/**
|
||||
* Create a new private key node.
|
||||
*
|
||||
* Returns the nullifier public key (npk) and viewing public key (vpk) to share with
|
||||
* senders. Account IDs are discovered later via sync when senders initialize accounts
|
||||
* under this key.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `out_keys`: Output pointer for the key data (npk + vpk)
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful creation
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Memory
|
||||
* The keys structure must be freed with `wallet_ffi_free_private_account_keys()`.
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
* - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_create_private_accounts_key(struct WalletHandle *handle,
|
||||
struct FfiPrivateAccountKeys *out_keys);
|
||||
|
||||
/**
|
||||
* List all accounts in the wallet.
|
||||
*
|
||||
@ -685,6 +718,7 @@ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle,
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `from`: Source account ID (must be owned by this wallet)
|
||||
* - `to_keys`: Destination account keys
|
||||
* - `to_identifier`: Identifier for the recipient's private account
|
||||
* - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||
* - `out_result`: Output pointer for transfer result
|
||||
*
|
||||
@ -707,6 +741,7 @@ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle,
|
||||
enum WalletFfiError wallet_ffi_transfer_shielded(struct WalletHandle *handle,
|
||||
const struct FfiBytes32 *from,
|
||||
const struct FfiPrivateAccountKeys *to_keys,
|
||||
const struct FfiU128 *to_identifier,
|
||||
const uint8_t (*amount)[16],
|
||||
struct FfiTransferResult *out_result);
|
||||
|
||||
@ -753,6 +788,7 @@ enum WalletFfiError wallet_ffi_transfer_deshielded(struct WalletHandle *handle,
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `from`: Source account ID (must be owned by this wallet)
|
||||
* - `to_keys`: Destination account keys
|
||||
* - `to_identifier`: Identifier for the recipient's private account
|
||||
* - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||
* - `out_result`: Output pointer for transfer result
|
||||
*
|
||||
@ -775,6 +811,7 @@ enum WalletFfiError wallet_ffi_transfer_deshielded(struct WalletHandle *handle,
|
||||
enum WalletFfiError wallet_ffi_transfer_private(struct WalletHandle *handle,
|
||||
const struct FfiBytes32 *from,
|
||||
const struct FfiPrivateAccountKeys *to_keys,
|
||||
const struct FfiU128 *to_identifier,
|
||||
const uint8_t (*amount)[16],
|
||||
struct FfiTransferResult *out_result);
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
},
|
||||
{
|
||||
"Private": {
|
||||
"account_id": "9DGDXnrNo4QhUUb2F8WDuDrPESja3eYDkZG5HkzvAvMC",
|
||||
"account_id": "GoKB6RuE6pT2KxCqDXQqiCuuuYZaGdJNfctzyqRdGBCy",
|
||||
"identifier": 0,
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
@ -214,7 +215,8 @@
|
||||
},
|
||||
{
|
||||
"Private": {
|
||||
"account_id": "A6AT9UvsgitUi8w4BH43n6DyX1bK37DtSCfjEWXQQUrQ",
|
||||
"account_id": "BCdMnPkdH2DrVhe7cGdawkPU9iapsSboRvJpWX8pWnLq",
|
||||
"identifier": 0,
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
|
||||
@ -7,7 +7,7 @@ use key_protocol::{
|
||||
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
|
||||
secret_holders::SeedHolder,
|
||||
},
|
||||
key_protocol_core::NSSAUserData,
|
||||
key_protocol_core::{NSSAUserData, UserPrivateAccountData},
|
||||
};
|
||||
use log::debug;
|
||||
use nssa::program::Program;
|
||||
@ -70,15 +70,28 @@ impl WalletChainStore {
|
||||
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);
|
||||
let npk = data.data.value.0.nullifier_public_key;
|
||||
let chain_index = data.chain_index;
|
||||
for identifier in &data.identifiers {
|
||||
let account_id = nssa::AccountId::from((&npk, *identifier));
|
||||
private_tree
|
||||
.account_id_map
|
||||
.insert(account_id, chain_index.clone());
|
||||
}
|
||||
private_tree.key_map.insert(chain_index, data.data);
|
||||
}
|
||||
PersistentAccountData::Preconfigured(acc_data) => match acc_data {
|
||||
InitialAccountData::Public(data) => {
|
||||
public_init_acc_map.insert(data.account_id, data.pub_sign_key);
|
||||
}
|
||||
InitialAccountData::Private(data) => {
|
||||
private_init_acc_map
|
||||
.insert(data.account_id, (data.key_chain, data.account));
|
||||
private_init_acc_map.insert(
|
||||
data.account_id(),
|
||||
UserPrivateAccountData {
|
||||
key_chain: data.key_chain,
|
||||
accounts: vec![(data.identifier, data.account)],
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -111,13 +124,20 @@ impl WalletChainStore {
|
||||
public_init_acc_map.insert(data.account_id, data.pub_sign_key);
|
||||
}
|
||||
InitialAccountData::Private(data) => {
|
||||
let account_id = data.account_id();
|
||||
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, (data.key_chain, account));
|
||||
private_init_acc_map.insert(
|
||||
account_id,
|
||||
UserPrivateAccountData {
|
||||
key_chain: data.key_chain,
|
||||
accounts: vec![(data.identifier, account)],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,28 +190,71 @@ impl WalletChainStore {
|
||||
pub fn insert_private_account_data(
|
||||
&mut self,
|
||||
account_id: nssa::AccountId,
|
||||
identifier: nssa_core::Identifier,
|
||||
account: nssa_core::account::Account,
|
||||
) {
|
||||
debug!("inserting at address {account_id}, this account {account:?}");
|
||||
|
||||
let entry = self
|
||||
// Update default accounts if present
|
||||
if let Entry::Occupied(mut entry) = self
|
||||
.user_data
|
||||
.default_user_private_accounts
|
||||
.entry(account_id)
|
||||
.and_modify(|data| data.1 = account.clone());
|
||||
{
|
||||
let entry = entry.get_mut();
|
||||
if let Some((_, acc)) = entry.accounts.iter_mut().find(|(id, _)| *id == identifier) {
|
||||
*acc = account;
|
||||
} else {
|
||||
entry.accounts.push((identifier, account));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(entry, Entry::Vacant(_)) {
|
||||
self.user_data
|
||||
// Otherwise update the private key tree
|
||||
|
||||
// Find the node by iterating all tree nodes for this account_id
|
||||
let chain_index = self
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.account_id_map
|
||||
.get(&account_id)
|
||||
.cloned();
|
||||
|
||||
if let Some(chain_index) = chain_index {
|
||||
// Node already in account_id_map — update its entry
|
||||
if let Some(node) = self
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.account_id_map
|
||||
.get(&account_id)
|
||||
.map(|chain_index| {
|
||||
.key_map
|
||||
.get_mut(&chain_index)
|
||||
{
|
||||
if let Some((_, acc)) = node.value.1.iter_mut().find(|(id, _)| *id == identifier) {
|
||||
*acc = account;
|
||||
} else {
|
||||
node.value.1.push((identifier, account));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Node not yet in account_id_map — find it by checking all nodes
|
||||
for (ci, node) in &mut self.user_data.private_key_tree.key_map {
|
||||
let expected_id =
|
||||
nssa::AccountId::from((&node.value.0.nullifier_public_key, identifier));
|
||||
if expected_id == account_id {
|
||||
if let Some((_, acc)) =
|
||||
node.value.1.iter_mut().find(|(id, _)| *id == identifier)
|
||||
{
|
||||
*acc = account;
|
||||
} else {
|
||||
node.value.1.push((identifier, account));
|
||||
}
|
||||
// Register in account_id_map
|
||||
self.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.entry(chain_index.clone())
|
||||
.and_modify(|data| data.value.1 = account)
|
||||
});
|
||||
.account_id_map
|
||||
.insert(account_id, ci.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,7 +262,7 @@ impl WalletChainStore {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use key_protocol::key_management::key_tree::{
|
||||
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode as _,
|
||||
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -228,7 +291,7 @@ mod tests {
|
||||
data: public_data,
|
||||
}),
|
||||
PersistentAccountData::Private(Box::new(PersistentAccountDataPrivate {
|
||||
account_id: private_data.account_id(),
|
||||
identifiers: vec![],
|
||||
chain_index: ChainIndex::root(),
|
||||
data: private_data,
|
||||
})),
|
||||
|
||||
@ -82,7 +82,8 @@ pub enum NewSubcommand {
|
||||
/// Label to assign to the new account.
|
||||
label: Option<String>,
|
||||
},
|
||||
/// Register new private account.
|
||||
/// Single-account convenience: creates a key node and auto-registers one account with a random
|
||||
/// identifier.
|
||||
Private {
|
||||
#[arg(long)]
|
||||
/// Chain index of a parent node.
|
||||
@ -91,6 +92,13 @@ pub enum NewSubcommand {
|
||||
/// Label to assign to the new account.
|
||||
label: Option<String>,
|
||||
},
|
||||
/// Recommended for receiving from multiple senders: creates a key node (npk + vpk) without
|
||||
/// registering any account.
|
||||
PrivateAccountsKey {
|
||||
#[arg(long)]
|
||||
/// Chain index of a parent node.
|
||||
cci: Option<ChainIndex>,
|
||||
},
|
||||
}
|
||||
|
||||
impl WalletSubcommand for NewSubcommand {
|
||||
@ -149,6 +157,15 @@ impl WalletSubcommand for NewSubcommand {
|
||||
|
||||
let (account_id, chain_index) = wallet_core.create_new_account_private(cci);
|
||||
|
||||
let node = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("Node was just inserted");
|
||||
let key = &node.value.0;
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
@ -156,14 +173,8 @@ impl WalletSubcommand for NewSubcommand {
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
let (key, _) = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"Generated new account with account_id Private/{account_id} at path {chain_index}",
|
||||
"Generated new account with account_id Private/{account_id} at path {chain_index}"
|
||||
);
|
||||
println!("With npk {}", hex::encode(key.nullifier_public_key.0));
|
||||
println!(
|
||||
@ -175,6 +186,29 @@ impl WalletSubcommand for NewSubcommand {
|
||||
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
Self::PrivateAccountsKey { cci } => {
|
||||
let chain_index = wallet_core.create_private_accounts_key(cci);
|
||||
|
||||
let node = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("Node was just inserted");
|
||||
let key = &node.value.0;
|
||||
|
||||
println!("Generated new private key node at path {chain_index}");
|
||||
println!("With npk {}", hex::encode(key.nullifier_public_key.0));
|
||||
println!(
|
||||
"With vpk {}",
|
||||
hex::encode(key.viewing_public_key.to_bytes())
|
||||
);
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,7 +263,7 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
println!("pk {}", hex::encode(public_key.value()));
|
||||
}
|
||||
AccountPrivacyKind::Private => {
|
||||
let (key, _) = wallet_core
|
||||
let (key, _, _) = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
@ -272,21 +306,7 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
Self::New(new_subcommand) => new_subcommand.handle_subcommand(wallet_core).await,
|
||||
Self::SyncPrivate => {
|
||||
let curr_last_block = wallet_core.sequencer_client.get_last_block_id().await?;
|
||||
|
||||
if wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.account_id_map
|
||||
.is_empty()
|
||||
{
|
||||
wallet_core.last_synced_block = curr_last_block;
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
} else {
|
||||
wallet_core.sync_to_block(curr_last_block).await?;
|
||||
}
|
||||
|
||||
wallet_core.sync_to_block(curr_last_block).await?;
|
||||
Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block))
|
||||
}
|
||||
Self::List { long } => {
|
||||
|
||||
295
wallet/src/cli/group.rs
Normal file
295
wallet/src/cli/group.rs
Normal file
@ -0,0 +1,295 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Subcommand;
|
||||
use key_protocol::key_management::group_key_holder::GroupKeyHolder;
|
||||
use nssa::AccountId;
|
||||
use nssa_core::program::PdaSeed;
|
||||
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
};
|
||||
|
||||
/// Group PDA management commands.
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum GroupSubcommand {
|
||||
/// Create a new group with a fresh random GMS.
|
||||
New {
|
||||
/// Human-readable name for the group.
|
||||
name: String,
|
||||
},
|
||||
/// Import a group from raw GMS bytes.
|
||||
Import {
|
||||
/// Human-readable name for the group.
|
||||
name: String,
|
||||
/// Raw GMS as 64-character hex string.
|
||||
#[arg(long)]
|
||||
gms: String,
|
||||
/// Epoch (defaults to 0).
|
||||
#[arg(long, default_value = "0")]
|
||||
epoch: u32,
|
||||
},
|
||||
/// Export the raw GMS hex for backup or manual distribution.
|
||||
Export {
|
||||
/// Group name.
|
||||
name: String,
|
||||
},
|
||||
/// List all groups with their epochs.
|
||||
#[command(visible_alias = "ls")]
|
||||
List,
|
||||
/// Derive keys for a PDA seed and show the resulting AccountId.
|
||||
Derive {
|
||||
/// Group name.
|
||||
name: String,
|
||||
/// PDA seed as 64-character hex string.
|
||||
#[arg(long)]
|
||||
seed: String,
|
||||
/// Program ID as hex string (u32x8 little-endian).
|
||||
#[arg(long)]
|
||||
program_id: String,
|
||||
},
|
||||
/// Remove a group from the wallet.
|
||||
Remove {
|
||||
/// Group name.
|
||||
name: String,
|
||||
},
|
||||
/// Seal the group's GMS for a recipient (invite).
|
||||
Invite {
|
||||
/// Group name.
|
||||
name: String,
|
||||
/// Recipient's viewing public key as hex string.
|
||||
#[arg(long)]
|
||||
vpk: String,
|
||||
},
|
||||
/// Unseal a received GMS and store it (join a group).
|
||||
Join {
|
||||
/// Human-readable name to store the group under.
|
||||
name: String,
|
||||
/// Sealed GMS as hex string (from the inviter).
|
||||
#[arg(long)]
|
||||
sealed: String,
|
||||
/// Account label or Private/<id> whose VSK to use for decryption.
|
||||
#[arg(long)]
|
||||
account: String,
|
||||
},
|
||||
/// Ratchet the GMS to exclude removed members.
|
||||
Ratchet {
|
||||
/// Group name.
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl WalletSubcommand for GroupSubcommand {
|
||||
async fn handle_subcommand(
|
||||
self,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
match self {
|
||||
Self::New { name } => {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
}
|
||||
|
||||
let holder = GroupKeyHolder::new();
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Created group '{name}' at epoch 0");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Import { name, gms, epoch } => {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
}
|
||||
|
||||
let gms_bytes: [u8; 32] = hex::decode(&gms)
|
||||
.context("Invalid GMS hex")?
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("GMS must be exactly 32 bytes"))?;
|
||||
|
||||
let holder = GroupKeyHolder::from_gms_and_epoch(gms_bytes, epoch);
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Imported group '{name}' at epoch {epoch}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Export { name } => {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let gms_hex = hex::encode(holder.dangerous_raw_gms());
|
||||
let epoch = holder.epoch();
|
||||
|
||||
println!("Group: {name}");
|
||||
println!("Epoch: {epoch}");
|
||||
println!("GMS: {gms_hex}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::List => {
|
||||
let holders = &wallet_core.storage().user_data.group_key_holders;
|
||||
if holders.is_empty() {
|
||||
println!("No groups found");
|
||||
} else {
|
||||
for (name, holder) in holders {
|
||||
println!("{name} (epoch {})", holder.epoch());
|
||||
}
|
||||
}
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Derive {
|
||||
name,
|
||||
seed,
|
||||
program_id,
|
||||
} => {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let seed_bytes: [u8; 32] = hex::decode(&seed)
|
||||
.context("Invalid seed hex")?
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("Seed must be exactly 32 bytes"))?;
|
||||
let pda_seed = PdaSeed::new(seed_bytes);
|
||||
|
||||
let pid_bytes =
|
||||
hex::decode(&program_id).context("Invalid program ID hex")?;
|
||||
if pid_bytes.len() != 32 {
|
||||
anyhow::bail!("Program ID must be exactly 32 bytes");
|
||||
}
|
||||
let mut pid: nssa_core::program::ProgramId = [0; 8];
|
||||
for (i, chunk) in pid_bytes.chunks_exact(4).enumerate() {
|
||||
pid[i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
||||
}
|
||||
|
||||
let keys = holder.derive_keys_for_pda(&pda_seed);
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_key();
|
||||
let account_id = AccountId::for_private_pda(&pid, &pda_seed, &npk);
|
||||
|
||||
println!("Group: {name}");
|
||||
println!("NPK: {}", hex::encode(npk.0));
|
||||
println!("VPK: {}", hex::encode(&vpk.0));
|
||||
println!("AccountId: {account_id}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Remove { name } => {
|
||||
if wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.group_key_holders
|
||||
.remove(&name)
|
||||
.is_none()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' not found");
|
||||
}
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
println!("Removed group '{name}'");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Invite { name, vpk } => {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let vpk_bytes = hex::decode(&vpk).context("Invalid VPK hex")?;
|
||||
let recipient_vpk =
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(vpk_bytes);
|
||||
|
||||
let sealed = holder.seal_for(&recipient_vpk);
|
||||
println!("{}", hex::encode(&sealed));
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Join {
|
||||
name,
|
||||
sealed,
|
||||
account,
|
||||
} => {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
}
|
||||
|
||||
let sealed_bytes = hex::decode(&sealed).context("Invalid sealed hex")?;
|
||||
|
||||
// Resolve the account to get the VSK
|
||||
let account_id: nssa::AccountId = account
|
||||
.parse()
|
||||
.context("Invalid account ID (use Private/<base58>)")?;
|
||||
let (keychain, _) = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
.context("Private account not found")?;
|
||||
let vsk = keychain.private_key_holder.viewing_secret_key;
|
||||
|
||||
let holder = GroupKeyHolder::unseal(&sealed_bytes, &vsk)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?;
|
||||
|
||||
let epoch = holder.epoch();
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Joined group '{name}' at epoch {epoch}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Ratchet { name } => {
|
||||
let holder = wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.group_key_holders
|
||||
.get_mut(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let mut salt = [0_u8; 32];
|
||||
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut salt);
|
||||
holder.ratchet(salt);
|
||||
|
||||
let epoch = holder.epoch();
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Ratcheted group '{name}' to epoch {epoch}");
|
||||
println!("Re-invite remaining members with 'group invite'");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,6 +59,10 @@ pub enum AuthTransferSubcommand {
|
||||
/// `to_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
to_vpk: Option<String>,
|
||||
/// Identifier for the recipient's private account (only used when sending to a foreign
|
||||
/// private account via `--to-npk`/`--to-vpk`).
|
||||
#[arg(long)]
|
||||
to_identifier: Option<u128>,
|
||||
/// amount - amount of balance to move.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
@ -132,6 +136,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
||||
to_label,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let from = resolve_id_or_label(
|
||||
@ -210,6 +215,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
},
|
||||
)
|
||||
@ -220,6 +226,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
},
|
||||
)
|
||||
@ -304,6 +311,9 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
|
||||
/// `to_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
to_vpk: String,
|
||||
/// Identifier for the recipient's private account.
|
||||
#[arg(long)]
|
||||
to_identifier: Option<u128>,
|
||||
/// amount - amount of balance to move.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
@ -341,6 +351,9 @@ pub enum NativeTokenTransferProgramSubcommandPrivate {
|
||||
/// `to_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
to_vpk: String,
|
||||
/// Identifier for the recipient's private account.
|
||||
#[arg(long)]
|
||||
to_identifier: Option<u128>,
|
||||
/// amount - amount of balance to move.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
@ -382,6 +395,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
@ -397,7 +411,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_vpk.to_vec());
|
||||
|
||||
let (tx_hash, [secret_from, _]) = NativeTokenTransfer(wallet_core)
|
||||
.send_private_transfer_to_outer_account(from, to_npk, to_vpk, amount)
|
||||
.send_private_transfer_to_outer_account(
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier.unwrap_or_else(rand::random),
|
||||
amount,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
@ -456,6 +476,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let from: AccountId = from.parse().unwrap();
|
||||
@ -472,7 +493,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_vpk.to_vec());
|
||||
|
||||
let (tx_hash, _) = NativeTokenTransfer(wallet_core)
|
||||
.send_shielded_transfer_to_outer_account(from, to_npk, to_vpk, amount)
|
||||
.send_shielded_transfer_to_outer_account(
|
||||
from,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier.unwrap_or_else(rand::random),
|
||||
amount,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Transaction hash is {tx_hash}");
|
||||
|
||||
@ -73,6 +73,10 @@ pub enum TokenProgramAgnosticSubcommand {
|
||||
/// `to_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
to_vpk: Option<String>,
|
||||
/// Identifier for the recipient's private account (only used when sending to a foreign
|
||||
/// private account via `--to-npk`/`--to-vpk`).
|
||||
#[arg(long)]
|
||||
to_identifier: Option<u128>,
|
||||
/// amount - amount of balance to move.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
@ -139,6 +143,10 @@ pub enum TokenProgramAgnosticSubcommand {
|
||||
/// `to_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
holder_vpk: Option<String>,
|
||||
/// Identifier for the holder's private account (only used when minting to a foreign
|
||||
/// private account via `--holder-npk`/`--holder-vpk`).
|
||||
#[arg(long)]
|
||||
holder_identifier: Option<u128>,
|
||||
/// amount - amount of balance to mint.
|
||||
#[arg(long)]
|
||||
amount: u128,
|
||||
@ -228,6 +236,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
to_label,
|
||||
to_npk,
|
||||
to_vpk,
|
||||
to_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let from = resolve_id_or_label(
|
||||
@ -313,6 +322,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
sender_account_id: from,
|
||||
recipient_npk: to_npk,
|
||||
recipient_vpk: to_vpk,
|
||||
recipient_identifier: to_identifier,
|
||||
balance_to_move: amount,
|
||||
},
|
||||
),
|
||||
@ -321,6 +331,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
sender_account_id: from,
|
||||
recipient_npk: to_npk,
|
||||
recipient_vpk: to_vpk,
|
||||
recipient_identifier: to_identifier,
|
||||
balance_to_move: amount,
|
||||
},
|
||||
),
|
||||
@ -403,6 +414,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
holder_label,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let definition = resolve_id_or_label(
|
||||
@ -490,6 +502,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
definition_account_id: definition,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier,
|
||||
amount,
|
||||
},
|
||||
),
|
||||
@ -498,6 +511,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
definition_account_id: definition,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier,
|
||||
amount,
|
||||
},
|
||||
),
|
||||
@ -585,6 +599,9 @@ pub enum TokenProgramSubcommandPrivate {
|
||||
/// `recipient_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
recipient_vpk: String,
|
||||
/// Identifier for the recipient's private account.
|
||||
#[arg(long)]
|
||||
recipient_identifier: Option<u128>,
|
||||
#[arg(short, long)]
|
||||
balance_to_move: u128,
|
||||
},
|
||||
@ -614,6 +631,9 @@ pub enum TokenProgramSubcommandPrivate {
|
||||
holder_npk: String,
|
||||
#[arg(short, long)]
|
||||
holder_vpk: String,
|
||||
/// Identifier for the holder's private account.
|
||||
#[arg(long)]
|
||||
holder_identifier: Option<u128>,
|
||||
#[arg(short, long)]
|
||||
amount: u128,
|
||||
},
|
||||
@ -673,6 +693,9 @@ pub enum TokenProgramSubcommandShielded {
|
||||
/// `recipient_vpk` - valid 33 byte hex string.
|
||||
#[arg(long)]
|
||||
recipient_vpk: String,
|
||||
/// Identifier for the recipient's private account.
|
||||
#[arg(long)]
|
||||
recipient_identifier: Option<u128>,
|
||||
#[arg(short, long)]
|
||||
balance_to_move: u128,
|
||||
},
|
||||
@ -702,6 +725,9 @@ pub enum TokenProgramSubcommandShielded {
|
||||
holder_npk: String,
|
||||
#[arg(short, long)]
|
||||
holder_vpk: String,
|
||||
/// Identifier for the holder's private account.
|
||||
#[arg(long)]
|
||||
holder_identifier: Option<u128>,
|
||||
#[arg(short, long)]
|
||||
amount: u128,
|
||||
},
|
||||
@ -862,6 +888,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_vpk,
|
||||
recipient_identifier,
|
||||
balance_to_move,
|
||||
} => {
|
||||
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
|
||||
@ -882,6 +909,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_vpk,
|
||||
recipient_identifier.unwrap_or_else(rand::random),
|
||||
balance_to_move,
|
||||
)
|
||||
.await?;
|
||||
@ -979,6 +1007,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
definition_account_id,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
@ -1000,6 +1029,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
|
||||
definition_account_id,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier.unwrap_or_else(rand::random),
|
||||
amount,
|
||||
)
|
||||
.await?;
|
||||
@ -1144,6 +1174,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_vpk,
|
||||
recipient_identifier,
|
||||
balance_to_move,
|
||||
} => {
|
||||
let sender_account_id: AccountId = sender_account_id.parse().unwrap();
|
||||
@ -1164,6 +1195,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
sender_account_id,
|
||||
recipient_npk,
|
||||
recipient_vpk,
|
||||
recipient_identifier.unwrap_or_else(rand::random),
|
||||
balance_to_move,
|
||||
)
|
||||
.await?;
|
||||
@ -1283,6 +1315,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
definition_account_id,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier,
|
||||
amount,
|
||||
} => {
|
||||
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
|
||||
@ -1304,6 +1337,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
||||
definition_account_id,
|
||||
holder_npk,
|
||||
holder_vpk,
|
||||
holder_identifier.unwrap_or_else(rand::random),
|
||||
amount,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -28,7 +28,7 @@ pub struct PersistentAccountDataPublic {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PersistentAccountDataPrivate {
|
||||
pub account_id: nssa::AccountId,
|
||||
pub identifiers: Vec<nssa_core::Identifier>,
|
||||
pub chain_index: ChainIndex,
|
||||
pub data: ChildKeysPrivate,
|
||||
}
|
||||
@ -44,10 +44,10 @@ pub enum InitialAccountData {
|
||||
|
||||
impl InitialAccountData {
|
||||
#[must_use]
|
||||
pub const fn account_id(&self) -> nssa::AccountId {
|
||||
pub fn account_id(&self) -> nssa::AccountId {
|
||||
match &self {
|
||||
Self::Public(acc) => acc.account_id,
|
||||
Self::Private(acc) => acc.account_id,
|
||||
Self::Private(acc) => acc.account_id(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,17 +123,6 @@ impl PersistentStorage {
|
||||
}
|
||||
}
|
||||
|
||||
impl PersistentAccountData {
|
||||
#[must_use]
|
||||
pub fn account_id(&self) -> nssa::AccountId {
|
||||
match &self {
|
||||
Self::Public(acc) => acc.account_id,
|
||||
Self::Private(acc) => acc.account_id,
|
||||
Self::Preconfigured(acc) => acc.account_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicAccountPrivateInitialData> for InitialAccountData {
|
||||
fn from(value: PublicAccountPrivateInitialData) -> Self {
|
||||
Self::Public(value)
|
||||
|
||||
@ -165,17 +165,16 @@ pub fn produce_data_for_storage(
|
||||
}
|
||||
}
|
||||
|
||||
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 (chain_index, node) in &user_data.private_key_tree.key_map {
|
||||
let identifiers = node.value.1.iter().map(|(id, _)| *id).collect();
|
||||
vec_for_storage.push(
|
||||
PersistentAccountDataPrivate {
|
||||
identifiers,
|
||||
chain_index: chain_index.clone(),
|
||||
data: node.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
for (account_id, key) in &user_data.default_pub_account_signing_keys {
|
||||
@ -188,15 +187,17 @@ pub fn produce_data_for_storage(
|
||||
);
|
||||
}
|
||||
|
||||
for (account_id, (key_chain, account)) in &user_data.default_user_private_accounts {
|
||||
vec_for_storage.push(
|
||||
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
|
||||
account_id: *account_id,
|
||||
account: account.clone(),
|
||||
key_chain: key_chain.clone(),
|
||||
}))
|
||||
.into(),
|
||||
);
|
||||
for entry in user_data.default_user_private_accounts.values() {
|
||||
for (identifier, account) in &entry.accounts {
|
||||
vec_for_storage.push(
|
||||
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
|
||||
account: account.clone(),
|
||||
key_chain: entry.key_chain.clone(),
|
||||
identifier: *identifier,
|
||||
}))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PersistentStorage {
|
||||
|
||||
@ -15,7 +15,7 @@ use bip39::Mnemonic;
|
||||
use chain_storage::WalletChainStore;
|
||||
use common::{HashType, transaction::NSSATransaction};
|
||||
use config::WalletConfig;
|
||||
use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode as _};
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use log::info;
|
||||
use nssa::{
|
||||
Account, AccountId, PrivacyPreservingTransaction,
|
||||
@ -256,13 +256,35 @@ impl WalletCore {
|
||||
.generate_new_public_transaction_private_key(chain_index)
|
||||
}
|
||||
|
||||
pub fn create_private_accounts_key(&mut self, chain_index: Option<ChainIndex>) -> ChainIndex {
|
||||
self.storage
|
||||
.user_data
|
||||
.create_private_accounts_key(chain_index)
|
||||
}
|
||||
|
||||
pub fn create_new_account_private(
|
||||
&mut self,
|
||||
chain_index: Option<ChainIndex>,
|
||||
) -> (AccountId, ChainIndex) {
|
||||
self.storage
|
||||
let cci = self
|
||||
.storage
|
||||
.user_data
|
||||
.generate_new_privacy_preserving_transaction_key_chain(chain_index)
|
||||
.create_private_accounts_key(chain_index);
|
||||
let identifier: nssa_core::Identifier = rand::random();
|
||||
let npk = self
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&cci)
|
||||
.expect("Node was just inserted")
|
||||
.value
|
||||
.0
|
||||
.nullifier_public_key;
|
||||
let account_id = AccountId::from((&npk, identifier));
|
||||
self.storage
|
||||
.insert_private_account_data(account_id, identifier, Account::default());
|
||||
(account_id, cci)
|
||||
}
|
||||
|
||||
/// Get account balance.
|
||||
@ -295,13 +317,14 @@ impl WalletCore {
|
||||
self.storage
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
.map(|value| value.1.clone())
|
||||
.map(|(_keys, account, _identifier)| account)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_private_account_commitment(&self, account_id: AccountId) -> Option<Commitment> {
|
||||
let (keys, account) = self.storage.user_data.get_private_account(account_id)?;
|
||||
Some(Commitment::new(&keys.nullifier_public_key, account))
|
||||
let (_keys, account, _identifier) =
|
||||
self.storage.user_data.get_private_account(account_id)?;
|
||||
Some(Commitment::new(&account_id, &account))
|
||||
}
|
||||
|
||||
/// Poll transactions.
|
||||
@ -334,7 +357,7 @@ impl WalletCore {
|
||||
let acc_ead = tx.message.encrypted_private_post_states[output_index].clone();
|
||||
let acc_comm = tx.message.new_commitments[output_index].clone();
|
||||
|
||||
let res_acc = nssa_core::EncryptionScheme::decrypt(
|
||||
let (identifier, res_acc) = nssa_core::EncryptionScheme::decrypt(
|
||||
&acc_ead.ciphertext,
|
||||
secret,
|
||||
&acc_comm,
|
||||
@ -347,7 +370,7 @@ impl WalletCore {
|
||||
println!("Received new acc {res_acc:#?}");
|
||||
|
||||
self.storage
|
||||
.insert_private_account_data(*acc_account_id, res_acc);
|
||||
.insert_private_account_data(*acc_account_id, identifier, res_acc);
|
||||
}
|
||||
AccDecodeData::Skip => {}
|
||||
}
|
||||
@ -393,7 +416,7 @@ impl WalletCore {
|
||||
acc_manager.visibility_mask().to_vec(),
|
||||
private_account_keys
|
||||
.iter()
|
||||
.map(|keys| (keys.npk, keys.ssk))
|
||||
.map(|keys| (keys.npk, keys.identifier, keys.ssk))
|
||||
.collect::<Vec<_>>(),
|
||||
acc_manager.private_account_auth(),
|
||||
acc_manager.private_account_membership_proofs(),
|
||||
@ -483,33 +506,33 @@ impl WalletCore {
|
||||
.storage
|
||||
.user_data
|
||||
.default_user_private_accounts
|
||||
.iter()
|
||||
.map(|(acc_account_id, (key_chain, _))| (*acc_account_id, key_chain, None))
|
||||
.chain(self.storage.user_data.private_key_tree.key_map.iter().map(
|
||||
|(chain_index, keys_node)| {
|
||||
(
|
||||
keys_node.account_id(),
|
||||
&keys_node.value.0,
|
||||
chain_index.index(),
|
||||
)
|
||||
},
|
||||
));
|
||||
.values()
|
||||
.map(|entry| (&entry.key_chain, None))
|
||||
.chain(
|
||||
self.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.iter()
|
||||
.map(|(chain_index, keys_node)| (&keys_node.value.0, chain_index.index())),
|
||||
);
|
||||
|
||||
let affected_accounts = private_account_key_chains
|
||||
.flat_map(|(acc_account_id, key_chain, index)| {
|
||||
.flat_map(|(key_chain, index)| {
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(
|
||||
&key_chain.nullifier_public_key,
|
||||
&key_chain.viewing_public_key,
|
||||
);
|
||||
let new_commitments = &tx.message.new_commitments;
|
||||
|
||||
tx.message()
|
||||
.encrypted_private_post_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |(_, encrypted_data)| encrypted_data.view_tag == view_tag)
|
||||
.filter_map(|(ciph_id, encrypted_data)| {
|
||||
.filter_map(move |(ciph_id, encrypted_data)| {
|
||||
let ciphertext = &encrypted_data.ciphertext;
|
||||
let commitment = &tx.message.new_commitments[ciph_id];
|
||||
let commitment = &new_commitments[ciph_id];
|
||||
let shared_secret =
|
||||
key_chain.calculate_shared_secret_receiver(&encrypted_data.epk, index);
|
||||
|
||||
@ -521,18 +544,24 @@ impl WalletCore {
|
||||
.try_into()
|
||||
.expect("Ciphertext ID is expected to fit in u32"),
|
||||
)
|
||||
.map(|(identifier, res_acc)| {
|
||||
let account_id = nssa::AccountId::from((
|
||||
&key_chain.nullifier_public_key,
|
||||
identifier,
|
||||
));
|
||||
(account_id, identifier, res_acc)
|
||||
})
|
||||
})
|
||||
.map(move |res_acc| (acc_account_id, res_acc))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (affected_account_id, new_acc) in affected_accounts {
|
||||
for (affected_account_id, identifier, new_acc) in affected_accounts {
|
||||
info!(
|
||||
"Received new account for account_id {affected_account_id:#?} with account object {new_acc:#?}"
|
||||
);
|
||||
self.storage
|
||||
.insert_private_account_data(affected_account_id, new_acc);
|
||||
.insert_private_account_data(affected_account_id, identifier, new_acc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{AccountId, PrivateKey};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
||||
program::{PdaSeed, ProgramId},
|
||||
@ -17,6 +17,7 @@ pub enum PrivacyPreservingAccount {
|
||||
PrivateForeign {
|
||||
npk: NullifierPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
identifier: Identifier,
|
||||
},
|
||||
/// A private PDA with externally-provided keys. The caller resolves the keys
|
||||
/// (e.g. via `GroupKeyHolder::derive_keys_for_pda`) before constructing this variant.
|
||||
@ -41,7 +42,11 @@ impl PrivacyPreservingAccount {
|
||||
matches!(
|
||||
&self,
|
||||
Self::PrivateOwned(_)
|
||||
| Self::PrivateForeign { npk: _, vpk: _ }
|
||||
| Self::PrivateForeign {
|
||||
npk: _,
|
||||
vpk: _,
|
||||
identifier: _,
|
||||
}
|
||||
| Self::PrivatePda { .. }
|
||||
)
|
||||
}
|
||||
@ -49,6 +54,7 @@ impl PrivacyPreservingAccount {
|
||||
|
||||
pub struct PrivateAccountKeys {
|
||||
pub npk: NullifierPublicKey,
|
||||
pub identifier: Identifier,
|
||||
pub ssk: SharedSecretKey,
|
||||
pub vpk: ViewingPublicKey,
|
||||
pub epk: EphemeralPublicKey,
|
||||
@ -94,12 +100,17 @@ impl AccountManager {
|
||||
|
||||
(State::Private(pre), mask)
|
||||
}
|
||||
PrivacyPreservingAccount::PrivateForeign { npk, vpk } => {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => {
|
||||
let acc = nssa_core::account::Account::default();
|
||||
let auth_acc = AccountWithMetadata::new(acc, false, &npk);
|
||||
let auth_acc = AccountWithMetadata::new(acc, false, (&npk, identifier));
|
||||
let pre = AccountPreparedData {
|
||||
nsk: None,
|
||||
npk,
|
||||
identifier,
|
||||
vpk,
|
||||
pre_state: auth_acc,
|
||||
proof: None,
|
||||
@ -168,6 +179,7 @@ impl AccountManager {
|
||||
|
||||
Some(PrivateAccountKeys {
|
||||
npk: pre.npk,
|
||||
identifier: pre.identifier,
|
||||
ssk: eph_holder.calculate_shared_secret_sender(&pre.vpk),
|
||||
vpk: pre.vpk.clone(),
|
||||
epk: eph_holder.generate_ephemeral_public_key(),
|
||||
@ -226,6 +238,7 @@ impl AccountManager {
|
||||
struct AccountPreparedData {
|
||||
nsk: Option<NullifierSecretKey>,
|
||||
npk: NullifierPublicKey,
|
||||
identifier: Identifier,
|
||||
vpk: ViewingPublicKey,
|
||||
pre_state: AccountWithMetadata,
|
||||
proof: Option<MembershipProof>,
|
||||
@ -271,6 +284,7 @@ async fn private_pda_preparation(
|
||||
Ok(AccountPreparedData {
|
||||
nsk: exists.then_some(nsk),
|
||||
npk,
|
||||
identifier: 0,
|
||||
vpk,
|
||||
pre_state,
|
||||
proof,
|
||||
@ -281,11 +295,8 @@ 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()
|
||||
let Some((from_keys, from_acc, from_identifier)) =
|
||||
wallet.storage.user_data.get_private_account(account_id)
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
@ -303,11 +314,12 @@ async fn private_acc_preparation(
|
||||
|
||||
// TODO: Technically we could allow unauthorized owned accounts, but currently we don't have
|
||||
// support from that in the wallet.
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk);
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, (&from_npk, from_identifier));
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk: Some(nsk),
|
||||
npk: from_npk,
|
||||
identifier: from_identifier,
|
||||
vpk: from_vpk,
|
||||
pre_state: sender_pre,
|
||||
proof,
|
||||
|
||||
@ -2,7 +2,7 @@ use std::vec;
|
||||
|
||||
use common::HashType;
|
||||
use nssa::{AccountId, program::Program};
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
|
||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||
use crate::{ExecutionFailureKind, PrivacyPreservingAccount};
|
||||
@ -33,6 +33,7 @@ impl NativeTokenTransfer<'_> {
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_vpk: ViewingPublicKey,
|
||||
to_identifier: Identifier,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
@ -44,6 +45,7 @@ impl NativeTokenTransfer<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: to_npk,
|
||||
vpk: to_vpk,
|
||||
identifier: to_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use common::HashType;
|
||||
use nssa::AccountId;
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
|
||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||
use crate::{ExecutionFailureKind, PrivacyPreservingAccount};
|
||||
@ -39,6 +39,7 @@ impl NativeTokenTransfer<'_> {
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_vpk: ViewingPublicKey,
|
||||
to_identifier: Identifier,
|
||||
balance_to_move: u128,
|
||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||
@ -50,6 +51,7 @@ impl NativeTokenTransfer<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: to_npk,
|
||||
vpk: to_vpk,
|
||||
identifier: to_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use common::{HashType, transaction::NSSATransaction};
|
||||
use nssa::{AccountId, program::Program};
|
||||
use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use token_core::Instruction;
|
||||
|
||||
@ -247,6 +247,7 @@ impl Token<'_> {
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_vpk: ViewingPublicKey,
|
||||
recipient_identifier: Identifier,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = Instruction::Transfer {
|
||||
@ -262,6 +263,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: recipient_npk,
|
||||
vpk: recipient_vpk,
|
||||
identifier: recipient_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
@ -343,6 +345,7 @@ impl Token<'_> {
|
||||
sender_account_id: AccountId,
|
||||
recipient_npk: NullifierPublicKey,
|
||||
recipient_vpk: ViewingPublicKey,
|
||||
recipient_identifier: Identifier,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = Instruction::Transfer {
|
||||
@ -358,6 +361,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: recipient_npk,
|
||||
vpk: recipient_vpk,
|
||||
identifier: recipient_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
@ -606,6 +610,7 @@ impl Token<'_> {
|
||||
definition_account_id: AccountId,
|
||||
holder_npk: NullifierPublicKey,
|
||||
holder_vpk: ViewingPublicKey,
|
||||
holder_identifier: Identifier,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let instruction = Instruction::Mint {
|
||||
@ -621,6 +626,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: holder_npk,
|
||||
vpk: holder_vpk,
|
||||
identifier: holder_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
@ -702,6 +708,7 @@ impl Token<'_> {
|
||||
definition_account_id: AccountId,
|
||||
holder_npk: NullifierPublicKey,
|
||||
holder_vpk: ViewingPublicKey,
|
||||
holder_identifier: Identifier,
|
||||
amount: u128,
|
||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||
let instruction = Instruction::Mint {
|
||||
@ -717,6 +724,7 @@ impl Token<'_> {
|
||||
PrivacyPreservingAccount::PrivateForeign {
|
||||
npk: holder_npk,
|
||||
vpk: holder_vpk,
|
||||
identifier: holder_identifier,
|
||||
},
|
||||
],
|
||||
instruction_data,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user