diff --git a/Cargo.lock b/Cargo.lock index e7d45e4fe..bb405467c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,7 @@ dependencies = [ "mockall", "node-primitives", "pallet-bioauth", + "parity-scale-codec", "sc-client-api", "sc-consensus", "sc-service", @@ -2385,6 +2386,7 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", + "primitives-auth-ticket", "robonode-crypto", "serde", "sp-api", @@ -4312,7 +4314,6 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", - "primitives-auth-ticket", "serde", "sp-api", "sp-core", diff --git a/crates/bioauth-consensus/Cargo.toml b/crates/bioauth-consensus/Cargo.toml index 8e0659183..46cf349c2 100644 --- a/crates/bioauth-consensus/Cargo.toml +++ b/crates/bioauth-consensus/Cargo.toml @@ -9,6 +9,7 @@ publish = false pallet-bioauth = { version = "0.1", path = "../pallet-bioauth", optional = true } async-trait = "0.1.42" +codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } sc-client-api = { git = "https://github.com/humanode-network/substrate", branch = "master" } sc-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" } sp-api = { git = "https://github.com/humanode-network/substrate", branch = "master" } diff --git a/crates/bioauth-consensus/src/aura.rs b/crates/bioauth-consensus/src/aura.rs index 60ec0ba85..754208b79 100644 --- a/crates/bioauth-consensus/src/aura.rs +++ b/crates/bioauth-consensus/src/aura.rs @@ -1,20 +1,20 @@ //! Aura consensus integration. -use sp_api::{BlockId, Decode, ProvideRuntimeApi}; -use sp_application_crypto::Public; +use sp_api::{BlockId, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; -use sp_consensus_aura::{AuraApi, Slot}; -use sp_runtime::generic::OpaqueDigestItemId; +use sp_consensus_aura::{digests::CompatibleDigestItem, AuraApi}; use sp_runtime::traits::{Block as BlockT, Header}; use std::{marker::PhantomData, sync::Arc}; /// Encapsulates block author extraction logic for aura consensus. #[derive(Debug)] -pub struct BlockAuthorExtractor { +pub struct BlockAuthorExtractor { /// Client provides access to the runtime. client: Arc, - /// The type from the block used in the chain. + /// The type of the block used in the chain. _phantom_block: PhantomData, + /// The type used an authority id in aura. + _phantom_aura_authority_id: PhantomData, } /// An error that can occur during block author extraction with the aura consensus. @@ -26,44 +26,47 @@ pub enum AuraBlockAuthorExtractorError { /// Unable to obtain the slot from the block header. #[error("unable to obtaion the slot from the block header")] UnableToObtainSlot, - /// Unable to decode the slot. - #[error("unable to decode the slot")] - UnableToDecodeSlot, } -impl BlockAuthorExtractor { +impl BlockAuthorExtractor { /// Create a new [`AuraBlockAuthorExtractor`]. pub fn new(client: Arc) -> Self { Self { client, _phantom_block: PhantomData, + _phantom_aura_authority_id: PhantomData, } } } -impl Clone for BlockAuthorExtractor { +impl Clone + for BlockAuthorExtractor +{ fn clone(&self) -> Self { Self { client: Arc::clone(&self.client), _phantom_block: PhantomData, + _phantom_aura_authority_id: PhantomData, } } } -impl crate::BlockAuthorExtractor for BlockAuthorExtractor +impl crate::BlockAuthorExtractor + for BlockAuthorExtractor where Client: HeaderBackend + ProvideRuntimeApi, - Client::Api: AuraApi, + Client::Api: AuraApi, + AuraAuthorityId: codec::Codec + Clone, { type Error = AuraBlockAuthorExtractorError; type Block = Block; - type PublicKeyType = Vec; + type PublicKeyType = AuraAuthorityId; fn extract_block_author( &self, at: &BlockId, block_header: &::Header, - ) -> Result, Self::Error> { + ) -> Result { // Extract aura authorities list. let authorities = self .client @@ -72,23 +75,21 @@ where .map_err(AuraBlockAuthorExtractorError::UnableToExtractAuthorities)?; // Extract the slot of a block. - let mut slot = block_header + let slot = block_header .digest() - .log(|l| l.try_as_raw(OpaqueDigestItemId::PreRuntime(b"aura"))) + .logs() + .iter() + .find_map(CompatibleDigestItem::<()>::as_aura_pre_digest) .ok_or(AuraBlockAuthorExtractorError::UnableToObtainSlot)?; - // Decode slot number. - let slot_decoded = Slot::decode(&mut slot) - .map_err(|_| AuraBlockAuthorExtractorError::UnableToDecodeSlot)?; - // Author index in aura is current slot number mod authories. - let author_index = *slot_decoded % authorities.len() as u64; + let author_index = *slot % authorities.len() as u64; // Determine the author of a block. let author_public_key = authorities .get(author_index as usize) .expect("author index is mod authories list len; qed"); - Ok(author_public_key.to_raw_vec()) + Ok(author_public_key.clone()) } } diff --git a/crates/bioauth-consensus/src/bioauth.rs b/crates/bioauth-consensus/src/bioauth.rs index 902b9858f..d3b087695 100644 --- a/crates/bioauth-consensus/src/bioauth.rs +++ b/crates/bioauth-consensus/src/bioauth.rs @@ -8,11 +8,13 @@ use std::{marker::PhantomData, sync::Arc}; /// Provides an authorization verifier on top of stored auth tickets. #[derive(Debug)] -pub struct AuthorizationVerifier { +pub struct AuthorizationVerifier { /// The client provides access to the runtime. client: Arc, - /// The type from the block used in the chain. + /// The type of the block used in the chain. _phantom_block: PhantomData, + /// The type of the validator public key used in the chain. + _phantom_validator_public_key: PhantomData, } /// An error that can occur during aura authorization verification. @@ -24,33 +26,41 @@ pub enum AuraAuthorizationVerifierError { UnableToExtractStoredAuthTickets(sp_api::ApiError), } -impl AuthorizationVerifier { +impl + AuthorizationVerifier +{ /// Create a new [`AuraAuthorizationVerifier`]. pub fn new(client: Arc) -> Self { Self { client, _phantom_block: PhantomData, + _phantom_validator_public_key: PhantomData, } } } -impl Clone for AuthorizationVerifier { +impl Clone + for AuthorizationVerifier +{ fn clone(&self) -> Self { Self { client: Arc::clone(&self.client), _phantom_block: PhantomData, + _phantom_validator_public_key: PhantomData, } } } -impl crate::AuthorizationVerifier for AuthorizationVerifier +impl crate::AuthorizationVerifier + for AuthorizationVerifier where Client: HeaderBackend + ProvideRuntimeApi, - Client::Api: BioauthApi, + Client::Api: BioauthApi, + ValidatorPublicKey: codec::Decode + PartialEq, { type Error = AuraAuthorizationVerifierError; type Block = Block; - type PublicKeyType = [u8]; + type PublicKeyType = ValidatorPublicKey; fn is_authorized( &self, @@ -66,7 +76,7 @@ where let is_authorized = stored_tickets .iter() - .any(|ticket| ticket.public_key == author_public_key); + .any(|ticket| &ticket.public_key == author_public_key); Ok(is_authorized) } diff --git a/crates/bioauth-consensus/src/lib.rs b/crates/bioauth-consensus/src/lib.rs index 000249cff..ce9729990 100644 --- a/crates/bioauth-consensus/src/lib.rs +++ b/crates/bioauth-consensus/src/lib.rs @@ -101,10 +101,8 @@ where BlockImport>, TransactionFor: 'static, BAX: BlockAuthorExtractor + Send, - AV: AuthorizationVerifier + Send, - ::PublicKeyType: Send, - ::PublicKeyType: - AsRef<::PublicKeyType>, + AV: AuthorizationVerifier + Send, + ::PublicKeyType: Send + Sync, ::Error: std::error::Error + Send + Sync + 'static, ::Error: std::error::Error + Send + Sync + 'static, BE: Backend, @@ -142,7 +140,7 @@ where let is_authorized = self .authorization_verifier - .is_authorized(&at, author_public_key.as_ref()) + .is_authorized(&at, &author_public_key) .map_err(|err| mkerr(BioauthBlockImportError::AuthorizationVerifier(err)))?; if !is_authorized { diff --git a/crates/bioauth-consensus/src/tests.rs b/crates/bioauth-consensus/src/tests.rs index 0dac1c939..af09b2f0a 100644 --- a/crates/bioauth-consensus/src/tests.rs +++ b/crates/bioauth-consensus/src/tests.rs @@ -1,11 +1,11 @@ use mockall::predicate::*; use mockall::*; use node_primitives::{Block, BlockNumber, Hash, Header}; -use pallet_bioauth::{BioauthApi, StoredAuthTicket}; +use pallet_bioauth::{self, BioauthApi, StoredAuthTicket}; use sc_client_api::Finalizer; use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; use sp_api::{ApiError, ApiRef, NativeOrEncoded, ProvideRuntimeApi, TransactionFor}; -use sp_application_crypto::{Pair, Public}; +use sp_application_crypto::Pair; use sp_blockchain::{well_known_cache_keys, HeaderBackend}; use sp_consensus::{BlockOrigin, Error as ConsensusError}; use sp_consensus_aura::{ @@ -16,10 +16,14 @@ use std::{collections::HashMap, str::FromStr, sync::Arc}; use crate::{BioauthBlockImport, BioauthBlockImportError}; +type MockValidatorPublicKey = AuraId; + +type MockAuthTicket = StoredAuthTicket; + mock! { RuntimeApi { fn stored_auth_tickets(&self, _at: &sp_api::BlockId) -> Result< - NativeOrEncoded>, + NativeOrEncoded>, ApiError >; @@ -108,10 +112,10 @@ impl<'a> BlockImport for &'a MockClient { } sp_api::mock_impl_runtime_apis! { - impl BioauthApi for MockWrapperRuntimeApi { + impl BioauthApi for MockWrapperRuntimeApi { #[advanced] fn stored_auth_tickets(&self, at: &sp_api::BlockId) -> Result< - NativeOrEncoded>, + NativeOrEncoded>, ApiError > { self.0.stored_auth_tickets(at) @@ -375,12 +379,15 @@ async fn it_denies_block_import_with_not_bioauth_authorized() { mock_runtime_api .expect_stored_auth_tickets() .returning(|_| { - Ok(NativeOrEncoded::from(vec![ - pallet_bioauth::StoredAuthTicket { - public_key: "invalid_author".as_bytes().to_vec(), - nonce: "1".as_bytes().to_vec(), - }, - ])) + Ok(NativeOrEncoded::from(vec![MockAuthTicket { + public_key: sp_consensus_aura::sr25519::AuthorityPair::from_string( + &format!("//{}", "Bob"), + None, + ) + .expect("static values are valid; qed") + .public(), + nonce: b"1".to_vec(), + }])) }); let runtime_api = MockWrapperRuntimeApi(Arc::new(mock_runtime_api)); @@ -433,18 +440,15 @@ async fn it_permits_block_import_with_valid_data() { mock_runtime_api .expect_stored_auth_tickets() .returning(|_| { - Ok(NativeOrEncoded::from(vec![ - pallet_bioauth::StoredAuthTicket { - public_key: sp_consensus_aura::sr25519::AuthorityPair::from_string( - &format!("//{}", "Alice"), - None, - ) - .expect("static values are valid; qed") - .public() - .to_raw_vec(), - nonce: "1".as_bytes().to_vec(), - }, - ])) + Ok(NativeOrEncoded::from(vec![MockAuthTicket { + public_key: sp_consensus_aura::sr25519::AuthorityPair::from_string( + &format!("//{}", "Alice"), + None, + ) + .expect("static values are valid; qed") + .public(), + nonce: b"1".to_vec(), + }])) }); let runtime_api = MockWrapperRuntimeApi(Arc::new(mock_runtime_api)); diff --git a/crates/humanode-peer/src/chain_spec.rs b/crates/humanode-peer/src/chain_spec.rs index c44a5a9eb..c68c5ac5a 100644 --- a/crates/humanode-peer/src/chain_spec.rs +++ b/crates/humanode-peer/src/chain_spec.rs @@ -81,7 +81,7 @@ pub fn local_testnet_config() -> Result { get_account_id_from_seed::("Ferdie//stash"), ], vec![pallet_bioauth::StoredAuthTicket { - public_key: authority_keys_from_seed("Alice").as_slice().to_vec(), + public_key: authority_keys_from_seed("Alice"), nonce: "1".as_bytes().to_vec(), }], robonode_public_key, @@ -130,7 +130,7 @@ pub fn development_config() -> Result { get_account_id_from_seed::("Bob//stash"), ], vec![pallet_bioauth::StoredAuthTicket { - public_key: authority_keys_from_seed("Alice").as_slice().to_vec(), + public_key: authority_keys_from_seed("Alice"), nonce: "1".as_bytes().to_vec(), }], robonode_public_key, @@ -155,7 +155,7 @@ fn testnet_genesis( initial_authorities: Vec, root_key: AccountId, endowed_accounts: Vec, - stored_auth_tickets: Vec, + stored_auth_tickets: Vec>, robonode_public_key: RobonodePublicKeyWrapper, ) -> GenesisConfig { GenesisConfig { diff --git a/crates/humanode-peer/src/service.rs b/crates/humanode-peer/src/service.rs index e83d9f042..2daa2b47b 100644 --- a/crates/humanode-peer/src/service.rs +++ b/crates/humanode-peer/src/service.rs @@ -10,7 +10,7 @@ use sc_executor::native_executor_instance; pub use sc_executor::NativeExecutor; use sc_service::{Error as ServiceError, KeystoreContainer, PartialComponents, TaskManager}; use sp_consensus::SlotData; -use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use sp_consensus_aura::sr25519::{AuthorityId as AuraId, AuthorityPair as AuraPair}; use tracing::*; use crate::configuration::Configuration; @@ -54,8 +54,8 @@ pub fn new_partial( FullBackend, Block, FullClient, - bioauth_consensus::aura::BlockAuthorExtractor, - bioauth_consensus::bioauth::AuthorizationVerifier, + bioauth_consensus::aura::BlockAuthorExtractor, + bioauth_consensus::bioauth::AuthorizationVerifier, >, SlotDuration, Duration, diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 61ad9aad4..d00f96264 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -10,6 +10,7 @@ substrate-wasm-builder = { git = "https://github.com/humanode-network/substrate" [dependencies] pallet-bioauth = { version = "0.1", path = "../pallet-bioauth", default-features = false } +primitives-auth-ticket = { version = "0.1", path = "../primitives-auth-ticket", default-features = false } robonode-crypto = { version = "0.1", path = "../robonode-crypto", default-features = false } codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } @@ -52,6 +53,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", ] std = [ + "primitives-auth-ticket/std", "codec/std", "serde", "frame-executive/std", diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index e29afe582..2f2367416 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -11,6 +11,8 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use pallet_bioauth::StoredAuthTicket; +use primitives_auth_ticket::OpaqueAuthTicket; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_api::impl_runtime_apis; @@ -297,9 +299,45 @@ impl pallet_sudo::Config for Runtime { type Call = Call; } +pub struct PrimitiveAuthTicketConverter; + +pub enum PrimitiveAuthTicketConverterError { + Ticket(codec::Error), + PublicKey(()), +} + +impl pallet_bioauth::TryConvert> + for PrimitiveAuthTicketConverter +{ + type Error = PrimitiveAuthTicketConverterError; + + fn try_convert( + value: OpaqueAuthTicket, + ) -> Result, Self::Error> { + use sp_std::convert::TryInto; + let primitives_auth_ticket::AuthTicket { + public_key, + authentication_nonce: nonce, + } = (&value) + .try_into() + .map_err(PrimitiveAuthTicketConverterError::Ticket)?; + + let public_key = public_key + .as_slice() + .try_into() + .map_err(PrimitiveAuthTicketConverterError::PublicKey)?; + + Ok(StoredAuthTicket { public_key, nonce }) + } +} + impl pallet_bioauth::Config for Runtime { type Event = Event; type RobonodePublicKey = RobonodePublicKeyWrapper; + type RobonodeSignature = Vec; + type ValidatorPublicKey = AuraId; + type OpaqueAuthTicket = primitives_auth_ticket::OpaqueAuthTicket; + type AuthTicketCoverter = PrimitiveAuthTicketConverter; } // Create the runtime by composing the FRAME pallets that were previously @@ -418,8 +456,8 @@ impl_runtime_apis! { } } - impl pallet_bioauth::BioauthApi for Runtime { - fn stored_auth_tickets() -> sp_std::prelude::Vec { + impl pallet_bioauth::BioauthApi::ValidatorPublicKey> for Runtime { + fn stored_auth_tickets() -> sp_std::prelude::Vec::ValidatorPublicKey>> { Bioauth::stored_auth_tickets() } } diff --git a/crates/pallet-bioauth/Cargo.toml b/crates/pallet-bioauth/Cargo.toml index f314d77e5..8b5c59eca 100644 --- a/crates/pallet-bioauth/Cargo.toml +++ b/crates/pallet-bioauth/Cargo.toml @@ -6,8 +6,6 @@ authors = ["Humanode Team "] publish = false [dependencies] -primitives-auth-ticket = { version = "0.1", path = "../primitives-auth-ticket", default-features = false } - codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } frame-benchmarking = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master", optional = true } frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } @@ -25,12 +23,12 @@ sp-io = { default-features = false, git = "https://github.com/humanode-network/s default = ["std"] std = [ "serde", - "primitives-auth-ticket/std", "codec/std", "frame-support/std", "frame-system/std", "frame-benchmarking/std", "sp-std/std", + "sp-runtime/std", ] runtime-benchmarks = ["frame-benchmarking"] try-runtime = ["frame-support/try-runtime"] diff --git a/crates/pallet-bioauth/src/lib.rs b/crates/pallet-bioauth/src/lib.rs index 85672c97c..8cd945d40 100644 --- a/crates/pallet-bioauth/src/lib.rs +++ b/crates/pallet-bioauth/src/lib.rs @@ -28,38 +28,6 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -/// Authentication extrinsic playload. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)] -pub struct Authenticate { - /// The opaque auth ticket. - pub ticket: Vec, - /// The robonode signatrure for the opaque auth ticket. - pub ticket_signature: Vec, -} - -/// The state that we keep in the blockchain for the authorized authentication tickets. -/// -/// It is decoupled from the [`primitives_auth_ticket::AuthTicket`], such that it's possible to version -/// and update those independently. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)] -pub struct StoredAuthTicket { - /// The public key of the validator. - pub public_key: Vec, - /// A one-time use value. - pub nonce: Vec, -} - -impl From for StoredAuthTicket { - fn from(val: primitives_auth_ticket::AuthTicket) -> Self { - Self { - public_key: val.public_key, - nonce: val.authentication_nonce, - } - } -} - /// Verifier provides the verification of the data accompanied with the /// signature or proof data. /// A non-async (blocking) variant, for use at runtime. @@ -75,14 +43,43 @@ pub trait Verifier { D: AsRef<[u8]> + Send + 'a; } -sp_api::decl_runtime_apis! { +/// A trait that enables a third-party type to define a potentially fallible conversion from A to B. +/// Is in analogous to [`sp_runtime::Convert`] is a sense that the third-party is acting as +/// the converter, and to [`std::convert::TryFtom`] in a sense that the converion is fallible. +pub trait TryConvert { + /// The error that can occur during conversion. + type Error; + /// Take A and return B on success, or an Error if the conversion fails. + fn try_convert(value: A) -> Result; +} + +/// Authentication extrinsic playload. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)] +pub struct Authenticate { + /// An auth ticket. + pub ticket: OpaqueAuthTicket, + /// The robonode signatrure for the opaque auth ticket. + pub ticket_signature: Commitment, +} + +/// The state that we keep in the blockchain for the authorized auth tickets. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(PartialEq, Eq, Default, Clone, Encode, Decode, Hash, Debug)] +pub struct StoredAuthTicket { + /// The public key of a validator that was authorized by a robonode. + pub public_key: PublicKey, + /// The nonce that the robonode has provided. + pub nonce: Vec, +} + +sp_api::decl_runtime_apis! { /// We need to provide a trait using decl_runtime_apis! macro to be able to call required methods /// from external sources using client and runtime_api(). - pub trait BioauthApi { - - /// Get existing stored tickets for current block. - fn stored_auth_tickets() -> Vec; + pub trait BioauthApi { + /// Get stored auth tickets for current block. + fn stored_auth_tickets() -> Vec>; } } @@ -95,12 +92,12 @@ sp_api::decl_runtime_apis! { )] #[frame_support::pallet] pub mod pallet { - use core::convert::TryInto; + use crate::{StoredAuthTicket, TryConvert, Verifier}; - use super::{Authenticate, StoredAuthTicket, Verifier}; + use super::Authenticate; use frame_support::{dispatch::DispatchResult, pallet_prelude::*, storage::types::ValueQuery}; use frame_system::pallet_prelude::*; - use primitives_auth_ticket::{AuthTicket, OpaqueAuthTicket}; + use sp_runtime::app_crypto::MaybeHash; use sp_std::prelude::*; /// Configure the pallet by specifying the parameters and types on which it depends. @@ -109,11 +106,27 @@ pub mod pallet { /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; + /// The type of the robonode signature. + type RobonodeSignature: Member + Parameter; + /// The public key of the robonode. - type RobonodePublicKey: Verifier> - + codec::FullCodec - + Default - + MaybeSerializeDeserialize; + type RobonodePublicKey: Member + + Parameter + + MaybeSerializeDeserialize + + Verifier + + Default; + + /// The public key of the validator. + type ValidatorPublicKey: Member + Parameter + MaybeSerializeDeserialize + MaybeHash; + + /// The opaque auth ticket type. + type OpaqueAuthTicket: Parameter + AsRef<[u8]> + Send + Sync; + + /// A converter from an opaque to a stored auth ticket. + type AuthTicketCoverter: TryConvert< + Self::OpaqueAuthTicket, + StoredAuthTicket, + >; } #[pallet::pallet] @@ -123,7 +136,8 @@ pub mod pallet { /// A list of the authorized auth tickets. #[pallet::storage] #[pallet::getter(fn stored_auth_tickets)] - pub type StoredAuthTickets = StorageValue<_, Vec, ValueQuery>; + pub type StoredAuthTickets = + StorageValue<_, Vec::ValidatorPublicKey>>, ValueQuery>; /// The public key of the robonode. #[pallet::storage] @@ -132,8 +146,8 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { - pub stored_auth_tickets: Vec, - pub robonode_public_key: ::RobonodePublicKey, + pub stored_auth_tickets: Vec>, + pub robonode_public_key: T::RobonodePublicKey, } // The default value for the genesis config type. @@ -163,7 +177,7 @@ pub mod pallet { pub enum Event { /// Event documentation should end with an array that provides descriptive names for event /// parameters. [stored_auth_ticket] - AuthTicketStored(StoredAuthTicket), + AuthTicketStored(StoredAuthTicket), } /// Possible error conditions during `authenticate` call processing. @@ -184,15 +198,15 @@ pub mod pallet { PublicKeyAlreadyUsed, } - pub enum AuthenticationAttemptValidationError<'a> { + pub enum AuthenticationAttemptValidationError<'a, T: Config> { NonceConflict, - ConflitingPublicKeys(Vec<&'a StoredAuthTicket>), + ConflitingPublicKeys(Vec<&'a StoredAuthTicket>), } - pub fn validate_authentication_attempt<'a>( - existing: &'a [StoredAuthTicket], - new: &StoredAuthTicket, - ) -> Result<(), AuthenticationAttemptValidationError<'a>> { + pub fn validate_authentication_attempt<'a, T: Config>( + existing: &'a [StoredAuthTicket], + new: &StoredAuthTicket, + ) -> Result<(), AuthenticationAttemptValidationError<'a, T>> { let mut conflicting_tickets = Vec::new(); for existing in existing.iter() { if existing.nonce == new.nonce { @@ -218,7 +232,10 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn authenticate(origin: OriginFor, req: Authenticate) -> DispatchResult { + pub fn authenticate( + origin: OriginFor, + req: Authenticate, + ) -> DispatchResult { ensure_none(origin)?; let stored_auth_ticket = Self::extract_auth_ticket_checked(req)?; @@ -226,7 +243,7 @@ pub mod pallet { // Update storage. >::try_mutate(move |list| { - match validate_authentication_attempt(list, &stored_auth_ticket) { + match validate_authentication_attempt::(list, &stored_auth_ticket) { Err(AuthenticationAttemptValidationError::NonceConflict) => { Err(Error::::NonceAlreadyUsed) } @@ -250,24 +267,23 @@ pub mod pallet { impl Pallet { pub fn extract_auth_ticket_checked( - req: Authenticate, - ) -> Result> { + req: Authenticate, + ) -> Result, Error> { let robonode_public_key = RobonodePublicKey::::get().ok_or(Error::::RobonodePublicKeyIsAbsent)?; + let signature_valid = robonode_public_key - .verify(&req.ticket, req.ticket_signature.clone()) + .verify(&req.ticket, req.ticket_signature) .map_err(|_| Error::::UnableToValidateAuthTicketSignature)?; + if !signature_valid { return Err(Error::::AuthTicketSignatureInvalid); } - let opaque_auth_ticket = OpaqueAuthTicket::from(req.ticket); - - let auth_ticket: AuthTicket = (&opaque_auth_ticket) - .try_into() + let auth_ticket = >::try_convert(req.ticket) .map_err(|_| Error::::UnableToParseAuthTicket)?; - Ok(auth_ticket.into()) + Ok(auth_ticket) } pub fn check_tx(call: &Call) -> TransactionValidity { @@ -293,7 +309,7 @@ pub mod pallet { let list = StoredAuthTickets::::get(); - validate_authentication_attempt(&list, &stored_auth_ticket).map_err(|_e| { + validate_authentication_attempt::(&list, &stored_auth_ticket).map_err(|_| { // Use custom code 'c' for "conflict" error. TransactionValidityError::Invalid(InvalidTransaction::Custom(b'c')) })?; diff --git a/crates/pallet-bioauth/src/mock.rs b/crates/pallet-bioauth/src/mock.rs index bdfe503c4..4efa5c5e6 100644 --- a/crates/pallet-bioauth/src/mock.rs +++ b/crates/pallet-bioauth/src/mock.rs @@ -1,4 +1,4 @@ -use crate as pallet_bioauth; +use crate::{self as pallet_bioauth, StoredAuthTicket, TryConvert}; use codec::{Decode, Encode}; use frame_support::{parameter_types, traits::GenesisBuild}; use frame_system as system; @@ -25,6 +25,26 @@ frame_support::construct_runtime!( } ); +#[derive(PartialEq, Eq, Default, Clone, Encode, Decode, Hash, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct MockOpaqueAuthTicket(pub StoredAuthTicket>); + +impl AsRef<[u8]> for MockOpaqueAuthTicket { + fn as_ref(&self) -> &[u8] { + panic!("should be ununsed in tests") + } +} + +pub struct MockAuthTicketConverter; + +impl TryConvert>> for MockAuthTicketConverter { + type Error = Infallible; + + fn try_convert(value: MockOpaqueAuthTicket) -> Result>, Self::Error> { + Ok(value.0) + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct MockVerifier; @@ -74,6 +94,10 @@ impl system::Config for Test { impl pallet_bioauth::Config for Test { type Event = Event; type RobonodePublicKey = MockVerifier; + type RobonodeSignature = Vec; + type ValidatorPublicKey = Vec; + type OpaqueAuthTicket = MockOpaqueAuthTicket; + type AuthTicketCoverter = MockAuthTicketConverter; } // Build genesis storage according to the mock runtime. diff --git a/crates/pallet-bioauth/src/tests.rs b/crates/pallet-bioauth/src/tests.rs index 62dd97a1e..52018c7e3 100644 --- a/crates/pallet-bioauth/src/tests.rs +++ b/crates/pallet-bioauth/src/tests.rs @@ -1,21 +1,20 @@ -use std::convert::TryInto; - use crate as pallet_bioauth; use crate::*; use crate::{mock::*, Error}; use frame_support::pallet_prelude::*; use frame_support::{assert_noop, assert_ok}; -use primitives_auth_ticket::{AuthTicket, OpaqueAuthTicket}; - -pub fn make_input(public_key: &[u8], nonce: &[u8], signature: &[u8]) -> crate::Authenticate { - let ticket = - primitives_auth_ticket::OpaqueAuthTicket::from(&primitives_auth_ticket::AuthTicket { - public_key: Vec::from(public_key), - authentication_nonce: Vec::from(nonce), - }); + +pub fn make_input( + public_key: &[u8], + nonce: &[u8], + signature: &[u8], +) -> crate::Authenticate> { crate::Authenticate { - ticket: ticket.into(), - ticket_signature: Vec::from(signature), + ticket: MockOpaqueAuthTicket(StoredAuthTicket { + public_key: public_key.into(), + nonce: nonce.into(), + }), + ticket_signature: signature.into(), } } @@ -28,9 +27,9 @@ fn it_permits_authentication_with_an_empty_state() { assert_ok!(Bioauth::authenticate(Origin::none(), input)); assert_eq!( Bioauth::stored_auth_tickets(), - vec![crate::StoredAuthTicket { - public_key: Vec::from(&b"qwe"[..]), - nonce: Vec::from(&b"rty"[..]), + vec![StoredAuthTicket { + public_key: b"qwe".to_vec(), + nonce: b"rty".to_vec(), }] ); }); @@ -106,10 +105,10 @@ fn signed_ext_check_bioauth_tx_permit_empty_state() { new_test_ext().execute_with(|| { // Prepare test input. let input = make_input(b"qwe", b"rty", b"should_be_valid"); - - let opaque_auth_ticket: OpaqueAuthTicket = input.ticket.clone().into(); - let auth_ticket: AuthTicket = (&opaque_auth_ticket).try_into().unwrap(); - let expected_tag: StoredAuthTicket = auth_ticket.into(); + let expected_tag = StoredAuthTicket { + public_key: b"qwe".to_vec(), + nonce: b"rty".to_vec(), + }; let call = >::authenticate(input).into(); let info = DispatchInfo::default(); diff --git a/crates/primitives-auth-ticket/src/lib.rs b/crates/primitives-auth-ticket/src/lib.rs index 4ecf73b78..2dc2a86f4 100644 --- a/crates/primitives-auth-ticket/src/lib.rs +++ b/crates/primitives-auth-ticket/src/lib.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize}; use sp_std::prelude::*; /// The one-time ticket to authenticate in the network. -#[derive(Debug, PartialEq, Encode, Decode)] +#[derive(Debug, PartialEq, Eq, Encode, Decode, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(transparent))] pub struct OpaqueAuthTicket(pub Vec); @@ -59,6 +59,12 @@ impl From> for OpaqueAuthTicket { } } +impl From> for OpaqueAuthTicket { + fn from(val: Box<[u8]>) -> Self { + Self(val.into()) + } +} + impl From for Vec { fn from(val: OpaqueAuthTicket) -> Self { val.0