diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..86f822e Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index aad3afe..df01a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,72 @@ [package] name = "dusk-wallet-core" -version = "0.21.0" +version = "0.21.2" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0" +respository = "https://github.com/dusk-network/wallet-core" [lib] crate-type = ["cdylib", "rlib"] [dependencies] -bytecheck = { version = "0.6", default-features = false } -bs58 = { version = "0.5", default-features = false, features = ["alloc", "cb58"] } -dusk-bls12_381-sign = { version = "0.4", default-features = false } -dusk-bytes = "^0.1" -dusk-jubjub = { version = "0.12", default-features = false } -dusk-pki = { version = "0.12", default-features = false, features = ["rkyv-impl"] } -dusk-schnorr = { version = "0.13", default-features = false } -phoenix-core = { version = "0.20.0-rc.0", default-features = false, features = ["alloc", "rkyv-impl"] } -poseidon-merkle = { version = "0.2.1-rc.0", features = ["rkyv-impl"] } +bytecheck = { version = "0.6.11", default-features = false } +bs58 = { version = "0.5", default-features = false, features = [ + "alloc", + "cb58", +] } +dusk-bls12_381-sign = { version = "0.5", default-features = false } +dusk-bytes = "0.1.7" +dusk-jubjub = { version = "0.13.1", default-features = false } +dusk-pki = { version = "0.13", default-features = false, features = [ + "rkyv-impl", +] } +dusk-schnorr = { version = "0.14", default-features = false, features = [ + "rkyv-impl", + "alloc", +] } +phoenix-core = { version = "0.21", default-features = false, features = [ + "alloc", + "rkyv-impl", +] } +poseidon-merkle = { version = "0.3", features = ["rkyv-impl"] } rand_chacha = { version = "^0.3", default-features = false } rand_core = "^0.6" rkyv = { version = "^0.7", default-features = false, features = ["size_32"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } sha2 = { version = "^0.10", default-features = false } +bip39 = { version = "2.0.0", optional = true, default-features = false } +dusk-plonk = { version = "0.16", default-features = false, features = [ + "alloc", + "rkyv-impl", +] } +dusk-poseidon = { version = "0.31", default-features = false, features = [ + "alloc", + "rkyv-impl", +] } +ff = { version = "0.13", default-features = false } +dusk-bls12_381 = { version = "0.12.3", default-features = false, features = [ + "alloc", + "rkyv-impl", +] } +hex = { version = "0.4", default_features = false, features = ["alloc"] } +hashbrown = "0.14.3" + + +[features] +# rust platforms can use this module without the compact feature +default = ["compat"] +compat = ["dep:bip39"] [target.'cfg(target_family = "wasm")'.dependencies] -rusk-abi = "0.10.0-piecrust.0.6" +rusk-abi = "0.11" [target.'cfg(not(target_family = "wasm"))'.dependencies] -rusk-abi = { version = "0.10.0-piecrust.0.6", default-features = false } +rusk-abi = { version = "0.11", default-features = false } [dev-dependencies] rand = "^0.8" diff --git a/assets/dusk-wallet-core-0.21.0.wasm b/assets/dusk-wallet-core-0.21.0.wasm deleted file mode 100644 index b00b491..0000000 Binary files a/assets/dusk-wallet-core-0.21.0.wasm and /dev/null differ diff --git a/assets/dusk_wallet_core.wasm b/assets/dusk_wallet_core.wasm index 187253c..a50e7ab 100755 Binary files a/assets/dusk_wallet_core.wasm and b/assets/dusk_wallet_core.wasm differ diff --git a/assets/schema.json b/assets/schema.json index 7c418f1..4235e36 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -139,6 +139,40 @@ } } }, + "CrossoverType": { + "description": "The value of the Crossover and the blinder", + "type": "object", + "required": [ + "crossover", + "blinder", + "value" + ], + "properties": { + "crossover": { + "description": "The rkyv serialized bytes of the crossover struct", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "blinder": { + "description": "The rkyv serialized blinder of the crossover", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "value": { + "description": "The value of the crossover", + "type": "integer", + "format": "uint64" + } + } + }, "ExecuteArgs": { "description": "The arguments of the execute function", "type": "object", @@ -149,6 +183,7 @@ "openings", "refund", "rng_seed", + "sender_index", "seed" ], "properties": { @@ -157,22 +192,27 @@ "$ref": "#/definitions/ExecuteCall" }, "crossover": { - "description": "The [phoenix_core::Crossover] value", - "type": "integer", - "format": "uint64", - "minimum": 0 + "description": "The crossover value", + "$ref": "#/definitions/CrossoverType" + }, + "fee": { + "description": "A rkyv serialized Fee", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } }, "gas_limit": { "description": "The gas limit of the transaction", "type": "integer", - "format": "uint64", - "minimum": 0 + "format": "uint64" }, "gas_price": { "description": "The gas price per unit for the transaction", "type": "integer", - "format": "uint64", - "minimum": 0 + "format": "uint64" }, "inputs": { "description": "A rkyv serialized [Vec] to be used as inputs", @@ -184,7 +224,7 @@ } }, "openings": { - "description": "A rkyv serialized [Vec] to open the inputs to a Merkle root", + "description": "A rkyv serialized [Vec] to open the inputs to a Merkle root, along with the positions of the notes the openings are of in a tuple (opening, position) rkyv serialized, see rkyv.rs/rkyv_openings_array", "type": "array", "items": { "type": "integer", @@ -208,8 +248,8 @@ "format": "uint8", "minimum": 0 }, - "maxItems": 64, - "minItems": 64 + "maxItems": 32, + "minItems": 32 }, "seed": { "description": "Seed used to derive the keys of the wallet", @@ -221,33 +261,25 @@ }, "maxItems": 64, "minItems": 64 + }, + "sender_index": { + "description": "The index of the sender in the seed", + "type": "integer", + "format": "uint64" } } }, "ExecuteResponse": { - "description": "The response of the execute function", + "description": "Response of the execute function", + "required": ["tx"], "type": "object", - "required": [ - "tx", - "unspent" - ], "properties": { "tx": { - "description": "A rkyv serialized [crate::tx::UnspentTransaction]", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0 - } - }, - "unspent": { - "description": "A rkyv serialized [Vec] containing the notes that weren't used", + "description": "The rkyv serialized unproven transaction", "type": "array", "items": { "type": "integer", - "format": "uint8", - "minimum": 0 + "format": "uint8" } } } @@ -384,6 +416,1014 @@ "minItems": 64 } } + }, + "RkyvU64": { + "description": "A serialized u64 using rkyv", + "type": "object", + "required": ["value"], + "properties": { + "value": { + "description": "A u64 rust string, representing a valid rust u64 (max: 18446744073709551615)", + "type": "integer", + "format": "uint64", + "minimum": 0 + } + } + }, + "RkyvTreeLeaf": { + "description": "The arguments of the balance function", + "type": "object", + "required": [ + "bytes" + ], + "properties": { + "bytes": { + "description": "Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 632 + } + } + } + }, + "RkyvTreeLeafResponse": { + "description": "The response of the public_spend_keys function", + "type": "object", + "required": [ + "block_height", + "note", + "last_pos" + ], + "properties": { + "block_height": { + "description": "The block height of the note.", + "type": "integer", + "format": "uint64" + }, + "note": { + "description": "Bytes of note at the block_height", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "last_pos": { + "description": "Last position of the note", + "type": "integer", + "format": "uint64" + } + } + }, + "RkyvNotesArray": { + "description": "The arguments of the rkyv_notes_array function", + "type": "object", + "required": [ + "notes" + ], + "properties": { + "notes": { + "description": "Array of notes which are rkyv serialized", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + } + }, + "MnemonicNewArgs": { + "description": "The arguments of the mnemonic_new function", + "type": "object", + "required": ["rng_seed"], + "properties": { + "rng_seed": { + "description": "Cryptographically secure [u8; 64]", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "maxItems": 64, + "minItems": 64 + } + } + } + }, + "MnewmonicNewResponse": { + "description": "Response of the new_mnemonic function", + "type": "object", + "required": ["mnemonic_string"], + "properties": { + "mnemonic_string": { + "description": "String from the generated mnemonic", + "type": "string" + } + } + }, + "GetMnemonicSeedArgs": { + "description": "Retrieve the seed bytes from the mnemonic and passphrase", + "type": "object", + "required": ["mnemonic", "passphrase"], + "properties": { + "mnemonic": { + "description": "The mnemonic string", + "type": "string" + }, + "passphrase": { + "description": "The passphrase tied to that mnemonic", + "type": "string" + } + } + }, + "GetMnemonicSeedResponse": { + "description": "Response of the get_mnemonic_seed function", + "type": "object", + "required": ["mnemonic_seed"], + "properties": { + "mnemonic_seed": { + "description": "Seed bytes from the given passphrase and Mnemonic", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "maxItems": 64, + "minItems": 64 + } + } + } + }, + "PublicSpendKeysAndNotesType": { + "description": "Type of the response of the check_note_validity function", + "type": "object", + "required": ["public_spend_key", "notes"], + "properties": { + "public_spend_key": { + "description": "The public spend key as a bs58 formated string", + "type": "string" + }, + "notes": { + "description": "Array of notes which are rkyv serialized", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "CheckNoteOwnershipArgs": { + "description": "Arguments of the check_note_ownership function", + "type": "object", + "required": ["note", "seed"], + "properties": { + "note": { + "description": "A singular note we want to check the validity of", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "seed": { + "description": "The seed to generate the view keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "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", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "public_spend_key": { + "description": "A base 58 encoded public spend key string", + "type": "string" + } + } + }, + "RkyvBlsScalarArrayArgs": { + "description": "Arguments of the rkyv_bls_scalar_array function", + "type": "object", + "required": ["bytes"], + "properties": { + "bytes": { + "description": "An array containing rkyv serialized bytes of each bls scalar", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + } + }, + "FilterNulifierNotesArgs": { + "description": "Arguments of the filter_nullifier_note function", + "type": "object", + "required": ["notes", "existing_nullifiers", "seed"], + "properties": { + "notes": { + "description": "notes we want to check the nullifiers of as a Vec", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "existing_nullifiers": { + "description": "The existing nullifiers that are spent as a Vec", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "seed": { + "description": "The seed to generate the view keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "RkyvOpeningsArray": { + "description": "Arguments of the rkyv_openings_array function", + "type": "object", + "required": ["openings"], + "properties": { + "openings": { + "description": "Vec containing the rkyv serialized bytes of each openings along with positions", + "type": "array", + "items": { + "$ref": "#/definitions/OpeningType" + } + } + } + }, + "UnprovenTxToBytesResponse": { + "description": "Arguments of the unproven_tx_to_bytes_response", + "type": "object", + "required": ["serialized"], + "properties": { + "serialized": { + "description": "Serialied unproven_Tx ready to be sent to the network", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "ProveTxArgs": { + "description": "Arguments of the prove_tx function", + "type": "object", + "required": ["unproven_tx", "proof"], + "properties": { + "unproven_tx": { + "description": "The unproven_tx bytes", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "proof": { + "description": "The bytes of the proof of the tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "ProveTxResponse": { + "description": "Response of the prove_tx function", + "type": "object", + "required": ["bytes", "hash"], + "properties": { + "bytes": { + "description": "The bytes of the proven transaction ready to be sent to the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "hash": { + "description": "The hash of the proven transaction", + "type": "string" + } + } + }, + "UnspentSpentNotesArgs": { + "description": "Arguents of the unspent_spent_notes function", + "type": "object", + "required": ["notes", "nullifiers_of_notes", "block_heights", "existing_nullifiers", "psks"], + "properties": { + "notes": { + "description": "The Array of rkyv serialized notes", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + }, + "nullifiers_of_notes": { + "description": "The Array of rkyv serialized nullifiers of the note in the same order as the notes", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + }, + "block_heights": { + "description": "The Array of block heights of thte notes in the same order as the notes", + "type": "array", + "items": { + "type": "number", + "format": "uint64" + } + }, + "existing_nullifiers": { + "description": "The UInt8Array of rkyv serialized nullifiers recieved from the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "psks": { + "description": "Array of bs58 encoded string to be sent with the response of the function", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "UnpsentSpentNotesResponse": { + "description": "Arguments of the unspent spent notes response", + "type": "object", + "required": ["unspent_notes", "spent_notes"], + "properties": { + "unspent_notes": { + "description": "The notes which are not spent yet", + "type": "array", + "items": { + "$ref": "#/definitions/NoteInfoType" + } + }, + "spent_notes": { + "description": "The notes which are spent", + "type": "array", + "items": { + "$ref": "#/definitions/NoteInfoType" + } + } + } + }, + "NoteInfoType": { + "description": "Information about the note", + "type": "object", + "required": ["pos", "psk", "note", "nullifier", "block_height"], + "properties": { + "pos": { + "description": "position of the note", + "type": "integer" + }, + "psk": { + "description": "public spend key belonging to that note", + "type": "string" + }, + "note": { + "description": "Singular Note rkyv serialized", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "nullifier": { + "description": "Nullifier of a Singular Note rkyv serialized", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "block_height": { + "description": "The block height of the note", + "type": "integer", + "format": "uint64" + } + } + }, + "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"], + "properties": { + "rng_seed": { + "description": "The rng seed to generate the entropy for the notes", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "seed": { + "description": "The seed to generate the sender keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "refund": { + "description": "The refund address in base58 format", + "type": "string" + }, + "value": { + "description": "The amount of value to send", + "type": "integer" + }, + "sender_index": { + "description": "index of the sender in the seed", + "type": "integer" + }, + "gas_limit": { + "description": "The gas limit of the transaction", + "type": "integer" + }, + "gas_price": { + "description": "The gas price of the transaction", + "type": "integer" + } + } + }, + "GetStctProofResponse": { + "description": "Response of the get_stct_proof function", + "type": "object", + "required": ["bytes", "signature", "crossover", "blinder", "fee"], + "properties": { + "bytes": { + "description": "The bytes of the stct proof to send to the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "signature": { + "description": "The signature of the stct proof", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "crossover": { + "description": "The crossover value of the stct proof", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "blinder": { + "description": "The blinder of the stct proof", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "fee": { + "description": "The Fee of the crossover note", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "GetStakeCallDataArgs": { + "description": "Get the call data for stakeing", + "type": "object", + "required": ["staker_index", "seed", "spend_proof", "value", "counter"], + "properties": { + "staker_index": { + "description": "Index of the address of the staker in the seed", + "type": "integer", + "format": "uint8" + }, + "seed": { + "description": "The seed to generate the sender keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "spend_proof": { + "description": "The stct proof as recieved from the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "value": { + "description": "The amount of value to stake", + "type": "integer", + "format": "uint64" + }, + "counter": { + "description": "The stake counter value", + "type": "integer", + "format": "uint64" + } + } + }, + "GetStakeCallDataResponse": { + "description": "Response of the get_stake_call_data function, send this to the call_data in execute", + "type": "object", + "required": ["contract", "method", "payload"], + "properties": { + "contract": { + "description": "The contract to call encoded in bs58 format", + "type": "string" + }, + "method": { + "description": "The method to call on the contract", + "type": "string" + }, + "payload": { + "description": "The payload of the call", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "DuskToLuxArgs": { + "description": "Arguments of the dusk_to_lux function", + "type": "object", + "required": ["dusk"], + "properties": { + "dusk": { + "description": "The amount of dusk to convert to lux", + "type": "integer", + "format": "uint64" + } + } + }, + "DuskToLuxResponse": { + "description": "Response of the dusk_to_lux function", + "type": "object", + "required": ["lux"], + "properties": { + "lux": { + "description": "The amount of lux that was converted from dusk", + "type": "number", + "format": "double" + } + } + }, + "GetStakeInfoArgs": { + "description": "Args of the get_stake_info function", + "type": "object", + "required": ["stake_info"], + "properties": { + "stake_info": { + "description": "The stake info of the stake obtained from the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "GetStakeInfoRespose": { + "description": "Response of the get_stake_info function", + "type": "object", + "required": ["has_staked", "has_key"], + "properties": { + "has_staked": { + "description": "Has the given address staked", + "type": "boolean" + }, + "amount": { + "description": "amount staked", + "type": "integer", + "format": "uint64" + }, + "eligiblity": { + "description": "eligiblity", + "type": "integer", + "format": "uint64" + }, + "reward": { + "description": "Reward for participating in concensus", + "type": "integer", + "format": "uint64" + }, + "counter": { + "description": "Signature counter to prevent replay", + "type": "integer", + "format": "uint64" + }, + "has_key": { + "description": "True if the key has been authorized to stake", + "type": "boolean" + } + } + }, + "GetPublicKeyRkyvSerializedArgs": { + "description": "Args of the get_public_key_rkyv_serialized function", + "type": "object", + "required": ["seed", "index"], + "properties": { + "seed": { + "description": "The seed to generate the sender keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "index": { + "description": "The index of the public key to get", + "type": "integer", + "format": "uint64" + } + } + }, + "GetUnstakeCallDataArgs": { + "description": "Args of the get_unstake_call_data function", + "type": "object", + "required": ["seed", "sender_index", "unstake_note", "counter", "unstake_proof"], + "properties": { + "seed": { + "description": "The seed to generate the sender keys from", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "sender_index": { + "description": "The index of the public key to get", + "type": "integer", + "format": "uint64" + }, + "unstake_note": { + "description": "The unstake note", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "counter": { + "description": "The counter of the unstake note", + "type": "integer", + "format": "uint64" + }, + "unstake_proof": { + "description": "The unstake proof", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "GetWfctProofResponse": { + "description": "Response of the get_wfct_proof function", + "type": "object", + "required": ["bytes", "unstake_note", "crossover", "blinder", "fee"], + "properties": { + "bytes": { + "description": "The bytes of the wfct proof to send to the node", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "unstake_note": { + "description": "The unstake note", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "crossover": { + "description": "Crossover of the tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "blinder": { + "description": "JubJubScalar Blinder for tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "fee": { + "description": "The fee of the tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "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"], + "properties": { + "seed": { + "description": "Seed of the wallet", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "rng_seed": { + "description": "random rng seed", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "sender_index": { + "description": "index of the sender of the tx", + "type": "integer", + "format": "uint64" + }, + "refund": { + "description": "psk in string of who to refund this tx to", + "type": "string" + }, + "owner_index": { + "description": "index of the owner of the stake", + "type": "integer", + "format": "uint64" + }, + "counter": { + "description": "Counter value from stakeinfo", + "type": "integer", + "format": "uint64" + }, + "gas_limit": { + "description": "gas_limit", + "type": "integer", + "format": "uint64" + }, + "gas_price": { + "description": "gas_price", + "type": "integer", + "format": "uint64" + } + } + }, + "GetAllowCallDataResponse": { + "description": "Response of the get_allow_call_data function", + "type": "object", + "required": ["contract", "method", "payload", "blinder", "crossover", "fee"], + "properties": { + "contract": { + "description": "The id of the contract to call in Base58 format", + "type": "string" + }, + "method": { + "description": "The name of the method to be called", + "type": "string" + }, + "payload": { + "description": "The payload of the call", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "blinder": { + "description": "Blinder used to make the crossover", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "crossover": { + "description": "Crossover of this tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "fee": { + "description": "The fee of the tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "OpeningType": { + "description": "The type represents the Opening and the position of the note, the opening is of", + "type": "object", + "required": ["pos", "opening"], + "properties": { + "pos": { + "description": "The position of the note the opening is of", + "type": "integer", + "format": "uint64" + }, + "opening": { + "description": "The rkyv serialized opening", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + } + } + }, + "TxDataType": { + "description": "Metadata of the transaction, used in calculating history", + "type": "object", + "required": ["raw_tx", "gas_spent"], + "properties": { + "raw_tx": { + "description": "The raw transaction bytes", + "type": "string" + }, + "gas_spent": { + "description": "The amount of gas spent in the transaction", + "type": "integer", + "format": "uint64" + } + } + }, + "TxsDataType": { + "description": "Collection of transactions at a given block height", + "type": "object", + "required": ["block_height", "txs"], + "properties": { + "block_height": { + "description": "The block height of the transactions", + "type": "integer", + "format": "uint64" + }, + "txs": { + "description": "The transactions at the given block height", + "type": "array", + "items": { + "$ref": "#/definitions/TxDataType" + } + } + } + }, + "GetHistoryArgs": { + "description": "arguments of the get_history function", + "type": "object", + "required": ["seed", "index", "notes", "tx_data"], + "properties": { + "seed": { + "description": "Seed of the wallet", + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "index": { + "description": "index of the key the notes belong to", + "type": "integer", + "format": "uint64" + }, + "notes": { + "description": "The notes of the wallet", + "type": "array", + "items": { + "$ref": "#/definitions/NoteInfoType" + } + }, + "tx_data": { + "description": "The tx data of the wallet", + "type": "array", + "items": { + "$ref": "#/definitions/TxsDataType" + } + } + } + }, + "TransactionDirectionType": { + "description": "The direction of the transaction", + "type": "string", + "enum": [ + "In", + "Out" + ] + }, + "TransactionHistoryType": { + "description": "The type of the transaction history", + "type": "object", + "required": ["direction", "block_height", "fee", "amount", "id"], + "properties": { + "direction": { + "description": "The direction of the transaction, in or out", + "$ref": "#/definitions/TransactionDirectionType" + }, + "block_height": { + "description": "The block height of the transaction", + "type": "integer", + "format": "uint64" + }, + "fee": { + "description": "The fee of the transaction", + "type": "integer", + "format": "uint64" + }, + "amount": { + "description": "The amount of the transaction", + "type": "number", + "format": "uint64" + }, + "id": { + "description": "The hash of the transaction", + "type": "string" + } + } + }, + "GetHistoryResponse": { + "description": "Response of the get_history function", + "type": "object", + "required": ["history"], + "properties": { + "history": { + "description": "The history of a address", + "type": "array", + "items": { + "$ref": "#/definitions/TransactionHistoryType" + } + } + } } } } diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..71be775 --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000..59f0771 Binary files /dev/null and b/dusk-wallet-core-0.21.0.wasm differ diff --git a/src/compat/allow.rs b/src/compat/allow.rs new file mode 100644 index 0000000..31e67f1 --- /dev/null +++ b/src/compat/allow.rs @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{key::*, types, utils, MAX_LEN}; + +use alloc::string::String; +use alloc::vec::Vec; + +use dusk_bls12_381_sign::{PublicKey, SecretKey, Signature as BlsSignature}; +use dusk_bytes::Serializable; +use dusk_jubjub::JubJubScalar; +use phoenix_core::{transaction::*, Note, *}; + +/// Get unstake call data +#[no_mangle] +pub fn get_allow_call_data(args: i32, len: i32) -> i64 { + let types::GetAllowCallDataArgs { + seed, + rng_seed, + sender_index, + refund, + owner_index, + counter, + gas_limit, + gas_price, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng_seed = match utils::sanitize_rng_seed(rng_seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let refund: dusk_pki::PublicSpendKey = match utils::bs58_to_psk(&refund) { + Some(a) => a, + None => return utils::fail(), + }; + + let sk = derive_sk(&seed, owner_index); + let staker = PublicKey::from(&sk); + + let owner_sk = derive_sk(&seed, sender_index); + let owner_pk = PublicKey::from(&owner_sk); + + let rng = &mut utils::rng(rng_seed); + + let signature = allow_sign(&owner_sk, &owner_pk, counter, &staker); + + let blinder = JubJubScalar::random(rng); + let note = Note::obfuscated(rng, &refund, 0, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let allow = Allow { + public_key: staker, + owner: owner_pk, + signature, + }; + + let contract = bs58::encode(rusk_abi::STAKE_CONTRACT).into_string(); + let method = String::from("allow"); + let payload = match rkyv::to_bytes::<_, MAX_LEN>(&allow).ok() { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + let crossover = match rkyv::to_bytes::(&crossover) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let blinder = match rkyv::to_bytes::(&blinder) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let fee = match rkyv::to_bytes::(&fee) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + // reusing this type + utils::into_ptr(types::GetAllowCallDataResponse { + contract, + method, + payload, + blinder, + crossover, + fee, + }) +} + +/// Creates a signature compatible with what the stake contract expects for a +/// ADD_ALLOWLIST transaction. +/// +/// The counter is the number of transactions that have been sent to the +/// transfer contract by a given key, and is reported in `StakeInfo`. +fn allow_sign( + sk: &SecretKey, + pk: &PublicKey, + counter: u64, + staker: &PublicKey, +) -> BlsSignature { + let mut msg = Vec::with_capacity(u64::SIZE + PublicKey::SIZE); + + msg.extend(counter.to_bytes()); + msg.extend(staker.to_bytes()); + + sk.sign(pk, &msg) +} diff --git a/src/compat/crypto.rs b/src/compat/crypto.rs new file mode 100644 index 0000000..87c70e3 --- /dev/null +++ b/src/compat/crypto.rs @@ -0,0 +1,174 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use dusk_bls12_381::BlsScalar; +use dusk_bytes::Serializable; +use phoenix_core::Note; + +use alloc::vec::Vec; + +use crate::{ + key::{self}, + types::{self}, + utils::{self}, + MAX_KEY, MAX_LEN, +}; + +/// 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 seed = match utils::sanitize_seed(seed) { + 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 psk_found: Option = None; + + 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 ssk = key::derive_ssk(&seed, idx); + let nullifier = note.gen_nullifier(&ssk); + + nullifier_found = nullifier; + is_owned = true; + psk_found = Some(ssk.public_spend_key()); + + break; + } + } + + let psk_found = + psk_found.map(|psk| bs58::encode(psk.to_bytes()).into_string()); + + let nullifier_found = + match rkyv::to_bytes::(&nullifier_found).ok() { + Some(n) => n.to_vec(), + None => return utils::fail(), + }; + + utils::into_ptr(types::CheckNoteOwnershipResponse { + is_owned, + nullifier: nullifier_found, + public_spend_key: psk_found, + }) +} + +/// Given array of notes, nullifiers of those notes and some existing +/// nullifiers, sort the notes into unspent and spent arrays +#[no_mangle] +pub fn unspent_spent_notes(args: i32, len: i32) -> i64 { + let types::UnspentSpentNotesArgs { + notes, + nullifiers_of_notes, + block_heights, + existing_nullifiers, + psks, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let existing_nullifiers = + match rkyv::from_bytes::>(&existing_nullifiers).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let mut spent_notes = Vec::new(); + let mut unspent_notes = Vec::new(); + + for (index, ((note, nullifier), psk)) in notes + .into_iter() + .zip(nullifiers_of_notes) + .zip(psks) + .enumerate() + { + let parsed_note: Note = match rkyv::from_bytes::(¬e).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let parsed_nullifier = + match rkyv::from_bytes::(&nullifier).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let block_height = match block_heights.get(index) { + Some(a) => *a as u64, + None => return utils::fail(), + }; + + if existing_nullifiers.contains(&parsed_nullifier) { + spent_notes.push(types::NoteInfoType { + pos: *parsed_note.pos(), + psk, + block_height, + note, + nullifier, + }); + } else { + unspent_notes.push(types::NoteInfoType { + pos: *parsed_note.pos(), + note, + block_height, + psk, + nullifier, + }); + } + } + + utils::into_ptr(types::UnpsentSpentNotesResponse { + spent_notes, + unspent_notes, + }) +} + +/// Convert dusk to lux to send to methods +#[no_mangle] +fn dusk_to_lux(args: i32, len: i32) -> i64 { + let types::DuskToLuxArgs { dusk } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + utils::into_ptr(types::DuskToLuxResponse { + lux: rusk_abi::dusk::from_dusk(dusk), + }) +} + +/// Convert lux to dusk +#[no_mangle] +fn lux_to_dusk(args: i32, len: i32) -> i64 { + // reusing the type from above, two less type definitions + let types::DuskToLuxResponse { lux } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + utils::into_ptr(types::DuskToLuxArgs { + dusk: rusk_abi::dusk::dusk(lux), + }) +} diff --git a/src/compat/mnemonic.rs b/src/compat/mnemonic.rs new file mode 100644 index 0000000..3d28867 --- /dev/null +++ b/src/compat/mnemonic.rs @@ -0,0 +1,66 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use bip39::Mnemonic; + +use crate::{ + types, + types::{GetMnemonicSeedArgs, MnemonicNewArgs}, + utils, +}; + +use alloc::string::ToString; + +/// Create a new mnemonic randomized on the seed bytes provided +/// Its the host's job to provide a crypto +/// secure seed because we cannot generate a secure rng +/// in no_std +#[no_mangle] +pub fn new_mnemonic(args: i32, len: i32) -> i64 { + let MnemonicNewArgs { rng_seed } = match utils::take_args(args, len) { + Some(val) => val, + None => return utils::fail(), + }; + + // check if we our seed is secure + let bytes_check: [u8; 32] = match rng_seed.try_into().ok() { + Some(bytes) => bytes, + None => return utils::fail(), + }; + + let mnemonic = match Mnemonic::from_entropy(&bytes_check).ok() { + Some(m) => m, + None => return utils::fail(), + }; + + utils::into_ptr(types::MnewmonicNewResponse { + mnemonic_string: mnemonic.to_string(), + }) +} + +/// Get the wallet seed bytes [u8; 64] from the given normalized +/// passphrase and Mnemomnic +#[no_mangle] +pub fn get_mnemonic_seed(args: i32, len: i32) -> i64 { + let GetMnemonicSeedArgs { + mnemonic, + passphrase, + } = match utils::take_args(args, len) { + Some(val) => val, + None => return utils::fail(), + }; + + let mnemonic = match Mnemonic::parse_normalized(&mnemonic).ok() { + Some(m) => m, + None => return utils::fail(), + }; + + let seed = mnemonic.to_seed_normalized(&passphrase).to_vec(); + + utils::into_ptr(types::GetMnemonicSeedResponse { + mnemonic_seed: seed, + }) +} diff --git a/src/compat/mod.rs b/src/compat/mod.rs new file mode 100644 index 0000000..fea6eb1 --- /dev/null +++ b/src/compat/mod.rs @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// 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 +pub mod mnemonic; +/// Includes functions to rkyv serialize types like phoenix_core and crypto +/// primitives +pub mod rkyv; +/// Includes functions to interact with the stake contract +pub mod stake; +/// Includes functions to deal with UnprovenTransaction and Transaction +pub mod tx; +/// Includes functions to interact with the stake contract unstake tx +pub mod unstake; +/// Includes functions to interact with the stake contract withdraw tx +pub mod withdraw; diff --git a/src/compat/rkyv.rs b/src/compat/rkyv.rs new file mode 100644 index 0000000..e869fb4 --- /dev/null +++ b/src/compat/rkyv.rs @@ -0,0 +1,186 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{ + key::{self}, + tx, + types::{self}, + utils, MAX_LEN, +}; + +use dusk_bls12_381::BlsScalar; +use dusk_bls12_381_sign::PublicKey; +use phoenix_core::{transaction::TreeLeaf, Note}; + +use alloc::vec::Vec; + +/// Serialize a u64 integer to bytes using rkyv so we can send it to the network +#[no_mangle] +pub fn rkyv_u64(args: i32, len: i32) -> i64 { + let types::RkyvU64 { value } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + 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] +pub fn rkyv_notes_array(args: i32, len: i32) -> i64 { + let types::RkyvNotesArray { notes } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let mut vec_notes = Vec::with_capacity(notes.len()); + + for note in notes { + let parsed_note: Note = match rkyv::from_bytes(¬e).ok() { + Some(t) => t, + None => return utils::fail(), + }; + + vec_notes.push(parsed_note); + } + + utils::rkyv_into_ptr(vec_notes) +} + +/// Convert a Vec> of rkyv serialized Vec to Vec +#[no_mangle] +pub fn rkyv_bls_scalar_array(args: i32, len: i32) -> i64 { + // we reuse this argument + let types::RkyvBlsScalarArrayArgs { bytes } = + match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let mut bls_scalars = Vec::new(); + + for scalar in bytes { + match rkyv::from_bytes::(&scalar).ok() { + Some(v) => bls_scalars.push(v), + None => return utils::fail(), + } + } + + 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) +} + +/// Opposite of the rkyv_bls_scalar_array function +/// Converts a rkyv serialized Vec of Vec to Array +#[no_mangle] +pub fn bls_scalar_array_rkyv(args: i32, len: i32) -> i64 { + // reusing this type + let types::RkyvTreeLeaf { bytes } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let scalars: Vec = match rkyv::from_bytes(&bytes).ok() { + Some(n) => n, + None => return utils::fail(), + }; + + let mut scalar_array = Vec::new(); + + for scalar in scalars { + let serialized = + match rkyv::to_bytes::(&scalar).ok() { + Some(n) => n.to_vec(), + None => return utils::fail(), + }; + + scalar_array.push(serialized); + } + + utils::into_ptr(types::RkyvBlsScalarArrayArgs { + bytes: scalar_array, + }) +} + +/// convert a [rkyv_serailized_tx::Opening] (we get it from the node) into a +/// Vec rkyv serialized Vec to send to execute fn +#[no_mangle] +pub fn rkyv_openings_array(args: i32, len: i32) -> i64 { + let types::RkyvOpeningsArray { openings } = + match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let mut openings_vec: Vec<(tx::Opening, u64)> = Vec::new(); + + for opening in openings { + let opening_parsed: tx::Opening = + match rkyv::from_bytes(&opening.opening).ok() { + Some(x) => x, + None => return utils::fail(), + }; + + openings_vec.push((opening_parsed, opening.pos)); + } + + utils::rkyv_into_ptr::>(openings_vec) +} + +/// Rkyv serialize public key to send to the node to obtain +/// stake-info +#[no_mangle] +fn get_public_key_rkyv_serialized(args: i32, len: i32) -> i64 { + let types::GetPublicKeyRkyvSerializedArgs { seed, index } = + match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let sk = key::derive_sk(&seed, index); + + let pk = PublicKey::from(&sk); + + utils::rkyv_into_ptr::(pk) +} diff --git a/src/compat/stake.rs b/src/compat/stake.rs new file mode 100644 index 0000000..0b0dfe2 --- /dev/null +++ b/src/compat/stake.rs @@ -0,0 +1,262 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{ + ffi::allocate, + key::*, + types::{self}, + utils::{self, *}, + MAX_LEN, +}; + +use alloc::string::String; +use alloc::vec::Vec; + +use dusk_bls12_381::BlsScalar; +use dusk_bls12_381_sign::{PublicKey, SecretKey, Signature as BlsSignature}; +use dusk_bytes::Serializable; +use dusk_bytes::Write; +use dusk_jubjub::JubJubScalar; +use dusk_pki::{Ownable, SecretKey as SchnorrKey}; +use dusk_plonk::proof_system::Proof; +use dusk_schnorr::Signature; +use phoenix_core::{transaction::*, Note, *}; + +const STCT_INPUT_SIZE: usize = Fee::SIZE + + Crossover::SIZE + + u64::SIZE + + JubJubScalar::SIZE + + BlsScalar::SIZE + + Signature::SIZE; + +/// Get the bytes to send to the node to prove stct proof +/// and then we can get the proof verified from the node +#[no_mangle] +pub fn get_stct_proof(args: i32, len: i32) -> i64 { + let types::GetStctProofArgs { + rng_seed, + seed, + refund, + value, + sender_index, + gas_limit, + gas_price, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng_seed = match utils::sanitize_rng_seed(rng_seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let sender = derive_ssk(&seed, sender_index); + let refund = match bs58_to_psk(&refund) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng = &mut utils::rng(rng_seed); + + let blinder = JubJubScalar::random(rng); + let note = Note::obfuscated(rng, &refund, value, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let contract_id = rusk_abi::STAKE_CONTRACT; + let address = rusk_abi::contract_to_scalar(&contract_id); + + let contract_id = rusk_abi::contract_to_scalar(&contract_id); + + let stct_message = stct_signature_message(&crossover, value, contract_id); + let stct_message = dusk_poseidon::sponge::hash(&stct_message); + + let sk_r = *sender.sk_r(fee.stealth_address()).as_ref(); + let secret = SchnorrKey::from(sk_r); + + let stct_signature = Signature::new(&secret, rng, stct_message); + + let vec_allocation = allocate(STCT_INPUT_SIZE as i32) as *mut _; + let mut buf: Vec = unsafe { + Vec::from_raw_parts(vec_allocation, STCT_INPUT_SIZE, STCT_INPUT_SIZE) + }; + + let mut writer = &mut buf[..]; + + let mut bytes = || { + writer.write(&fee.to_bytes()).ok()?; + writer.write(&crossover.to_bytes()).ok()?; + writer.write(&value.to_bytes()).ok()?; + writer.write(&blinder.to_bytes()).ok()?; + writer.write(&address.to_bytes()).ok()?; + writer.write(&stct_signature.to_bytes()).ok()?; + + Some(()) + }; + + let bytes = match bytes() { + Some(_) => buf, + None => return utils::fail(), + } + .to_vec(); + + let signature = match rkyv::to_bytes::(&stct_signature) + { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let crossover = match rkyv::to_bytes::(&crossover) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let blinder = match rkyv::to_bytes::(&blinder) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let fee = match rkyv::to_bytes::(&fee) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + utils::into_ptr(types::GetStctProofResponse { + bytes, + signature, + crossover, + blinder, + fee, + }) +} + +/// Get the (contract_id, method, payload) for stake +#[no_mangle] +pub fn get_stake_call_data(args: i32, len: i32) -> i64 { + let types::GetStakeCallDataArgs { + staker_index, + seed, + spend_proof, + value, + counter, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let spend_proof: [u8; Proof::SIZE] = match spend_proof.try_into().ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let proof = match Proof::from_bytes(&spend_proof).ok() { + Some(a) => a.to_bytes().to_vec(), + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let sk = derive_sk(&seed, staker_index); + let pk = PublicKey::from(&sk); + + let signature = stake_sign(&sk, &pk, counter, value); + + let stake = Stake { + public_key: pk, + signature, + value, + proof, + }; + + let contract = bs58::encode(rusk_abi::STAKE_CONTRACT).into_string(); + let method = String::from("stake"); + let payload = match rkyv::to_bytes::<_, MAX_LEN>(&stake).ok() { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + utils::into_ptr(types::GetStakeCallDataResponse { + contract, + method, + payload, + }) +} + +#[no_mangle] +fn get_stake_info(args: i32, len: i32) -> i64 { + let types::GetStakeInfoArgs { stake_info } = + match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let mut has_staked = false; + + match rkyv::from_bytes::>(&stake_info).ok() { + Some(Some(a)) => { + let (amount, eligiblity) = match a.amount { + Some((x, y)) => { + has_staked = true; + + (Some(x), Some(y)) + } + None => (None, None), + }; + let reward = Some(a.reward); + let counter = Some(a.counter); + + utils::into_ptr(types::GetStakeInfoRespose { + has_staked, + amount, + eligiblity, + reward, + counter, + has_key: true, + }) + } + Some(None) | None => utils::into_ptr(types::GetStakeInfoRespose { + has_staked, + amount: None, + reward: None, + eligiblity: None, + counter: None, + has_key: false, + }), + } +} + +/// Creates a signature compatible with what the stake contract expects for a +/// stake transaction. +/// +/// The counter is the number of transactions that have been sent to the +/// transfer contract by a given key, and is reported in `StakeInfo`. +fn stake_sign( + sk: &SecretKey, + pk: &PublicKey, + counter: u64, + value: u64, +) -> BlsSignature { + let size = u64::SIZE + u64::SIZE; + let mut msg = Vec::with_capacity(size); + + msg.extend(counter.to_bytes()); + msg.extend(value.to_bytes()); + + sk.sign(pk, &msg) +} diff --git a/src/compat/tx.rs b/src/compat/tx.rs new file mode 100644 index 0000000..16d9c79 --- /dev/null +++ b/src/compat/tx.rs @@ -0,0 +1,434 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{ + ffi::allocate, + key::derive_vk, + tx::{self}, + types, utils, +}; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; + +use dusk_bytes::{ + DeserializableSlice, Error as BytesError, Serializable, Write, +}; +use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; +use dusk_plonk::proof_system::Proof; +use dusk_schnorr::Proof as SchnorrSig; +use hashbrown::{hash_map::Entry, HashMap}; +use phoenix_core::{transaction, Crossover, Fee, Note, Transaction}; +use rusk_abi::{hash::Hasher, ContractId, CONTRACT_ID_BYTES}; + +/// Convert a tx::UnprovenTransaction to bytes ready to be sent to the node +#[no_mangle] +pub fn unproven_tx_to_bytes(args: i32, len: i32) -> i64 { + // re-using this type + let types::RkyvTreeLeaf { bytes } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let tx: tx::UnprovenTransaction = match rkyv::from_bytes(&bytes).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let bytes = utx_to_var_bytes(&tx); + + let serialized = match bytes.ok() { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + utils::into_ptr(types::UnprovenTxToBytesResponse { serialized }) +} + +/// Make sure the proof is okay and convert the given unproven tx +/// to a Proven Transaction +#[no_mangle] +pub fn prove_tx(args: i32, len: i32) -> i64 { + let types::ProveTxArgs { unproven_tx, proof } = + match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let utx: tx::UnprovenTransaction = match rkyv::from_bytes(&unproven_tx).ok() + { + Some(a) => a, + None => { + return utils::fail(); + } + }; + + let proof = match Proof::from_slice(&proof).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let mut call = None; + + if let Some(tx::CallData { + contract, + method, + payload, + }) = utx.clone().call + { + call = Some((contract.to_bytes(), method, payload)); + } + + let anchor = utx.anchor; + + let crossover = utx.crossover.clone().map(|e| e.crossover); + let inputs = &utx.inputs; + let outputs = utx.outputs.iter().map(|output| output.note).collect(); + let fee = utx.fee; + let proof = proof.to_bytes().to_vec(); + let nullifiers = inputs.iter().map(|input| input.nullifier).collect(); + + let tx = transaction::Transaction { + nullifiers, + anchor, + outputs, + proof, + fee, + crossover, + call, + }; + + let bytes = tx.to_var_bytes(); + + let tx_hash = rusk_abi::hash::Hasher::digest(tx.to_hash_input_bytes()); + let hash = hex::encode(tx_hash.to_bytes()); + + utils::into_ptr(types::ProveTxResponse { bytes, hash }) +} + +/// Calculate the history given the notes and tx data +#[no_mangle] +pub fn get_history(args: i32, len: i32) -> i64 { + let types::GetHistoryArgs { + seed, + index, + notes, + tx_data, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let mut ret: Vec = Vec::new(); + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let mut nullifiers = Vec::new(); + + for note_data in notes.iter() { + let nullifier = + match rkyv::from_bytes::(¬e_data.nullifier) { + Ok(a) => a, + Err(_) => return utils::fail(), + }; + + let note = match rkyv::from_bytes::(¬e_data.note).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + nullifiers + .push((nullifier, note.value(Some(&derive_vk(&seed, index))))); + } + + let mut block_txs = HashMap::new(); + let vk = derive_vk(&seed, index); + + for (index, note_data) in notes.iter().enumerate() { + let mut note = match rkyv::from_bytes::(¬e_data.note).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + note.set_pos(u64::MAX); + + let note_hash = note.hash(); + + let txs = match block_txs.entry(note_data.block_height) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) => { + let transactions: Option> = tx_data + [index] + .txs + .iter() + .map(|raw_tx| { + let decoded = hex::decode(&raw_tx.raw_tx).ok()?; + + let tx = Transaction::from_slice(&decoded).ok()?; + + Some((tx, raw_tx.gas_spent)) + }) + .collect(); + + let txn = match transactions { + Some(a) => a, + None => return utils::fail(), + }; + + v.insert(txn) + } + }; + + let note_amount = match note.value(Some(&vk)).ok() { + Some(a) => a, + None => return utils::fail(), + } as f64; + + let note_creator = txs.iter().find(|(t, _)| { + t.outputs().iter().any(|&n| n.hash().eq(¬e_hash)) + }); + + if let Some((t, gas_spent)) = note_creator { + let inputs_amount: Result, _> = t + .nullifiers() + .iter() + .filter_map(|input| { + nullifiers + .clone() + .into_iter() + .find_map(|n| n.0.eq(input).then_some(n.1)) + }) + .collect(); + + let inputs_amount = match inputs_amount { + Ok(a) => a.iter().sum::() as f64, + Err(_) => return utils::fail(), + }; + + let direction = match inputs_amount > 0f64 { + true => types::TransactionDirectionType::Out, + false => types::TransactionDirectionType::In, + }; + let hash_to_find = Hasher::digest(t.to_hash_input_bytes()); + match ret.iter_mut().find(|th| th.id == hash_to_find.to_string()) { + Some(tx) => tx.amount += note_amount, + None => ret.push(types::TransactionHistoryType { + direction, + block_height: note_data.block_height, + amount: note_amount - inputs_amount, + fee: gas_spent * t.fee().gas_price, + id: hash_to_find.to_string(), + }), + } + } else { + let outgoing_tx = ret.iter_mut().find(|th| { + th.direction == types::TransactionDirectionType::Out + && th.block_height == note_data.block_height + }); + + if let Some(th) = outgoing_tx { + th.amount += note_amount + } + } + } + + ret.sort_by(|a, b| a.block_height.cmp(&b.block_height)); + + ret = ret + .into_iter() + .map(|mut th| { + let dusk = th.amount / rusk_abi::dusk::dusk(1.0) as f64; + + th.amount = dusk; + + th + }) + .collect::>(); + + utils::into_ptr(types::GetHistoryResponse { history: ret }) +} + +/// Serialize a unprovenTx we recieved from the wallet-core +/// this is copied from old wallet-core (0.20.0-piecrust.0.6) +fn utx_to_var_bytes( + tx: &tx::UnprovenTransaction, +) -> Result, BytesError> { + let serialized_inputs: Vec> = + tx.inputs.iter().map(input_to_var_bytes).collect(); + let num_inputs = tx.inputs.len(); + let total_input_len = serialized_inputs + .iter() + .fold(0, |len, input| len + input.len()); + + let serialized_outputs: Vec< + [u8; Note::SIZE + u64::SIZE + JubJubScalar::SIZE], + > = tx + .outputs + .iter() + .map( + |tx::Output { + note, + value, + blinder, + }| { + let mut buf = [0; Note::SIZE + u64::SIZE + JubJubScalar::SIZE]; + + buf[..Note::SIZE].copy_from_slice(¬e.to_bytes()); + buf[Note::SIZE..Note::SIZE + u64::SIZE] + .copy_from_slice(&value.to_bytes()); + buf[Note::SIZE + u64::SIZE + ..Note::SIZE + u64::SIZE + JubJubScalar::SIZE] + .copy_from_slice(&blinder.to_bytes()); + + buf + }, + ) + .collect(); + let num_outputs = tx.outputs.len(); + let total_output_len = serialized_outputs + .iter() + .fold(0, |len, output| len + output.len()); + + let size = u64::SIZE + + num_inputs * u64::SIZE + + total_input_len + + u64::SIZE + + total_output_len + + BlsScalar::SIZE + + Fee::SIZE + + u64::SIZE + + tx.crossover + .clone() + .map_or(0, |_| Crossover::SIZE + u64::SIZE + JubJubScalar::SIZE) + + u64::SIZE + + tx.call + .as_ref() + .map( + |tx::CallData { + contract: _, + method, + payload, + }| { + CONTRACT_ID_BYTES + u64::SIZE + method.len() + payload.len() + }, + ) + .unwrap_or(0); + + let vec_allocation = allocate(size as i32) as *mut _; + let mut buf = unsafe { Vec::from_raw_parts(vec_allocation, size, size) }; + let mut writer = &mut buf[..]; + + writer.write(&(num_inputs as u64).to_bytes())?; + for sinput in serialized_inputs { + writer.write(&(sinput.len() as u64).to_bytes())?; + writer.write(&sinput)?; + } + + writer.write(&(num_outputs as u64).to_bytes())?; + for soutput in serialized_outputs { + writer.write(&soutput)?; + } + + writer.write(&tx.anchor.to_bytes())?; + writer.write(&tx.fee.to_bytes())?; + + let crossover = &tx.crossover; + + write_crossover_value_blinder( + &mut writer, + crossover.clone().map(|crossover| { + (crossover.crossover, crossover.value, crossover.blinder) + }), + )?; + write_optional_call( + &mut writer, + &tx.call + .clone() + .map(|call| (call.contract, call.method, call.payload)), + )?; + + Ok(buf) +} + +fn write_crossover_value_blinder( + writer: &mut W, + crossover: Option<(Crossover, u64, JubJubScalar)>, +) -> Result<(), BytesError> { + match crossover { + Some((crossover, value, blinder)) => { + writer.write(&1_u64.to_bytes())?; + writer.write(&crossover.to_bytes())?; + writer.write(&value.to_bytes())?; + writer.write(&blinder.to_bytes())?; + } + None => { + writer.write(&0_u64.to_bytes())?; + } + } + + Ok(()) +} + +/// Writes an optional call into the writer, prepending it with a `u64` denoting +/// if it is present or not. This should be called at the end of writing other +/// fields since it doesn't write any information about the length of the call +/// data. +fn write_optional_call( + writer: &mut W, + call: &Option<(ContractId, String, Vec)>, +) -> Result<(), BytesError> { + match call { + Some((cid, cname, cdata)) => { + writer.write(&1_u64.to_bytes())?; + + writer.write(cid.as_bytes())?; + + let cname_len = cname.len() as u64; + writer.write(&cname_len.to_bytes())?; + writer.write(cname.as_bytes())?; + + writer.write(cdata)?; + } + None => { + writer.write(&0_u64.to_bytes())?; + } + }; + + Ok(()) +} + +fn input_to_var_bytes(input: &tx::Input) -> Vec { + let affine_pkr = JubJubAffine::from(&input.pk_r_prime); + + let opening_bytes = rkyv::to_bytes::<_, 256>(&input.opening) + .expect("Rkyv serialization should always succeed for an opening") + .to_vec(); + + let size = BlsScalar::SIZE + + Note::SIZE + + JubJubAffine::SIZE + + SchnorrSig::SIZE + + u64::SIZE + + JubJubScalar::SIZE + + opening_bytes.len(); + + let mut bytes: Vec<_> = Vec::with_capacity(size); + + bytes.extend_from_slice(&input.nullifier.to_bytes()); + bytes.extend_from_slice(&input.note.to_bytes()); + bytes.extend_from_slice(&input.value.to_bytes()); + bytes.extend_from_slice(&input.blinder.to_bytes()); + bytes.extend_from_slice(&affine_pkr.to_bytes()); + bytes.extend_from_slice(&input.sig.to_bytes()); + bytes.extend(opening_bytes); + + bytes +} diff --git a/src/compat/unstake.rs b/src/compat/unstake.rs new file mode 100644 index 0000000..a22da3b --- /dev/null +++ b/src/compat/unstake.rs @@ -0,0 +1,209 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{ + ffi::allocate, + key::*, + types::{self}, + utils::{self, *}, + MAX_LEN, +}; + +use alloc::string::String; +use alloc::vec::Vec; + +use dusk_bls12_381_sign::{PublicKey, SecretKey, Signature as BlsSignature}; +use dusk_bytes::Serializable; +use dusk_bytes::Write; +use dusk_jubjub::{JubJubAffine, JubJubScalar}; +use dusk_plonk::proof_system::Proof; +use phoenix_core::{transaction::*, Note, *}; + +const WFCT_INPUT_SIZE: usize = + JubJubAffine::SIZE + u64::SIZE + JubJubScalar::SIZE; + +/// Get the bytes to send to the node to prove wfct proof +#[no_mangle] +pub fn get_wfct_proof(args: i32, len: i32) -> i64 { + // re-using the type + let types::GetStctProofArgs { + rng_seed, + seed, + refund, + value, + sender_index, + gas_limit, + gas_price, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng_seed = match utils::sanitize_rng_seed(rng_seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let sender = derive_ssk(&seed, sender_index); + let refund = match bs58_to_psk(&refund) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng = &mut utils::rng(rng_seed); + + let blinder = JubJubScalar::random(rng); + let note = Note::obfuscated(rng, &refund, 0, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let unstake_note = + Note::transparent(rng, &sender.public_spend_key(), value); + let unstake_blinder: dusk_jubjub::Fr = unstake_note + .blinding_factor(None) + .expect("Note is transparent so blinding factor is unencrypted"); + + let commitment: JubJubAffine = unstake_note.value_commitment().into(); + + let vec_allocation = allocate(WFCT_INPUT_SIZE as i32) as *mut _; + let mut buf: Vec = unsafe { + Vec::from_raw_parts(vec_allocation, WFCT_INPUT_SIZE, WFCT_INPUT_SIZE) + }; + + let mut writer = &mut buf[..]; + + let mut bytes = || { + writer.write(&commitment.to_bytes()).ok()?; + writer.write(&value.to_bytes()).ok()?; + writer.write(&unstake_blinder.to_bytes()).ok()?; + + Some(()) + }; + + let bytes = match bytes() { + Some(_) => buf, + None => return utils::fail(), + } + .to_vec(); + + let unstake_note = match rkyv::to_bytes::(&unstake_note).ok() + { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + let crossover = match rkyv::to_bytes::(&crossover) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let blinder = match rkyv::to_bytes::(&blinder) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let fee = match rkyv::to_bytes::(&fee) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + utils::into_ptr(types::GetWfctProofResponse { + bytes, + blinder, + crossover, + fee, + unstake_note, + }) +} + +/// Get unstake call data +#[no_mangle] +pub fn get_unstake_call_data(args: i32, len: i32) -> i64 { + let types::GetUnstakeCallDataArgs { + seed, + sender_index, + unstake_note, + counter, + unstake_proof, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let unstake_proof: [u8; Proof::SIZE] = match unstake_proof.try_into().ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let proof = match Proof::from_bytes(&unstake_proof).ok() { + Some(a) => a.to_bytes().to_vec(), + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let unstake_note = match rkyv::from_bytes::(&unstake_note).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + let sk = derive_sk(&seed, sender_index); + let pk = PublicKey::from(&sk); + + let signature = unstake_sign(&sk, &pk, counter, unstake_note); + + let unstake = Unstake { + public_key: pk, + signature, + note: unstake_note, + proof, + }; + + let contract = bs58::encode(rusk_abi::STAKE_CONTRACT).into_string(); + let method = String::from("unstake"); + let payload = match rkyv::to_bytes::<_, MAX_LEN>(&unstake).ok() { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + // reusing this type + utils::into_ptr(types::GetStakeCallDataResponse { + contract, + method, + payload, + }) +} + +/// Creates a signature compatible with what the stake contract expects for a +/// unstake transaction. +/// +/// The counter is the number of transactions that have been sent to the +/// transfer contract by a given key, and is reported in `StakeInfo`. +fn unstake_sign( + sk: &SecretKey, + pk: &PublicKey, + counter: u64, + note: Note, +) -> BlsSignature { + let mut msg: Vec = Vec::with_capacity(u64::SIZE + Note::SIZE); + + msg.extend(counter.to_bytes()); + msg.extend(note.to_bytes()); + + sk.sign(pk, &msg) +} diff --git a/src/compat/withdraw.rs b/src/compat/withdraw.rs new file mode 100644 index 0000000..9f93078 --- /dev/null +++ b/src/compat/withdraw.rs @@ -0,0 +1,136 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{key::*, types, utils, MAX_LEN}; + +use alloc::string::String; +use alloc::vec::Vec; +use ff::Field; + +use dusk_bls12_381_sign::{PublicKey, SecretKey, Signature as BlsSignature}; +use dusk_bytes::Serializable; +use dusk_jubjub::{BlsScalar, JubJubScalar}; +use dusk_pki::StealthAddress; +use phoenix_core::{transaction::*, Note, *}; + +/// Get unstake call data +#[no_mangle] +pub fn get_withdraw_call_data(args: i32, len: i32) -> i64 { + // reusing the type + let types::GetAllowCallDataArgs { + seed, + rng_seed, + sender_index, + refund, + owner_index, + counter, + gas_limit, + gas_price, + } = match utils::take_args(args, len) { + Some(a) => a, + None => return utils::fail(), + }; + + let rng_seed = match utils::sanitize_rng_seed(rng_seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let seed = match utils::sanitize_seed(seed) { + Some(s) => s, + None => return utils::fail(), + }; + + let refund = match utils::bs58_to_psk(&refund) { + Some(a) => a, + None => return utils::fail(), + }; + + let sender_psk = derive_ssk(&seed, sender_index).public_spend_key(); + let sk = derive_sk(&seed, owner_index); + let pk = PublicKey::from(&sk); + + let rng = &mut utils::rng(rng_seed); + + let withdraw_r = JubJubScalar::random(rng); + let address: StealthAddress = sender_psk.gen_stealth_address(&withdraw_r); + let nonce = BlsScalar::random(&mut *rng); + + let signature = withdraw_sign(&sk, &pk, counter, address, nonce); + + // Since we're not transferring value *to* the contract the crossover + // shouldn't contain a value. As such the note used to created it should + // be valueless as well. + let blinder = JubJubScalar::random(rng); + let note = Note::obfuscated(rng, &refund, 0, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let withdraw = Withdraw { + public_key: pk, + signature, + address, + nonce, + }; + + let contract = bs58::encode(rusk_abi::STAKE_CONTRACT).into_string(); + let method = String::from("withdraw"); + let payload = match rkyv::to_bytes::<_, MAX_LEN>(&withdraw).ok() { + Some(a) => a.to_vec(), + None => return utils::fail(), + }; + + let crossover = match rkyv::to_bytes::(&crossover) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let blinder = match rkyv::to_bytes::(&blinder) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + let fee = match rkyv::to_bytes::(&fee) { + Ok(a) => a.to_vec(), + Err(_) => return utils::fail(), + }; + + // reusing this type + utils::into_ptr(types::GetAllowCallDataResponse { + contract, + method, + payload, + blinder, + crossover, + fee, + }) +} + +/// Creates a signature compatible with what the stake contract expects for a +/// withdraw transaction. +/// +/// The counter is the number of transactions that have been sent to the +/// transfer contract by a given key, and is reported in `StakeInfo`. +fn withdraw_sign( + sk: &SecretKey, + pk: &PublicKey, + counter: u64, + address: StealthAddress, + nonce: BlsScalar, +) -> BlsSignature { + let mut msg = + Vec::with_capacity(u64::SIZE + StealthAddress::SIZE + BlsScalar::SIZE); + + msg.extend(counter.to_bytes()); + msg.extend(address.to_bytes()); + msg.extend(nonce.to_bytes()); + + sk.sign(pk, &msg) +} diff --git a/src/ffi.rs b/src/ffi.rs index 2f8821a..00b9382 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -10,7 +10,7 @@ use alloc::{vec, vec::Vec}; use core::mem; use dusk_bytes::Serializable; -use phoenix_core::Note; +use phoenix_core::{Fee, Note}; use sha2::{Digest, Sha512}; use crate::{key, tx, types, utils, MAX_KEY, MAX_LEN}; @@ -132,13 +132,15 @@ pub fn execute(args: i32, len: i32) -> i64 { let types::ExecuteArgs { call, crossover, - gas_limit, - gas_price, + fee, inputs, openings, output, + gas_limit, + gas_price, refund, rng_seed, + sender_index, seed, } = match utils::take_args(args, len) { Some(a) => a, @@ -150,7 +152,13 @@ pub fn execute(args: i32, len: i32) -> i64 { Err(_) => return utils::fail(), }; - let openings: Vec = match rkyv::from_bytes(&openings) { + let fee: Option = + fee.and_then(|fee| match rkyv::from_bytes::(&fee) { + Ok(n) => Some(n), + Err(_) => None, + }); + + let openings: Vec<(tx::Opening, u64)> = match rkyv::from_bytes(&openings) { Ok(n) => n, Err(_) => return utils::fail(), }; @@ -160,7 +168,7 @@ pub fn execute(args: i32, len: i32) -> i64 { None => return utils::fail(), }; - let rng_seed = match utils::sanitize_seed(rng_seed) { + let rng_seed: [u8; 32] = match utils::sanitize_rng_seed(rng_seed) { Some(s) => s, None => return utils::fail(), }; @@ -169,51 +177,48 @@ pub fn execute(args: i32, len: i32) -> i64 { let total_output = gas_limit .saturating_mul(gas_price) .saturating_add(value) - .saturating_add(crossover.unwrap_or_default()); + .saturating_add(crossover.clone().map(|c| c.value).unwrap_or_default()); - let mut keys = unsafe { [mem::zeroed(); MAX_KEY + 1] }; - let mut keys_ssk = unsafe { [mem::zeroed(); MAX_KEY + 1] }; - let mut keys_len = 0; - let mut openings = openings.into_iter(); let mut full_inputs = Vec::with_capacity(inputs.len()); - 'outer: for input in inputs { - // we iterate all the available keys until one can successfully - // decrypt the note. if any fails, returns false - for idx in 0..=MAX_KEY { - if keys_len == idx { - keys_ssk[idx] = key::derive_ssk(&seed, idx as u64); - keys[idx] = keys_ssk[idx].view_key(); - keys_len += 1; - } + let view_key = key::derive_vk(&seed, sender_index); + let ssk = key::derive_ssk(&seed, sender_index); - if let Ok(value) = input.value(Some(&keys[idx])) { - let opening = match openings.next() { - Some(o) => o, - None => return utils::fail(), + 'outer: for input in inputs { + if let Ok(value) = input.value(Some(&view_key)) { + let opening = + match openings.iter().find(|(_, pos)| input.pos() == pos) { + Some(a) => a.0, + None => { + return utils::fail(); + } }; - full_inputs.push((input, opening, value, idx)); - continue 'outer; - } + let blinder = match input.blinding_factor(Some(&view_key)).ok() { + Some(a) => a, + None => return utils::fail(), + }; + + full_inputs.push((input, opening, value, blinder)); + continue 'outer; } return utils::fail(); } // optimizes the inputs given the total amount - let (unspent, inputs) = match utils::knapsack(full_inputs, total_output) { + let inputs = match utils::inputs(full_inputs, total_output) { Some(k) => k, None => return utils::fail(), }; let inputs: Vec<_> = inputs .into_iter() - .map(|(note, opening, value, idx)| tx::PreInput { + .map(|(note, opening, value, _)| tx::PreInput { note, opening, value, - ssk: &keys_ssk[idx], + ssk: &ssk, }) .collect(); @@ -221,9 +226,6 @@ pub fn execute(args: i32, len: i32) -> i64 { let total_refund = total_input.saturating_sub(total_output); let mut outputs = Vec::with_capacity(2); - if let Some(o) = output { - outputs.push(o); - } if total_refund > 0 { outputs.push(types::ExecuteOutput { note_type: types::OutputType::Obfuscated, @@ -232,27 +234,39 @@ pub fn execute(args: i32, len: i32) -> i64 { value: total_refund, }); } + if let Some(o) = output { + outputs.push(o); + } + + let rng: &mut rand_chacha::ChaCha12Rng = &mut utils::rng(rng_seed); + let actual_fee; + let refund = match utils::bs58_to_psk(&refund) { + Some(r) => r, + None => return utils::fail(), + }; + + if let Some(fee) = fee { + actual_fee = fee; + } else { + actual_fee = Fee::new(rng, gas_limit, gas_price, &refund); + } - let rng = &mut utils::rng(&rng_seed); let tx = tx::UnprovenTransaction::new( - rng, inputs, outputs, refund, gas_limit, gas_price, crossover, call, + rng, inputs, outputs, actual_fee, crossover, call, ); + let tx = match tx { Some(t) => t, None => return utils::fail(), }; - let unspent = match rkyv::to_bytes::<_, MAX_LEN>(&unspent).ok() { - Some(t) => t.into_vec(), - None => return utils::fail(), - }; - - let tx = match rkyv::to_bytes::<_, MAX_LEN>(&tx).ok() { - Some(t) => t.into_vec(), + let tx = match rkyv::to_bytes::(&tx).ok() + { + Some(t) => t.to_vec(), None => return utils::fail(), }; - utils::into_ptr(types::ExecuteResponse { tx, unspent }) + utils::into_ptr(types::ExecuteResponse { tx }) } /// Merges many lists of serialized notes into a unique, sanitized set. @@ -307,7 +321,7 @@ pub fn filter_notes(args: i32, len: i32) -> i64 { let notes: Vec<_> = notes .into_iter() - .zip(flags.into_iter()) + .zip(flags) .filter_map(|(n, f)| (!f).then_some(n)) .collect(); diff --git a/src/lib.rs b/src/lib.rs index df998c1..8abcfae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,14 @@ extern crate alloc; +#[cfg(feature = "compat")] +/// compat module adds compatiblity functions for non rust platforms +pub mod compat; pub mod ffi; pub mod key; pub mod tx; 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; @@ -28,3 +30,6 @@ pub const RNG_SEED: usize = 64; /// The length of the allocated response. pub const RESPONSE_LEN: usize = 3 * i32::BITS as usize / 8; + +/// The maximum number of input notes that are sent with the transaction +pub const MAX_INPUT_NOTES: usize = 4; diff --git a/src/tx.rs b/src/tx.rs index 008982f..8bbe065 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -11,11 +11,11 @@ use alloc::vec::Vec; use core::mem; use bytecheck::CheckBytes; -use dusk_jubjub::{ - BlsScalar, JubJubExtended, JubJubScalar, GENERATOR_NUMS_EXTENDED, -}; +use dusk_bls12_381::BlsScalar; +use dusk_jubjub::{JubJubExtended, JubJubScalar, GENERATOR_NUMS_EXTENDED}; use dusk_pki::{Ownable, PublicSpendKey, SecretSpendKey}; use dusk_schnorr::Proof as SchnorrSig; +use ff::Field; use phoenix_core::{ Crossover as PhoenixCrossover, Fee, Note, NoteType, Transaction, }; @@ -24,7 +24,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use rusk_abi::hash::Hasher; use rusk_abi::{ContractId, POSEIDON_TREE_DEPTH}; -use crate::{types, utils}; +use crate::{types, types::CrossoverType, utils}; /// Chosen arity for the Notes tree implementation. pub const POSEIDON_TREE_ARITY: usize = 4; @@ -94,7 +94,7 @@ pub struct Output { /// A crossover to a transaction that is yet to be proven. #[derive(Debug, Clone, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] -pub struct Crossover { +pub struct WasmCrossover { /// Crossover value to be used in inter-contract calls. pub crossover: PhoenixCrossover, /// Value of the crossover. @@ -128,7 +128,7 @@ pub struct UnprovenTransaction { /// Fee setup for the transaction. pub fee: Fee, /// Crossover value for inter-contract calls. - pub crossover: Option, + pub crossover: Option, /// Call data payload for contract calls. pub call: Option, } @@ -143,10 +143,8 @@ impl UnprovenTransaction { rng: &mut Rng, inputs: I, outputs: O, - refund: String, - gas_limit: u64, - gas_price: u64, - crossover: Option, + fee: Fee, + crossover: Option, call: Option, ) -> Option where @@ -163,7 +161,6 @@ impl UnprovenTransaction { .unzip(); let anchor = inputs.first().map(|i| i.opening.root().hash)?; - let refund = utils::bs58_to_psk(&refund)?; let mut output_notes = Vec::with_capacity(4); let mut outputs_values = Vec::with_capacity(4); @@ -171,7 +168,7 @@ impl UnprovenTransaction { for types::ExecuteOutput { note_type, receiver, - ref_id, + ref_id: _, value, } in outputs.into_iter() { @@ -182,7 +179,7 @@ impl UnprovenTransaction { let r = JubJubScalar::random(rng); let blinder = JubJubScalar::random(rng); - let nonce = BlsScalar::from(ref_id.unwrap_or_default()); + let nonce = BlsScalar::random(&mut *rng); let receiver = utils::bs58_to_psk(&receiver)?; let note = Note::deterministic( r#type, &r, nonce, &receiver, value, blinder, @@ -222,20 +219,25 @@ impl UnprovenTransaction { (c.contract.to_bytes(), c.method.clone(), c.payload.clone()) }); - let fee = Fee::new(rng, gas_limit, gas_price, &refund); - - let crossover = crossover.map(|crossover| { - let blinder = JubJubScalar::random(rng); - let (_, crossover_note) = - Note::obfuscated(rng, &refund, crossover, blinder) - .try_into() - .expect("Obfuscated notes should always yield crossovers"); - Crossover { - crossover: crossover_note, - value: crossover, - blinder, - } - }); + let crossover = crossover.and_then( + |CrossoverType { + blinder, + crossover, + value, + }| { + Some({ + WasmCrossover { + crossover: rkyv::from_bytes::( + &crossover, + ) + .ok()?, + value, + blinder: rkyv::from_bytes::(&blinder) + .ok()?, + } + }) + }, + ); let tx_hash = Transaction::hash_input_bytes_from_components( &nullifiers, @@ -262,9 +264,9 @@ impl UnprovenTransaction { )| { let vk = ssk.view_key(); let sk_r = ssk.sk_r(note.stealth_address()); - let blinder = note.blinding_factor(Some(&vk)).map_err(|_| ())?; + let pk_r_prime = GENERATOR_NUMS_EXTENDED * sk_r.as_ref(); let sig = SchnorrSig::new(&sk_r, rng, tx_hash); diff --git a/src/types.rs b/src/types.rs index f58ac56..9486f97 100644 --- a/src/types.rs +++ b/src/types.rs @@ -31,22 +31,68 @@ 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 spend key string"] + #[serde(skip_serializing_if = "Option::is_none")] + pub public_spend_key: Option, +} +#[doc = " The value of the Crossover and the blinder"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct CrossoverType { + #[doc = " The rkyv serialized blinder of the crossover"] + pub blinder: Vec, + #[doc = " The rkyv serialized bytes of the crossover struct"] + pub crossover: Vec, + #[doc = " The value of the crossover"] + pub value: u64, +} +#[doc = " Arguments of the dusk_to_lux function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct DuskToLuxArgs { + #[doc = " The amount of dusk to convert to lux"] + pub dusk: u64, +} +#[doc = " Response of the dusk_to_lux function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct DuskToLuxResponse { + #[doc = " The amount of lux that was converted from dusk"] + pub lux: f64, +} #[doc = " The arguments of the execute function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct ExecuteArgs { #[doc = " A call to a contract method"] #[serde(skip_serializing_if = "Option::is_none")] pub call: Option, - #[doc = " The [phoenix_core::Crossover] value"] + #[doc = " The crossover value"] + #[serde(skip_serializing_if = "Option::is_none")] + pub crossover: Option, + #[doc = " A rkyv serialized Fee"] #[serde(skip_serializing_if = "Option::is_none")] - pub crossover: Option, + pub fee: Option>, #[doc = " The gas limit of the transaction"] pub gas_limit: u64, #[doc = " The gas price per unit for the transaction"] pub gas_price: u64, #[doc = " A rkyv serialized [Vec] to be used as inputs"] pub inputs: Vec, - #[doc = " A rkyv serialized [Vec] to open the inputs to a Merkle root"] + #[doc = " A rkyv serialized [Vec] to open the inputs to a Merkle root, along with the "] + #[doc = " positions of the notes the openings are of in a tuple (opening, position) rkyv serialized, "] + #[doc = " see rkyv.rs/rkyv_openings_array"] pub openings: Vec, #[doc = " The transfer output note"] #[serde(skip_serializing_if = "Option::is_none")] @@ -57,6 +103,8 @@ pub struct ExecuteArgs { pub rng_seed: Vec, #[doc = " Seed used to derive the keys of the wallet"] pub seed: Vec, + #[doc = " The index of the sender in the seed"] + pub sender_index: u64, } #[doc = " A call to a contract method"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -81,13 +129,11 @@ pub struct ExecuteOutput { #[doc = " The value of the output"] pub value: u64, } -#[doc = " The response of the execute function"] +#[doc = " Response of the execute function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct ExecuteResponse { - #[doc = " A rkyv serialized [crate::tx::UnspentTransaction]"] + #[doc = " The rkyv serialized unproven transaction"] pub tx: Vec, - #[doc = " A rkyv serialized [Vec] containing the notes that weren't used"] - pub unspent: Vec, } #[doc = " The arguments of the filter_notes function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -97,12 +143,234 @@ pub struct FilterNotesArgs { #[doc = " A rkyv serialized [Vec] to be filtered"] pub notes: Vec, } +#[doc = " Arguments of the filter_nullifier_note function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct FilterNulifierNotesArgs { + #[doc = " The existing nullifiers that are spent as a Vec"] + pub existing_nullifiers: Vec, + #[doc = " notes we want to check the nullifiers of as a Vec"] + pub notes: Vec, + #[doc = " The seed to generate the view keys from"] + pub seed: Vec, +} +#[doc = " Arguments for get_allow_call_data function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetAllowCallDataArgs { + #[doc = " Counter value from stakeinfo"] + pub counter: u64, + #[doc = " gas_limit"] + pub gas_limit: u64, + #[doc = " gas_price"] + pub gas_price: u64, + #[doc = " index of the owner of the stake"] + pub owner_index: u64, + #[doc = " psk in string of who to refund this tx to"] + pub refund: String, + #[doc = " random rng seed"] + pub rng_seed: Vec, + #[doc = " Seed of the wallet"] + pub seed: Vec, + #[doc = " index of the sender of the tx"] + pub sender_index: u64, +} +#[doc = " Response of the get_allow_call_data function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetAllowCallDataResponse { + #[doc = " Blinder used to make the crossover"] + pub blinder: Vec, + #[doc = " The id of the contract to call in Base58 format"] + pub contract: String, + #[doc = " Crossover of this tx"] + pub crossover: Vec, + #[doc = " The fee of the tx"] + pub fee: Vec, + #[doc = " The name of the method to be called"] + pub method: String, + #[doc = " The payload of the call"] + pub payload: Vec, +} +#[doc = " arguments of the get_history function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetHistoryArgs { + #[doc = " index of the key the notes belong to"] + pub index: u64, + #[doc = " The notes of the wallet"] + pub notes: Vec, + #[doc = " Seed of the wallet"] + pub seed: Vec, + #[doc = " The tx data of the wallet"] + pub tx_data: Vec, +} +#[doc = " Response of the get_history function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetHistoryResponse { + #[doc = " The history of a address"] + pub history: Vec, +} +#[doc = " Retrieve the seed bytes from the mnemonic and passphrase"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetMnemonicSeedArgs { + #[doc = " The mnemonic string"] + pub mnemonic: String, + #[doc = " The passphrase tied to that mnemonic"] + pub passphrase: String, +} +#[doc = " Response of the get_mnemonic_seed function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetMnemonicSeedResponse { + #[doc = " Seed bytes from the given passphrase and Mnemonic"] + pub mnemonic_seed: Vec, +} +#[doc = " Args of the get_public_key_rkyv_serialized function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetPublicKeyRkyvSerializedArgs { + #[doc = " The index of the public key to get"] + pub index: u64, + #[doc = " The seed to generate the sender keys from"] + pub seed: Vec, +} +#[doc = " Get the call data for stakeing"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStakeCallDataArgs { + #[doc = " The stake counter value"] + pub counter: u64, + #[doc = " The seed to generate the sender keys from"] + pub seed: Vec, + #[doc = " The stct proof as recieved from the node"] + pub spend_proof: Vec, + #[doc = " Index of the address of the staker in the seed"] + pub staker_index: u64, + #[doc = " The amount of value to stake"] + pub value: u64, +} +#[doc = " Response of the get_stake_call_data function, send this to the call_data in execute"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStakeCallDataResponse { + #[doc = " The contract to call encoded in bs58 format"] + pub contract: String, + #[doc = " The method to call on the contract"] + pub method: String, + #[doc = " The payload of the call"] + pub payload: Vec, +} +#[doc = " Args of the get_stake_info function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStakeInfoArgs { + #[doc = " The stake info of the stake obtained from the node"] + pub stake_info: Vec, +} +#[doc = " Response of the get_stake_info function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStakeInfoRespose { + #[doc = " amount staked"] + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[doc = " Signature counter to prevent replay"] + #[serde(skip_serializing_if = "Option::is_none")] + pub counter: Option, + #[doc = " eligiblity"] + #[serde(skip_serializing_if = "Option::is_none")] + pub eligiblity: Option, + #[doc = " True if the key has been authorized to stake"] + pub has_key: bool, + #[doc = " Has the given address staked"] + pub has_staked: bool, + #[doc = " Reward for participating in concensus"] + #[serde(skip_serializing_if = "Option::is_none")] + pub reward: Option, +} +#[doc = " Get the bytes for the stct proof to send to the node"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStctProofArgs { + #[doc = " The gas limit of the transaction"] + pub gas_limit: u64, + #[doc = " The gas price of the transaction"] + pub gas_price: u64, + #[doc = " The refund address in base58 format"] + pub refund: String, + #[doc = " The rng seed to generate the entropy for the notes"] + pub rng_seed: Vec, + #[doc = " The seed to generate the sender keys from"] + pub seed: Vec, + #[doc = " index of the sender in the seed"] + pub sender_index: u64, + #[doc = " The amount of value to send"] + pub value: u64, +} +#[doc = " Response of the get_stct_proof function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetStctProofResponse { + #[doc = " The blinder of the stct proof"] + pub blinder: Vec, + #[doc = " The bytes of the stct proof to send to the node"] + pub bytes: Vec, + #[doc = " The crossover value of the stct proof"] + pub crossover: Vec, + #[doc = " The Fee of the crossover note"] + pub fee: Vec, + #[doc = " The signature of the stct proof"] + pub signature: Vec, +} +#[doc = " Args of the get_unstake_call_data function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetUnstakeCallDataArgs { + #[doc = " The counter of the unstake note"] + pub counter: u64, + #[doc = " The seed to generate the sender keys from"] + pub seed: Vec, + #[doc = " The index of the public key to get"] + pub sender_index: u64, + #[doc = " The unstake note"] + pub unstake_note: Vec, + #[doc = " The unstake proof"] + pub unstake_proof: Vec, +} +#[doc = " Response of the get_wfct_proof function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct GetWfctProofResponse { + #[doc = " JubJubScalar Blinder for tx"] + pub blinder: Vec, + #[doc = " The bytes of the wfct proof to send to the node"] + pub bytes: Vec, + #[doc = " Crossover of the tx"] + pub crossover: Vec, + #[doc = " The fee of the tx"] + pub fee: Vec, + #[doc = " The unstake note"] + pub unstake_note: Vec, +} #[doc = " The arguments of the merge_notes function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct MergeNotesArgs { #[doc = " All serialized list of notes to be merged"] pub notes: Vec>, } +#[doc = " The arguments of the mnemonic_new function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct MnemonicNewArgs { + #[doc = " Cryptographically secure [u8; 64]"] + pub rng_seed: Vec, +} +#[doc = " Response of the new_mnemonic function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct MnewmonicNewResponse { + #[doc = " String from the generated mnemonic"] + pub mnemonic_string: String, +} +#[doc = " Information about the note"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct NoteInfoType { + #[doc = " The block height of the note"] + pub block_height: u64, + #[doc = " Singular Note rkyv serialized"] + pub note: Vec, + #[doc = " Nullifier of a Singular Note rkyv serialized"] + pub nullifier: Vec, + #[doc = " position of the note"] + pub pos: u64, + #[doc = " public spend key belonging to that note"] + pub psk: String, +} #[doc = " The arguments of the nullifiers function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct NullifiersArgs { @@ -111,12 +379,44 @@ pub struct NullifiersArgs { #[doc = " Seed used to derive the keys of the wallet"] pub seed: Vec, } +#[doc = " The type represents the Opening and the position of the note, the opening is of"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct OpeningType { + #[doc = " The rkyv serialized opening"] + pub opening: Vec, + #[doc = " The position of the note the opening is of"] + pub pos: u64, +} #[doc = " A note type variant"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum OutputType { Transparent, Obfuscated, } +#[doc = " Arguments of the prove_tx function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ProveTxArgs { + #[doc = " The bytes of the proof of the tx"] + pub proof: Vec, + #[doc = " The unproven_tx bytes"] + pub unproven_tx: Vec, +} +#[doc = " Response of the prove_tx function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct ProveTxResponse { + #[doc = " The bytes of the proven transaction ready to be sent to the node"] + pub bytes: Vec, + #[doc = " The hash of the proven transaction"] + pub hash: String, +} +#[doc = " Type of the response of the check_note_validity function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct PublicSpendKeysAndNotesType { + #[doc = " Array of notes which are rkyv serialized"] + pub notes: Vec, + #[doc = " The public spend key as a bs58 formated string"] + pub public_spend_key: String, +} #[doc = " The arguments of the public_spend_keys function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct PublicSpendKeysArgs { @@ -129,12 +429,117 @@ pub struct PublicSpendKeysResponse { #[doc = " The Base58 public spend keys of the wallet."] pub keys: Vec, } +#[doc = " Arguments of the rkyv_bls_scalar_array function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RkyvBlsScalarArrayArgs { + #[doc = " An array containing rkyv serialized bytes of each bls scalar"] + pub bytes: Vec>, +} +#[doc = " The arguments of the rkyv_notes_array function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RkyvNotesArray { + #[doc = " Array of notes which are rkyv serialized"] + pub notes: Vec>, +} +#[doc = " Arguments of the rkyv_openings_array function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RkyvOpeningsArray { + #[doc = " Vec containing the rkyv serialized bytes of each openings along with positions"] + pub openings: Vec, +} +#[doc = " The arguments of the balance function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RkyvTreeLeaf { + #[doc = " Bytes that are rkyv serialized into a phoenix_core::transaction::TreeLeaf"] + pub bytes: Vec, +} +#[doc = " The response of the public_spend_keys 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, +} +#[doc = " A serialized u64 using rkyv"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct RkyvU64 { + #[doc = " A u64 rust string, representing a valid rust u64 (max: 18446744073709551615)"] + pub value: u64, +} #[doc = " The arguments of the seed function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct SeedArgs { #[doc = " An arbitrary sequence of bytes used to generate a secure seed"] pub passphrase: Vec, } +#[doc = " The direction of the transaction"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub enum TransactionDirectionType { + In, + Out, +} +#[doc = " The type of the transaction history"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct TransactionHistoryType { + #[doc = " The amount of the transaction"] + pub amount: f64, + #[doc = " The block height of the transaction"] + pub block_height: u64, + #[doc = " The direction of the transaction, in or out"] + pub direction: TransactionDirectionType, + #[doc = " The fee of the transaction"] + pub fee: u64, + #[doc = " The hash of the transaction"] + pub id: String, +} +#[doc = " Metadata of the transaction, used in calculating history"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct TxDataType { + #[doc = " The amount of gas spent in the transaction"] + pub gas_spent: u64, + #[doc = " The raw transaction bytes"] + pub raw_tx: String, +} +#[doc = " Collection of transactions at a given block height"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct TxsDataType { + #[doc = " The block height of the transactions"] + pub block_height: u64, + #[doc = " The transactions at the given block height"] + pub txs: Vec, +} +#[doc = " Arguments of the unproven_tx_to_bytes_response"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct UnprovenTxToBytesResponse { + #[doc = " Serialied unproven_Tx ready to be sent to the network"] + pub serialized: Vec, +} +#[doc = " Arguments of the unspent spent notes response"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct UnpsentSpentNotesResponse { + #[doc = " The notes which are spent"] + pub spent_notes: Vec, + #[doc = " The notes which are not spent yet"] + pub unspent_notes: Vec, +} +#[doc = " Arguents of the unspent_spent_notes function"] +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct UnspentSpentNotesArgs { + #[doc = " The Array of block heights of thte notes in the same order as the notes"] + pub block_heights: Vec, + #[doc = " The UInt8Array of rkyv serialized nullifiers recieved from the node"] + pub existing_nullifiers: Vec, + #[doc = " The Array of rkyv serialized notes"] + pub notes: Vec>, + #[doc = " The Array of rkyv serialized nullifiers of the note in the same order as the "] + #[doc = " notes"] + pub nullifiers_of_notes: Vec>, + #[doc = " Array of bs58 encoded string to be sent with the response of the function"] + pub psks: Vec, +} #[doc = " The arguments of the view_keys function"] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct ViewKeysArgs { diff --git a/src/utils.rs b/src/utils.rs index 8614b81..9dd6349 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,12 +6,13 @@ //! Misc utilities required by the library implementation. -use crate::{tx, MAX_LEN, RNG_SEED}; +use crate::{tx, MAX_INPUT_NOTES, MAX_LEN, RNG_SEED}; use alloc::vec::Vec; use core::mem; use dusk_bytes::DeserializableSlice; +use dusk_jubjub::JubJubScalar; use dusk_pki::PublicSpendKey; use phoenix_core::Note; use rand_chacha::ChaCha12Rng; @@ -19,6 +20,8 @@ use rand_core::SeedableRng; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +type Node = (Note, tx::Opening, u64, JubJubScalar); + /// Composes a `i64` from the provided arguments. This will be returned from the /// WASM module functions. pub const fn compose(success: bool, ptr: u32, len: u32) -> i64 { @@ -33,9 +36,9 @@ pub const fn compose(success: bool, ptr: u32, len: u32) -> i64 { /// - status: a boolean indicating the success of the operation /// - ptr: a pointer to the underlying data /// - len: the length of the underlying data -pub const fn decompose(result: i64) -> (bool, u64, u64) { - let ptr = (result >> 32) as u64; - let len = ((result << 32) >> 48) as u64; +pub const fn decompose(result: i64) -> (bool, u32, u32) { + let ptr = (result >> 32) as u32; + let len = ((result & 0xFFFFFF00) >> 16) as u32; let success = ((result << 63) >> 63) == 0; (success, ptr, len) @@ -49,7 +52,7 @@ where { let args = args as *mut u8; let len = len as usize; - let args = unsafe { Vec::from_raw_parts(args, len, len) }; + let args: Vec = unsafe { Vec::from_raw_parts(args, len, len) }; let args = alloc::string::String::from_utf8(args).ok()?; serde_json::from_str(&args).ok() } @@ -63,11 +66,25 @@ pub fn sanitize_seed(bytes: Vec) -> Option<[u8; RNG_SEED]> { }) } +/// Sanitizes arbitrary bytes into well-formed seed. +pub fn sanitize_rng_seed(bytes: Vec) -> Option<[u8; 32]> { + (bytes.len() == 32).then(|| { + let mut seed = [0u8; 32]; + seed.copy_from_slice(&bytes); + seed + }) +} + /// Fails the operation pub const fn fail() -> i64 { compose(false, 0, 0) } +/// fail +pub const fn fail_with() -> i64 { + compose(true, 0, 0) +} + /// Converts the provided response into an allocated pointer and returns the /// composed success value. pub fn into_ptr(response: T) -> i64 @@ -101,15 +118,9 @@ where compose(true, ptr, len) } -/// Creates a secure RNG from a seed. -pub fn rng(seed: &[u8; RNG_SEED]) -> ChaCha12Rng { - let mut hash = Sha256::new(); - - hash.update(seed); - hash.update(b"RNG"); - - let hash = hash.finalize().into(); - ChaCha12Rng::from_seed(hash) +/// Creates a secure RNG directly a seed. +pub fn rng(seed: [u8; 32]) -> ChaCha12Rng { + ChaCha12Rng::from_seed(seed) } /// Creates a secure RNG from a seed with embedded index. @@ -142,27 +153,12 @@ pub fn bs58_to_psk(psk: &str) -> Option { PublicSpendKey::from_reader(&mut &bytes[..]).ok() } -/// Perform a knapsack algorithm to define the notes to be used as input. -/// -/// Returns a tuple containing (unspent, inputs). `unspent` contains the notes -/// that are not used. -#[allow(clippy::type_complexity)] -pub fn knapsack( - mut nodes: Vec<(Note, tx::Opening, u64, usize)>, - target_sum: u64, -) -> Option<(Vec, Vec<(Note, tx::Opening, u64, usize)>)> { +/// Calculate the inputs for a transaction. +pub fn inputs(nodes: Vec, target_sum: u64) -> Option> { if nodes.is_empty() { return None; } - // TODO implement a knapsack algorithm - // here we do a naive, desc order pick. optimally, we should maximize the - // number of smaller inputs that fits the target sum so we reduce the number - // of available small notes on the wallet. a knapsack implementation is - // optimal for such problems as it can deliver high confidence results - // with moderate memory space. - nodes.sort_by(|a, b| b.2.cmp(&a.2)); - let mut i = 0; let mut sum = 0; while sum < target_sum && i < nodes.len() { @@ -173,9 +169,84 @@ pub fn knapsack( if sum < target_sum { return None; } - let unspent = nodes.split_off(i).into_iter().map(|n| n.0).collect(); - Some((unspent, nodes)) + let inputs = pick_notes(target_sum, nodes); + + Some(inputs) +} + +/// Pick the notes to be used in a transaction from a vector of notes. +/// +/// The notes are picked in a way to maximize the number of notes used, while +/// minimizing the value employed. To do this we sort the notes in ascending +/// value order, and go through each combination in a lexicographic order +/// until we find the first combination whose sum is larger or equal to +/// the given value. If such a slice is not found, an empty vector is returned. +/// +/// Note: it is presupposed that the input notes contain enough balance to cover +/// the given `value`. +fn pick_notes(value: u64, notes_and_values: Vec) -> Vec { + let mut notes_and_values = notes_and_values; + let len = notes_and_values.len(); + + if len <= MAX_INPUT_NOTES { + return notes_and_values; + } + + notes_and_values.sort_by(|(_, _, aval, _), (_, _, bval, _)| aval.cmp(bval)); + + pick_lexicographic(notes_and_values.len(), |indices| { + indices + .iter() + .map(|index| notes_and_values[*index].2) + .sum::() + >= value + }) + .map(|indices| { + indices + .into_iter() + .map(|index| notes_and_values[index]) + .collect() + }) + .unwrap_or_default() +} + +fn pick_lexicographic bool>( + max_len: usize, + is_valid: F, +) -> Option<[usize; MAX_INPUT_NOTES]> { + let mut indices = [0; MAX_INPUT_NOTES]; + indices + .iter_mut() + .enumerate() + .for_each(|(i, index)| *index = i); + + loop { + if is_valid(&indices) { + return Some(indices); + } + + let mut i = MAX_INPUT_NOTES - 1; + + while indices[i] == i + max_len - MAX_INPUT_NOTES { + if i > 0 { + i -= 1; + } else { + break; + } + } + + indices[i] += 1; + for j in i + 1..MAX_INPUT_NOTES { + indices[j] = indices[j - 1] + 1; + } + + if indices[MAX_INPUT_NOTES - 1] == max_len { + break; + } + } + + None } #[test] @@ -199,52 +270,56 @@ fn knapsack_works() { let rng = &mut StdRng::seed_from_u64(0xbeef); // sanity check - assert_eq!(knapsack(vec![], 70), None); + assert_eq!(inputs(vec![], 70), None); // basic check let key = SecretSpendKey::random(rng); let blinder = JubJubScalar::random(rng); let note = Note::obfuscated(rng, &key.public_spend_key(), 100, blinder); - let available = vec![(note, o, 100, 0)]; - let unspent = vec![]; - let inputs = available.clone(); - assert_eq!(knapsack(available, 70), Some((unspent, inputs))); + let available = vec![(note, o, 100, blinder)]; + let inputs_notes = available.clone(); + assert_eq!(inputs(available, 70), Some(inputs_notes)); // out of balance basic check let key = SecretSpendKey::random(rng); let blinder = JubJubScalar::random(rng); let note = Note::obfuscated(rng, &key.public_spend_key(), 100, blinder); - let available = vec![(note, o, 100, 0)]; - assert_eq!(knapsack(available, 101), None); + let available = vec![(note, o, 100, blinder)]; + assert_eq!(inputs(available, 101), None); // multiple inputs check // note: this test is checking a naive, simple order-based output let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder1 = JubJubScalar::random(rng); let note1 = Note::obfuscated(rng, &key.public_spend_key(), 100, blinder); let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder2 = JubJubScalar::random(rng); let note2 = Note::obfuscated(rng, &key.public_spend_key(), 500, blinder); let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder3 = JubJubScalar::random(rng); let note3 = Note::obfuscated(rng, &key.public_spend_key(), 300, blinder); - let available = - vec![(note1, o, 100, 0), (note2, o, 500, 1), (note3, o, 300, 2)]; - let unspent = vec![note1]; - let inputs = vec![(note2, o, 500, 1), (note3, o, 300, 2)]; - assert_eq!(knapsack(available, 600), Some((unspent, inputs))); + let available = vec![ + (note1, o, 100, blinder1), + (note2, o, 500, blinder2), + (note3, o, 300, blinder3), + ]; + + assert_eq!(inputs(available.clone(), 600), Some(available)); // multiple inputs, out of balance check let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder1 = JubJubScalar::random(rng); let note1 = Note::obfuscated(rng, &key.public_spend_key(), 100, blinder); let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder2 = JubJubScalar::random(rng); let note2 = Note::obfuscated(rng, &key.public_spend_key(), 500, blinder); let key = SecretSpendKey::random(rng); - let blinder = JubJubScalar::random(rng); + let blinder3 = JubJubScalar::random(rng); let note3 = Note::obfuscated(rng, &key.public_spend_key(), 300, blinder); - let available = - vec![(note1, o, 100, 0), (note2, o, 500, 1), (note3, o, 300, 2)]; - assert_eq!(knapsack(available, 901), None); + let available = vec![ + (note1, o, 100, blinder1), + (note2, o, 500, blinder2), + (note3, o, 300, blinder3), + ]; + assert_eq!(inputs(available, 901), None); } diff --git a/tests/wallet.rs b/tests/wallet.rs index fa1611a..9cf60fe 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -7,8 +7,15 @@ //! Wallet library tests. use dusk_bytes::Serializable; +use dusk_jubjub::JubJubScalar; use dusk_pki::PublicSpendKey; -use dusk_wallet_core::{tx, types, utils, MAX_KEY, MAX_LEN, RNG_SEED}; +use dusk_wallet_core::{ + tx, + types::{self, CrossoverType as WasmCrossover}, + utils, MAX_KEY, MAX_LEN, RNG_SEED, +}; +use phoenix_core::Crossover; + use rusk_abi::ContractId; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -35,7 +42,7 @@ fn balance_works() { .call( "balance", json!({ - "notes": node::notes(&seed, values), + "notes": node::notes(&seed, values).0, "seed": seed.to_vec(), }), ) @@ -48,7 +55,7 @@ fn balance_works() { #[test] fn execute_works() { let seed = [0xfa; RNG_SEED]; - let rng_seed = [0xfb; RNG_SEED]; + let rng_seed = [0xfb; 32]; let values = [10, 250, 15, 7500]; let mut wallet = Wallet::default(); @@ -63,25 +70,39 @@ fn execute_works() { .take_contents(); let psk = &keys[0]; - let mut contract = ContractId::uninitialized(); + let mut contract: ContractId = ContractId::uninitialized(); contract.as_bytes_mut().iter_mut().for_each(|b| *b = 0xfa); let contract = bs58::encode(contract.as_bytes()).into_string(); let (inputs, openings) = node::notes_and_openings(&seed, values); + let crossover = Crossover::default(); + let blinder = JubJubScalar::default(); + + let crossover = WasmCrossover { + blinder: rkyv::to_bytes::(&blinder) + .unwrap() + .to_vec(), + crossover: rkyv::to_bytes::(&crossover) + .unwrap() + .to_vec(), + value: 0, + }; + let args = json!({ "call": { "contract": contract, "method": "commit", "payload": b"We lost because we told ourselves we lost.".to_vec(), }, - "crossover": 25, + "crossover": crossover, "gas_limit": 100, "gas_price": 2, "inputs": inputs, + "sender_index": 0, "openings": openings, "output": { - "note_type": "Transparent", - "receiver": psk, + "note_type": "Obfuscated", + "receiver": &keys[1], "ref_id": 15, "value": 10, }, @@ -89,11 +110,11 @@ fn execute_works() { "rng_seed": rng_seed.to_vec(), "seed": seed.to_vec() }); - let types::ExecuteResponse { tx, unspent } = + + let types::ExecuteResponse { tx } = wallet.call("execute", args).take_contents(); rkyv::from_bytes::(&tx).unwrap(); - rkyv::from_bytes::>(&unspent).unwrap(); } #[test] @@ -101,10 +122,10 @@ fn merge_notes_works() { let seed = [0xfa; RNG_SEED]; let notes1 = node::raw_notes(&seed, [10, 250, 15, 39, 55]); - let notes2 = vec![notes1[1].clone(), notes1[3].clone()]; + let notes2 = vec![notes1[1], notes1[3]]; let notes3: Vec<_> = node::raw_notes(&seed, [10, 250, 15, 39, 55]) .into_iter() - .chain([notes1[4].clone()]) + .chain([notes1[4]]) .collect(); let notes_unmerged: Vec<_> = notes1 @@ -143,7 +164,7 @@ fn filter_notes_works() { let notes = node::raw_notes(&seed, [10, 250, 15, 39, 55]); let flags = vec![true, true, false, true, false]; - let filtered = vec![notes[2].clone(), notes[4].clone()]; + let filtered = vec![notes[2], notes[4]]; let filtered = utils::sanitize_notes(filtered); let notes = rkyv::to_bytes::<_, MAX_LEN>(¬es).unwrap().into_vec(); @@ -236,21 +257,21 @@ mod node { use core::mem; use dusk_jubjub::{BlsScalar, JubJubScalar}; - use dusk_wallet_core::{key, tx, utils, MAX_KEY, MAX_LEN, RNG_SEED}; + use dusk_wallet_core::{key, tx, MAX_KEY, MAX_LEN, RNG_SEED}; use phoenix_core::Note; - use rand::RngCore; + use rand::{rngs::StdRng, RngCore}; + use rand_core::SeedableRng; pub fn raw_notes(seed: &[u8; RNG_SEED], values: Values) -> Vec where Values: IntoIterator, { - let rng = &mut utils::rng(seed); + let rng = &mut StdRng::from_entropy(); values .into_iter() .map(|value| { let obfuscated = (rng.next_u32() & 1) == 1; - let idx = rng.next_u64() % MAX_KEY as u64; - let psk = key::derive_ssk(seed, idx).public_spend_key(); + let psk = key::derive_ssk(seed, 0).public_spend_key(); if obfuscated { let blinder = JubJubScalar::random(rng); @@ -262,13 +283,32 @@ mod node { .collect() } - pub fn notes(seed: &[u8; RNG_SEED], values: Values) -> Vec + pub fn notes( + seed: &[u8; RNG_SEED], + values: Values, + ) -> (Vec, Vec) where Values: IntoIterator, { - rkyv::to_bytes::<_, MAX_LEN>(&raw_notes(seed, values)) - .expect("failed to serialize notes") - .into_vec() + let notes = raw_notes(seed, values); + let len = notes.len(); + + let openings: Vec<_> = (0..len) + .zip(notes.clone()) + .map(|(_, note)| { + let opening = unsafe { mem::zeroed::() }; + (opening, *note.pos()) + }) + .collect(); + + ( + rkyv::to_bytes::<_, MAX_LEN>(¬es) + .expect("failed to serialize notes") + .into_vec(), + rkyv::to_bytes::, MAX_LEN>(&openings) + .expect("failed to serialize openings") + .into_vec(), + ) } pub fn notes_and_openings( @@ -279,15 +319,8 @@ mod node { Values: IntoIterator, { let values: Vec<_> = values.into_iter().collect(); - let len = values.len(); - let notes = notes(seed, values); - let openings: Vec<_> = (0..len) - .map(|_| unsafe { mem::zeroed::() }) - .collect(); - let openings = rkyv::to_bytes::<_, MAX_LEN>(&openings) - .expect("failed to serialize openings") - .into_vec(); + let (notes, openings) = notes(seed, values); (notes, openings) } @@ -299,7 +332,7 @@ mod node { where Values: IntoIterator, { - let rng = &mut utils::rng(seed); + let rng = &mut StdRng::from_entropy(); values .into_iter() .map(|value| { @@ -330,8 +363,8 @@ pub struct Wallet { pub struct CallResult<'a> { pub status: bool, - pub val: u64, - pub aux: u64, + pub val: u32, + pub aux: u32, pub wallet: &'a mut Wallet, } @@ -357,7 +390,7 @@ impl<'a> CallResult<'a> { .get_memory("memory") .unwrap() .view(&self.wallet.store) - .read(self.val, &mut bytes) + .read(self.val as u64, &mut bytes) .unwrap(); self.wallet @@ -384,7 +417,7 @@ impl<'a> CallResult<'a> { serde_json::from_str(&json).unwrap() } - pub fn take_val(self) -> u64 { + pub fn take_val(self) -> u32 { assert!(self.status); self.val }