From fc1f1de25a9b2e98f48746bf3e16477288a7d4c2 Mon Sep 17 00:00:00 2001 From: Milosz Muszynski Date: Tue, 7 Nov 2023 11:15:21 +0100 Subject: [PATCH] Refactored proving and using license into LicenseUser Reused proving license and sending use licensed from core in cli command Refactored commands submit-request and list-requests Refactored list requests lp command Refactored issue license lp command Refactored list licenses Refactored use license command Refactored get session and show state commands Introduced run result implementing Display Run result based display for submit request and list requests lp Run result based display for issue license Run result based display for list licenses Run result based display for get session and show state commands Run result based display for the use license command Pushed find owner licenses code from int test and cli to the core lib Cosmetic - removed todo Adjusted to zk-citadel 0.5.1 Hashing password according to wallet.dat format --- .../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(