diff --git a/Cargo.lock b/Cargo.lock index a42c5977a..dc127ed3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,7 +2761,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.91" +version = "1.1.92" dependencies = [ "actix-rt", "aes-gcm", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.91" +version = "1.1.92" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/apple/Sources/Sargon/SargonOS/ThrowingHostInteractor+Static+Shared.swift b/apple/Sources/Sargon/SargonOS/ThrowingHostInteractor+Static+Shared.swift index 857b61680..f2d9c8264 100644 --- a/apple/Sources/Sargon/SargonOS/ThrowingHostInteractor+Static+Shared.swift +++ b/apple/Sources/Sargon/SargonOS/ThrowingHostInteractor+Static+Shared.swift @@ -2,7 +2,7 @@ public class ThrowingHostInteractor: HostInteractor { public nonisolated(unsafe) static var shared: HostInteractor = ThrowingHostInteractor() - public func signAuth(request: SargonUniFFI.AuthenticationSigningRequest) async throws -> SargonUniFFI.AuthenticationSigningResponse { + public func signAuth(request: SargonUniFFI.SignRequestOfAuthIntent) async throws -> SargonUniFFI.SignWithFactorsOutcomeOfAuthIntentHash { throw CommonError.SigningRejected } diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index ab9876ac5..604fc535e 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.91" +version = "1.1.92" edition = "2021" build = "build.rs" diff --git a/crates/sargon-uniffi/src/signing/authentication/auth_intent.rs b/crates/sargon-uniffi/src/signing/authentication/auth_intent.rs new file mode 100644 index 000000000..c41329b39 --- /dev/null +++ b/crates/sargon-uniffi/src/signing/authentication/auth_intent.rs @@ -0,0 +1,95 @@ +use crate::prelude::*; +use sargon::AuthIntent as InternalAuthIntent; +use std::hash::Hasher; + +#[derive(Clone, PartialEq, Eq, uniffi::Record)] +pub struct AuthIntent { + /// The challenge nonce that with some `metadata` values are generating the `RolaChallenge` + /// needed to be signed + pub challenge_nonce: Exactly32Bytes, + + /// The `NetworkID` on which the request was made + pub network_id: NetworkID, + + /// The origin `Url` of the dApp from which the request was made + pub origin: Url, + + /// The dApp's definition address + pub dapp_definition_address: DappDefinitionAddress, + + /// The entities needed to be signed. + pub entities_to_sign: Vec, +} + +/// Since `AuthIntent` is also acting as a payload in `SignaturesCollector` when signing auth, +/// needs to conform to Hash too. For other `Signable`s like `TransactionIntent` or `Subintent` +/// there are specific compiled versions of them like `CompiledTransactionIntent` and +/// `CompiledSubintent` respectively, which conform to Hash. +impl std::hash::Hash for AuthIntent { + fn hash(&self, state: &mut H) { + self.into_internal().hash(state); + } +} + +impl AuthIntent { + pub fn into_internal(&self) -> InternalAuthIntent { + self.clone().into() + } +} + +impl From for AuthIntent { + fn from(value: InternalAuthIntent) -> Self { + Self { + challenge_nonce: value.challenge_nonce.into(), + network_id: value.network_id.into(), + origin: value.origin, + dapp_definition_address: value.dapp_definition_address.into(), + entities_to_sign: value + .entities_to_sign + .into_iter() + .map(Into::into) + .collect(), + } + } +} + +impl From for InternalAuthIntent { + fn from(value: AuthIntent) -> Self { + Self::new( + value.challenge_nonce.into(), + value.network_id.into(), + value.origin, + value.dapp_definition_address.into(), + value.entities_to_sign.into_iter().map(Into::into).collect(), + ) + } +} + +#[uniffi::export] +pub fn new_auth_intent_from_request( + challenge_nonce: DappToWalletInteractionAuthChallengeNonce, + metadata: DappToWalletInteractionMetadata, + entities_to_sign: Vec, +) -> Result { + InternalAuthIntent::new_from_request( + challenge_nonce.into(), + metadata.into(), + entities_to_sign.into_iter().map(|a| a.into_internal()), + ) + .into_result() +} + +#[uniffi::export] +pub fn auth_intent_get_hash(auth_intent: AuthIntent) -> AuthIntentHash { + auth_intent.into_internal().auth_intent_hash().into() +} + +#[uniffi::export] +pub fn new_auth_intent_sample() -> AuthIntent { + InternalAuthIntent::sample().into() +} + +#[uniffi::export] +pub fn new_auth_intent_sample_other() -> AuthIntent { + InternalAuthIntent::sample_other().into() +} diff --git a/crates/sargon-uniffi/src/signing/authentication/auth_intent_hash.rs b/crates/sargon-uniffi/src/signing/authentication/auth_intent_hash.rs new file mode 100644 index 000000000..b61b66bf0 --- /dev/null +++ b/crates/sargon-uniffi/src/signing/authentication/auth_intent_hash.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; +use sargon::AuthIntentHash as InternalAuthIntentHash; + +#[derive( + Clone, PartialEq, Eq, std::hash::Hash, InternalConversion, uniffi::Record, +)] +pub struct AuthIntentHash { + pub payload: BagOfBytes, +} + +#[uniffi::export] +pub fn new_auth_intent_hash_sample() -> AuthIntentHash { + InternalAuthIntentHash::sample().into() +} + +#[uniffi::export] +pub fn new_auth_intent_hash_sample_other() -> AuthIntentHash { + InternalAuthIntentHash::sample_other().into() +} diff --git a/crates/sargon-uniffi/src/signing/authentication/mod.rs b/crates/sargon-uniffi/src/signing/authentication/mod.rs new file mode 100644 index 000000000..bfefd3b6a --- /dev/null +++ b/crates/sargon-uniffi/src/signing/authentication/mod.rs @@ -0,0 +1,7 @@ +mod auth_intent; +mod auth_intent_hash; +mod signed_auth_intent; + +pub use auth_intent::*; +pub use auth_intent_hash::*; +pub use signed_auth_intent::*; diff --git a/crates/sargon-uniffi/src/signing/authentication/signed_auth_intent.rs b/crates/sargon-uniffi/src/signing/authentication/signed_auth_intent.rs new file mode 100644 index 000000000..0c3174de4 --- /dev/null +++ b/crates/sargon-uniffi/src/signing/authentication/signed_auth_intent.rs @@ -0,0 +1,65 @@ +use crate::prelude::*; +use sargon::AddressOfAccountOrPersona as InternalAddressOfAccountOrPersona; +use sargon::IntentSignature as InternalIntentSignature; +use sargon::SignedAuthIntent as InternalSignedAuthIntent; + +#[derive(Clone, PartialEq, Eq, uniffi::Record)] +pub struct SignedAuthIntent { + pub intent: AuthIntent, + pub intent_signatures_per_owner: Vec, +} + +#[uniffi::export] +pub fn new_signed_auth_intent_sample() -> SignedAuthIntent { + InternalSignedAuthIntent::sample().into() +} + +#[uniffi::export] +pub fn new_signed_auth_intent_sample_other() -> SignedAuthIntent { + InternalSignedAuthIntent::sample_other().into() +} + +impl SignedAuthIntent { + pub fn into_internal(&self) -> InternalSignedAuthIntent { + self.clone().into() + } +} + +impl From for SignedAuthIntent { + fn from(value: InternalSignedAuthIntent) -> Self { + SignedAuthIntent { + intent: value.intent.into(), + intent_signatures_per_owner: value + .intent_signatures_per_owner + .iter() + .map(|(owner, signature)| { + IntentSignatureOfOwner::new( + (*owner).into(), + (*signature).into(), + ) + }) + .collect(), + } + } +} + +impl From for InternalSignedAuthIntent { + fn from(value: SignedAuthIntent) -> Self { + Self { + intent: value.intent.into(), + intent_signatures_per_owner: value + .intent_signatures_per_owner + .into_iter() + .map(|item| { + ( + item.owner.into_internal(), + item.intent_signature.into_internal(), + ) + }) + .collect::>(), + } + } +} diff --git a/crates/sargon-uniffi/src/signing/authentication_signing_input.rs b/crates/sargon-uniffi/src/signing/authentication_signing_input.rs deleted file mode 100644 index fe9d5e1d3..000000000 --- a/crates/sargon-uniffi/src/signing/authentication_signing_input.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::prelude::*; -use sargon::AuthenticationSigningInput as InternalAuthenticationSigningInput; - -#[derive(Clone, PartialEq, InternalConversion, uniffi::Record)] -pub struct AuthenticationSigningInput { - /// The account or identity address of the entity which signs the rola challenge, - /// with expected public key and with derivation path to derive PrivateKey - /// with. - pub owned_factor_instance: OwnedFactorInstance, - - /// The challenge nonce that with some `metadata` values are generating the `RolaChallenge` - /// needed to be signed - pub challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - - /// The metadata that together with the `challenge_nonce` are generating the `RolaChallenge` - /// needed to be signed - pub metadata: DappToWalletInteractionMetadata, -} - -#[uniffi::export] -pub fn authentication_signing_input_get_rola_challenge( - input: &AuthenticationSigningInput, -) -> Result { - input.into_internal().rola_challenge().into_result() -} - -#[uniffi::export] -pub fn new_authentication_signing_input_sample() -> AuthenticationSigningInput { - InternalAuthenticationSigningInput::sample().into() -} - -#[uniffi::export] -pub fn new_authentication_signing_input_sample_other( -) -> AuthenticationSigningInput { - InternalAuthenticationSigningInput::sample_other().into() -} diff --git a/crates/sargon-uniffi/src/signing/authentication_signing_request.rs b/crates/sargon-uniffi/src/signing/authentication_signing_request.rs deleted file mode 100644 index 44d978d17..000000000 --- a/crates/sargon-uniffi/src/signing/authentication_signing_request.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::prelude::*; -use sargon::AuthenticationSigningRequest as InternalAuthenticationSigningRequest; - -#[derive(Clone, PartialEq, InternalConversion, uniffi::Record)] -pub struct AuthenticationSigningRequest { - pub input: AuthenticationSigningInput, -} diff --git a/crates/sargon-uniffi/src/signing/authentication_signing_response.rs b/crates/sargon-uniffi/src/signing/authentication_signing_response.rs deleted file mode 100644 index e5c62f2e3..000000000 --- a/crates/sargon-uniffi/src/signing/authentication_signing_response.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::prelude::*; -use sargon::AuthenticationSigningResponse as InternalAuthenticationSigningResponse; - -#[derive(Clone, PartialEq, InternalConversion, uniffi::Record)] -pub struct AuthenticationSigningResponse { - pub rola_challenge: RolaChallenge, - pub signature_with_public_key: SignatureWithPublicKey, -} - -#[uniffi::export] -pub fn new_authentication_signing_response( - rola_challenge: RolaChallenge, - signature_with_public_key: SignatureWithPublicKey, -) -> Result { - InternalAuthenticationSigningResponse::new( - rola_challenge.into(), - signature_with_public_key.into(), - ) - .into_result() -} - -#[uniffi::export] -pub fn new_authentication_signing_response_sample( -) -> AuthenticationSigningResponse { - InternalAuthenticationSigningResponse::sample().into() -} - -#[uniffi::export] -pub fn new_authentication_signing_response_sample_other( -) -> AuthenticationSigningResponse { - InternalAuthenticationSigningResponse::sample_other().into() -} diff --git a/crates/sargon-uniffi/src/signing/hd_signature.rs b/crates/sargon-uniffi/src/signing/hd_signature.rs index 238e8e5d3..4d3160670 100644 --- a/crates/sargon-uniffi/src/signing/hd_signature.rs +++ b/crates/sargon-uniffi/src/signing/hd_signature.rs @@ -37,3 +37,4 @@ macro_rules! decl_hd_signature { decl_hd_signature!(TransactionIntentHash); decl_hd_signature!(SubintentHash); +decl_hd_signature!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/hd_signature_input.rs b/crates/sargon-uniffi/src/signing/hd_signature_input.rs index dc1713b50..e7f3d8843 100644 --- a/crates/sargon-uniffi/src/signing/hd_signature_input.rs +++ b/crates/sargon-uniffi/src/signing/hd_signature_input.rs @@ -33,3 +33,4 @@ macro_rules! decl_hd_signature_input { decl_hd_signature_input!(TransactionIntentHash); decl_hd_signature_input!(SubintentHash); +decl_hd_signature_input!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/intent_signature_of_owner.rs b/crates/sargon-uniffi/src/signing/intent_signature_of_owner.rs new file mode 100644 index 000000000..0515fdfec --- /dev/null +++ b/crates/sargon-uniffi/src/signing/intent_signature_of_owner.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +#[derive(Clone, PartialEq, Eq, uniffi::Record)] +pub struct IntentSignatureOfOwner { + pub owner: AddressOfAccountOrPersona, + pub intent_signature: IntentSignature, +} + +impl IntentSignatureOfOwner { + pub fn new( + owner: AddressOfAccountOrPersona, + intent_signature: IntentSignature, + ) -> Self { + Self { + owner, + intent_signature, + } + } +} diff --git a/crates/sargon-uniffi/src/signing/invalid_transaction_if_neglected.rs b/crates/sargon-uniffi/src/signing/invalid_transaction_if_neglected.rs index 3fe3c8b50..3d5695023 100644 --- a/crates/sargon-uniffi/src/signing/invalid_transaction_if_neglected.rs +++ b/crates/sargon-uniffi/src/signing/invalid_transaction_if_neglected.rs @@ -39,3 +39,4 @@ macro_rules! decl_invalid_transaction_if_neglected { decl_invalid_transaction_if_neglected!(TransactionIntentHash); decl_invalid_transaction_if_neglected!(SubintentHash); +decl_invalid_transaction_if_neglected!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/mod.rs b/crates/sargon-uniffi/src/signing/mod.rs index 690786f0c..d4fbf93d1 100644 --- a/crates/sargon-uniffi/src/signing/mod.rs +++ b/crates/sargon-uniffi/src/signing/mod.rs @@ -1,11 +1,9 @@ -mod authentication_signing_input; -mod authentication_signing_request; -mod authentication_signing_response; +mod authentication; mod hd_signature; mod hd_signature_input; +mod intent_signature_of_owner; mod invalid_transaction_if_neglected; mod neglected_factors; -mod rola_challenge; mod sign_request; mod sign_response; mod sign_with_factors_outcome; @@ -13,14 +11,12 @@ mod signatures_per_fractor_source; mod transaction_sign_request_input; mod transactions_to_sign_per_factor_source; -pub use authentication_signing_input::*; -pub use authentication_signing_request::*; -pub use authentication_signing_response::*; +pub use authentication::*; pub use hd_signature::*; pub use hd_signature_input::*; +pub use intent_signature_of_owner::*; pub use invalid_transaction_if_neglected::*; pub use neglected_factors::*; -pub use rola_challenge::*; pub use sign_request::*; pub use sign_response::*; pub use sign_with_factors_outcome::*; diff --git a/crates/sargon-uniffi/src/signing/rola_challenge.rs b/crates/sargon-uniffi/src/signing/rola_challenge.rs deleted file mode 100644 index b49498df6..000000000 --- a/crates/sargon-uniffi/src/signing/rola_challenge.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::prelude::*; -use sargon::RolaChallenge as InternalRolaChallenge; - -#[derive(Debug, Clone, PartialEq, InternalConversion, uniffi::Record)] -pub struct RolaChallenge { - pub payload: BagOfBytes, -} - -#[uniffi::export] -pub fn rola_challenge_get_hash(rola_challenge: &RolaChallenge) -> Hash { - rola_challenge.into_internal().hash().into() -} - -#[uniffi::export] -pub fn new_rola_challenge_sample() -> RolaChallenge { - InternalRolaChallenge::sample().into() -} - -#[uniffi::export] -pub fn new_rola_challenge_sample_other() -> RolaChallenge { - InternalRolaChallenge::sample_other().into() -} diff --git a/crates/sargon-uniffi/src/signing/sign_request.rs b/crates/sargon-uniffi/src/signing/sign_request.rs index 078a96886..9f337733f 100644 --- a/crates/sargon-uniffi/src/signing/sign_request.rs +++ b/crates/sargon-uniffi/src/signing/sign_request.rs @@ -106,3 +106,7 @@ decl_sign_request!( signable: Subintent, signable_id: SubintentHash ); +decl_sign_request!( + signable: AuthIntent, + signable_id: AuthIntentHash +); diff --git a/crates/sargon-uniffi/src/signing/sign_response.rs b/crates/sargon-uniffi/src/signing/sign_response.rs index e629402f5..5e186c224 100644 --- a/crates/sargon-uniffi/src/signing/sign_response.rs +++ b/crates/sargon-uniffi/src/signing/sign_response.rs @@ -74,3 +74,4 @@ macro_rules! decl_sign_response { decl_sign_response!(TransactionIntentHash); decl_sign_response!(SubintentHash); +decl_sign_response!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/sign_with_factors_outcome.rs b/crates/sargon-uniffi/src/signing/sign_with_factors_outcome.rs index b8d2d0cb1..51c7ae346 100644 --- a/crates/sargon-uniffi/src/signing/sign_with_factors_outcome.rs +++ b/crates/sargon-uniffi/src/signing/sign_with_factors_outcome.rs @@ -37,3 +37,4 @@ macro_rules! decl_sign_with_factors_outcome { decl_sign_with_factors_outcome!(TransactionIntentHash); decl_sign_with_factors_outcome!(SubintentHash); +decl_sign_with_factors_outcome!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/signatures_per_fractor_source.rs b/crates/sargon-uniffi/src/signing/signatures_per_fractor_source.rs index 248b30ba9..9241ba332 100644 --- a/crates/sargon-uniffi/src/signing/signatures_per_fractor_source.rs +++ b/crates/sargon-uniffi/src/signing/signatures_per_fractor_source.rs @@ -39,3 +39,4 @@ macro_rules! decl_signatures_per_factor_source { decl_signatures_per_factor_source!(TransactionIntentHash); decl_signatures_per_factor_source!(SubintentHash); +decl_signatures_per_factor_source!(AuthIntentHash); diff --git a/crates/sargon-uniffi/src/signing/transaction_sign_request_input.rs b/crates/sargon-uniffi/src/signing/transaction_sign_request_input.rs index 9525372b2..9f717b9aa 100644 --- a/crates/sargon-uniffi/src/signing/transaction_sign_request_input.rs +++ b/crates/sargon-uniffi/src/signing/transaction_sign_request_input.rs @@ -38,6 +38,19 @@ macro_rules! decl_transaction_sign_request_input { ); } }; + (signable_and_payload: $signable:ty) => { + paste! { + use sargon::[< $signable >] as [< Internal $signable >]; + + type [< InternalTransactionSignRequestInputOf $signable >] = + sargon::TransactionSignRequestInput<[< Internal $signable >]>; + + decl_transaction_sign_request_input!( + struct_name: [< TransactionSignRequestInputOf $signable >], + payload: [< $signable >], + ); + } + }; } decl_transaction_sign_request_input!( @@ -48,3 +61,6 @@ decl_transaction_sign_request_input!( signable: Subintent, payload: CompiledSubintent ); +decl_transaction_sign_request_input!( + signable_and_payload: AuthIntent +); diff --git a/crates/sargon-uniffi/src/signing/transactions_to_sign_per_factor_source.rs b/crates/sargon-uniffi/src/signing/transactions_to_sign_per_factor_source.rs index 9bc573889..75ea6d535 100644 --- a/crates/sargon-uniffi/src/signing/transactions_to_sign_per_factor_source.rs +++ b/crates/sargon-uniffi/src/signing/transactions_to_sign_per_factor_source.rs @@ -65,3 +65,7 @@ decl_transaction_to_sign_per_factor_source!( signable: Subintent, signable_id: SubintentHash ); +decl_transaction_to_sign_per_factor_source!( + signable: AuthIntent, + signable_id: AuthIntentHash +); diff --git a/crates/sargon-uniffi/src/system/interactors/host_interactor.rs b/crates/sargon-uniffi/src/system/interactors/host_interactor.rs index 7f1432016..7389bef40 100644 --- a/crates/sargon-uniffi/src/system/interactors/host_interactor.rs +++ b/crates/sargon-uniffi/src/system/interactors/host_interactor.rs @@ -1,7 +1,6 @@ use crate::prelude::*; -use sargon::AuthenticationSigningInteractor as InternalAuthenticationSigningInteractor; -use sargon::AuthenticationSigningRequest as InternalAuthenticationSigningInteractorRequest; -use sargon::AuthenticationSigningResponse as InternalAuthenticationSigningResponse; +use sargon::AuthIntent as InternalAuthIntent; +use sargon::AuthIntentHash as InternalAuthIntentHash; use sargon::KeyDerivationInteractor as InternalKeyDerivationInteractor; use sargon::KeyDerivationRequest as InternalKeyDerivationRequest; use sargon::KeyDerivationResponse as InternalKeyDerivationResponse; @@ -20,6 +19,9 @@ type InternalSignWithFactorsOutcomeForTransactionIntent = type InternalSignRequestForSubintent = sargon::SignRequest; type InternalSignWithFactorsOutcomeForSubintent = sargon::SignWithFactorsOutcome; +type InternalSignRequestForAuthIntent = sargon::SignRequest; +type InternalSignWithFactorsOutcomeForAuthIntent = + sargon::SignWithFactorsOutcome; /// Sargon os #[uniffi::export(with_foreign)] @@ -42,8 +44,8 @@ pub trait HostInteractor: Send + Sync + std::fmt::Debug { async fn sign_auth( &self, - request: AuthenticationSigningRequest, - ) -> Result; + request: SignRequestOfAuthIntent, + ) -> Result; } #[derive(Debug)] @@ -104,13 +106,13 @@ impl InternalKeyDerivationInteractor for UseFactorSourcesInteractorAdapter { } #[async_trait::async_trait] -impl InternalAuthenticationSigningInteractor +impl InternalSignInteractor for UseFactorSourcesInteractorAdapter { async fn sign( &self, - request: InternalAuthenticationSigningInteractorRequest, - ) -> InternalResult { + request: InternalSignRequestForAuthIntent, + ) -> InternalResult { self.wrapped .sign_auth(request.into()) .await diff --git a/crates/sargon-uniffi/src/system/sargon_os/sargon_os_signing.rs b/crates/sargon-uniffi/src/system/sargon_os/sargon_os_signing.rs index 371582c81..80e52697a 100644 --- a/crates/sargon-uniffi/src/system/sargon_os/sargon_os_signing.rs +++ b/crates/sargon-uniffi/src/system/sargon_os/sargon_os_signing.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use sargon::AuthIntent as InternalAuthIntent; use sargon::Subintent as InternalSubintent; use sargon::TransactionIntent as InternalTransactionIntent; @@ -26,19 +27,36 @@ impl SargonOS { .into_result() } - pub async fn sign_auth( + pub async fn sign_auth_accounts( &self, - address_of_entity: AddressOfAccountOrPersona, + account_addresses: Vec, challenge_nonce: DappToWalletInteractionAuthChallengeNonce, metadata: DappToWalletInteractionMetadata, - ) -> Result { - self.wrapped - .sign_auth( - address_of_entity.into(), - challenge_nonce.into(), - metadata.into(), - ) - .await - .into_result() + ) -> Result { + let auth_intent = InternalAuthIntent::new_from_request( + challenge_nonce.into(), + metadata.into(), + account_addresses + .into_iter() + .map(|a| AddressOfAccountOrPersona::Account(a).into()) + .collect_vec(), + )?; + + self.wrapped.sign_auth(auth_intent).await.into_result() + } + + pub async fn sign_auth_persona( + &self, + identity_address: IdentityAddress, + challenge_nonce: DappToWalletInteractionAuthChallengeNonce, + metadata: DappToWalletInteractionMetadata, + ) -> Result { + let auth_intent = InternalAuthIntent::new_from_request( + challenge_nonce.into(), + metadata.into(), + vec![AddressOfAccountOrPersona::Identity(identity_address).into()], + )?; + + self.wrapped.sign_auth(auth_intent).await.into_result() } } diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index f69838049..e3d95d302 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.91" +version = "1.1.92" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/radix_connect/interaction_version.rs b/crates/sargon/src/radix_connect/interaction_version.rs index 709940545..a047e7ab5 100644 --- a/crates/sargon/src/radix_connect/interaction_version.rs +++ b/crates/sargon/src/radix_connect/interaction_version.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct WalletInteractionVersion(pub u64); impl From for WalletInteractionVersion { diff --git a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs index 7a9d5a4f5..b8b528fed 100644 --- a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs +++ b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/dapp_to_wallet/dapp_metadata/interaction_metadata.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct DappToWalletInteractionMetadata { pub version: WalletInteractionVersion, @@ -37,7 +37,7 @@ impl HasSampleValues for DappToWalletInteractionMetadata { fn sample() -> Self { Self::new( WalletInteractionVersion::sample(), - NetworkID::Stokenet, + NetworkID::Mainnet, "https://example.com", DappDefinitionAddress::sample(), ) @@ -46,7 +46,7 @@ impl HasSampleValues for DappToWalletInteractionMetadata { fn sample_other() -> Self { Self::new( WalletInteractionVersion::sample_other(), - NetworkID::Stokenet, + NetworkID::Mainnet, "https://example.org", DappDefinitionAddress::sample_other(), ) diff --git a/crates/sargon/src/signing/authentication/auth_intent.rs b/crates/sargon/src/signing/authentication/auth_intent.rs new file mode 100644 index 000000000..4391f565f --- /dev/null +++ b/crates/sargon/src/signing/authentication/auth_intent.rs @@ -0,0 +1,196 @@ +use crate::prelude::*; +use std::hash::Hasher; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthIntent { + /// The challenge nonce that with some `metadata` values are generating the `RolaChallenge` + /// needed to be signed + pub challenge_nonce: Exactly32Bytes, + + /// The `NetworkID` on which the request was made + pub network_id: NetworkID, + + /// The origin `Url` of the dApp from which the request was made + pub origin: Url, + + /// The dApp's definition address + pub dapp_definition_address: DappDefinitionAddress, + + /// The entities needed to be signed. + pub entities_to_sign: IndexSet, +} + +impl AuthIntent { + /// Creates an `AuthIntent` from the request received from a dApp. + /// + /// Fails if the `OriginUrl` in metadata is not a valid url or the network id supplied does not + /// match the network id of the given dApp definition address or the entities to sign. + pub fn new_from_request( + challenge_nonce: DappToWalletInteractionAuthChallengeNonce, + metadata: DappToWalletInteractionMetadata, + entities_to_sign: impl IntoIterator, + ) -> Result { + let origin = TryInto::::try_into(metadata.origin.clone())?; + + if metadata.network_id != metadata.dapp_definition_address.network_id() + { + return Err(CommonError::NetworkDiscrepancy { + expected: metadata.network_id, + actual: metadata.dapp_definition_address.network_id(), + }); + } + + let entities = entities_to_sign.into_iter().collect::>(); + for entity in &entities { + if entity.network_id() != metadata.network_id { + return Err(CommonError::NetworkDiscrepancy { + expected: metadata.network_id, + actual: entity.network_id(), + }); + } + } + + Ok(Self::new( + challenge_nonce.0, + metadata.network_id, + origin, + metadata.dapp_definition_address, + entities, + )) + } + + pub fn new( + challenge_nonce: Exactly32Bytes, + network_id: NetworkID, + origin: Url, + dapp_definition_address: DappDefinitionAddress, + entities_to_sign: IndexSet, + ) -> Self { + Self { + challenge_nonce, + network_id, + origin, + dapp_definition_address, + entities_to_sign, + } + } + + pub fn auth_intent_hash(&self) -> AuthIntentHash { + From::::from(self.clone()) + } +} + +impl std::hash::Hash for AuthIntent { + fn hash(&self, state: &mut H) { + state.write(self.auth_intent_hash().payload.bytes()) + } +} + +impl HasSampleValues for AuthIntent { + fn sample() -> Self { + Self::new_from_request( + DappToWalletInteractionAuthChallengeNonce::sample(), + DappToWalletInteractionMetadata::sample(), + vec![AddressOfAccountOrPersona::sample()], + ) + .unwrap() + } + + fn sample_other() -> Self { + Self::new_from_request( + DappToWalletInteractionAuthChallengeNonce::sample_other(), + DappToWalletInteractionMetadata::sample_other(), + vec![AddressOfAccountOrPersona::sample_other()], + ) + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AuthIntent; + + #[test] + fn test_new_from_valid_request() { + let challenge_nonce = + DappToWalletInteractionAuthChallengeNonce::sample(); + let metadata = DappToWalletInteractionMetadata::sample(); + + assert!(SUT::new_from_request( + challenge_nonce, + metadata, + vec![AddressOfAccountOrPersona::sample()] + ) + .is_ok()) + } + + #[test] + fn test_new_from_invalid_request_due_to_invalid_url() { + let challenge_nonce = + DappToWalletInteractionAuthChallengeNonce::sample(); + let metadata = DappToWalletInteractionMetadata::new( + WalletInteractionVersion::current(), + NetworkID::Mainnet, + DappOrigin("".to_string()), + DappDefinitionAddress::sample_mainnet(), + ); + + assert_eq!( + SUT::new_from_request( + challenge_nonce, + metadata, + vec![AddressOfAccountOrPersona::sample()] + ), + Err(CommonError::InvalidURL { + bad_value: "".to_string() + }) + ) + } + + #[test] + fn test_new_from_invalid_request_due_to_network_discrepancy_in_dapp_definition( + ) { + let challenge_nonce = + DappToWalletInteractionAuthChallengeNonce::sample(); + let metadata = DappToWalletInteractionMetadata::new( + WalletInteractionVersion::current(), + NetworkID::Mainnet, + DappOrigin::sample(), + DappDefinitionAddress::sample_stokenet(), + ); + + assert_eq!( + SUT::new_from_request( + challenge_nonce, + metadata, + vec![AddressOfAccountOrPersona::sample()] + ), + Err(CommonError::NetworkDiscrepancy { + expected: NetworkID::Mainnet, + actual: NetworkID::Stokenet, + }) + ) + } + + #[test] + fn test_new_from_invalid_request_due_to_network_discrepancy_in_entities() { + let challenge_nonce = + DappToWalletInteractionAuthChallengeNonce::sample(); + let metadata = DappToWalletInteractionMetadata::sample(); + + assert_eq!( + SUT::new_from_request( + challenge_nonce, + metadata, + vec![AddressOfAccountOrPersona::sample_stokenet()] + ), + Err(CommonError::NetworkDiscrepancy { + expected: NetworkID::Mainnet, + actual: NetworkID::Stokenet, + }) + ) + } +} diff --git a/crates/sargon/src/signing/authentication/rola_challenge.rs b/crates/sargon/src/signing/authentication/auth_intent_hash.rs similarity index 60% rename from crates/sargon/src/signing/authentication/rola_challenge.rs rename to crates/sargon/src/signing/authentication/auth_intent_hash.rs index 18545bc53..f6f4dd041 100644 --- a/crates/sargon/src/signing/authentication/rola_challenge.rs +++ b/crates/sargon/src/signing/authentication/auth_intent_hash.rs @@ -2,37 +2,37 @@ use crate::prelude::*; const ROLA_PREFIX: u8 = 0x52; -#[derive(Debug, Clone, PartialEq, derive_more::Display)] +#[derive( + Debug, Clone, PartialEq, Eq, derive_more::Display, std::hash::Hash, +)] #[display("{}", self.payload.to_hex())] -pub struct RolaChallenge { +pub struct AuthIntentHash { pub payload: BagOfBytes, } -impl RolaChallenge { +impl AuthIntentHash { pub fn hash(&self) -> Hash { hash_of(self.payload.clone()) } } -impl HasSampleValues for RolaChallenge { +impl From for Hash { + fn from(val: AuthIntentHash) -> Self { + val.hash() + } +} + +impl HasSampleValues for AuthIntentHash { fn sample() -> Self { - Self::from_request( - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap() + From::::from(AuthIntent::sample()) } fn sample_other() -> Self { - Self::from_request( - DappToWalletInteractionAuthChallengeNonce::sample_other(), - DappToWalletInteractionMetadata::sample_other(), - ) - .unwrap() + From::::from(AuthIntent::sample_other()) } } -impl RolaChallenge { +impl From for AuthIntentHash { /// Constructs a payload to sign in conjunction with the `challenge_nonce` received and /// the `metadata` of the dApp that sent the request. /// @@ -42,28 +42,22 @@ impl RolaChallenge { /// * Pushes 1 byte which is the length of the bech32-encoded dapp-definition address /// * Extends with the bytes of the bech32-encoded dapp-definition address /// * Extends with the bytes of the origin UTF-8 encoded. - /// - /// Fails if the `origin` Url is not a valid url - pub fn from_request( - challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - metadata: DappToWalletInteractionMetadata, - ) -> Result { - TryInto::::try_into(metadata.origin.clone())?; - let mut origin_str = metadata.origin.0; + fn from(value: AuthIntent) -> Self { + let mut origin_str = value.origin.to_string(); if origin_str.ends_with("/") { origin_str.truncate(origin_str.len() - 1); } let mut payload = Vec::::new(); payload.push(ROLA_PREFIX); - payload.extend(challenge_nonce.0.bytes()); - payload.push(metadata.dapp_definition_address.address().len() as u8); - payload.extend(metadata.dapp_definition_address.address().bytes()); + payload.extend(value.challenge_nonce.bytes()); + payload.push(value.dapp_definition_address.address().len() as u8); + payload.extend(value.dapp_definition_address.address().bytes()); payload.extend(origin_str.as_bytes()); - Ok(RolaChallenge { + Self { payload: BagOfBytes::from(payload), - }) + } } } @@ -71,9 +65,12 @@ impl RolaChallenge { mod tests { use super::*; + #[allow(clippy::upper_case_acronyms)] + type SUT = AuthIntentHash; + #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] - struct RolaChallengeVectorItem { + struct AuthIntentHashVectorItem { pub payload_to_hash: BagOfBytes, #[serde(deserialize_with = "deserialize_hash")] pub blake_hash_of_payload: Hash, @@ -91,19 +88,25 @@ mod tests { Hash::from_str(&buf).map_err(de::Error::custom) } - impl TryInto for RolaChallengeVectorItem { + impl TryInto for AuthIntentHashVectorItem { type Error = CommonError; - fn try_into(self) -> std::result::Result { - RolaChallenge::from_request( + fn try_into(self) -> std::result::Result { + let network_id = self.d_app_definition_address.network_id(); + let intent = AuthIntent::new_from_request( DappToWalletInteractionAuthChallengeNonce(self.challenge), DappToWalletInteractionMetadata::new( WalletInteractionVersion::current(), - self.d_app_definition_address.network_id(), + network_id, self.origin, self.d_app_definition_address, ), - ) + vec![AddressOfAccountOrPersona::Account( + AccountAddress::random(network_id), + )], + )?; + + Ok(From::::from(intent)) } } @@ -114,10 +117,11 @@ mod tests { "rola_challenge_payload_hash_vectors.json" )); let vector = - serde_json::from_str::>(json).unwrap(); + serde_json::from_str::>(json) + .unwrap(); vector.iter().for_each(|v| { - let rola_challenge: RolaChallenge = v.clone().try_into().unwrap(); + let rola_challenge: SUT = v.clone().try_into().unwrap(); assert_eq!(rola_challenge.payload, v.payload_to_hash); @@ -128,11 +132,9 @@ mod tests { #[test] fn test_valid_url() { let nonce = Exactly32Bytes::sample(); - let challenge = RolaChallenge::from_request( - DappToWalletInteractionAuthChallengeNonce(nonce), - metadata("https://stokenet-dashboard.radixdlt.com"), - ) - .unwrap(); + let challenge = + sut(nonce, metadata("https://stokenet-dashboard.radixdlt.com")) + .unwrap(); let expected_payload_hex = "52deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead\ dead426163636f756e745f72647831323879366a37386d74306171763633373265767a323868727870386d6e30\ @@ -141,11 +143,9 @@ mod tests { assert_eq!(expected_payload_hex, challenge.payload.to_hex()); - let challenge_with_origin_with_slash = RolaChallenge::from_request( - DappToWalletInteractionAuthChallengeNonce(nonce), - metadata("https://stokenet-dashboard.radixdlt.com/"), - ) - .unwrap(); + let challenge_with_origin_with_slash = + sut(nonce, metadata("https://stokenet-dashboard.radixdlt.com/")) + .unwrap(); assert_eq!( expected_payload_hex, @@ -156,10 +156,7 @@ mod tests { #[test] fn test_invalid_url() { let nonce = Exactly32Bytes::sample(); - let challenge = RolaChallenge::from_request( - DappToWalletInteractionAuthChallengeNonce(nonce), - metadata("/"), - ); + let challenge = sut(nonce, metadata("/")); assert_eq!( challenge, @@ -169,6 +166,32 @@ mod tests { ); } + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + fn sut( + nonce: Exactly32Bytes, + metadata: DappToWalletInteractionMetadata, + ) -> Result { + let intent = AuthIntent::new_from_request( + DappToWalletInteractionAuthChallengeNonce(nonce), + metadata.clone(), + [AddressOfAccountOrPersona::Account(AccountAddress::random( + metadata.network_id, + ))], + )?; + + Ok(From::::from(intent)) + } + fn metadata(url: impl Into) -> DappToWalletInteractionMetadata { DappToWalletInteractionMetadata::new( WalletInteractionVersion(1), diff --git a/crates/sargon/src/signing/authentication/authentication_signer.rs b/crates/sargon/src/signing/authentication/authentication_signer.rs deleted file mode 100644 index d623be2e7..000000000 --- a/crates/sargon/src/signing/authentication/authentication_signer.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::prelude::*; - -pub struct AuthenticationSigner { - input: AuthenticationSigningInput, - interactor: Arc, -} - -impl AuthenticationSigner { - pub fn new( - interactor: Arc, - profile: &Profile, - address_of_entity: AddressOfAccountOrPersona, - challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - metadata: DappToWalletInteractionMetadata, - ) -> Result { - let input = AuthenticationSigningInput::try_from_profile( - profile, - address_of_entity, - challenge_nonce, - metadata, - )?; - - Ok(Self { input, interactor }) - } - - pub async fn sign(self) -> Result { - self.interactor - .sign(self.input.clone().into()) - .await - .map(WalletToDappInteractionAuthProof::from) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = AuthenticationSigner; - - #[actix_rt::test] - async fn test_with_account_in_profile() { - let factor_sources = FactorSource::sample_all(); - - let factor_instance = - HierarchicalDeterministicFactorInstance::sample_fia0(); - let account = Account::sample_unsecurified_mainnet( - "Unsecurified", - factor_instance.clone(), - ); - let sut = SUT::new( - Arc::new(TestAuthenticationInteractor::new_succeeding()), - &Profile::sample_from(factor_sources, [&account], []), - AddressOfAccountOrPersona::from(account.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - let result = sut.sign().await.unwrap(); - - assert_eq!( - result, - WalletToDappInteractionAuthProof::new( - factor_instance.clone().public_key.public_key, - Signature::try_from( - BagOfBytes::from_hex( - "eaa9a0eb1c9061e1999afa309db7bc9eecd30ad008f09cbfb2c4cf202759e914f6dbe01f67a7b8b1f1149f02b0e2662982fff4c1a765ee0d4d77651f1b91100c" - ).unwrap() - ).unwrap() - ) - ); - } - - #[actix_rt::test] - async fn test_with_persona_in_profile() { - let factor_sources = FactorSource::sample_all(); - - let factor_instance = - HierarchicalDeterministicFactorInstance::sample_fii0(); - let persona = Persona::sample_unsecurified_mainnet( - "Alice", - factor_instance.clone(), - ); - let sut = SUT::new( - Arc::new(TestAuthenticationInteractor::new_succeeding()), - &Profile::sample_from(factor_sources, [], [&persona]), - AddressOfAccountOrPersona::from(persona.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - let result = sut.sign().await.unwrap(); - - assert_eq!( - result, - WalletToDappInteractionAuthProof::new( - factor_instance.clone().public_key.public_key, - Signature::try_from( - BagOfBytes::from_hex( - "fb4f502e6a8bdbe3e66d365fe619270bafb9237e79a3c68dcf34448293f00840a0e5d48d1060eea49bf219cd3727c15cd6e305871956bc8d8ed8bcdc7fe97909" - ).unwrap() - ).unwrap() - ) - ); - } - - #[actix_rt::test] - async fn test_with_failing_interactor() { - let factor_sources = FactorSource::sample_all(); - - let factor_instance = - HierarchicalDeterministicFactorInstance::sample_fii0(); - let persona = Persona::sample_unsecurified_mainnet( - "Alice", - factor_instance.clone(), - ); - let sut = SUT::new( - Arc::new(TestAuthenticationInteractor::new_failing()), - &Profile::sample_from(factor_sources, [], [&persona]), - AddressOfAccountOrPersona::from(persona.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - let result = sut.sign().await; - - assert!(result.is_err()); - } - - #[actix_rt::test] - async fn securified_account() { - let factor_sources = FactorSource::sample_all(); - - let securified_account = Account::sample_at(2); - let factor_instance = securified_account - .security_state() - .as_securified() - .unwrap() - .authentication_signing_factor_instance(); - let sut = SUT::new( - Arc::new(TestAuthenticationInteractor::new_succeeding()), - &Profile::sample_from(factor_sources, [&securified_account], []), - AddressOfAccountOrPersona::from(securified_account.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - let result = sut.sign().await.unwrap(); - - pretty_assertions::assert_eq!( - result, - WalletToDappInteractionAuthProof::new( - factor_instance.clone().public_key.public_key, - "be359ca8edf8dccb2155e841952e88591fe42903da7e9a1560e863fe2fea94ed412d9eed282d201d48e5602fcde9a1dc201f440e86cb5f919b493bafc0ba2002".parse::().unwrap() - ) - ); - } - - #[actix_rt::test] - async fn securified_account_with_failing_interactor() { - let factor_sources = FactorSource::sample_all(); - - let securified_account = Account::sample_at(2); - let sut = SUT::new( - Arc::new(TestAuthenticationInteractor::new_failing()), - &Profile::sample_from(factor_sources, [&securified_account], []), - AddressOfAccountOrPersona::from(securified_account.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - let res = sut.sign().await; - assert!(res.is_err()); - } -} diff --git a/crates/sargon/src/signing/authentication/authentication_signing_input.rs b/crates/sargon/src/signing/authentication/authentication_signing_input.rs deleted file mode 100644 index 2596c1ce2..000000000 --- a/crates/sargon/src/signing/authentication/authentication_signing_input.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::prelude::*; - -#[derive(Debug, Clone, PartialEq)] -pub struct AuthenticationSigningInput { - /// The account or identity address of the entity which signs the rola challenge, - /// with expected public key and with derivation path to derive PrivateKey - /// with. - pub owned_factor_instance: OwnedFactorInstance, - - /// The challenge nonce that with some `metadata` values are generating the `RolaChallenge` - /// needed to be signed - pub challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - - /// The metadata that together with the `challenge_nonce` are generating the `RolaChallenge` - /// needed to be signed - pub metadata: DappToWalletInteractionMetadata, -} - -impl AuthenticationSigningInput { - pub fn new( - owned_factor_instance: OwnedFactorInstance, - challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - metadata: DappToWalletInteractionMetadata, - ) -> Self { - Self { - owned_factor_instance, - challenge_nonce, - metadata, - } - } - - pub fn try_from_profile( - profile: &Profile, - address_of_entity: AddressOfAccountOrPersona, - challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - metadata: DappToWalletInteractionMetadata, - ) -> Result { - // First check the validity of the challenge - let _ = RolaChallenge::from_request( - challenge_nonce.clone(), - metadata.clone(), - )?; - - let security_state = match address_of_entity { - AddressOfAccountOrPersona::Account(account_address) => profile - .account_by_address(account_address) - .map(|a| a.security_state), - AddressOfAccountOrPersona::Identity(identity_address) => profile - .persona_by_address(identity_address) - .map(|a| a.security_state), - }?; - - let factor_instance = match security_state { - EntitySecurityState::Unsecured { value } => { - value.transaction_signing - } - EntitySecurityState::Securified { value } => { - value.authentication_signing_factor_instance() - } - }; - - let owned_factor_instance = - OwnedFactorInstance::new(address_of_entity, factor_instance); - - Ok(Self::new(owned_factor_instance, challenge_nonce, metadata)) - } - - pub fn rola_challenge(&self) -> Result { - RolaChallenge::from_request( - self.challenge_nonce.clone(), - self.metadata.clone(), - ) - } -} - -impl HasSampleValues for AuthenticationSigningInput { - fn sample() -> Self { - Self::new( - OwnedFactorInstance::sample(), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - } - - fn sample_other() -> Self { - Self::new( - OwnedFactorInstance::sample_other(), - DappToWalletInteractionAuthChallengeNonce::sample_other(), - DappToWalletInteractionMetadata::sample_other(), - ) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = AuthenticationSigningInput; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn test_with_usecurified_account() { - let expected_factor_instance = - HierarchicalDeterministicFactorInstance::sample_fia0(); - let unsecurified_account = Account::sample_unsecurified_mainnet( - "Unsecurified", - expected_factor_instance.clone(), - ); - - let factor_sources = FactorSource::sample_all(); - let profile = - Profile::sample_from(factor_sources, [&unsecurified_account], []); - - let sut = SUT::try_from_profile( - &profile, - AddressOfAccountOrPersona::from(unsecurified_account.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - assert_eq!( - sut.owned_factor_instance.owner, - AddressOfAccountOrPersona::from(unsecurified_account.address) - ); - assert_eq!(sut.owned_factor_instance.value, expected_factor_instance); - - assert_eq!( - "6fc75ec1d5c00941dc587c0a07409da1740c423c337c323ba7bdf68d61d4dd8e" - .parse::() - .unwrap(), - sut.rola_challenge().unwrap().hash() - ) - } - - #[test] - fn test_with_usecurified_persona() { - let expected_factor_instance = - HierarchicalDeterministicFactorInstance::sample_fii0(); - let unsecurified_persona = Persona::sample_unsecurified_mainnet( - "Alice", - expected_factor_instance.clone(), - ); - - let factor_sources = FactorSource::sample_all(); - let profile = - Profile::sample_from(factor_sources, [], [&unsecurified_persona]); - - let sut = SUT::try_from_profile( - &profile, - AddressOfAccountOrPersona::from(unsecurified_persona.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ) - .unwrap(); - - assert_eq!( - sut.owned_factor_instance.owner, - AddressOfAccountOrPersona::from(unsecurified_persona.address) - ); - assert_eq!(sut.owned_factor_instance.value, expected_factor_instance); - - assert_eq!( - "6fc75ec1d5c00941dc587c0a07409da1740c423c337c323ba7bdf68d61d4dd8e" - .parse::() - .unwrap(), - sut.rola_challenge().unwrap().hash() - ) - } - - #[test] - fn test_with_unknown_account_address() { - let factor_sources = FactorSource::sample_all(); - let profile = Profile::sample_from(factor_sources, [], []); - - let sut = SUT::try_from_profile( - &profile, - AddressOfAccountOrPersona::sample_account_mainnet(), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ); - - assert_eq!(sut, Err(CommonError::UnknownAccount)) - } - - #[test] - fn test_with_unknown_identity_address() { - let factor_sources = FactorSource::sample_all(); - let profile = Profile::sample_from(factor_sources, [], []); - - let sut = SUT::try_from_profile( - &profile, - AddressOfAccountOrPersona::sample_identity_mainnet(), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::sample(), - ); - - assert_eq!(sut, Err(CommonError::UnknownPersona)) - } - - #[test] - fn test_with_invalid_url() { - let expected_factor_instance = - HierarchicalDeterministicFactorInstance::sample_fii0(); - let unsecurified_persona = Persona::sample_unsecurified_mainnet( - "Alice", - expected_factor_instance.clone(), - ); - - let factor_sources = FactorSource::sample_all(); - let profile = - Profile::sample_from(factor_sources, [], [&unsecurified_persona]); - - let sut = SUT::try_from_profile( - &profile, - AddressOfAccountOrPersona::from(unsecurified_persona.address), - DappToWalletInteractionAuthChallengeNonce::sample(), - DappToWalletInteractionMetadata::new( - WalletInteractionVersion::current(), - NetworkID::Mainnet, - DappOrigin("/".to_string()), - AccountAddress::sample_mainnet(), - ), - ); - - assert_eq!( - sut, - Err(CommonError::InvalidURL { - bad_value: "/".to_string() - }) - ) - } -} diff --git a/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs b/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs deleted file mode 100644 index 4f90758fe..000000000 --- a/crates/sargon/src/signing/authentication/authentication_signing_interactor.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::prelude::*; - -#[async_trait::async_trait] -pub trait AuthenticationSigningInteractor: Send + Sync { - async fn sign( - &self, - request: AuthenticationSigningRequest, - ) -> Result; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct AuthenticationSigningRequest { - pub input: AuthenticationSigningInput, -} - -impl AuthenticationSigningRequest { - pub fn new(input: AuthenticationSigningInput) -> Self { - Self { input } - } -} - -impl From for AuthenticationSigningRequest { - fn from(value: AuthenticationSigningInput) -> Self { - Self::new(value) - } -} - -impl HasSampleValues for AuthenticationSigningRequest { - fn sample() -> Self { - Self::new(AuthenticationSigningInput::sample()) - } - - fn sample_other() -> Self { - Self::new(AuthenticationSigningInput::sample_other()) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct AuthenticationSigningResponse { - pub rola_challenge: RolaChallenge, - pub signature_with_public_key: SignatureWithPublicKey, -} - -impl AuthenticationSigningResponse { - pub fn new( - rola_challenge: RolaChallenge, - signature_with_public_key: SignatureWithPublicKey, - ) -> Result { - if !signature_with_public_key.is_valid_for_hash(&rola_challenge.hash()) - { - Err(CommonError::InvalidSignatureForRolaChallenge) - } else { - Ok(Self { - rola_challenge, - signature_with_public_key, - }) - } - } -} - -impl HasSampleValues for AuthenticationSigningResponse { - fn sample() -> Self { - let rola_challenge = RolaChallenge::sample(); - let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); - - let signature = mnemonic_with_passphrase - .sign(&rola_challenge.hash(), &DerivationPath::sample()); - - Self::new(rola_challenge, signature).unwrap() - } - - fn sample_other() -> Self { - let rola_challenge = RolaChallenge::sample_other(); - let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); - - let signature = mnemonic_with_passphrase - .sign(&rola_challenge.hash(), &DerivationPath::sample()); - - Self::new(rola_challenge, signature).unwrap() - } -} - -impl From for WalletToDappInteractionAuthProof { - fn from(value: AuthenticationSigningResponse) -> Self { - let signature_with_public_key = value.signature_with_public_key; - - let public_key = signature_with_public_key.public_key(); - Self::new(public_key, signature_with_public_key.signature()) - } -} - -#[cfg(test)] -mod test_auth_sign_request { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = AuthenticationSigningRequest; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} - -#[cfg(test)] -mod test_auth_sign_response { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = AuthenticationSigningResponse; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn test_invalid_signature_for_rola_challenge() { - let challenge = RolaChallenge::sample(); - let invalid_signature_with_public_key = - SignatureWithPublicKey::sample(); - - assert_eq!( - SUT::new(challenge, invalid_signature_with_public_key), - Err(CommonError::InvalidSignatureForRolaChallenge) - ) - } -} diff --git a/crates/sargon/src/signing/authentication/mod.rs b/crates/sargon/src/signing/authentication/mod.rs index 3f04d6008..bfefd3b6a 100644 --- a/crates/sargon/src/signing/authentication/mod.rs +++ b/crates/sargon/src/signing/authentication/mod.rs @@ -1,9 +1,7 @@ -mod authentication_signer; -mod authentication_signing_input; -mod authentication_signing_interactor; -mod rola_challenge; +mod auth_intent; +mod auth_intent_hash; +mod signed_auth_intent; -pub use authentication_signer::*; -pub use authentication_signing_input::*; -pub use authentication_signing_interactor::*; -pub use rola_challenge::*; +pub use auth_intent::*; +pub use auth_intent_hash::*; +pub use signed_auth_intent::*; diff --git a/crates/sargon/src/signing/authentication/signed_auth_intent.rs b/crates/sargon/src/signing/authentication/signed_auth_intent.rs new file mode 100644 index 000000000..8420a1155 --- /dev/null +++ b/crates/sargon/src/signing/authentication/signed_auth_intent.rs @@ -0,0 +1,125 @@ +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignedAuthIntent { + pub intent: AuthIntent, + pub intent_signatures_per_owner: + IndexMap, +} + +impl SignedAuthIntent { + pub fn new( + intent: AuthIntent, + intent_signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, + ) -> Result { + let all_intent_signatures = IntentSignatures::new( + intent_signatures_per_owner.values().cloned().collect_vec(), + ); + if !all_intent_signatures.validate(intent.auth_intent_hash()) { + return Err(CommonError::InvalidSignaturesForIntentSomeDidNotValidateIntentHash); + } + + Ok(Self { + intent, + intent_signatures_per_owner, + }) + } + + pub fn intent(&self) -> &AuthIntent { + &self.intent + } +} + +impl HasSampleValues for SignedAuthIntent { + fn sample() -> Self { + let intent = AuthIntent::sample(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + + let signature = mnemonic_with_passphrase + .sign(&intent.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + + SignedAuthIntent::new(intent, intent_signatures).unwrap() + } + + fn sample_other() -> Self { + let intent = AuthIntent::sample_other(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + + let signature = mnemonic_with_passphrase + .sign(&intent.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + + SignedAuthIntent::new(intent, intent_signatures).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SignedAuthIntent; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn test_valid_signatures() { + let intent = AuthIntent::sample(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + + let signature = mnemonic_with_passphrase + .sign(&intent.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + assert!(SignedAuthIntent::new(intent, intent_signatures).is_ok()) + } + + #[test] + fn test_invalid_signatures() { + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature::sample() + ); + assert_eq!( + SUT::new(AuthIntent::sample(), intent_signatures), + Err(CommonError::InvalidSignaturesForIntentSomeDidNotValidateIntentHash) + ) + } + + #[test] + fn test_get_intent() { + let intent = AuthIntent::sample(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + + let signature = mnemonic_with_passphrase + .sign(&intent.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + + assert_eq!( + SUT::new(intent.clone(), intent_signatures) + .unwrap() + .intent() + .clone(), + intent + ) + } +} diff --git a/crates/sargon/src/signing/collector/extractor_of_instances_required_to_sign_transactions.rs b/crates/sargon/src/signing/collector/extractor_of_instances_required_to_sign_transactions.rs index e2b65c9bc..67da0be14 100644 --- a/crates/sargon/src/signing/collector/extractor_of_instances_required_to_sign_transactions.rs +++ b/crates/sargon/src/signing/collector/extractor_of_instances_required_to_sign_transactions.rs @@ -10,7 +10,7 @@ impl ExtractorOfInstancesRequiredToSignTransactions { pub fn extract( profile: &Profile, transactions: Vec, - for_any_securified_entity_select_role: RoleKind, + signing_purpose: SigningPurpose, ) -> Result> { let preprocessor = SignaturesCollectorPreprocessor::analyzing_signables( @@ -19,7 +19,7 @@ impl ExtractorOfInstancesRequiredToSignTransactions { )?; let (petitions, _) = preprocessor.preprocess( IndexSet::from_iter(profile.factor_sources.iter()), - for_any_securified_entity_select_role, + signing_purpose, ); let factor_instances = petitions @@ -56,7 +56,7 @@ mod tests { let result = ExtractorOfInstancesRequiredToSignTransactions::extract( &Profile::sample_other(), vec![intent_with_invalid_persona], - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ); assert!(matches!(result, Err(CommonError::UnknownPersona))); @@ -113,7 +113,7 @@ mod tests { let result = ExtractorOfInstancesRequiredToSignTransactions::extract( &Profile::sample(), vec![intent_1, intent_2], - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ); let account_fi_1 = HierarchicalDeterministicFactorInstance::from( diff --git a/crates/sargon/src/signing/collector/signatures_collector.rs b/crates/sargon/src/signing/collector/signatures_collector.rs index 80e53a3eb..b7ba69bc7 100644 --- a/crates/sargon/src/signing/collector/signatures_collector.rs +++ b/crates/sargon/src/signing/collector/signatures_collector.rs @@ -32,14 +32,14 @@ impl SignaturesCollector { transactions: impl IntoIterator, interactor: Arc>, profile: &Profile, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> Result { Self::with_signers_extraction( finish_early_strategy, IndexSet::from_iter(profile.factor_sources.iter()), transactions, interactor, - role_kind, + purpose, |i| SignableWithEntities::extracting_from_profile(&i, profile), ) } @@ -62,12 +62,12 @@ impl SignaturesCollector { profile_factor_sources: IndexSet, transactions: IdentifiedVecOf>, interactor: Arc>, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> Self { debug!("Init SignaturesCollector"); let preprocessor = SignaturesCollectorPreprocessor::new(transactions); let (petitions, factors) = - preprocessor.preprocess(profile_factor_sources, role_kind); + preprocessor.preprocess(profile_factor_sources, purpose); let dependencies = SignaturesCollectorDependencies::new( finish_early_strategy, @@ -87,7 +87,7 @@ impl SignaturesCollector { all_factor_sources_in_profile: IndexSet, transactions: impl IntoIterator, interactor: Arc>, - role_kind: RoleKind, + purpose: SigningPurpose, extract_signers: F, ) -> Result where @@ -104,7 +104,7 @@ impl SignaturesCollector { all_factor_sources_in_profile, transactions, interactor, - role_kind, + purpose, ); Ok(collector) @@ -421,9 +421,9 @@ mod tests { use super::*; - impl SignaturesCollector { + impl SignaturesCollector { /// Used by tests - pub(crate) fn petitions(self) -> Petitions { + pub(crate) fn petitions(self) -> Petitions { self.state .read() .expect("SignaturesCollector lock should not have been poisoned.") @@ -434,6 +434,101 @@ mod tests { } } + fn assert_petition( + petitions: &Petitions, + t: &S, + threshold_factors: HashMap< + AddressOfAccountOrPersona, + HashSet, + >, + override_factors: HashMap< + AddressOfAccountOrPersona, + HashSet, + >, + ) { + let petitions_ref = petitions + .txid_to_petition + .read() + .expect("Petitions lock should not have been poisoned"); + let signable_id = t.get_id(); + let petition = petitions_ref.get(&signable_id).unwrap(); + assert_eq!(petition.signable.get_id(), signable_id); + + let mut addresses = threshold_factors.keys().collect::>(); + addresses.extend(override_factors.keys().collect::>()); + + assert_eq!( + petition + .for_entities + .read() + .expect("PetitionForTransaction lock should not have been poisoned.") + .keys() + .collect::>(), + addresses + ); + + assert!(petition + .for_entities + .read() + .expect( + "PetitionForTransaction lock should not have been poisoned." + ) + .iter() + .all(|(a, p)| { p.entity == *a })); + + assert!(petition + .for_entities + .read() + .expect( + "PetitionForTransaction lock should not have been poisoned." + ) + .iter() + .all(|(_, p)| { p.payload_id == t.get_id() })); + + for (k, v) in petition + .for_entities + .read() + .expect( + "PetitionForTransaction lock should not have been poisoned.", + ) + .iter() + { + let threshold = threshold_factors.get(k); + if let Some(actual_threshold) = &v.threshold_factors { + let threshold = threshold.unwrap().clone(); + assert_eq!( + actual_threshold + .read() + .expect("PetitionForEntity lock should not have been poisoned.") + .factor_instances() + .into_iter() + .map(|f| f.factor_source_id) + .collect::>(), + threshold + ); + } else { + assert!(threshold.is_none()); + } + + let override_ = override_factors.get(k); + if let Some(actual_override) = &v.override_factors { + let override_ = override_.unwrap().clone(); + assert_eq!( + actual_override + .read() + .expect("PetitionForEntity lock should not have been poisoned.") + .factor_instances() + .into_iter() + .map(|f| f.factor_source_id) + .collect::>(), + override_ + ); + } else { + assert!(override_.is_none()); + } + } + } + #[test] fn profile_with_unknown_account() { let res = SignaturesCollector::new( @@ -444,7 +539,7 @@ mod tests { )], Arc::new(TestSignInteractor::new(SimulatedUser::prudent_no_fail())), &Profile::sample_from(IndexSet::new(), [], []), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ); assert!(res.is_ok()); } @@ -459,7 +554,7 @@ mod tests { )], Arc::new(TestSignInteractor::new(SimulatedUser::prudent_no_fail())), &Profile::sample_from(IndexSet::new(), [], []), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ); assert!(matches!(res, Err(CommonError::UnknownPersona))); } @@ -477,7 +572,7 @@ mod tests { )], Arc::new(TestSignInteractor::new(SimulatedUser::prudent_no_fail())), &Profile::sample_from(factors_sources, [], [&persona]), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); let outcome = collector.collect_signatures().await.unwrap(); @@ -510,7 +605,7 @@ mod tests { ), )), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -543,7 +638,7 @@ mod tests { SimulatedUser::prudent_no_fail(), )), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -615,7 +710,7 @@ mod tests { [t0.clone(), t1.clone(), t2.clone(), t3.clone()], Arc::new(TestSignInteractor::new(SimulatedUser::prudent_no_fail())), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -665,96 +760,8 @@ mod tests { ); } - let assert_petition = |t: &TransactionIntent, - threshold_factors: HashMap< - AddressOfAccountOrPersona, - HashSet, - >, - override_factors: HashMap< - AddressOfAccountOrPersona, - HashSet, - >| { - let petitions_ref = petitions - .txid_to_petition - .read() - .expect("Petitions lock should not have been poisoned"); - let petition = - petitions_ref.get(&t.transaction_intent_hash()).unwrap(); - assert_eq!( - petition.signable.transaction_intent_hash(), - t.transaction_intent_hash() - ); - - let mut addresses = - threshold_factors.keys().collect::>(); - addresses.extend(override_factors.keys().collect::>()); - - assert_eq!( - petition - .for_entities - .read() - .expect("PetitionForTransaction lock should not have been poisoned.") - .keys() - .collect::>(), - addresses - ); - - assert!(petition - .for_entities - .read() - .expect("PetitionForTransaction lock should not have been poisoned.") - .iter() - .all(|(a, p)| { p.entity == *a })); - - assert!(petition - .for_entities - .read() - .expect("PetitionForTransaction lock should not have been poisoned.") - .iter() - .all(|(_, p)| { p.payload_id == t.transaction_intent_hash() })); - - for (k, v) in petition - .for_entities - .read() - .expect("PetitionForTransaction lock should not have been poisoned.") - .iter() - { - let threshold = threshold_factors.get(k); - if let Some(actual_threshold) = &v.threshold_factors { - let threshold = threshold.unwrap().clone(); - assert_eq!( - actual_threshold - .read() - .expect("PetitionForEntity lock should not have been poisoned.") - .factor_instances() - .into_iter() - .map(|f| f.factor_source_id) - .collect::>(), - threshold - ); - } else { - assert!(threshold.is_none()); - } - - let override_ = override_factors.get(k); - if let Some(actual_override) = &v.override_factors { - let override_ = override_.unwrap().clone(); - assert_eq!( - actual_override - .read() - .expect("PetitionForEntity lock should not have been poisoned.") - .factor_instances() - .into_iter() - .map(|f| f.factor_source_id) - .collect::>(), - override_ - ); - } else { - assert!(override_.is_none()); - } - } - }; assert_petition( + &petitions, &t0, HashMap::from_iter([ ( @@ -778,6 +785,7 @@ mod tests { ); assert_petition( + &petitions, &t1, HashMap::from_iter([ ( @@ -797,6 +805,7 @@ mod tests { ); assert_petition( + &petitions, &t2, HashMap::from_iter([ ( @@ -816,6 +825,7 @@ mod tests { ); assert_petition( + &petitions, &t3, HashMap::from_iter([ ( @@ -893,7 +903,7 @@ mod tests { [t0.clone(), t1.clone(), t2.clone()], Arc::new(TestSignInteractor::new(sim)), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -1036,7 +1046,7 @@ mod tests { [t0.clone(), t1.clone(), t2.clone(), t3.clone()], Arc::new(TestSignInteractor::new(vector.simulated_user)), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -1130,7 +1140,7 @@ mod tests { ), )), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -1192,7 +1202,7 @@ mod tests { ), )), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -1279,7 +1289,7 @@ mod tests { ]), ))), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); @@ -2080,7 +2090,7 @@ mod tests { FactorSourceIDFromHash::sample_at(4), ]), ), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ); let outcome = collector.collect_signatures().await.unwrap(); @@ -2509,4 +2519,103 @@ mod tests { } } } + + mod rola { + use super::*; + + #[actix_rt::test] + async fn test_petitions_for() { + let factor_sources = &FactorSource::sample_all(); + + let a0 = &Account::sample_at(0); + let a1 = &Account::sample_at(1); + let a6 = &Account::sample_at(6); + + let p0 = &Persona::sample_at(0); + let p1 = &Persona::sample_at(1); + let p6 = &Persona::sample_at(6); + + let entities_to_sign = vec![ + AddressOfAccountOrPersona::Account(a0.address), + AddressOfAccountOrPersona::Account(a1.address), + AddressOfAccountOrPersona::Account(a6.address), + AddressOfAccountOrPersona::Identity(p0.address), + AddressOfAccountOrPersona::Identity(p1.address), + AddressOfAccountOrPersona::Identity(p6.address), + ]; + + let auth_intent = AuthIntent::new_from_request( + DappToWalletInteractionAuthChallengeNonce::sample(), + DappToWalletInteractionMetadata::sample(), + entities_to_sign, + ) + .unwrap(); + + let profile = Profile::sample_from( + factor_sources.clone(), + [a0, a1, a6], + [p0, p1, p6], + ); + + let collector = SignaturesCollector::new( + SigningFinishEarlyStrategy::default(), + [auth_intent.clone()], + Arc::new(TestSignInteractor::new( + SimulatedUser::prudent_no_fail(), + )), + &profile, + SigningPurpose::ROLA, + ) + .unwrap(); + + let petitions = collector.petitions(); + + assert_eq!( + petitions + .txid_to_petition + .read() + .expect("Petitions lock should not have been poisoned") + .len(), + 1 + ); + + assert_petition( + &petitions, + &auth_intent, + HashMap::from_iter([ + ( + a0.address.into(), + HashSet::just(FactorSourceIDFromHash::sample_at(0)), + ), + ( + a1.address.into(), + HashSet::just(FactorSourceIDFromHash::sample_at(1)), + ), + ( + a6.address.into(), + HashSet::from_iter([ + // Only device factor source is used for signing auth for securified entity + FactorSourceIDFromHash::sample_at(0), + ]), + ), + ( + p0.address.into(), + HashSet::just(FactorSourceIDFromHash::sample_at(0)), + ), + ( + p1.address.into(), + HashSet::just(FactorSourceIDFromHash::sample_at(1)), + ), + ( + p6.address.into(), + HashSet::from_iter([ + // Only device factor source is used for signing auth for securified entity + FactorSourceIDFromHash::sample_at(0), + ]), + ), + ]), + HashMap::from_iter([]), + ); + } + } } diff --git a/crates/sargon/src/signing/collector/signatures_collector_preprocessor.rs b/crates/sargon/src/signing/collector/signatures_collector_preprocessor.rs index 05265a28a..5a64f43be 100644 --- a/crates/sargon/src/signing/collector/signatures_collector_preprocessor.rs +++ b/crates/sargon/src/signing/collector/signatures_collector_preprocessor.rs @@ -55,7 +55,7 @@ impl SignaturesCollectorPreprocessor { pub(super) fn preprocess( self, profile_factor_sources: IndexSet, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> (Petitions, IndexSet) { let transactions = self.signables_with_entities; let mut petitions_for_all_transactions = @@ -106,7 +106,7 @@ impl SignaturesCollectorPreprocessor { let petition = PetitionForEntity::new_from_entity( id.clone(), entity, - role_kind, + purpose, ); petition.all_factor_instances().iter().for_each(|f| { diff --git a/crates/sargon/src/signing/petition_types/mod.rs b/crates/sargon/src/signing/petition_types/mod.rs index 406165902..00f0590d9 100644 --- a/crates/sargon/src/signing/petition_types/mod.rs +++ b/crates/sargon/src/signing/petition_types/mod.rs @@ -5,6 +5,7 @@ mod petition_for_transaction; mod petition_status; mod petitions; mod role_kind; +mod signing_purpose; pub(crate) use factor_list_kind::*; pub(crate) use petition_for_entity::*; @@ -14,3 +15,4 @@ pub(crate) use petitions::*; pub use petition_for_factors_types::*; pub use role_kind::*; +pub use signing_purpose::*; diff --git a/crates/sargon/src/signing/petition_types/petition_for_entity.rs b/crates/sargon/src/signing/petition_types/petition_for_entity.rs index e2e600e0b..ea15aa01a 100644 --- a/crates/sargon/src/signing/petition_types/petition_for_entity.rs +++ b/crates/sargon/src/signing/petition_types/petition_for_entity.rs @@ -64,10 +64,11 @@ impl PetitionForEntity { pub(crate) fn new_from_entity( payload_id: ID, entity: AccountOrPersona, - if_securified_select_role: RoleKind, + purpose: SigningPurpose, ) -> Self { match entity.entity_security_state() { EntitySecurityState::Unsecured { value } => { + // Transaction signing factor instance is used in both transaction and rola purposes let factor_instance = value.transaction_signing; Self::new_unsecurified( @@ -76,18 +77,33 @@ impl PetitionForEntity { factor_instance, ) } - EntitySecurityState::Securified { value } => { - let general_role = - GeneralRoleWithHierarchicalDeterministicFactorInstances::try_from( - (value.security_structure.matrix_of_factors, if_securified_select_role) - ).unwrap(); - - PetitionForEntity::new_securified( - payload_id, - entity.address(), - general_role, - ) - } + EntitySecurityState::Securified { value } => match purpose { + SigningPurpose::SignTX { role_kind } => { + let general_role = + GeneralRoleWithHierarchicalDeterministicFactorInstances::try_from( + (value.security_structure.matrix_of_factors, role_kind) + ).unwrap(); + + Self::new_securified( + payload_id, + entity.address(), + general_role, + ) + } + SigningPurpose::ROLA => { + let threshold_factors = + PetitionForFactors::::new_threshold( + vec![value.authentication_signing_factor_instance()], + 1, + ); + Self::new( + payload_id, + entity.address(), + threshold_factors, + None, + ) + } + }, } } diff --git a/crates/sargon/src/signing/petition_types/petition_for_transaction.rs b/crates/sargon/src/signing/petition_types/petition_for_transaction.rs index 7b053d44d..7547a2c4d 100644 --- a/crates/sargon/src/signing/petition_types/petition_for_transaction.rs +++ b/crates/sargon/src/signing/petition_types/petition_for_transaction.rs @@ -255,7 +255,9 @@ impl PetitionForTransaction { } } -impl HasSampleValues for PetitionForTransaction { +impl HasSampleValues + for PetitionForTransaction +{ fn sample() -> Self { let account = Account::sample_securified_mainnet( "Grace", diff --git a/crates/sargon/src/signing/petition_types/petitions.rs b/crates/sargon/src/signing/petition_types/petitions.rs index 893b8a153..a443ac10d 100644 --- a/crates/sargon/src/signing/petition_types/petitions.rs +++ b/crates/sargon/src/signing/petition_types/petitions.rs @@ -251,7 +251,9 @@ impl Petitions { } } -impl HasSampleValues for Petitions { +impl HasSampleValues + for Petitions +{ fn sample() -> Self { let p0 = PetitionForTransaction::::sample(); Self::new( diff --git a/crates/sargon/src/signing/petition_types/signing_purpose.rs b/crates/sargon/src/signing/petition_types/signing_purpose.rs new file mode 100644 index 000000000..23546c0a7 --- /dev/null +++ b/crates/sargon/src/signing/petition_types/signing_purpose.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +/// Designates the purpose of initiating the `SignaturesCollector`. The collector can either +/// sign transactions, or prove ownership of entities such as personas or accounts. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SigningPurpose { + /// Transactions can be signed using different roles from the MFA setup, for securified + /// entities, or with `RoleKind::Primary` for unsecurified ones. + SignTX { role_kind: RoleKind }, + + /// Using `SignaturesCollector` for proving ownership of entities. + ROLA, +} + +impl SigningPurpose { + pub fn sign_transaction(of_role_kind: RoleKind) -> Self { + Self::SignTX { + role_kind: of_role_kind, + } + } + + pub fn sign_transaction_primary() -> Self { + Self::sign_transaction(RoleKind::Primary) + } + + pub fn sign_transaction_recovery() -> Self { + Self::sign_transaction(RoleKind::Recovery) + } + + pub fn sign_transaction_confirmation() -> Self { + Self::sign_transaction(RoleKind::Confirmation) + } + + pub fn rola() -> Self { + Self::ROLA + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::SigningPurpose::SignTX; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SigningPurpose; + + #[test] + fn test_for_primary_transaction() { + assert_eq!( + SUT::sign_transaction_primary(), + SignTX { + role_kind: RoleKind::Primary + } + ) + } + + #[test] + fn test_for_recovery_transaction() { + assert_eq!( + SUT::sign_transaction_recovery(), + SignTX { + role_kind: RoleKind::Recovery + } + ) + } + + #[test] + fn test_for_confirmation_transaction() { + assert_eq!( + SUT::sign_transaction_confirmation(), + SignTX { + role_kind: RoleKind::Confirmation + } + ) + } + + #[test] + fn test_for_rola() { + assert_eq!(SUT::rola(), SUT::ROLA) + } +} diff --git a/crates/sargon/src/signing/signable_with_entities.rs b/crates/sargon/src/signing/signable_with_entities.rs index 90c124558..a9f1106d0 100644 --- a/crates/sargon/src/signing/signable_with_entities.rs +++ b/crates/sargon/src/signing/signable_with_entities.rs @@ -48,7 +48,7 @@ impl SignableWithEntities { } // -- Samples -impl SignableWithEntities { +impl SignableWithEntities { #[allow(unused)] pub(crate) fn sample( entities_requiring_auth: impl IntoIterator< diff --git a/crates/sargon/src/signing/signables/mod.rs b/crates/sargon/src/signing/signables/mod.rs index 85d3c74f3..61feda80a 100644 --- a/crates/sargon/src/signing/signables/mod.rs +++ b/crates/sargon/src/signing/signables/mod.rs @@ -1,7 +1,9 @@ mod signable; +mod signable_auth_intent; mod signable_subintent; mod signable_transaction_intent; pub use signable::*; +pub use signable_auth_intent::*; pub use signable_subintent::*; pub use signable_transaction_intent::*; diff --git a/crates/sargon/src/signing/signables/signable.rs b/crates/sargon/src/signing/signables/signable.rs index 7afb5a136..73bea311a 100644 --- a/crates/sargon/src/signing/signables/signable.rs +++ b/crates/sargon/src/signing/signables/signable.rs @@ -42,9 +42,23 @@ pub trait Signable: fn signed( &self, - intent_signatures: IntentSignatures, + signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, ) -> Result; +} + +/// An identifier that is unique for each `Signable` +pub trait SignableID: + Eq + StdHash + Clone + Debug + Into + Send + Sync +{ +} +/// A trait which provides the ability to construct a `Signable` sample by building a manifest. +pub trait ProvidesSamplesByBuildingManifest: + PartialEq + Eq + Clone + Send + Sync +{ /// Returns a sample `Signable` that its summary will involve all the /// `accounts_requiring_auth` and `personas_requiring_auth` in entities requiring auth. /// This can be accomplished by building a manifest that constructs owner keys from these @@ -124,9 +138,3 @@ pub trait Signable: network_id: Option, ) -> Self; } - -/// An identifier that is unique for each `Signable` -pub trait SignableID: - Eq + StdHash + Clone + Debug + Into + Send + Sync -{ -} diff --git a/crates/sargon/src/signing/signables/signable_auth_intent.rs b/crates/sargon/src/signing/signables/signable_auth_intent.rs new file mode 100644 index 000000000..c0ec8f4ee --- /dev/null +++ b/crates/sargon/src/signing/signables/signable_auth_intent.rs @@ -0,0 +1,170 @@ +use crate::prelude::*; + +impl Signable for AuthIntent { + type ID = AuthIntentHash; + type Payload = Self; + type Signed = SignedAuthIntent; + + fn entities_requiring_signing( + &self, + profile: &Profile, + ) -> Result> { + let entities = self + .entities_to_sign + .iter() + .filter_map(|address| match address { + AddressOfAccountOrPersona::Account(account_address) => profile + .account_by_address(*account_address) + .map(AccountOrPersona::AccountEntity) + .ok(), + AddressOfAccountOrPersona::Identity(identity_address) => { + profile + .persona_by_address(*identity_address) + .map(AccountOrPersona::PersonaEntity) + .ok() + } + }) + .collect_vec(); + + Ok(IndexSet::from_iter(entities)) + } + + fn signed( + &self, + signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, + ) -> Result { + SignedAuthIntent::new(self.clone(), signatures_per_owner) + } +} + +impl From for AuthIntent { + fn from(val: SignedAuthIntent) -> Self { + val.intent + } +} + +impl IntoIterator for SignedAuthIntent { + type Item = SignatureWithPublicKey; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.intent_signatures_per_owner + .values() + .map(|s| s.0) + .collect_vec() + .into_iter() + } +} + +impl SignableID for AuthIntentHash {} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = AuthIntent; + + #[test] + fn test_get_entities_requiring_signing() { + let profile = Profile::sample(); + let accounts_in_profile = + profile.accounts_on_current_network().unwrap(); + let personas_in_profile = + profile.personas_on_current_network().unwrap(); + + let unknown_account_address_to_profile = + AddressOfAccountOrPersona::Account( + Account::sample_mainnet_bob().address, + ); + let unknown_identity_address_to_profile = + AddressOfAccountOrPersona::Identity( + Persona::sample_mainnet_third().address, + ); + let mut addresses_requested = accounts_in_profile + .clone() + .into_iter() + .map(|account| AddressOfAccountOrPersona::Account(account.address)) + .collect_vec(); + // Push an unknown address, this should be filtered out from the result + addresses_requested.push(unknown_account_address_to_profile); + + addresses_requested.extend( + personas_in_profile + .clone() + .into_iter() + .map(|persona| { + AddressOfAccountOrPersona::Identity(persona.address) + }) + .collect_vec(), + ); + addresses_requested.push(unknown_identity_address_to_profile); + + let auth_intent = SUT::new_from_request( + DappToWalletInteractionAuthChallengeNonce::sample(), + DappToWalletInteractionMetadata::sample(), + addresses_requested, + ) + .unwrap(); + + let mut expected_entities = accounts_in_profile + .into_iter() + .map(AccountOrPersona::AccountEntity) + .collect_vec(); + + expected_entities.extend( + personas_in_profile + .into_iter() + .map(AccountOrPersona::PersonaEntity), + ); + + assert_eq!( + auth_intent.entities_requiring_signing(&profile).unwrap(), + IndexSet::::from_iter(expected_entities) + ) + } + + #[test] + fn test_signed() { + let sut = SUT::sample_other(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + let signature = mnemonic_with_passphrase + .sign(&sut.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + + let signed = sut.signed(intent_signatures.clone()).unwrap(); + + assert_eq!( + signed, + SignedAuthIntent::new(sut.clone(), intent_signatures).unwrap() + ); + assert_eq!(AuthIntent::from(signed), sut) + } + + #[test] + fn test_signed_get_signatures() { + let sut = SUT::sample(); + let mnemonic_with_passphrase = MnemonicWithPassphrase::sample(); + let signature = mnemonic_with_passphrase + .sign(&sut.auth_intent_hash().hash(), &DerivationPath::sample()); + let intent_signatures = indexmap!( + AddressOfAccountOrPersona::sample() => IntentSignature(signature) + ); + + let signed = sut.signed(intent_signatures.clone()).unwrap(); + + assert_eq!( + signed.into_iter().collect_vec(), + intent_signatures + .values() + .cloned() + .map(|i| i.0) + .collect_vec() + ) + } +} diff --git a/crates/sargon/src/signing/signables/signable_subintent.rs b/crates/sargon/src/signing/signables/signable_subintent.rs index 852caaf76..78db57555 100644 --- a/crates/sargon/src/signing/signables/signable_subintent.rs +++ b/crates/sargon/src/signing/signables/signable_subintent.rs @@ -19,11 +19,43 @@ impl Signable for Subintent { fn signed( &self, - intent_signatures: IntentSignatures, + signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, ) -> Result { - SignedSubintent::new(self.clone(), intent_signatures) + let intent_signatures = + signatures_per_owner.values().cloned().collect_vec(); + SignedSubintent::new( + self.clone(), + IntentSignatures::new(intent_signatures), + ) + } +} + +impl From for Subintent { + fn from(val: SignedSubintent) -> Self { + val.subintent + } +} + +impl IntoIterator for SignedSubintent { + type Item = SignatureWithPublicKey; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subintent_signatures + .signatures + .into_iter() + .map(|s| s.0) + .collect_vec() + .into_iter() } +} +impl SignableID for SubintentHash {} + +impl ProvidesSamplesByBuildingManifest for Subintent { fn sample_entity_addresses_with_pub_key_hashes( all_addresses_with_hashes: Vec<( AddressOfAccountOrPersona, @@ -50,28 +82,6 @@ impl Signable for Subintent { } } -impl From for Subintent { - fn from(val: SignedSubintent) -> Self { - val.subintent - } -} - -impl IntoIterator for SignedSubintent { - type Item = SignatureWithPublicKey; - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.subintent_signatures - .signatures - .into_iter() - .map(|s| s.0) - .collect_vec() - .into_iter() - } -} - -impl SignableID for SubintentHash {} - #[cfg(test)] mod test { use super::*; diff --git a/crates/sargon/src/signing/signables/signable_transaction_intent.rs b/crates/sargon/src/signing/signables/signable_transaction_intent.rs index 893ac3983..fe4a4b9fb 100644 --- a/crates/sargon/src/signing/signables/signable_transaction_intent.rs +++ b/crates/sargon/src/signing/signables/signable_transaction_intent.rs @@ -18,11 +18,43 @@ impl Signable for TransactionIntent { fn signed( &self, - intent_signatures: IntentSignatures, + signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, ) -> Result { - SignedIntent::new(self.clone(), intent_signatures) + let intent_signatures = + signatures_per_owner.values().cloned().collect_vec(); + SignedIntent::new( + self.clone(), + IntentSignatures::new(intent_signatures), + ) + } +} + +impl From for TransactionIntent { + fn from(val: SignedIntent) -> Self { + val.intent + } +} + +impl IntoIterator for SignedIntent { + type Item = SignatureWithPublicKey; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.intent_signatures + .signatures + .into_iter() + .map(|s| s.0) + .collect_vec() + .into_iter() } +} +impl SignableID for TransactionIntentHash {} + +impl ProvidesSamplesByBuildingManifest for TransactionIntent { fn sample_entity_addresses_with_pub_key_hashes( all_addresses_with_hashes: Vec<( AddressOfAccountOrPersona, @@ -47,28 +79,6 @@ impl Signable for TransactionIntent { } } -impl From for TransactionIntent { - fn from(val: SignedIntent) -> Self { - val.intent - } -} - -impl IntoIterator for SignedIntent { - type Item = SignatureWithPublicKey; - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.intent_signatures - .signatures - .into_iter() - .map(|s| s.0) - .collect_vec() - .into_iter() - } -} - -impl SignableID for TransactionIntentHash {} - #[cfg(test)] mod test { use super::*; diff --git a/crates/sargon/src/signing/testing/interactors/mod.rs b/crates/sargon/src/signing/testing/interactors/mod.rs index db6ea8b52..87e315414 100644 --- a/crates/sargon/src/signing/testing/interactors/mod.rs +++ b/crates/sargon/src/signing/testing/interactors/mod.rs @@ -1,10 +1,8 @@ #![cfg(test)] #![allow(unused)] -mod test_authentication_interactor; mod test_interactor; mod test_sign_interactor; -pub(crate) use test_authentication_interactor::*; pub(crate) use test_interactor::*; pub(crate) use test_sign_interactor::*; diff --git a/crates/sargon/src/signing/testing/interactors/test_authentication_interactor.rs b/crates/sargon/src/signing/testing/interactors/test_authentication_interactor.rs deleted file mode 100644 index 7cb8e2c54..000000000 --- a/crates/sargon/src/signing/testing/interactors/test_authentication_interactor.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![allow(unused)] - -use crate::prelude::*; - -pub(crate) struct TestAuthenticationInteractor { - should_fail: bool, -} - -impl TestAuthenticationInteractor { - pub(crate) fn new_failing() -> Self { - Self { should_fail: true } - } - - pub(crate) fn new_succeeding() -> Self { - Self { should_fail: false } - } -} - -#[async_trait::async_trait] -impl AuthenticationSigningInteractor for TestAuthenticationInteractor { - async fn sign( - &self, - request: AuthenticationSigningRequest, - ) -> Result { - let id = request.input.owned_factor_instance.factor_source_id(); - - let mnemonic_with_passphrase = id.sample_associated_mnemonic(); - - let challenge = request.input.rola_challenge()?; - let signature = mnemonic_with_passphrase.sign( - &challenge.hash(), - &request.input.owned_factor_instance.value.derivation_path(), - ); - - if self.should_fail { - Err(CommonError::SigningRejected) - } else { - AuthenticationSigningResponse::new(challenge, signature) - } - } -} diff --git a/crates/sargon/src/signing/testing/test_signatures_collector.rs b/crates/sargon/src/signing/testing/test_signatures_collector.rs index bd8d6535b..82be54b80 100644 --- a/crates/sargon/src/signing/testing/test_signatures_collector.rs +++ b/crates/sargon/src/signing/testing/test_signatures_collector.rs @@ -8,14 +8,14 @@ impl SignaturesCollector { all_factor_sources_in_profile: IndexSet, transactions: IdentifiedVecOf>, interactor: Arc>, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> Self { Self::with( finish_early_strategy, all_factor_sources_in_profile, transactions, interactor, - role_kind, + purpose, ) } pub(crate) fn new_test( @@ -23,14 +23,14 @@ impl SignaturesCollector { all_factor_sources_in_profile: impl IntoIterator, transactions: impl IntoIterator>, simulated_user: SimulatedUser, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> Self { Self::new_test_with( finish_early_strategy, all_factor_sources_in_profile.into_iter().collect(), IdentifiedVecOf::from_iter(transactions), Arc::new(TestSignInteractor::new(simulated_user)), - role_kind, + purpose, ) } @@ -44,7 +44,7 @@ impl SignaturesCollector { all_factor_sources_in_profile, transactions, SimulatedUser::prudent_no_fail(), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) } @@ -77,7 +77,7 @@ impl SignaturesCollector { FactorSource::sample_all(), transactions, SimulatedUser::prudent_with_failures(simulated_failures), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) } @@ -90,7 +90,7 @@ impl SignaturesCollector { all_factor_sources_in_profile, transactions, SimulatedUser::lazy_sign_minimum([]), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) } @@ -112,7 +112,7 @@ impl SignaturesCollector { all_factor_sources_in_profile, transactions, SimulatedUser::lazy_always_skip_no_fail(), - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) } diff --git a/crates/sargon/src/system/interactors/interactors.rs b/crates/sargon/src/system/interactors/interactors.rs index fd004c327..ec53ece72 100644 --- a/crates/sargon/src/system/interactors/interactors.rs +++ b/crates/sargon/src/system/interactors/interactors.rs @@ -31,7 +31,9 @@ impl Interactors { SimulatedUser::prudent_no_fail(), )), keys_derivation_interactor, - Arc::new(TestAuthenticationInteractor::new_succeeding()), + Arc::new(TestSignInteractor::::new( + SimulatedUser::prudent_no_fail(), + )), ); Self::new(Arc::new(use_factor_sources_interactors)) diff --git a/crates/sargon/src/system/interactors/testing/test_use_factor_sources_interactors.rs b/crates/sargon/src/system/interactors/testing/test_use_factor_sources_interactors.rs index a70f69862..a402496f5 100644 --- a/crates/sargon/src/system/interactors/testing/test_use_factor_sources_interactors.rs +++ b/crates/sargon/src/system/interactors/testing/test_use_factor_sources_interactors.rs @@ -4,7 +4,7 @@ pub struct TestUseFactorSourcesInteractors { transaction_signing: Arc>, subintent_signing: Arc>, key_derivation: Arc, - auth_signing: Arc, + auth_signing: Arc>, } impl TestUseFactorSourcesInteractors { @@ -12,7 +12,7 @@ impl TestUseFactorSourcesInteractors { transaction_signing: Arc>, subintent_signing: Arc>, key_derivation: Arc, - auth_signing: Arc, + auth_signing: Arc>, ) -> Self { Self { transaction_signing, @@ -56,11 +56,11 @@ impl KeyDerivationInteractor for TestUseFactorSourcesInteractors { } #[async_trait::async_trait] -impl AuthenticationSigningInteractor for TestUseFactorSourcesInteractors { +impl SignInteractor for TestUseFactorSourcesInteractors { async fn sign( &self, - request: AuthenticationSigningRequest, - ) -> Result { + request: SignRequest, + ) -> Result> { self.auth_signing.sign(request).await } } diff --git a/crates/sargon/src/system/interactors/use_factor_sources_interactor.rs b/crates/sargon/src/system/interactors/use_factor_sources_interactor.rs index b70bc83fb..7d8e1b734 100644 --- a/crates/sargon/src/system/interactors/use_factor_sources_interactor.rs +++ b/crates/sargon/src/system/interactors/use_factor_sources_interactor.rs @@ -10,7 +10,7 @@ use crate::prelude::*; pub trait UseFactorSourcesInteractor: SignInteractor + SignInteractor + + SignInteractor + KeyDerivationInteractor - + AuthenticationSigningInteractor { } diff --git a/crates/sargon/src/system/sargon_os/sargon_os.rs b/crates/sargon/src/system/sargon_os/sargon_os.rs index a046a6090..a9f0ee7a6 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os.rs @@ -269,18 +269,18 @@ impl SargonOS { as Arc> } - pub(crate) fn keys_derivation_interactor( + pub(crate) fn sign_auth_interactor( &self, - ) -> Arc { + ) -> Arc> { self.interactors.use_factor_sources_interactor.clone() - as Arc + as Arc> } - pub(crate) fn auth_signing_interactor( + pub(crate) fn keys_derivation_interactor( &self, - ) -> Arc { + ) -> Arc { self.interactors.use_factor_sources_interactor.clone() - as Arc + as Arc } pub fn host_id(&self) -> HostId { diff --git a/crates/sargon/src/system/sargon_os/sargon_os_signing.rs b/crates/sargon/src/system/sargon_os/sargon_os_signing.rs index d0e1660e1..f4379fbbb 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_signing.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_signing.rs @@ -7,21 +7,14 @@ use std::ops::Index; impl SargonOS { pub async fn sign_auth( &self, - address_of_entity: AddressOfAccountOrPersona, - challenge_nonce: DappToWalletInteractionAuthChallengeNonce, - metadata: DappToWalletInteractionMetadata, - ) -> Result { - let profile = &self.profile_state_holder.profile()?; - - let auth_signer = AuthenticationSigner::new( - self.auth_signing_interactor(), - profile, - address_of_entity, - challenge_nonce, - metadata, - )?; - - auth_signer.sign().await + auth_intent: AuthIntent, + ) -> Result { + self.sign( + auth_intent.clone(), + self.sign_auth_interactor(), + SigningPurpose::ROLA, + ) + .await } pub async fn sign_transaction( @@ -32,7 +25,7 @@ impl SargonOS { self.sign( transaction_intent.clone(), self.sign_transactions_interactor(), - role_kind, + SigningPurpose::sign_transaction(role_kind), ) .await } @@ -45,7 +38,7 @@ impl SargonOS { self.sign( subintent.clone(), self.sign_subintents_interactor(), - role_kind, + SigningPurpose::sign_transaction(role_kind), ) .await } @@ -54,7 +47,7 @@ impl SargonOS { &self, signable: S, sign_interactor: Arc>, - role_kind: RoleKind, + purpose: SigningPurpose, ) -> Result { let profile = &self.profile_state_holder.profile()?; @@ -63,22 +56,21 @@ impl SargonOS { vec![signable.clone()], sign_interactor, profile, - role_kind, + purpose, )?; let outcome = collector.collect_signatures().await?; let payload_id = signable.get_id(); if outcome.successful() { - let intent_signatures = IndexSet::::from_iter( - outcome - .signatures_of_successful_transactions() - .iter() - .filter(|hd| hd.input.payload_id == payload_id) - .map(|hd| IntentSignature(hd.signature)), - ); - - signable.signed(IntentSignatures::new(intent_signatures)) + let signatures_per_owner = outcome + .signatures_of_successful_transactions() + .iter() + .filter(|hd| hd.input.payload_id == payload_id) + .map(|hd| (hd.input.owned_factor_instance.owner, IntentSignature(hd.signature))) + .collect::>(); + + signable.signed(signatures_per_owner) } else { Err(CommonError::SigningRejected) } @@ -96,7 +88,6 @@ mod test { async fn test_sign_auth_success() { let profile = Profile::sample(); let sut = boot_with_profile(&profile, None).await; - let all_accounts = profile.accounts_on_current_network().unwrap(); let account = all_accounts.first().unwrap(); let nonce = DappToWalletInteractionAuthChallengeNonce::sample(); @@ -106,27 +97,25 @@ mod test { "https://example.com", DappDefinitionAddress::sample(), ); + let auth_intent = AuthIntent::new_from_request( + nonce, + metadata, + [AddressOfAccountOrPersona::Account(account.address)], + ) + .unwrap(); - let expected_challenge = - RolaChallenge::from_request(nonce.clone(), metadata.clone()) - .unwrap(); - - let signed = sut - .sign_auth( - AddressOfAccountOrPersona::Account(account.address), - nonce, - metadata, - ) - .await - .unwrap(); + let signed = sut.sign_auth(auth_intent.clone()).await.unwrap(); - let signature_with_public_key = SignatureWithPublicKey::from(( - *signed.public_key.as_ed25519().unwrap(), - *signed.signature.as_ed25519().unwrap(), - )); + let signature_with_public_key = signed + .intent_signatures_per_owner + .values() + .collect_vec() + .first() + .unwrap() + .0; assert!(signature_with_public_key - .is_valid_for_hash(&expected_challenge.hash())) + .is_valid_for_hash(&auth_intent.auth_intent_hash().hash())) } #[actix_rt::test] @@ -147,13 +136,14 @@ mod test { DappDefinitionAddress::sample(), ); - let result = sut - .sign_auth( - AddressOfAccountOrPersona::Account(account.address), - nonce, - metadata, - ) - .await; + let auth_intent = AuthIntent::new_from_request( + nonce, + metadata, + vec![AddressOfAccountOrPersona::Account(account.address)], + ) + .unwrap(); + + let result = sut.sign_auth(auth_intent).await; assert_eq!(result, Err(CommonError::SigningRejected)) } @@ -348,7 +338,9 @@ mod test { false, Arc::new(clients.secure_storage.clone()), )), - get_test_auth_interactor(&maybe_signing_failure), + Arc::new(TestSignInteractor::::new( + get_simulated_user::(&maybe_signing_failure), + )), )); let interactors = Interactors::new(use_factor_sources_interactors); SUT::boot_with_clients_and_interactor(clients, interactors).await @@ -372,16 +364,9 @@ mod test { } } - fn get_test_auth_interactor( - maybe_signing_failure: &Option, - ) -> Arc { - match maybe_signing_failure { - None => Arc::new(TestAuthenticationInteractor::new_succeeding()), - Some(_) => Arc::new(TestAuthenticationInteractor::new_failing()), - } - } - - fn get_signable_with_entities( + fn get_signable_with_entities< + S: Signable + ProvidesSamplesByBuildingManifest, + >( profile: &Profile, ) -> (S, Vec) { let accounts_addresses_involved = profile diff --git a/crates/sargon/src/system/sargon_os/transactions/sargon_os_transaction_analysis.rs b/crates/sargon/src/system/sargon_os/transactions/sargon_os_transaction_analysis.rs index d04c65ce9..4dc91df0b 100644 --- a/crates/sargon/src/system/sargon_os/transactions/sargon_os_transaction_analysis.rs +++ b/crates/sargon/src/system/sargon_os/transactions/sargon_os_transaction_analysis.rs @@ -159,7 +159,7 @@ impl SargonOS { Ok(ExtractorOfInstancesRequiredToSignTransactions::extract( &profile, vec![signable_summary], - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), )? .iter() .map(|i| i.public_key.public_key) diff --git a/crates/sargon/src/system/sargon_os/transactions/support/signable_manifest_summary.rs b/crates/sargon/src/system/sargon_os/transactions/support/signable_manifest_summary.rs index aad738a39..6945713d2 100644 --- a/crates/sargon/src/system/sargon_os/transactions/support/signable_manifest_summary.rs +++ b/crates/sargon/src/system/sargon_os/transactions/support/signable_manifest_summary.rs @@ -77,10 +77,18 @@ impl Signable for SignableManifestSummary { } #[cfg(not(tarpaulin_include))] - fn signed(&self, _: IntentSignatures) -> Result { + fn signed( + &self, + _signatures_per_owner: IndexMap< + AddressOfAccountOrPersona, + IntentSignature, + >, + ) -> Result { panic!("Manifest summary cannot be actually signed") } +} +impl ProvidesSamplesByBuildingManifest for SignableManifestSummary { fn sample_entity_addresses_with_pub_key_hashes( all_addresses_with_hashes: Vec<( AddressOfAccountOrPersona, diff --git a/crates/sargon/tests/integration/main.rs b/crates/sargon/tests/integration/main.rs index c7bd8ff28..2a2af9f63 100644 --- a/crates/sargon/tests/integration/main.rs +++ b/crates/sargon/tests/integration/main.rs @@ -468,7 +468,7 @@ mod integration_tests { transactions, Arc::new(TestTransactionSignInteractor), &profile, - RoleKind::Primary, + SigningPurpose::sign_transaction_primary(), ) .unwrap(); diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/AuthIntent.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/AuthIntent.kt new file mode 100644 index 000000000..5b4265c99 --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/AuthIntent.kt @@ -0,0 +1,7 @@ +package com.radixdlt.sargon.extensions + +import com.radixdlt.sargon.AuthIntent +import com.radixdlt.sargon.AuthIntentHash +import com.radixdlt.sargon.authIntentGetHash + +fun AuthIntent.hash(): AuthIntentHash = authIntentGetHash(authIntent = this) \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/RolaChallenge.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/RolaChallenge.kt deleted file mode 100644 index cd2b489b1..000000000 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/extensions/RolaChallenge.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.radixdlt.sargon.extensions - -import com.radixdlt.sargon.Hash -import com.radixdlt.sargon.RolaChallenge -import com.radixdlt.sargon.rolaChallengeGetHash - -fun RolaChallenge.hash(): Hash = rolaChallengeGetHash(rolaChallenge = this) \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentHashSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentHashSample.kt new file mode 100644 index 000000000..268b6b9c6 --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentHashSample.kt @@ -0,0 +1,14 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.AuthIntentHash +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.newAuthIntentHashSample +import com.radixdlt.sargon.newAuthIntentHashSampleOther + +@UsesSampleValues +val AuthIntentHash.Companion.sample: Sample + get() = object : Sample { + override fun invoke(): AuthIntentHash = newAuthIntentHashSample() + + override fun other(): AuthIntentHash = newAuthIntentHashSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentSample.kt new file mode 100644 index 000000000..9f2607bd4 --- /dev/null +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/AuthIntentSample.kt @@ -0,0 +1,14 @@ +package com.radixdlt.sargon.samples + +import com.radixdlt.sargon.AuthIntent +import com.radixdlt.sargon.annotation.UsesSampleValues +import com.radixdlt.sargon.newAuthIntentSample +import com.radixdlt.sargon.newAuthIntentSampleOther + +@UsesSampleValues +val AuthIntent.Companion.sample: Sample + get() = object : Sample { + override fun invoke(): AuthIntent = newAuthIntentSample() + + override fun other(): AuthIntent = newAuthIntentSampleOther() + } \ No newline at end of file diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/RolaChallenge.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/RolaChallenge.kt deleted file mode 100644 index cebf816eb..000000000 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/RolaChallenge.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.radixdlt.sargon.samples - -import com.radixdlt.sargon.RolaChallenge -import com.radixdlt.sargon.annotation.UsesSampleValues -import com.radixdlt.sargon.newRolaChallengeSample -import com.radixdlt.sargon.newRolaChallengeSampleOther - -@UsesSampleValues -val RolaChallenge.Companion.sample: Sample - get() = object : Sample { - override fun invoke(): RolaChallenge = newRolaChallengeSample() - - override fun other(): RolaChallenge = newRolaChallengeSampleOther() - } \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentHashTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentHashTest.kt new file mode 100644 index 000000000..6a0524ed6 --- /dev/null +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentHashTest.kt @@ -0,0 +1,9 @@ +package com.radixdlt.sargon + +import com.radixdlt.sargon.samples.Sample +import com.radixdlt.sargon.samples.sample + +class AuthIntentHashTest: SampleTestable { + override val samples: List> + get() = listOf(AuthIntentHash.sample) +} \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentTest.kt new file mode 100644 index 000000000..e9fec7b4f --- /dev/null +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/AuthIntentTest.kt @@ -0,0 +1,23 @@ +package com.radixdlt.sargon + +import com.radixdlt.sargon.extensions.hash +import com.radixdlt.sargon.samples.Sample +import com.radixdlt.sargon.samples.sample +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class AuthIntentTest : SampleTestable { + override val samples: List> + get() = listOf(AuthIntent.sample) + + @Test + fun testHash() { + val authIntent = AuthIntent.sample() + + assertEquals( + AuthIntentHash.sample(), + authIntent.hash() + ) + } + +} \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/RolaChallengeTest.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/RolaChallengeTest.kt deleted file mode 100644 index 552db5309..000000000 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/RolaChallengeTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.radixdlt.sargon - -import com.radixdlt.sargon.extensions.hash -import com.radixdlt.sargon.extensions.hex -import com.radixdlt.sargon.samples.Sample -import com.radixdlt.sargon.samples.sample -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class RolaChallengeTest: SampleTestable { - override val samples: List> - get() = listOf(RolaChallenge.sample) - - @Test - fun testHash() { - - assertEquals( - "6fc75ec1d5c00941dc587c0a07409da1740c423c337c323ba7bdf68d61d4dd8e", - RolaChallenge.sample().hash().hex, - ) - } -} \ No newline at end of file diff --git a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/os/interactor/FakeHostInteractor.kt b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/os/interactor/FakeHostInteractor.kt index b7701bace..75104a31a 100644 --- a/jvm/sargon-android/src/test/java/com/radixdlt/sargon/os/interactor/FakeHostInteractor.kt +++ b/jvm/sargon-android/src/test/java/com/radixdlt/sargon/os/interactor/FakeHostInteractor.kt @@ -1,15 +1,13 @@ package com.radixdlt.sargon.os.interactor -import com.radixdlt.sargon.AuthenticationSigningRequest -import com.radixdlt.sargon.AuthenticationSigningResponse import com.radixdlt.sargon.CommonException import com.radixdlt.sargon.HostInteractor import com.radixdlt.sargon.KeyDerivationRequest import com.radixdlt.sargon.KeyDerivationResponse -import com.radixdlt.sargon.NeglectFactorReason -import com.radixdlt.sargon.NeglectedFactors +import com.radixdlt.sargon.SignRequestOfAuthIntent import com.radixdlt.sargon.SignRequestOfSubintent import com.radixdlt.sargon.SignRequestOfTransactionIntent +import com.radixdlt.sargon.SignWithFactorsOutcomeOfAuthIntentHash import com.radixdlt.sargon.SignWithFactorsOutcomeOfSubintentHash import com.radixdlt.sargon.SignWithFactorsOutcomeOfTransactionIntentHash @@ -28,7 +26,8 @@ class FakeHostInteractor: HostInteractor { throw CommonException.SigningRejected() } - override suspend fun signAuth(request: AuthenticationSigningRequest): AuthenticationSigningResponse { + override suspend fun signAuth(request: SignRequestOfAuthIntent): SignWithFactorsOutcomeOfAuthIntentHash { throw CommonException.SigningRejected() } + } \ No newline at end of file