From 59362632da34f0c07a567c18ba338bc33494fdfa Mon Sep 17 00:00:00 2001 From: Milosz Muszynski Date: Tue, 7 Nov 2023 11:15:21 +0100 Subject: [PATCH] Refactor, run result display, zk-citadel 0.5.1, wallet.dat format psw --- .../tests/citadel/int_test_user.rs | 157 +--- license-provider/src/license_issuer.rs | 4 +- moat-cli/src/command.rs | 756 ++++++++---------- moat-cli/src/interactor.rs | 16 +- moat-cli/src/main.rs | 4 + moat-cli/src/run_result.rs | 263 ++++++ .../src/blockchain_payloads/payload_sender.rs | 2 +- .../src/citadel_licenses/license_user.rs | 86 ++ moat-core/src/citadel_licenses/mod.rs | 9 + .../src/citadel_queries/citadel_inquirer.rs | 66 +- moat-core/src/lib.rs | 2 + wallet-accessor/Cargo.toml | 1 + wallet-accessor/src/wallet_accessor.rs | 30 +- 13 files changed, 827 insertions(+), 569 deletions(-) create mode 100644 moat-cli/src/run_result.rs create mode 100644 moat-core/src/citadel_licenses/license_user.rs create mode 100644 moat-core/src/citadel_licenses/mod.rs diff --git a/integration-tests/tests/citadel/int_test_user.rs b/integration-tests/tests/citadel/int_test_user.rs index 2cecba6..046c06d 100644 --- a/integration-tests/tests/citadel/int_test_user.rs +++ b/integration-tests/tests/citadel/int_test_user.rs @@ -19,33 +19,25 @@ //! nullifier (or session id) in a collection which stops us from double //! usage of the license) -use bytecheck::CheckBytes; -use bytes::Bytes; use dusk_bls12_381::BlsScalar; use dusk_bytes::DeserializableSlice; -use dusk_pki::{PublicSpendKey, SecretSpendKey}; +use dusk_pki::SecretSpendKey; use dusk_plonk::prelude::*; use dusk_wallet::{RuskHttpClient, WalletPath}; use license_provider::{LicenseIssuer, ReferenceLP}; -use moat_core::Error::InvalidQueryResponse; use moat_core::{ BcInquirer, CitadelInquirer, Error, JsonLoader, LicenseCircuit, - LicenseSessionId, PayloadRetriever, PayloadSender, RequestCreator, - RequestJson, RequestSender, StreamAux, TxAwaiter, ARITY, DEPTH, - LICENSE_CONTRACT_ID, USE_LICENSE_METHOD_NAME, + LicenseSessionId, LicenseUser, PayloadRetriever, RequestCreator, + RequestJson, RequestSender, TxAwaiter, }; -use poseidon_merkle::Opening; use rand::rngs::StdRng; use rand::SeedableRng; -use rkyv::{check_archived_root, Archive, Deserialize, Infallible, Serialize}; use std::path::PathBuf; use toml_base_config::BaseConfig; use tracing::{info, Level}; use wallet_accessor::BlockchainAccessConfig; use wallet_accessor::Password::PwdHash; -use zk_citadel::license::{ - CitadelProverParameters, License, Request, SessionCookie, -}; +use zk_citadel::license::Request; const WALLET_PATH: &str = concat!(env!("HOME"), "/.dusk/rusk-wallet"); const PWD_HASH: &str = @@ -56,34 +48,6 @@ const GAS_PRICE: u64 = 1; static LABEL: &[u8] = b"dusk-network"; const CAPACITY: usize = 17; // capacity required for the setup -/// Use License Argument. -#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct UseLicenseArg { - pub proof: Proof, - pub public_inputs: Vec, -} - -fn compute_citadel_parameters( - rng: &mut StdRng, - ssk: SecretSpendKey, - psk_lp: PublicSpendKey, - lic: &License, - merkle_proof: Opening<(), DEPTH, ARITY>, - challenge: &JubJubScalar, -) -> (CitadelProverParameters, SessionCookie) { - let (cpp, sc) = CitadelProverParameters::compute_parameters( - &ssk, - &lic, - &psk_lp, - &psk_lp, - challenge, - rng, - merkle_proof, - ); - (cpp, sc) -} - /// Calls license contract's issue license method. /// Awaits for confirmation of the contract-calling transaction. async fn issue_license( @@ -107,78 +71,6 @@ async fn issue_license( Ok(tx_id) } -/// Calculates and verified proof, sends proof along with public parameters -/// as arguments to the license contract's use_license method. -/// Awaits for confirmation of the contract-calling transaction. -async fn prove_and_send_use_license( - client: &RuskHttpClient, - blockchain_config: &BlockchainAccessConfig, - wallet_path: &WalletPath, - reference_lp: &ReferenceLP, - ssk_user: SecretSpendKey, - prover: &Prover, - verifier: &Verifier, - license: &License, - opening: Opening<(), DEPTH, ARITY>, - rng: &mut StdRng, - challenge: &JubJubScalar, -) -> Result { - let (cpp, sc) = compute_citadel_parameters( - rng, - ssk_user, - reference_lp.psk_lp, - license, - opening, - &challenge, - ); - let circuit = LicenseCircuit::new(&cpp, &sc); - - info!("calculating proof"); - let (proof, public_inputs) = - prover.prove(rng, &circuit).expect("Proving should succeed"); - - assert!(!public_inputs.is_empty()); - let session_id = public_inputs[0]; - - verifier - .verify(&proof, &public_inputs) - .expect("Verifying the circuit should succeed"); - info!("proof validated locally"); - - let use_license_arg = UseLicenseArg { - proof, - public_inputs, - }; - - info!("calling license contract's use_license"); - let tx_id = PayloadSender::execute_contract_method( - use_license_arg, - &blockchain_config, - &wallet_path, - &PwdHash(PWD_HASH.to_string()), - GAS_LIMIT, - GAS_PRICE, - LICENSE_CONTRACT_ID, - USE_LICENSE_METHOD_NAME, - ) - .await?; - TxAwaiter::wait_for(&client, tx_id).await?; - Ok(session_id) -} - -/// Deserializes license, panics if deserialization fails. -fn deserialise_license(v: &Vec) -> License { - let response_data = check_archived_root::(v.as_slice()) - .map_err(|_| { - InvalidQueryResponse(Box::from("rkyv deserialization error")) - }) - .expect("License should deserialize correctly"); - let license: License = response_data - .deserialize(&mut Infallible) - .expect("Infallible"); - license -} - /// Displays license contract current state summary. async fn show_state( client: &RuskHttpClient, @@ -196,24 +88,6 @@ async fn show_state( Ok(()) } -/// Finds owned license in a stream of licenses. -/// It searches in a reverse order to return a newest license. -fn find_owned_license( - ssk_user: SecretSpendKey, - stream: &mut (impl futures_core::Stream> - + std::marker::Unpin), -) -> Result<(u64, License), Error> { - const ITEM_LEN: usize = CitadelInquirer::GET_LICENSES_ITEM_LEN; - let (pos, lic_ser) = StreamAux::find_item::<(u64, Vec), ITEM_LEN>( - |(_, lic_vec)| { - let license = deserialise_license(lic_vec); - Ok(ssk_user.view_key().owns(&license.lsa)) - }, - stream, - )?; - Ok((pos, deserialise_license(&lic_ser))) -} - /// /// test user_round_trip realizes the following scenario: /// - creates request (User) @@ -336,8 +210,9 @@ async fn user_round_trip() -> Result<(), Error> { let mut licenses_stream = CitadelInquirer::get_licenses(&client, block_heights).await?; - let (pos, license) = find_owned_license(ssk_user, &mut licenses_stream) - .expect("owned license found"); + let owned_licenses = + CitadelInquirer::find_owned_licenses(ssk_user, &mut licenses_stream)?; + let (pos, license) = owned_licenses.last().expect("owned license found"); // as a User, call get_merkle_opening, obtain opening info!("calling get_merkle_opening (as a user)"); @@ -351,23 +226,29 @@ async fn user_round_trip() -> Result<(), Error> { // so that it is different every time we run the test let (_, _, num_sessions) = CitadelInquirer::get_info(&client).await?; let challenge = JubJubScalar::from(num_sessions as u64 + 1); - info!("calling use_license (as a user)"); - let session_id = prove_and_send_use_license( - &client, + info!("proving license and calling use_license (as a user)"); + let (tx_id, session_cookie) = LicenseUser::prove_and_use_license( &blockchain_config, &wallet_path, - &reference_lp, - ssk_user, + &PwdHash(PWD_HASH.to_string()), + &ssk_user, + &reference_lp.psk_lp, &prover, &verifier, &license, opening.unwrap(), &mut rng, &challenge, + GAS_LIMIT, + GAS_PRICE, ) .await?; + TxAwaiter::wait_for(&client, tx_id).await?; + show_state(&client, "after use_license").await?; - let session_id = LicenseSessionId { id: session_id }; + let session_id = LicenseSessionId { + id: session_cookie.session_id, + }; // as an SP, call get_session info!("calling get_session (as an SP)"); diff --git a/license-provider/src/license_issuer.rs b/license-provider/src/license_issuer.rs index b73408a..85a9c4e 100644 --- a/license-provider/src/license_issuer.rs +++ b/license-provider/src/license_issuer.rs @@ -26,7 +26,7 @@ pub struct LicenseIssuer { gas_price: u64, } -const USER_ATTRIBUTES: u64 = 1 << 17; +const ATTRIBUTE_DATA: u64 = 1 << 17; impl LicenseIssuer { pub fn new( @@ -51,7 +51,7 @@ impl LicenseIssuer { request: &Request, ssk_lp: &SecretSpendKey, ) -> Result<(BlsScalar, Vec), Error> { - let attr = JubJubScalar::from(USER_ATTRIBUTES); + let attr = JubJubScalar::from(ATTRIBUTE_DATA); let license = License::new(&attr, ssk_lp, request, rng); let license_blob = rkyv::to_bytes::<_, MAX_LICENSE_SIZE>(&license) .expect("License should serialize correctly") diff --git a/moat-cli/src/command.rs b/moat-cli/src/command.rs index ccb81d8..1bda2b4 100644 --- a/moat-cli/src/command.rs +++ b/moat-cli/src/command.rs @@ -5,29 +5,27 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::interactor::SetupHolder; +use crate::run_result::{ + IssueLicenseSummary, LicenseContractSummary, RequestsLPSummary, + RequestsSummary, RunResult, SessionSummary, SubmitRequestSummary, + UseLicenseSummary, +}; use crate::SeedableRng; -use bytecheck::CheckBytes; -use bytes::Bytes; use dusk_bls12_381::BlsScalar; use dusk_bytes::DeserializableSlice; use dusk_pki::{PublicSpendKey, SecretSpendKey}; use dusk_plonk::prelude::*; use dusk_wallet::{RuskHttpClient, WalletPath}; use license_provider::{LicenseIssuer, ReferenceLP}; -use moat_core::Error::InvalidQueryResponse; use moat_core::{ BcInquirer, CitadelInquirer, Error, JsonLoader, LicenseCircuit, - LicenseSessionId, PayloadSender, RequestCreator, RequestJson, - RequestScanner, RequestSender, StreamAux, TxAwaiter, LICENSE_CONTRACT_ID, - USE_LICENSE_METHOD_NAME, + LicenseSessionId, LicenseUser, RequestCreator, RequestJson, RequestScanner, + RequestSender, TxAwaiter, }; use rand::rngs::StdRng; -use rkyv::ser::serializers::AllocSerializer; -use rkyv::{check_archived_root, Archive, Deserialize, Infallible, Serialize}; -use sha3::{Digest, Sha3_256}; use std::path::{Path, PathBuf}; use wallet_accessor::{BlockchainAccessConfig, Password, WalletAccessor}; -use zk_citadel::license::{CitadelProverParameters, License}; +use zk_citadel::license::{License, SessionCookie}; /// Commands that can be run against the Moat #[derive(PartialEq, Eq, Hash, Clone, Debug)] @@ -35,7 +33,7 @@ pub(crate) enum Command { /// Submit request (User) SubmitRequest { request_path: Option }, /// List requests (User) - ListRequestsUser { dummy: bool }, + ListRequestsUser, /// List requests (LP) ListRequestsLP { lp_config_path: Option }, /// Issue license (LP) @@ -55,84 +53,9 @@ pub(crate) enum Command { /// Get session (SP) GetSession { session_id: String }, /// Show state - ShowState { dummy: bool }, -} - -// todo: move this function somewhere else -/// Deserializes license, panics if deserialization fails. -fn deserialise_license(v: &Vec) -> License { - let response_data = check_archived_root::(v.as_slice()) - .map_err(|_| { - InvalidQueryResponse(Box::from("rkyv deserialization error")) - }) - .expect("License should deserialize correctly"); - let license: License = response_data - .deserialize(&mut Infallible) - .expect("Infallible"); - license + ShowState, } -// todo: move this function somewhere else -/// Finds owned license in a stream of licenses. -/// It searches in a reverse order to return a newest license. -fn find_owned_licenses( - ssk_user: SecretSpendKey, - stream: &mut (impl futures_core::Stream> - + std::marker::Unpin), -) -> Result, Error> { - const ITEM_LEN: usize = CitadelInquirer::GET_LICENSES_ITEM_LEN; - let mut pairs = vec![]; - loop { - let r = StreamAux::find_item::<(u64, Vec), ITEM_LEN>( - |(_, lic_vec)| { - let license = deserialise_license(lic_vec); - Ok(ssk_user.view_key().owns(&license.lsa)) - }, - stream, - ); - if r.is_err() { - break; - } - let (pos, lic_ser) = r?; - pairs.push((pos, deserialise_license(&lic_ser))) - } - Ok(pairs) -} - -// todo: move this function somewhere else and possibly merge with -// find_owned_licenses -/// Finds owned license in a stream of licenses. -/// It searches in a reverse order to return a newest license. -fn find_all_licenses( - stream: &mut (impl futures_core::Stream> - + std::marker::Unpin), -) -> Result, Error> { - const ITEM_LEN: usize = CitadelInquirer::GET_LICENSES_ITEM_LEN; - let mut pairs = vec![]; - loop { - let r = StreamAux::find_item::<(u64, Vec), ITEM_LEN>( - |_| Ok(true), - stream, - ); - if r.is_err() { - break; - } - let (pos, lic_ser) = r?; - pairs.push((pos, deserialise_license(&lic_ser))) - } - Ok(pairs) -} - -// todo: move this struct to its proper place -/// Use License Argument. -#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct UseLicenseArg { - pub proof: Proof, - pub public_inputs: Vec, -} - -// todo: move these consts to their proper place static LABEL: &[u8] = b"dusk-network"; const CAPACITY: usize = 17; // capacity required for the setup @@ -148,310 +71,375 @@ impl Command { gas_price: u64, request_json: Option, setup_holder: &mut Option, - ) -> Result<(), Error> { - match self { + ) -> Result { + let run_result = match self { Command::SubmitRequest { request_path } => { - let request_json = match request_path { - Some(request_path) => RequestJson::from_file(request_path)?, - _ => request_json.expect("request should be provided"), - }; - let rng = &mut StdRng::from_entropy(); // seed_from_u64(0xcafe); - let request = RequestCreator::create_from_hex_args( - request_json.user_ssk, - request_json.provider_psk.clone(), - rng, - )?; - let request_hash_hex = Self::to_hash_hex(&request); - println!( - "submitting request to provider psk: {}", - request_json.provider_psk - ); - let tx_id = RequestSender::send_request( - request, - blockchain_access_config, + Self::submit_request( wallet_path, psw, + blockchain_access_config, gas_limit, gas_price, + request_json, + request_path, ) - .await?; - let client = RuskHttpClient::new( - blockchain_access_config.rusk_address.clone(), - ); - TxAwaiter::wait_for(&client, tx_id).await?; - println!( - "request submitting transaction {} confirmed", - hex::encode(tx_id.to_bytes()) - ); - println!("request submitted: {}", request_hash_hex); - println!(); + .await? } - Command::ListRequestsUser { dummy: true } => { - let wallet_accessor = - WalletAccessor::new(wallet_path.clone(), psw.clone()); - let note_hashes: Vec = wallet_accessor - .get_notes(blockchain_access_config) + Command::ListRequestsUser => { + Self::list_requests(wallet_path, psw, blockchain_access_config) .await? - .iter() - .flat_map(|n| n.nullified_by) - .collect(); - // println!("current address has {} notes", note_hashes.len()); - - let mut found_requests = vec![]; - let mut height = 0; - let mut total_requests = 0usize; - loop { - let height_end = height + 10000; - let (requests, top, total) = - RequestScanner::scan_related_to_notes_in_block_range( - height, - height_end, - blockchain_access_config, - ¬e_hashes, - ) - .await?; - found_requests.extend(requests); - total_requests += total; - if top <= height_end { - height = top; - break; - } - height = height_end; - } - let owned_requests = found_requests.len(); - println!( - "scanned {} blocks, found {} requests, {} owned requests:", - height, total_requests, owned_requests, - ); - for request in found_requests.iter() { - println!("request: {}", Self::to_hash_hex(request)); - } - println!(); } Command::ListRequestsLP { lp_config_path } => { - let lp_config_path = match lp_config_path { - Some(lp_config_path) => lp_config_path, - _ => PathBuf::from(lp_config), - }; - let mut reference_lp = ReferenceLP::create(lp_config_path)?; - let (total_count, this_lp_count) = - reference_lp.scan(blockchain_access_config).await?; - println!( - "found {} requests total, {} requests for this LP:", - total_count, this_lp_count - ); - for request in reference_lp.requests_to_process.iter() { - println!( - "request to process by LP: {}", - Self::to_hash_hex(request) - ); - } - println!(); + Self::list_requests_lp( + blockchain_access_config, + lp_config, + lp_config_path, + ) + .await? } Command::IssueLicenseLP { lp_config_path, request_hash, } => { - let mut rng = StdRng::from_entropy(); // seed_from_u64(0xbeef); - let lp_config_path = match lp_config_path { - Some(lp_config_path) => lp_config_path, - _ => PathBuf::from(lp_config), - }; - let mut reference_lp = ReferenceLP::create(lp_config_path)?; - let (_total_count, _this_lp_count) = - reference_lp.scan(blockchain_access_config).await?; - - let request = reference_lp.get_request(&request_hash); - match request { - Some(request) => { - let license_issuer = LicenseIssuer::new( - blockchain_access_config.clone(), - wallet_path.clone(), - psw.clone(), - gas_limit, - gas_price, - ); - - println!( - "issuing license for request: {}", - Self::to_hash_hex(&request) - ); - let (tx_id, license_blob) = license_issuer - .issue_license( - &mut rng, - &request, - &reference_lp.ssk_lp, - ) - .await?; - println!( - "license issuing transaction {} confirmed", - hex::encode(tx_id.to_bytes()) - ); - println!( - "issued license: {}", - Self::blob_to_hash_hex(license_blob.as_slice()) - ); - } - _ => { - println!("Request not found"); - } - } - - println!(); + Self::issue_license_lp( + wallet_path, + psw, + blockchain_access_config, + lp_config, + gas_limit, + gas_price, + lp_config_path, + request_hash, + ) + .await? } Command::ListLicenses { request_path } => { - let request_json = match request_path { - Some(request_path) => RequestJson::from_file(request_path)?, - _ => request_json.expect("request should be provided"), - }; Self::list_licenses( blockchain_access_config, - Some(&request_json), + request_json, + request_path, ) - .await?; - println!(); + .await? } Command::UseLicense { request_path, license_hash, } => { - let request_json = match request_path { - Some(request_path) => RequestJson::from_file(request_path)?, - _ => request_json.expect("request should be provided"), - }; - let pos_license = Self::get_license_to_use( + Self::use_license( + wallet_path, + psw, blockchain_access_config, - Some(&request_json), - license_hash.clone(), + gas_limit, + gas_price, + request_json, + setup_holder, + request_path, + license_hash, ) - .await?; - match pos_license { - Some((pos, license)) => { - println!( - "using license: {}", - Self::to_hash_hex(&license) - ); - // println!("user_ssk={}", request_json.user_ssk); - // println!("lp_psk={}", request_json.provider_psk); - let ssk_user = SecretSpendKey::from_slice( - hex::decode(request_json.user_ssk)?.as_slice(), - )?; - let psk_lp = PublicSpendKey::from_slice( - hex::decode(request_json.provider_psk)?.as_slice(), - )?; - let _session_id = Self::prove_and_send_use_license( - blockchain_access_config, - wallet_path, - psw, - psk_lp, - ssk_user, - &license, - pos, - gas_limit, - gas_price, - setup_holder, - ) - .await?; - } - _ => { - println!("Please obtain a license"); - } - } - println!(); + .await? } Command::RequestService { session_cookie: _ } => { println!("Off-chain request service to be placed here"); - println!(); + RunResult::Empty } Command::GetSession { session_id } => { - let client = RuskHttpClient::new( - blockchain_access_config.rusk_address.clone(), - ); - let id = LicenseSessionId { - id: BlsScalar::from_slice( - hex::decode(session_id.clone())?.as_slice(), - )?, - }; - match CitadelInquirer::get_session(&client, id).await? { - Some(session) => { - println!("obtained session with id={}:", session_id); - println!(); - for s in session.public_inputs.iter() { - println!("{}", hex::encode(s.to_bytes())); - } - } - _ => { - println!("session not found"); - } - } - println!(); + Self::get_session(blockchain_access_config, session_id).await? } - Command::ShowState { dummy: true } => { - let client = RuskHttpClient::new( - blockchain_access_config.rusk_address.clone(), - ); - let (num_licenses, _, num_sessions) = - CitadelInquirer::get_info(&client).await?; - println!( - "license contract state - licenses: {}, sessions: {}", - num_licenses, num_sessions - ); - println!(); + Command::ShowState => { + Self::show_state(blockchain_access_config).await? + } + }; + Ok(run_result) + } + + /// Command: Submit Request + async fn submit_request( + wallet_path: &WalletPath, + psw: &Password, + blockchain_access_config: &BlockchainAccessConfig, + gas_limit: u64, + gas_price: u64, + request_json: Option, + request_path: Option, + ) -> Result { + let request_json = match request_path { + Some(request_path) => RequestJson::from_file(request_path)?, + _ => request_json.expect("request should be provided"), + }; + let rng = &mut StdRng::from_entropy(); // seed_from_u64(0xcafe); + let request = RequestCreator::create_from_hex_args( + request_json.user_ssk, + request_json.provider_psk.clone(), + rng, + )?; + let request_hash = RunResult::to_hash_hex(&request); + let tx_id = RequestSender::send_request( + request, + blockchain_access_config, + wallet_path, + psw, + gas_limit, + gas_price, + ) + .await?; + let client = + RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); + TxAwaiter::wait_for(&client, tx_id).await?; + let summary = SubmitRequestSummary { + psk_lp: request_json.provider_psk, + tx_id: hex::encode(tx_id.to_bytes()), + request_hash, + }; + Ok(RunResult::SubmitRequest(summary)) + } + + /// Command: List Requests + async fn list_requests( + wallet_path: &WalletPath, + psw: &Password, + blockchain_access_config: &BlockchainAccessConfig, + ) -> Result { + let wallet_accessor = + WalletAccessor::create(wallet_path.clone(), psw.clone())?; + let note_hashes: Vec = wallet_accessor + .get_notes(blockchain_access_config) + .await? + .iter() + .flat_map(|n| n.nullified_by) + .collect(); + + let mut found_requests = vec![]; + let mut height = 0; + let mut found_total = 0usize; + loop { + let height_end = height + 10000; + let (requests, top, total) = + RequestScanner::scan_related_to_notes_in_block_range( + height, + height_end, + blockchain_access_config, + ¬e_hashes, + ) + .await?; + found_requests.extend(requests); + found_total += total; + if top <= height_end { + height = top; + break; } - _ => (), + height = height_end; } - Ok(()) + let found_owned = found_requests.len(); + let summary = RequestsSummary { + height, + found_total, + found_owned, + }; + let run_result = RunResult::Requests(summary, found_requests); + Ok(run_result) + } + + /// Command: List Requests LP + async fn list_requests_lp( + blockchain_access_config: &BlockchainAccessConfig, + lp_config: &Path, + lp_config_path: Option, + ) -> Result { + let lp_config_path = match lp_config_path { + Some(lp_config_path) => lp_config_path, + _ => PathBuf::from(lp_config), + }; + let mut reference_lp = ReferenceLP::create(lp_config_path)?; + let (found_total, found_owned) = + reference_lp.scan(blockchain_access_config).await?; + let summary = RequestsLPSummary { + found_total, + found_owned, + }; + Ok(RunResult::RequestsLP( + summary, + reference_lp.requests_to_process, + )) } + #[allow(clippy::too_many_arguments)] + /// Command: Issue License LP + async fn issue_license_lp( + wallet_path: &WalletPath, + psw: &Password, + blockchain_access_config: &BlockchainAccessConfig, + lp_config: &Path, + gas_limit: u64, + gas_price: u64, + lp_config_path: Option, + request_hash: String, + ) -> Result { + let mut rng = StdRng::from_entropy(); // seed_from_u64(0xbeef); + let lp_config_path = match lp_config_path { + Some(lp_config_path) => lp_config_path, + _ => PathBuf::from(lp_config), + }; + let mut reference_lp = ReferenceLP::create(lp_config_path)?; + let (_total_count, _this_lp_count) = + reference_lp.scan(blockchain_access_config).await?; + + let request = reference_lp.get_request(&request_hash); + Ok(match request { + Some(request) => { + let license_issuer = LicenseIssuer::new( + blockchain_access_config.clone(), + wallet_path.clone(), + psw.clone(), + gas_limit, + gas_price, + ); + let (tx_id, license_blob) = license_issuer + .issue_license(&mut rng, &request, &reference_lp.ssk_lp) + .await?; + let summary = IssueLicenseSummary { + request, + tx_id: hex::encode(tx_id.to_bytes()), + license_blob, + }; + RunResult::IssueLicense(Some(summary)) + } + _ => RunResult::IssueLicense(None), + }) + } + + /// Command: List Licenses async fn list_licenses( blockchain_access_config: &BlockchainAccessConfig, - request_json: Option<&RequestJson>, - ) -> Result<(), Error> { + request_json: Option, + request_path: Option, + ) -> Result { + let request_json = match request_path { + Some(request_path) => RequestJson::from_file(request_path)?, + _ => request_json.expect("request should be provided"), + }; + let client = RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); let end_height = BcInquirer::block_height(&client).await?; - let block_heights = 0..(end_height + 1); + let block_range = 0..(end_height + 1); - println!( - "getting licenses within the block height range {:?}:", - block_heights - ); let mut licenses_stream = - CitadelInquirer::get_licenses(&client, block_heights).await?; + CitadelInquirer::get_licenses(&client, block_range.clone()).await?; let ssk_user = SecretSpendKey::from_slice( - hex::decode( - request_json - .expect("request should be provided") - .user_ssk - .clone(), - )? - .as_slice(), + hex::decode(request_json.user_ssk.clone())?.as_slice(), )?; - // let owned_pairs = find_owned_licenses(ssk_user, &mut - // licenses_stream)?; if owned_pairs.is_empty() { - // println!("licenses not found"); - // } else { - // for (_pos, license) in owned_pairs.iter() { - // println!("license: {}", Self::to_hash_hex(license)) - // } - // }; - let pairs = find_all_licenses(&mut licenses_stream)?; - if pairs.is_empty() { - println!("licenses not found"); - } else { - let vk = ssk_user.view_key(); - for (_pos, license) in pairs.iter() { - let is_owned = vk.owns(&license.lsa); - println!( - "license: {} {}", - Self::to_hash_hex(license), - if is_owned { "owned" } else { "" } + let pairs = CitadelInquirer::find_all_licenses(&mut licenses_stream)?; + let vk = ssk_user.view_key(); + let mut licenses = vec![]; + for (_pos, license) in pairs.into_iter() { + let is_owned = vk.owns(&license.lsa); + licenses.push((license, is_owned)); + } + Ok(RunResult::ListLicenses(block_range, licenses)) + } + + #[allow(clippy::too_many_arguments)] + /// Command: Use License + async fn use_license( + wallet_path: &WalletPath, + psw: &Password, + blockchain_access_config: &BlockchainAccessConfig, + gas_limit: u64, + gas_price: u64, + request_json: Option, + setup_holder: &mut Option, + request_path: Option, + license_hash: String, + ) -> Result { + let request_json = match request_path { + Some(request_path) => RequestJson::from_file(request_path)?, + _ => request_json.expect("request should be provided"), + }; + let pos_license = Self::get_license_to_use( + blockchain_access_config, + Some(&request_json), + license_hash.clone(), + ) + .await?; + Ok(match pos_license { + Some((pos, license)) => { + println!("using license: {}", RunResult::to_hash_hex(&license)); + let ssk_user = SecretSpendKey::from_slice( + hex::decode(request_json.user_ssk)?.as_slice(), + )?; + let psk_lp = PublicSpendKey::from_slice( + hex::decode(request_json.provider_psk)?.as_slice(), + )?; + let (tx_id, session_cookie) = Self::prove_and_send_use_license( + blockchain_access_config, + wallet_path, + psw, + psk_lp, + ssk_user, + &license, + pos, + gas_limit, + gas_price, + setup_holder, ) + .await?; + let summary = UseLicenseSummary { + license_blob: RunResult::to_blob(&license), + tx_id: hex::encode(tx_id.to_bytes()), + user_attr: hex::encode(session_cookie.attr_data.to_bytes()), + session_id: hex::encode( + session_cookie.session_id.to_bytes(), + ), + session_cookie: RunResult::to_blob_hex(&session_cookie), + }; + RunResult::UseLicense(Some(summary)) + } + _ => RunResult::UseLicense(None), + }) + } + + /// Command: Get Session + async fn get_session( + blockchain_access_config: &BlockchainAccessConfig, + session_id: String, + ) -> Result { + let client = + RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); + let id = LicenseSessionId { + id: BlsScalar::from_slice( + hex::decode(session_id.clone())?.as_slice(), + )?, + }; + Ok(match CitadelInquirer::get_session(&client, id).await? { + Some(session) => { + let mut summary = SessionSummary { + session_id, + session: vec![], + }; + for s in session.public_inputs.iter() { + summary.session.push(hex::encode(s.to_bytes())); + } + RunResult::GetSession(Some(summary)) } + _ => RunResult::GetSession(None), + }) + } + + /// Command: Show State + async fn show_state( + blockchain_access_config: &BlockchainAccessConfig, + ) -> Result { + let client = + RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); + let (num_licenses, _, num_sessions) = + CitadelInquirer::get_info(&client).await?; + let summary = LicenseContractSummary { + num_licenses, + num_sessions, }; - Ok(()) + Ok(RunResult::ShowState(summary)) } async fn get_license_to_use( @@ -477,12 +465,15 @@ impl Command { .as_slice(), )?; - let pairs = find_owned_licenses(ssk_user, &mut licenses_stream)?; + let pairs = CitadelInquirer::find_owned_licenses( + ssk_user, + &mut licenses_stream, + )?; Ok(if pairs.is_empty() { None } else { for (pos, license) in pairs.iter() { - if license_hash == Self::to_hash_hex(license) { + if license_hash == RunResult::to_hash_hex(license) { return Ok(Some((*pos, license.clone()))); } } @@ -502,7 +493,7 @@ impl Command { gas_limit: u64, gas_price: u64, sh_opt: &mut Option, - ) -> Result { + ) -> Result<(BlsScalar, SessionCookie), Error> { let client = RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); // let (_, _, num_sessions) = CitadelInquirer::get_info(&client).await?; @@ -534,81 +525,26 @@ impl Command { .await? .expect("Opening obtained successfully"); - let (cpp, sc) = CitadelProverParameters::compute_parameters( - &ssk_user, license, &psk_lp, &psk_lp, &challenge, &mut rng, opening, + println!( + "calculating proof and calling license contract's use_license" ); - let circuit = LicenseCircuit::new(&cpp, &sc); - - println!("calculating proof"); - let (proof, public_inputs) = setup_holder - .prover - .prove(&mut rng, &circuit) - .expect("Proving should succeed"); - - assert!(!public_inputs.is_empty()); - let session_id = public_inputs[0]; - - setup_holder.verifier - .verify(&proof, &public_inputs) - .expect("Verifying the circuit should succeed"); - println!("proof validated locally"); - - let use_license_arg = UseLicenseArg { - proof, - public_inputs, - }; - - println!("calling license contract's use_license"); - let tx_id = PayloadSender::execute_contract_method( - use_license_arg, + let (tx_id, session_cookie) = LicenseUser::prove_and_use_license( blockchain_access_config, wallet_path, psw, + &ssk_user, + &psk_lp, + &setup_holder.prover, + &setup_holder.verifier, + license, + opening, + &mut rng, + &challenge, gas_limit, gas_price, - LICENSE_CONTRACT_ID, - USE_LICENSE_METHOD_NAME, ) .await?; TxAwaiter::wait_for(&client, tx_id).await?; - println!( - "use license executing transaction {} confirmed", - hex::encode(tx_id.to_bytes()) - ); - println!(); - println!("license {} used", Self::to_hash_hex(license),); - println!(); - println!("session cookie: {}", Self::to_blob_hex(&sc)); - println!(); - println!("user attributes: {}", hex::encode(sc.attr.to_bytes())); - println!("session id: {}", hex::encode(sc.session_id.to_bytes())); - Ok(session_id) - } - - fn to_hash_hex(object: &T) -> String - where - T: rkyv::Serialize>, - { - let blob = rkyv::to_bytes::<_, 16386>(object) - .expect("type should serialize correctly") - .to_vec(); - Self::blob_to_hash_hex(blob.as_slice()) - } - - fn blob_to_hash_hex(blob: &[u8]) -> String { - let mut hasher = Sha3_256::new(); - hasher.update(blob); - let result = hasher.finalize(); - hex::encode(result) - } - - fn to_blob_hex(object: &T) -> String - where - T: rkyv::Serialize>, - { - let blob = rkyv::to_bytes::<_, 16386>(object) - .expect("type should serialize correctly") - .to_vec(); - hex::encode(blob) + Ok((tx_id, session_cookie)) } } diff --git a/moat-cli/src/interactor.rs b/moat-cli/src/interactor.rs index 42b3c0b..11536b7 100644 --- a/moat-cli/src/interactor.rs +++ b/moat-cli/src/interactor.rs @@ -68,9 +68,7 @@ fn menu_operation() -> Result { })) } CommandMenuItem::ListRequestsUser => { - OpSelection::Run(Box::from(Command::ListRequestsUser { - dummy: true, - })) + OpSelection::Run(Box::from(Command::ListRequestsUser)) } CommandMenuItem::ListRequestsLP => { OpSelection::Run(Box::from(Command::ListRequestsLP { @@ -117,7 +115,7 @@ fn menu_operation() -> Result { })) } CommandMenuItem::ShowState => { - OpSelection::Run(Box::from(Command::ShowState { dummy: true })) + OpSelection::Run(Box::from(Command::ShowState)) } CommandMenuItem::Exit => OpSelection::Exit, }) @@ -159,9 +157,11 @@ impl Interactor { &mut self.setup_holder, ) .await; - if result.is_err() { - let error = result.unwrap_err(); - match error { + match result { + Ok(run_result) => { + println!("{}", run_result); + } + Err(error) => match error { Error::IO(arc) => { println!("{}", arc.as_ref().to_string()); } @@ -171,7 +171,7 @@ impl Interactor { _ => { println!("{:?}", error); } - } + }, } continue; } diff --git a/moat-cli/src/main.rs b/moat-cli/src/main.rs index b2bd627..d526af4 100644 --- a/moat-cli/src/main.rs +++ b/moat-cli/src/main.rs @@ -12,6 +12,7 @@ mod error; mod interactor; mod menu; mod prompt; +mod run_result; use crate::args::Args; use crate::command::Command; @@ -66,7 +67,10 @@ async fn main() -> Result<(), CliError> { interactor.run_loop().await?; #[rustfmt::skip] + // old wallet.dat file format: // cargo r --release --bin moat-cli -- --wallet-path ~/.dusk/rusk-wallet --config-path ./moat-cli/config.toml --lp-config-path ./moat-cli/lp.json --pwd-hash 7f2611ba158b6dcea4a69c229c303358c5e04493abeadee106a4bfa464d55787 ./moat-cli/request.json + // new wallet.dat file format: + // cargo r --release --bin moat-cli -- --wallet-path ~/.dusk/rusk-wallet --config-path ./moat-cli/config.toml --lp-config-path ./moat-cli/lp.json --pwd-hash 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 ./moat-cli/request.json Ok(()) } diff --git a/moat-cli/src/run_result.rs b/moat-cli/src/run_result.rs new file mode 100644 index 0000000..3a90e74 --- /dev/null +++ b/moat-cli/src/run_result.rs @@ -0,0 +1,263 @@ +// 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 rkyv::ser::serializers::AllocSerializer; +use std::fmt; +use std::ops::Range; +use zk_citadel::license::{License, Request}; +// use rkyv::{check_archived_root, Archive, Deserialize, Infallible, Serialize}; +use sha3::{Digest, Sha3_256}; + +pub struct SubmitRequestSummary { + pub psk_lp: String, + pub tx_id: String, + pub request_hash: String, +} + +pub struct RequestsSummary { + pub height: u64, + pub found_total: usize, + pub found_owned: usize, +} + +pub struct RequestsLPSummary { + pub found_total: usize, + pub found_owned: usize, +} + +pub struct IssueLicenseSummary { + pub request: Request, + pub tx_id: String, + pub license_blob: Vec, +} + +pub struct UseLicenseSummary { + pub license_blob: Vec, + pub tx_id: String, + pub session_cookie: String, + pub user_attr: String, + pub session_id: String, +} + +pub struct SessionSummary { + pub session_id: String, + pub session: Vec, +} + +pub struct LicenseContractSummary { + pub num_licenses: u32, + pub num_sessions: u32, +} + +#[allow(clippy::large_enum_variant)] +/// Possible results of running a command in interactive mode +pub enum RunResult { + SubmitRequest(SubmitRequestSummary), + Requests(RequestsSummary, Vec), + RequestsLP(RequestsLPSummary, Vec), + IssueLicense(Option), + ListLicenses(Range, Vec<(License, bool)>), + UseLicense(Option), + GetSession(Option), + ShowState(LicenseContractSummary), + Empty, +} + +impl fmt::Display for RunResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use RunResult::*; + match self { + SubmitRequest(summary) => { + writeln!( + f, + "submitting request to provider psk: {}", + summary.psk_lp + )?; + writeln!( + f, + "request submitting transaction {} confirmed", + summary.tx_id + )?; + writeln!(f, "request submitted: {}", summary.request_hash)?; + Ok(()) + } + Requests(summary, requests) => { + writeln!( + f, + "scanned {} blocks, found {} requests, {} owned requests:", + summary.height, summary.found_total, summary.found_owned, + )?; + for request in requests.iter() { + writeln!(f, "request: {}", Self::to_hash_hex(request))?; + } + Ok(()) + } + RequestsLP(summary, requests) => { + writeln!( + f, + "found {} requests total, {} requests for this LP:", + summary.found_total, summary.found_owned + )?; + for request in requests.iter() { + writeln!( + f, + "request to process by LP: {}", + RunResult::to_hash_hex(request) + )?; + } + Ok(()) + } + IssueLicense(summary) => match summary { + Some(summary) => { + writeln!( + f, + "issuing license for request: {}", + RunResult::to_hash_hex(&summary.request) + )?; + writeln!( + f, + "license issuing transaction {} confirmed", + summary.tx_id + )?; + writeln!( + f, + "issued license: {}", + RunResult::blob_to_hash_hex( + summary.license_blob.as_slice() + ) + )?; + Ok(()) + } + _ => { + writeln!(f, "Request not found")?; + Ok(()) + } + }, + ListLicenses(block_range, licenses) => { + writeln!( + f, + "getting licenses within the block height range {:?}:", + block_range + )?; + if licenses.is_empty() { + writeln!(f, "licenses not found")?; + } else { + for (license, is_owned) in licenses.iter() { + writeln!( + f, + "license: {} {}", + RunResult::to_hash_hex(license), + if *is_owned { "owned" } else { "" } + )?; + } + } + Ok(()) + } + UseLicense(summary) => { + match summary { + Some(summary) => { + writeln!( + f, + "using license: {}", + Self::blob_to_hash_hex( + summary.license_blob.as_slice() + ) + )?; + writeln!( + f, + "use license executing transaction {} confirmed", + summary.tx_id + )?; + writeln!(f)?; + writeln!( + f, + "license {} used", + Self::blob_to_hash_hex( + summary.license_blob.as_slice() + ), + )?; + writeln!(f)?; + writeln!( + f, + "session cookie: {}", + summary.session_cookie + )?; + writeln!(f)?; + writeln!(f, "user attributes: {}", summary.user_attr)?; + writeln!(f, "session id: {}", summary.session_id)?; + } + _ => { + writeln!(f, "Please obtain a license")?; + } + } + Ok(()) + } + GetSession(summary) => { + match summary { + Some(summary) => { + writeln!( + f, + "obtained session with id={}:", + summary.session_id + )?; + for s in summary.session.iter() { + writeln!(f, "{}", s)?; + } + } + _ => { + writeln!(f, "session not found")?; + } + } + Ok(()) + } + ShowState(summary) => { + writeln!( + f, + "license contract state - licenses: {}, sessions: {}", + summary.num_licenses, summary.num_sessions + )?; + Ok(()) + } + Empty => Ok(()), + } + } +} + +impl RunResult { + pub fn to_hash_hex(object: &T) -> String + where + T: rkyv::Serialize>, + { + let blob = rkyv::to_bytes::<_, 16386>(object) + .expect("type should serialize correctly") + .to_vec(); + Self::blob_to_hash_hex(blob.as_slice()) + } + + pub fn blob_to_hash_hex(blob: &[u8]) -> String { + let mut hasher = Sha3_256::new(); + hasher.update(blob); + let result = hasher.finalize(); + hex::encode(result) + } + + pub fn to_blob_hex(object: &T) -> String + where + T: rkyv::Serialize>, + { + let blob = Self::to_blob(object); + hex::encode(blob) + } + + pub fn to_blob(object: &T) -> Vec + where + T: rkyv::Serialize>, + { + rkyv::to_bytes::<_, 16386>(object) + .expect("type should serialize correctly") + .to_vec() + } +} diff --git a/moat-core/src/blockchain_payloads/payload_sender.rs b/moat-core/src/blockchain_payloads/payload_sender.rs index e151d48..3a2feb9 100644 --- a/moat-core/src/blockchain_payloads/payload_sender.rs +++ b/moat-core/src/blockchain_payloads/payload_sender.rs @@ -32,7 +32,7 @@ impl PayloadSender { M: AsRef, { let wallet_accessor = - WalletAccessor::new(wallet_path.clone(), password.clone()); + WalletAccessor::create(wallet_path.clone(), password.clone())?; let tx_id = wallet_accessor .execute_contract_method( payload, diff --git a/moat-core/src/citadel_licenses/license_user.rs b/moat-core/src/citadel_licenses/license_user.rs new file mode 100644 index 0000000..1e3ebb9 --- /dev/null +++ b/moat-core/src/citadel_licenses/license_user.rs @@ -0,0 +1,86 @@ +// 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::{ + Error, LicenseCircuit, PayloadSender, LICENSE_CONTRACT_ID, + USE_LICENSE_METHOD_NAME, +}; +use crate::{ARITY, DEPTH}; +use bytecheck::CheckBytes; +use dusk_bls12_381::BlsScalar; +use dusk_jubjub::JubJubScalar; +use dusk_pki::{PublicSpendKey, SecretSpendKey}; +use dusk_plonk::prelude::{Proof, Prover, Verifier}; +use dusk_wallet::WalletPath; +use poseidon_merkle::Opening; +use rand::rngs::StdRng; +use rkyv::{Archive, Deserialize, Serialize}; +use wallet_accessor::{BlockchainAccessConfig, Password}; +use zk_citadel::license::{CitadelProverParameters, License, SessionCookie}; + +/// Use License Argument. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct UseLicenseArg { + pub proof: Proof, + pub public_inputs: Vec, +} + +pub struct LicenseUser; + +impl LicenseUser { + #[allow(clippy::too_many_arguments)] + /// Calculates and verified proof, sends proof along with public parameters + /// as arguments to the license contract's use_license method. + /// Returns transaction id and a session cookie. + pub async fn prove_and_use_license( + blockchain_config: &BlockchainAccessConfig, + wallet_path: &WalletPath, + password: &Password, + ssk_user: &SecretSpendKey, + psk_lp: &PublicSpendKey, + prover: &Prover, + verifier: &Verifier, + license: &License, + opening: Opening<(), DEPTH, ARITY>, + rng: &mut StdRng, + challenge: &JubJubScalar, + gas_limit: u64, + gas_price: u64, + ) -> Result<(BlsScalar, SessionCookie), Error> { + let (cpp, sc) = CitadelProverParameters::compute_parameters( + ssk_user, license, psk_lp, psk_lp, challenge, rng, opening, + ); + let circuit = LicenseCircuit::new(&cpp, &sc); + + let (proof, public_inputs) = + prover.prove(rng, &circuit).expect("Proving should succeed"); + + assert!(!public_inputs.is_empty()); + + verifier + .verify(&proof, &public_inputs) + .expect("Verifying the circuit should succeed"); + + let use_license_arg = UseLicenseArg { + proof, + public_inputs, + }; + + let tx_id = PayloadSender::execute_contract_method( + use_license_arg, + blockchain_config, + wallet_path, + password, + gas_limit, + gas_price, + LICENSE_CONTRACT_ID, + USE_LICENSE_METHOD_NAME, + ) + .await?; + Ok((tx_id, sc)) + } +} diff --git a/moat-core/src/citadel_licenses/mod.rs b/moat-core/src/citadel_licenses/mod.rs new file mode 100644 index 0000000..c3d6485 --- /dev/null +++ b/moat-core/src/citadel_licenses/mod.rs @@ -0,0 +1,9 @@ +// 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. + +mod license_user; + +pub use license_user::LicenseUser; diff --git a/moat-core/src/citadel_queries/citadel_inquirer.rs b/moat-core/src/citadel_queries/citadel_inquirer.rs index 7175a7c..5b29443 100644 --- a/moat-core/src/citadel_queries/citadel_inquirer.rs +++ b/moat-core/src/citadel_queries/citadel_inquirer.rs @@ -5,7 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::error::Error; -use crate::BlockInPlace; +use crate::Error::InvalidQueryResponse; +use crate::{BlockInPlace, StreamAux}; use crate::{ ContractInquirer, LicenseSession, LicenseSessionId, ARITY, DEPTH, GET_INFO_METHOD_NAME, GET_LICENSES_METHOD_NAME, @@ -13,8 +14,10 @@ use crate::{ LICENSE_CONTRACT_ID, }; use bytes::Bytes; +use dusk_pki::SecretSpendKey; use dusk_wallet::RuskHttpClient; use poseidon_merkle::Opening; +use rkyv::{check_archived_root, Deserialize, Infallible}; use std::ops::Range; use zk_citadel::license::License; @@ -80,4 +83,65 @@ impl CitadelInquirer { ) .await } + + /// Deserializes license, panics if deserialization fails. + fn deserialise_license(v: &Vec) -> License { + let response_data = check_archived_root::(v.as_slice()) + .map_err(|_| { + InvalidQueryResponse(Box::from("rkyv deserialization error")) + }) + .expect("License should deserialize correctly"); + let license: License = response_data + .deserialize(&mut Infallible) + .expect("Infallible"); + license + } + + /// Finds owned license in a stream of licenses. + /// It searches in a reverse order to return a newest license. + pub fn find_owned_licenses( + ssk_user: SecretSpendKey, + stream: &mut (impl futures_core::Stream> + + std::marker::Unpin), + ) -> Result, Error> { + const ITEM_LEN: usize = CitadelInquirer::GET_LICENSES_ITEM_LEN; + let mut pairs = vec![]; + loop { + let r = StreamAux::find_item::<(u64, Vec), ITEM_LEN>( + |(_, lic_vec)| { + let license = Self::deserialise_license(lic_vec); + Ok(ssk_user.view_key().owns(&license.lsa)) + }, + stream, + ); + if r.is_err() { + break; + } + let (pos, lic_ser) = r?; + pairs.push((pos, Self::deserialise_license(&lic_ser))) + } + Ok(pairs) + } + + /// Finds owned license in a stream of licenses. + /// It searches in a reverse order to return a newest license. + pub fn find_all_licenses( + stream: &mut (impl futures_core::Stream> + + std::marker::Unpin), + ) -> Result, Error> { + const ITEM_LEN: usize = CitadelInquirer::GET_LICENSES_ITEM_LEN; + let mut pairs = vec![]; + loop { + let r = StreamAux::find_item::<(u64, Vec), ITEM_LEN>( + |_| Ok(true), + stream, + ); + if r.is_err() { + break; + } + let (pos, lic_ser) = r?; + pairs.push((pos, Self::deserialise_license(&lic_ser))) + } + Ok(pairs) + } } diff --git a/moat-core/src/lib.rs b/moat-core/src/lib.rs index 1d94737..a22abe8 100644 --- a/moat-core/src/lib.rs +++ b/moat-core/src/lib.rs @@ -15,6 +15,7 @@ mod bc_types; mod blockchain_payloads; mod blockchain_queries; mod circuit; +mod citadel_licenses; mod citadel_queries; mod citadel_requests; mod citadel_types; @@ -28,6 +29,7 @@ pub use blockchain_payloads::{ }; pub use blockchain_queries::{BcInquirer, TxAwaiter, TxInquirer}; pub use circuit::*; +pub use citadel_licenses::LicenseUser; pub use citadel_queries::{ CitadelInquirer, CitadelInquirerWs, LicenseSession, LicenseSessionId, }; diff --git a/wallet-accessor/Cargo.toml b/wallet-accessor/Cargo.toml index bd41872..4367d18 100644 --- a/wallet-accessor/Cargo.toml +++ b/wallet-accessor/Cargo.toml @@ -15,3 +15,4 @@ serde = { version = "1", features = ["derive"] } toml-base-config = "0.1" sha2 = "0.10" hex = "0.4" +blake3 = "1.4" diff --git a/wallet-accessor/src/wallet_accessor.rs b/wallet-accessor/src/wallet_accessor.rs index 444e38e..7f054af 100644 --- a/wallet-accessor/src/wallet_accessor.rs +++ b/wallet-accessor/src/wallet_accessor.rs @@ -7,8 +7,9 @@ use crate::wallet_accessor::Password::{Pwd, PwdHash}; use crate::BlockchainAccessConfig; use dusk_bls12_381::BlsScalar; +use dusk_wallet::dat::{read_file_version, DatFileVersion}; use dusk_wallet::gas::Gas; -use dusk_wallet::{DecodedNote, SecureWalletFile, Wallet, WalletPath}; +use dusk_wallet::{DecodedNote, Error, SecureWalletFile, Wallet, WalletPath}; use dusk_wallet_core::MAX_CALL_SIZE; use phoenix_core::transaction::ModuleId; use rkyv::ser::serializers::AllocSerializer; @@ -39,23 +40,34 @@ impl SecureWalletFile for WalletAccessor { } impl WalletAccessor { - pub fn new(path: WalletPath, pwd: Password) -> Self { - Self { - path, + pub fn create( + wallet_path: WalletPath, + pwd: Password, + ) -> Result { + let dat_file_version = read_file_version(&wallet_path)?; + let is_sha256 = + matches!(dat_file_version, DatFileVersion::RuskBinaryFileFormat(_)); + Ok(Self { + path: wallet_path, pwd: pwd.clone(), pwd_bytes: { match &pwd { Pwd(s) => { - let mut hasher = Sha256::new(); - hasher.update(s.as_bytes()); - hasher.finalize().to_vec() + if is_sha256 { + let mut hasher = Sha256::new(); + hasher.update(s.as_bytes()); + hasher.finalize().to_vec() + } else { + let hash = blake3::hash(s.as_bytes()); + hash.as_bytes().to_vec() + } } PwdHash(h) => hex::decode(h.as_str()) .expect("Password hash should be valid hex string") .to_vec(), } }, - } + }) } async fn get_wallet( @@ -63,7 +75,7 @@ impl WalletAccessor { cfg: &BlockchainAccessConfig, ) -> Result, dusk_wallet::Error> { let wallet_accessor = - WalletAccessor::new(self.path.clone(), self.pwd.clone()); + WalletAccessor::create(self.path.clone(), self.pwd.clone())?; let mut wallet = Wallet::from_file(wallet_accessor)?; wallet .connect_with_status(