diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ff2cd..90e4b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.1] + +### Added +- Add the compact module to make the web assembly API compatiblite on non rust endpoints +- Add support for phoenix_core methods +- Add support for stake contract functions +- Add support for transfer contract functions + +### Changed +- Change the whole api to support `wasm32-unkonwn-unknown` +- Set json as the function argument and return value format with types defined in assets/schema.json + +### Fixed +- Pass in the Fee and the crossover instead of creating it from rng at wallet-core side which caused courrpted proof +- Fix the input selecting algorithm from custom new one to old wallet-core to fix wrong picking of notes +- Fix the execute to work with only one public spend key and one `Transaction` +- Fix double rkyv serialization bug #113 + +## [Old Changelog below] + ## [Unreleased] ### Added @@ -121,6 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#44]: https://github.com/dusk-network/wallet-core/issues/44 [#41]: https://github.com/dusk-network/wallet-core/issues/41 [#40]: https://github.com/dusk-network/wallet-core/issues/40 +[#113]: https://github.com/dusk-network/wallet-core/issues/113 [#34]: https://github.com/dusk-network/wallet-core/issues/34 [#31]: https://github.com/dusk-network/wallet-core/issues/31 [#25]: https://github.com/dusk-network/wallet-core/issues/25 diff --git a/Cargo.toml b/Cargo.toml index d310388..49cd890 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-wallet-core" -version = "0.21.6" +version = "0.21.8" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0" @@ -21,7 +21,7 @@ dusk-jubjub = { version = "0.14", default-features = false } jubjub-schnorr = { version = "0.2", default-features = false, features = [ "rkyv-impl", "alloc", - "double", + "double", ] } phoenix-core = { version = "0.26", default-features = false, features = [ "alloc", @@ -73,3 +73,8 @@ wasmtime = "20" [build-dependencies] schemafy_lib = "0.6" + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 'z' diff --git a/assets/dusk_wallet_core.wasm b/assets/dusk_wallet_core.wasm deleted file mode 100755 index c0c1655..0000000 Binary files a/assets/dusk_wallet_core.wasm and /dev/null differ diff --git a/assets/schema.json b/assets/schema.json index 3cedb8d..5dabe35 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -5,9 +5,7 @@ "SeedArgs": { "description": "The arguments of the seed function", "type": "object", - "required": [ - "passphrase" - ], + "required": ["passphrase"], "properties": { "passphrase": { "description": "An arbitrary sequence of bytes used to generate a secure seed", @@ -23,10 +21,7 @@ "BalanceArgs": { "description": "The arguments of the balance function", "type": "object", - "required": [ - "notes", - "seed" - ], + "required": ["notes", "seed"], "properties": { "notes": { "description": "A rkyv serialized [Vec]; all notes should have their keys derived from `seed`", @@ -53,10 +48,7 @@ "BalanceResponse": { "description": "The response of the balance function", "type": "object", - "required": [ - "maximum", - "value" - ], + "required": ["maximum", "value"], "properties": { "maximum": { "description": "Maximum value per transaction", @@ -75,11 +67,7 @@ "ExecuteCall": { "description": "A call to a contract method", "type": "object", - "required": [ - "contract", - "method", - "payload" - ], + "required": ["contract", "method", "payload"], "properties": { "contract": { "description": "The id of the contract to call in Base58 format", @@ -103,19 +91,12 @@ "OutputType": { "description": "A note type variant", "type": "string", - "enum": [ - "Transparent", - "Obfuscated" - ] + "enum": ["Transparent", "Obfuscated"] }, "ExecuteOutput": { "description": "The output of a transfer", "type": "object", - "required": [ - "note_type", - "value", - "receiver" - ], + "required": ["note_type", "value", "receiver"], "properties": { "note_type": { "description": "The type of the note", @@ -142,11 +123,7 @@ "CrossoverType": { "description": "The value of the Crossover and the blinder", "type": "object", - "required": [ - "crossover", - "blinder", - "value" - ], + "required": ["crossover", "blinder", "value"], "properties": { "crossover": { "description": "The rkyv serialized bytes of the crossover struct", @@ -287,9 +264,7 @@ "MergeNotesArgs": { "description": "The arguments of the merge_notes function", "type": "object", - "required": [ - "notes" - ], + "required": ["notes"], "properties": { "notes": { "description": "All serialized list of notes to be merged", @@ -308,10 +283,7 @@ "FilterNotesArgs": { "description": "The arguments of the filter_notes function", "type": "object", - "required": [ - "flags", - "notes" - ], + "required": ["flags", "notes"], "properties": { "flags": { "description": "Boolean flags to be negative filtered", @@ -334,9 +306,7 @@ "PublicKeysArgs": { "description": "The arguments of the public_keys function", "type": "object", - "required": [ - "seed" - ], + "required": ["seed"], "properties": { "seed": { "description": "Seed used to derive the keys of the wallet", @@ -354,9 +324,7 @@ "PublicKeysResponse": { "description": "The response of the public_keys function", "type": "object", - "required": [ - "keys" - ], + "required": ["keys"], "properties": { "keys": { "description": "The Base58 public keys of the wallet.", @@ -370,9 +338,7 @@ "ViewKeysArgs": { "description": "The arguments of the view_keys function", "type": "object", - "required": [ - "seed" - ], + "required": ["seed"], "properties": { "seed": { "description": "Seed used to derive the keys of the wallet", @@ -390,10 +356,7 @@ "NullifiersArgs": { "description": "The arguments of the nullifiers function", "type": "object", - "required": [ - "notes", - "seed" - ], + "required": ["notes", "seed"], "properties": { "notes": { "description": "A rkyv serialized [Vec] to have nullifiers generated", @@ -433,9 +396,7 @@ "RkyvTreeLeaf": { "description": "The arguments of the balance function", "type": "object", - "required": [ - "bytes" - ], + "required": ["bytes"], "properties": { "bytes": { "description": "Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf", @@ -448,41 +409,33 @@ } } }, - "RkyvTreeLeafResponse": { - "description": "The response of the public_keys function", + "RkyvTreeLeafArgs": { + "description": "The arguments of the rkyv tree leaf function", "type": "object", - "required": [ - "block_height", - "note", - "last_pos" - ], + "required": ["bytes", "seed"], "properties": { - "block_height": { - "description": "The block height of the note.", - "type": "integer", - "format": "uint64" - }, - "note": { - "description": "Bytes of note at the block_height", + "bytes": { + "description": "Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf", "type": "array", "items": { "type": "integer", "format": "uint8" } }, - "last_pos": { - "description": "Last position of the note", - "type": "integer", - "format": "uint64" + "seed": { + "description": "Seed used to derive the keys of the wallet", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } } } }, "RkyvNotesArray": { "description": "The arguments of the rkyv_notes_array function", "type": "object", - "required": [ - "notes" - ], + "required": ["notes"], "properties": { "notes": { "description": "Array of notes which are rkyv serialized", @@ -576,49 +529,54 @@ } } }, - "CheckNoteOwnershipArgs": { - "description": "Arguments of the check_note_ownership function", + "CheckNoteOwnershipResponse": { + "description": "Response of check_note_ownership function", "type": "object", - "required": ["note", "seed"], + "required": [ + "notes", + "last_pos", + "block_heights", + "public_spend_keys", + "nullifiers" + ], "properties": { - "note": { - "description": "A singular note we want to check the validity of", + "notes": { + "description": "The raw owned note", "type": "array", "items": { - "type": "integer", - "format": "uint8" + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } } }, - "seed": { - "description": "The seed to generate the view keys from", + "last_pos": { + "description": "The last position of the note", + "type": "integer", + "format": "uint64" + }, + "block_heights": { + "description": "The block heights of the notes in the same order the notes were returned seperated by comma", + "type": "string" + }, + "public_spend_keys": { + "description": "The public spend keys of the notes in the same order the notes were returned", "type": "array", "items": { - "type": "integer", - "format": "uint8" + "type": "string" } - } - } - }, - "CheckNoteOwnershipResponse": { - "description": "Response of check_note_ownership function", - "type": "object", - "required": ["is_owned", "nullifier"], - "properties": { - "is_owned": { - "description": "Is the note owned by any of the view keys in the provided seed", - "type": "boolean" }, - "nullifier": { - "description": "Nullifier of the note that we were checking the ownership of", + "nullifiers": { + "description": "The nullifiers of the notes in the same order the notes were returned", "type": "array", "items": { - "type": "integer", - "format": "uint8" + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } } - }, - "public_key": { - "description": "A base 58 encoded public key string", - "type": "string" } } }, @@ -745,7 +703,13 @@ "UnspentSpentNotesArgs": { "description": "Arguents of the unspent_spent_notes function", "type": "object", - "required": ["notes", "nullifiers_of_notes", "block_heights", "existing_nullifiers", "pks"], + "required": [ + "notes", + "nullifiers_of_notes", + "block_heights", + "existing_nullifiers", + "pks" + ], "properties": { "notes": { "description": "The Array of rkyv serialized notes", @@ -854,7 +818,15 @@ "GetStctProofArgs": { "description": "Get the bytes for the stct proof to send to the node", "type": "object", - "required": ["rng_seed", "seed", "refund", "value", "sender_index", "gas_limit", "gas_price"], + "required": [ + "rng_seed", + "seed", + "refund", + "value", + "sender_index", + "gas_limit", + "gas_price" + ], "properties": { "rng_seed": { "description": "The rng seed to generate the entropy for the notes", @@ -1099,7 +1071,13 @@ "GetUnstakeCallDataArgs": { "description": "Args of the get_unstake_call_data function", "type": "object", - "required": ["seed", "sender_index", "unstake_note", "counter", "unstake_proof"], + "required": [ + "seed", + "sender_index", + "unstake_note", + "counter", + "unstake_proof" + ], "properties": { "seed": { "description": "The seed to generate the sender keys from", @@ -1187,7 +1165,16 @@ "GetAllowCallDataArgs": { "description": "Arguments for get_allow_call_data function", "type": "object", - "required": ["seed", "rng_seed", "sender_index", "refund", "owner_index", "counter", "gas_limit", "gas_price"], + "required": [ + "seed", + "rng_seed", + "sender_index", + "refund", + "owner_index", + "counter", + "gas_limit", + "gas_price" + ], "properties": { "seed": { "description": "Seed of the wallet", @@ -1239,7 +1226,14 @@ "GetAllowCallDataResponse": { "description": "Response of the get_allow_call_data function", "type": "object", - "required": ["contract", "method", "payload", "blinder", "crossover", "fee"], + "required": [ + "contract", + "method", + "payload", + "blinder", + "crossover", + "fee" + ], "properties": { "contract": { "description": "The id of the contract to call in Base58 format", @@ -1376,15 +1370,19 @@ "TransactionDirectionType": { "description": "The direction of the transaction", "type": "string", - "enum": [ - "In", - "Out" - ] + "enum": ["In", "Out"] }, "TransactionHistoryType": { "description": "The type of the transaction history", "type": "object", - "required": ["direction", "block_height", "fee", "amount", "id", "tx_type"], + "required": [ + "direction", + "block_height", + "fee", + "amount", + "id", + "tx_type" + ], "properties": { "direction": { "description": "The direction of the transaction, in or out", diff --git a/build.sh b/build.sh deleted file mode 100644 index 71be775..0000000 --- a/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -make wasm -cp target/wasm32-unknown-unknown/release/dusk_wallet_core.wasm ./assets -wasm-opt -O3 ./assets/dusk_wallet_core.wasm -o "dusk-wallet-core-0.21.0.wasm" -cp ./dusk-wallet-core-0.21.0.wasm ../../Web/dusk-wallet-js/assets diff --git a/dusk-wallet-core-0.21.0.wasm b/dusk-wallet-core-0.21.0.wasm deleted file mode 100644 index 5ac33bc..0000000 Binary files a/dusk-wallet-core-0.21.0.wasm and /dev/null differ diff --git a/src/compat/crypto.rs b/src/compat/crypto.rs index 7c2595e..26f7f32 100644 --- a/src/compat/crypto.rs +++ b/src/compat/crypto.rs @@ -4,12 +4,18 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use core::mem::size_of; + use dusk_bls12_381::BlsScalar; use dusk_bytes::Serializable; -use phoenix_core::{Note, PublicKey, ViewKey}; +use phoenix_core::{ + transaction::{ArchivedTreeLeaf, TreeLeaf}, + Note, PublicKey, +}; -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; +use crate::alloc::borrow::ToOwned; use crate::{ key::{self}, types::{self}, @@ -17,60 +23,85 @@ use crate::{ MAX_KEY, MAX_LEN, }; +const TREE_LEAF_SIZE: usize = size_of::(); + /// Returns true or false if the note is owned by the index /// if its true then nullifier of that note if sent with it #[no_mangle] pub fn check_note_ownership(args: i32, len: i32) -> i64 { - // we just use BalanceArgs again as we don't want to add more cluter types - // when the data you want is the same - let types::CheckNoteOwnershipArgs { note, seed } = - match utils::take_args(args, len) { - Some(a) => a, - None => return utils::fail(), - }; + let args = utils::take_args_raw(args, len); + + let seed = &args[..64]; + let leaves: &[u8] = &args[64..]; - let seed = match utils::sanitize_seed(seed) { + let seed = match seed.try_into().ok() { Some(s) => s, None => return utils::fail(), }; - let note: Note = match rkyv::from_bytes(¬e) { - Ok(n) => n, - Err(_) => return utils::fail(), - }; - - let mut is_owned: bool = false; - let mut nullifier_found = BlsScalar::default(); - let mut pk_found: Option = None; + let mut leaf_chunk = leaves.chunks_exact(TREE_LEAF_SIZE); + let mut last_pos = 0; - for idx in 0..=MAX_KEY { - let idx = idx as u64; - let sk = key::derive_sk(&seed, idx); - let vk = ViewKey::from(&sk); + let mut notes = Vec::new(); + let mut nullifiers = Vec::new(); + let mut block_heights = Vec::new(); + let mut public_spend_keys = Vec::new(); - if vk.owns(¬e) { - let nullifier = note.gen_nullifier(&sk); - - nullifier_found = nullifier; - is_owned = true; - pk_found = Some(PublicKey::from(&sk)); + for leaf_bytes in leaf_chunk.by_ref() { + let TreeLeaf { block_height, note } = + match rkyv::from_bytes(leaf_bytes).ok() { + Some(a) => a, + None => { + return utils::fail(); + } + }; - break; + last_pos = core::cmp::max(last_pos, *note.pos()); + + for idx in 0..=MAX_KEY { + let idx = idx as u64; + let view_key = key::derive_vk(&seed, idx); + + if view_key.owns(¬e) { + let sk = key::derive_sk(&seed, idx); + let nullifier = note.gen_nullifier(&sk); + + let nullifier_found = + match rkyv::to_bytes::(&nullifier).ok() + { + Some(n) => n.to_vec(), + None => return utils::fail(), + }; + + let psk_found = + bs58::encode(PublicKey::from(sk).to_bytes()).into_string(); + + let raw_note: Vec = + match rkyv::to_bytes::(¬e).ok() { + Some(n) => n.to_vec(), + None => return utils::fail(), + }; + + notes.push(raw_note.to_owned()); + block_heights.push(block_height); + public_spend_keys.push(psk_found); + nullifiers.push(nullifier_found); + } } } - let pk_found = pk_found.map(|pk| bs58::encode(pk.to_bytes()).into_string()); - - let nullifier_found = - match rkyv::to_bytes::(&nullifier_found).ok() { - Some(n) => n.to_vec(), - None => return utils::fail(), - }; + let block_heights = block_heights + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(","); utils::into_ptr(types::CheckNoteOwnershipResponse { - is_owned, - nullifier: nullifier_found, - public_key: pk_found, + notes, + block_heights, + public_spend_keys, + nullifiers, + last_pos, }) } diff --git a/src/compat/mod.rs b/src/compat/mod.rs index fea6eb1..a03ff5a 100644 --- a/src/compat/mod.rs +++ b/src/compat/mod.rs @@ -4,8 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -/// Includes functions to interact with the stake contract allow tx -pub mod allow; /// Helping us with the crypto primitives pub mod crypto; /// Includes methods to deal with bip39::Mnemonic diff --git a/src/compat/rkyv.rs b/src/compat/rkyv.rs index 4abdb72..aeca6e2 100644 --- a/src/compat/rkyv.rs +++ b/src/compat/rkyv.rs @@ -13,7 +13,7 @@ use crate::{ use bls12_381_bls::PublicKey as StakePublicKey; use dusk_bls12_381::BlsScalar; -use phoenix_core::{transaction::TreeLeaf, Note}; +use phoenix_core::Note; use alloc::vec::Vec; @@ -28,33 +28,6 @@ pub fn rkyv_u64(args: i32, len: i32) -> i64 { utils::rkyv_into_ptr(value) } -/// Get block_heignt a rkyv serialized note from a tree leaf -#[no_mangle] -pub fn rkyv_tree_leaf(args: i32, len: i32) -> i64 { - let types::RkyvTreeLeaf { bytes } = match utils::take_args(args, len) { - Some(a) => a, - None => return utils::fail(), - }; - - let TreeLeaf { block_height, note } = match rkyv::from_bytes(&bytes) { - Ok(n) => n, - Err(_) => return utils::fail(), - }; - - let last_pos = *note.pos(); - - let note = match rkyv::to_bytes::<_, MAX_LEN>(¬e).ok() { - Some(t) => t.into_vec(), - None => return utils::fail(), - }; - - utils::into_ptr(types::RkyvTreeLeafResponse { - block_height, - note, - last_pos, - }) -} - /// Convert a Vec (where note is a U8initArray into a rkyv serialized /// Vec #[no_mangle] @@ -97,12 +70,6 @@ pub fn rkyv_bls_scalar_array(args: i32, len: i32) -> i64 { } } - let bls_scalars = - match rkyv::to_bytes::, MAX_LEN>(&bls_scalars).ok() { - Some(v) => v.to_vec(), - None => return utils::fail(), - }; - utils::rkyv_into_ptr(bls_scalars) } diff --git a/src/lib.rs b/src/lib.rs index 8abcfae..2d8e023 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub mod types; pub mod utils; /// The maximum number of keys (inclusive) to derive when attempting to decrypt /// a note. -pub const MAX_KEY: usize = 24; +pub const MAX_KEY: usize = 0; /// The maximum allocated buffer for rkyv serialization. pub const MAX_LEN: usize = rusk_abi::ARGBUF_LEN; diff --git a/src/types.rs b/src/types.rs index efff049..ad61b70 100644 --- a/src/types.rs +++ b/src/types.rs @@ -31,24 +31,19 @@ pub struct BalanceResponse { #[doc = " Total computed balance"] pub value: u64, } -#[doc = " Arguments of the check_note_ownership function"] -#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct CheckNoteOwnershipArgs { - #[doc = " A singular note we want to check the validity of"] - pub note: Vec, - #[doc = " The seed to generate the view keys from"] - pub seed: Vec, -} #[doc = " Response of check_note_ownership function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct CheckNoteOwnershipResponse { - #[doc = " Is the note owned by any of the view keys in the provided seed"] - pub is_owned: bool, - #[doc = " Nullifier of the note that we were checking the ownership of"] - pub nullifier: Vec, - #[doc = " A base 58 encoded public key string"] - #[serde(skip_serializing_if = "Option::is_none")] - pub public_key: Option, + #[doc = " The block heights of the notes in the same order the notes were returned seperated by comma"] + pub block_heights: String, + #[doc = " The last position of the note"] + pub last_pos: u64, + #[doc = " The raw owned note"] + pub notes: Vec>, + #[doc = " The nullifiers of the notes in the same order the notes were returned"] + pub nullifiers: Vec>, + #[doc = " The public spend keys of the notes in the same order the notes were returned"] + pub public_spend_keys: Vec, } #[doc = " The value of the Crossover and the blinder"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -453,15 +448,13 @@ pub struct RkyvTreeLeaf { #[doc = " Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf"] pub bytes: Vec, } -#[doc = " The response of the public_keys function"] +#[doc = " The arguments of the rkyv tree leaf function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] -pub struct RkyvTreeLeafResponse { - #[doc = " The block height of the note."] - pub block_height: u64, - #[doc = " Last position of the note"] - pub last_pos: u64, - #[doc = " Bytes of note at the block_height"] - pub note: Vec, +pub struct RkyvTreeLeafArgs { + #[doc = " Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf"] + pub bytes: Vec, + #[doc = " Seed used to derive the keys of the wallet"] + pub seed: Vec, } #[doc = " A serialized u64 using rkyv"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] diff --git a/src/utils.rs b/src/utils.rs index bbccded..77405c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -63,6 +63,14 @@ where serde_json::from_str(&args).ok() } +/// reads the raw bytes at the pointer for the length and returns what it reason +pub fn take_args_raw<'a>(args: i32, len: i32) -> &'a [u8] { + let args = args as *mut u8; + let len = len as usize; + + unsafe { core::slice::from_raw_parts(args, len) } +} + /// Sanitizes arbitrary bytes into well-formed seed. pub fn sanitize_seed(bytes: Vec) -> Option<[u8; RNG_SEED]> { (bytes.len() == RNG_SEED).then(|| {