From a4d09967d47c7427648f755e84ee26372c472055 Mon Sep 17 00:00:00 2001 From: Milosz Muszynski Date: Thu, 2 Nov 2023 12:45:51 +0100 Subject: [PATCH] Show license hash after issuing license possibility of entering license to use Showing if license is owned listing licenses for specific user Issuing particular request Fixed the secondary flow Performing setup only once --- .../tests/citadel/int_test_user.rs | 5 +- license-provider/Cargo.toml | 1 + license-provider/src/license_issuer.rs | 6 +- license-provider/src/reference_lp.rs | 29 ++ moat-cli/request2.json | 2 +- moat-cli/src/command.rs | 280 ++++++++++++------ moat-cli/src/interactor.rs | 21 +- moat-cli/src/main.rs | 3 +- moat-cli/src/prompt.rs | 28 ++ moat-core/tests/utils.rs | 2 +- 10 files changed, 275 insertions(+), 102 deletions(-) diff --git a/integration-tests/tests/citadel/int_test_user.rs b/integration-tests/tests/citadel/int_test_user.rs index 3f2d52f..2cecba6 100644 --- a/integration-tests/tests/citadel/int_test_user.rs +++ b/integration-tests/tests/citadel/int_test_user.rs @@ -101,9 +101,10 @@ async fn issue_license( GAS_PRICE, ); - license_issuer + let (tx_id, _) = license_issuer .issue_license(rng, &request, &reference_lp.ssk_lp) - .await + .await?; + Ok(tx_id) } /// Calculates and verified proof, sends proof along with public parameters diff --git a/license-provider/Cargo.toml b/license-provider/Cargo.toml index f1edc26..633a957 100644 --- a/license-provider/Cargo.toml +++ b/license-provider/Cargo.toml @@ -26,6 +26,7 @@ rand = "0.8" dusk-bytes = "0.1" blake3 = "1.3" tracing = "0.1" +sha3 = "0.10" [dev-dependencies] tokio = { version = "1.15", features = ["rt-multi-thread", "time", "fs", "macros"] } diff --git a/license-provider/src/license_issuer.rs b/license-provider/src/license_issuer.rs index 475fc6f..b73408a 100644 --- a/license-provider/src/license_issuer.rs +++ b/license-provider/src/license_issuer.rs @@ -50,7 +50,7 @@ impl LicenseIssuer { rng: &mut R, request: &Request, ssk_lp: &SecretSpendKey, - ) -> Result { + ) -> Result<(BlsScalar, Vec), Error> { let attr = JubJubScalar::from(USER_ATTRIBUTES); let license = License::new(&attr, ssk_lp, request, rng); let license_blob = rkyv::to_bytes::<_, MAX_LICENSE_SIZE>(&license) @@ -58,7 +58,7 @@ impl LicenseIssuer { .to_vec(); let lpk = JubJubAffine::from(license.lsa.pk_r().as_ref()); let license_hash = sponge::hash(&[lpk.get_u(), lpk.get_v()]); - let tuple = (license_blob, license_hash); + let tuple = (license_blob.clone(), license_hash); trace!( "sending issue license with license blob size={}", tuple.0.len() @@ -76,6 +76,6 @@ impl LicenseIssuer { .await?; let client = RuskHttpClient::new(self.config.rusk_address.clone()); TxAwaiter::wait_for(&client, tx_id).await?; - Ok(tx_id) + Ok((tx_id, license_blob)) } } diff --git a/license-provider/src/reference_lp.rs b/license-provider/src/reference_lp.rs index 330cfff..1aae847 100644 --- a/license-provider/src/reference_lp.rs +++ b/license-provider/src/reference_lp.rs @@ -8,6 +8,8 @@ use blake3::OUT_LEN; use dusk_bytes::DeserializableSlice; use dusk_pki::{PublicSpendKey, SecretSpendKey, ViewKey}; use moat_core::{Error, JsonLoader, RequestScanner, MAX_REQUEST_SIZE}; +use rkyv::ser::serializers::AllocSerializer; +use sha3::{Digest, Sha3_256}; use std::collections::BTreeSet; use std::path::Path; use wallet_accessor::BlockchainAccessConfig; @@ -136,6 +138,16 @@ impl ReferenceLP { }) } + pub fn get_request(&mut self, request_hash: &String) -> Option { + for (index, request) in self.requests_to_process.iter().enumerate() { + if Self::to_hash_hex(request) == *request_hash { + self.requests_hashes.remove(&Self::hash_request(request)); + return Some(self.requests_to_process.remove(index)); + } + } + None + } + fn hash_request(request: &Request) -> [u8; OUT_LEN] { *blake3::hash( rkyv::to_bytes::<_, MAX_REQUEST_SIZE>(request) @@ -144,4 +156,21 @@ impl ReferenceLP { ) .as_bytes() } + + 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) + } } diff --git a/moat-cli/request2.json b/moat-cli/request2.json index d3182e1..bb65dbf 100644 --- a/moat-cli/request2.json +++ b/moat-cli/request2.json @@ -1,4 +1,4 @@ { - "user_ssk": "c6afd78c8b3902b474d4c0972b62888e4b880dccf8da68e86266fefa45ee7505926f06ab82ac200995f1239d518fdb74903f225f4460d8db62f2449f6d4dc402", + "user_ssk": "45654c72b065e143645ae5877524b96126c222005a8d6a1eca24c99627a45803a48481395dabdbe33cec4f89b36878b3c2f638c9796e34cffac0a02f27c21702", "provider_psk": "29c4336ef24e585f4506e32e269c5363a71f7dcd74586b210c56e569ad2644e832c785f102dd3c985c705008ec188be819bac85b65c9f70decb9adcf4a72cc43" } diff --git a/moat-cli/src/command.rs b/moat-cli/src/command.rs index 34a29b5..1b82295 100644 --- a/moat-cli/src/command.rs +++ b/moat-cli/src/command.rs @@ -9,7 +9,7 @@ use bytecheck::CheckBytes; use bytes::Bytes; use dusk_bls12_381::BlsScalar; use dusk_bytes::DeserializableSlice; -use dusk_pki::SecretSpendKey; +use dusk_pki::{PublicSpendKey, SecretSpendKey}; use dusk_plonk::prelude::*; use dusk_wallet::{RuskHttpClient, WalletPath}; use license_provider::{LicenseIssuer, ReferenceLP}; @@ -38,11 +38,17 @@ pub(crate) enum Command { /// List requests (LP) ListRequestsLP { lp_config_path: Option }, /// Issue license (LP) - IssueLicenseLP { lp_config_path: Option }, + IssueLicenseLP { + lp_config_path: Option, + request_hash: String, + }, /// List licenses (User) - ListLicenses { dummy: bool }, + ListLicenses { request_path: Option }, /// Use license (User) - UseLicense { dummy: bool }, + UseLicense { + request_path: Option, + license_hash: String, + }, /// Get session (SP) GetSession { session_id: String }, /// Show state @@ -90,6 +96,30 @@ fn find_owned_licenses( 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)] @@ -114,10 +144,10 @@ impl Command { gas_limit: u64, gas_price: u64, request_json: Option, + pp: &mut Option, ) -> Result<(), Error> { match self { Command::SubmitRequest { request_path } => { - println!("obtained request path={:?}", request_path); let request_json = match request_path { Some(request_path) => RequestJson::from_file(request_path)?, _ => request_json.expect("request should be provided"), @@ -142,15 +172,14 @@ impl Command { gas_price, ) .await?; - println!( - "tx {} submitted, waiting for confirmation", - hex::encode(tx_id.to_bytes()) - ); let client = RuskHttpClient::new( blockchain_access_config.rusk_address.clone(), ); TxAwaiter::wait_for(&client, tx_id).await?; - println!("tx {} confirmed", hex::encode(tx_id.to_bytes())); + println!( + "request submitting transaction {} confirmed", + hex::encode(tx_id.to_bytes()) + ); println!("request submitted: {}", request_hash_hex); println!(); } @@ -216,7 +245,10 @@ impl Command { } println!(); } - Command::IssueLicenseLP { lp_config_path } => { + 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, @@ -225,83 +257,106 @@ impl Command { 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.take_request().expect("at least one request"); - let license_issuer = LicenseIssuer::new( - blockchain_access_config.clone(), - wallet_path.clone(), - psw.clone(), - gas_limit, - gas_price, - ); + 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!( - "issuing license for request: {}", - Self::to_hash_hex(&request) - ); - let tx_id = license_issuer - .issue_license(&mut rng, &request, &reference_lp.ssk_lp) - .await?; - println!( - "license issuing transaction {} confirmed", - hex::encode(tx_id.to_bytes()) - ); println!(); } - Command::ListLicenses { dummy: true } => { - let _ = self - .list_licenses( - blockchain_access_config, - request_json.as_ref(), - true, - ) - .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), + ) + .await?; println!(); } - Command::UseLicense { dummy: true } => { - let pos_license = self - .list_licenses( - blockchain_access_config, - request_json.as_ref(), - false, - ) - .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( + blockchain_access_config, + Some(&request_json), + license_hash.clone(), + ) + .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 - .expect("request should be provided") - .user_ssk, - )? - .as_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, - lp_config, + psk_lp, ssk_user, &license, pos, gas_limit, gas_price, + pp, ) .await?; println!( - "license used, obtained session id: {}", + "license {} used, obtained session id: {}", + Self::to_hash_hex(&license), hex::encode(session_id.to_bytes()) ); } _ => { - println!( - "No license available, please obtain a license" - ); + println!("Please obtain a license"); } } println!(); @@ -347,22 +402,66 @@ impl Command { } async fn list_licenses( - self, blockchain_access_config: &BlockchainAccessConfig, request_json: Option<&RequestJson>, - ui: bool, + ) -> Result<(), Error> { + 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); + + println!( + "getting licenses within the block height range {:?}:", + block_heights + ); + let mut licenses_stream = + CitadelInquirer::get_licenses(&client, block_heights).await?; + + let ssk_user = SecretSpendKey::from_slice( + hex::decode( + request_json + .expect("request should be provided") + .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 { "" } + ) + } + }; + Ok(()) + } + + async fn get_license_to_use( + blockchain_access_config: &BlockchainAccessConfig, + request_json: Option<&RequestJson>, + license_hash: String, ) -> Result, Error> { 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); - if ui { - println!( - "getting licenses within the block height range {:?}:", - block_heights - ); - } let mut licenses_stream = CitadelInquirer::get_licenses(&client, block_heights).await?; @@ -378,17 +477,14 @@ impl Command { let pairs = find_owned_licenses(ssk_user, &mut licenses_stream)?; Ok(if pairs.is_empty() { - if ui { - println!("licenses not found"); - } None } else { - if ui { - for (_, license) in pairs.iter() { - println!("license: {}", Self::to_hash_hex(license)) + for (pos, license) in pairs.iter() { + if license_hash == Self::to_hash_hex(license) { + return Ok(Some((*pos, license.clone()))); } } - pairs.last().map(|(pos, license)| (*pos, license.clone())) + None }) } @@ -397,12 +493,13 @@ impl Command { blockchain_access_config: &BlockchainAccessConfig, wallet_path: &WalletPath, psw: &Password, - lp_config: &Path, + psk_lp: PublicSpendKey, ssk_user: SecretSpendKey, license: &License, pos: u64, gas_limit: u64, gas_price: u64, + pp_opt: &mut Option, ) -> Result { let client = RuskHttpClient::new(blockchain_access_config.rusk_address.clone()); @@ -412,28 +509,26 @@ impl Command { let mut rng = StdRng::seed_from_u64(0xbeef); println!("performing setup"); - let pp = PublicParameters::setup(1 << CAPACITY, &mut rng) - .expect("Initializing public parameters should succeed"); + let pp: &PublicParameters = match pp_opt { + Some(pp) => pp, + _ => { + let pp = PublicParameters::setup(1 << CAPACITY, &mut rng) + .expect("Initializing public parameters should succeed"); + *pp_opt = Some(pp); + pp_opt.as_ref().unwrap() + } + }; println!("compiling circuit"); - let (prover, verifier) = - Compiler::compile::(&pp, LABEL) - .expect("Compiling circuit should succeed"); + let (prover, verifier) = Compiler::compile::(pp, LABEL) + .expect("Compiling circuit should succeed"); let opening = CitadelInquirer::get_merkle_opening(&client, pos) .await? .expect("Opening obtained successfully"); - let reference_lp = ReferenceLP::create(lp_config)?; - let (cpp, sc) = CitadelProverParameters::compute_parameters( - &ssk_user, - license, - &reference_lp.psk_lp, - &reference_lp.psk_lp, - &challenge, - &mut rng, - opening, + &ssk_user, license, &psk_lp, &psk_lp, &challenge, &mut rng, opening, ); let circuit = LicenseCircuit::new(&cpp, &sc); @@ -467,12 +562,11 @@ impl Command { USE_LICENSE_METHOD_NAME, ) .await?; + TxAwaiter::wait_for(&client, tx_id).await?; println!( - "tx {} submitted, waiting for confirmation", + "use license executing transaction {} confirmed", hex::encode(tx_id.to_bytes()) ); - TxAwaiter::wait_for(&client, tx_id).await?; - println!("tx {} confirmed", hex::encode(tx_id.to_bytes())); Ok(session_id) } @@ -483,6 +577,10 @@ impl Command { 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(); diff --git a/moat-cli/src/interactor.rs b/moat-cli/src/interactor.rs index a79153a..1301120 100644 --- a/moat-cli/src/interactor.rs +++ b/moat-cli/src/interactor.rs @@ -7,6 +7,7 @@ use crate::error::CliError; use crate::prompt; use crate::{Command, Menu}; +use dusk_plonk::prelude::PublicParameters; use dusk_wallet::WalletPath; use moat_core::RequestJson; use requestty::{ErrorKind, Question}; @@ -80,13 +81,25 @@ fn menu_operation() -> Result { "LP config (e.g. moat-cli/lp2.json)", "moat-cli/lp2.json", )?, + request_hash: prompt::request_request_hash()?, })) } CommandMenuItem::ListLicenses => { - OpSelection::Run(Box::from(Command::ListLicenses { dummy: true })) + OpSelection::Run(Box::from(Command::ListLicenses { + request_path: prompt::request_pathbuf( + "request (e.g. moat-cli/request2.json)", + "moat-cli/request2.json", + )?, + })) } CommandMenuItem::UseLicense => { - OpSelection::Run(Box::from(Command::UseLicense { dummy: true })) + OpSelection::Run(Box::from(Command::UseLicense { + request_path: prompt::request_pathbuf( + "request (e.g. moat-cli/request2.json)", + "moat-cli/request2.json", + )?, + license_hash: prompt::request_license_hash()?, + })) } CommandMenuItem::GetSession => { OpSelection::Run(Box::from(Command::GetSession { @@ -108,10 +121,11 @@ pub struct Interactor { pub gas_limit: u64, pub gas_price: u64, pub request_json: Option, + pub pp: Option, } impl Interactor { - pub async fn run_loop(&self) -> Result<(), CliError> { + pub async fn run_loop(&mut self) -> Result<(), CliError> { loop { let op = menu_operation()?; match op { @@ -126,6 +140,7 @@ impl Interactor { self.gas_limit, self.gas_price, self.request_json.clone(), + &mut self.pp, ) .await? } diff --git a/moat-cli/src/main.rs b/moat-cli/src/main.rs index 06032cf..4ddfef8 100644 --- a/moat-cli/src/main.rs +++ b/moat-cli/src/main.rs @@ -52,7 +52,7 @@ async fn main() -> Result<(), CliError> { PwdHash(pwd_hash) }; - let interactor = Interactor { + let mut interactor = Interactor { wallet_path, psw, blockchain_access_config, @@ -60,6 +60,7 @@ async fn main() -> Result<(), CliError> { gas_limit, gas_price, request_json: Some(request_json), + pp: None, }; interactor.run_loop().await?; diff --git a/moat-cli/src/prompt.rs b/moat-cli/src/prompt.rs index 32ab7db..38c8b59 100644 --- a/moat-cli/src/prompt.rs +++ b/moat-cli/src/prompt.rs @@ -27,6 +27,34 @@ pub(crate) fn request_session_id() -> Result { Ok(a_str) } +pub(crate) fn request_request_hash() -> Result { + let q = Question::input("request_hash") + .message("Please enter request hash:".to_string()) + .validate_on_key(|_, _| { + true // todo: add some validation of the request hash + }) + .validate(|_, _| Ok(())) + .build(); + + let a = requestty::prompt_one(q)?; + let a_str = a.as_string().expect("answer to be a string").to_string(); + Ok(a_str) +} + +pub(crate) fn request_license_hash() -> Result { + let q = Question::input("license_hash") + .message("Please enter license hash:".to_string()) + .validate_on_key(|_, _| { + true // todo: add some validation of the license hash + }) + .validate(|_, _| Ok(())) + .build(); + + let a = requestty::prompt_one(q)?; + let a_str = a.as_string().expect("answer to be a string").to_string(); + Ok(a_str) +} + pub(crate) fn request_pathbuf( hint: &str, dflt: &str, diff --git a/moat-core/tests/utils.rs b/moat-core/tests/utils.rs index a2111bd..c2f8e65 100644 --- a/moat-core/tests/utils.rs +++ b/moat-core/tests/utils.rs @@ -61,7 +61,7 @@ fn encode_password_old() -> Result<(), Error> { #[test] #[ignore] fn new_ssk() -> Result<(), Error> { - let rng = &mut StdRng::seed_from_u64(0xcafe); + let rng = &mut StdRng::from_entropy(); let ssk = SecretSpendKey::random(rng); println!("ssk={}", hex::encode(ssk.to_bytes())); Ok(())