v0.5.2-pre.006-fix-010

This commit is contained in:
2026-08-11 12:03:35 +02:00
parent 1ee1d0297d
commit 56572cec40
67 changed files with 2484 additions and 539 deletions

View File

@@ -0,0 +1,23 @@
// file: ks-wallet-demo-scenarios/src/lib.rs
// version: 1
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
//! Reusable integration scenarios for the public `ks-wallet` boundary.
mod password_lifecycle;
/// Safe summary returned after a password is rejected as expected.
pub use self::password_lifecycle::WalletPasswordRejectionSummary;
/// Safe summary returned after a password rotation preserving wallet identity.
pub use self::password_lifecycle::WalletPasswordRotationSummary;
/// Safe summary returned after authenticated message signing.
pub use self::password_lifecycle::WalletSigningScenarioSummary;
/// Requires one supplied password to fail native wallet authentication.
pub use self::password_lifecycle::expect_wallet_password_rejected;
/// Changes one native wallet password and verifies that its public identity is preserved.
pub use self::password_lifecycle::rotate_wallet_password;
/// Authenticates one native wallet and signs an explicit non-empty challenge.
pub use self::password_lifecycle::unlock_and_sign_wallet;

View File

@@ -0,0 +1,161 @@
// file: ks-wallet-demo-scenarios/src/password_lifecycle.rs
// version: 1
//! Password lifecycle scenarios driven only through the public wallet API.
/// Safe summary returned after a password rotation preserving wallet identity.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletPasswordRotationSummary {
/// Persistent wallet identity retained across the password rotation.
pub identity: ks_wallet::WalletIdentity,
}
/// Safe summary returned after a password is rejected as expected.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletPasswordRejectionSummary {
/// Persistent wallet identity whose old password was rejected.
pub identity: ks_wallet::WalletIdentity,
/// Stable `ks-wallet` error code that proved the authentication rejection.
pub rejection_code: std::string::String,
}
/// Safe summary returned after authenticated message signing.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletSigningScenarioSummary {
/// Persistent wallet identity used by the signing capability.
pub identity: ks_wallet::WalletIdentity,
/// Base58 signature produced for the supplied challenge.
pub signature: std::string::String,
/// Number of challenge bytes signed by the wallet.
pub message_length: usize,
}
/// Changes one native wallet password and verifies that its public identity is preserved.
pub async fn rotate_wallet_password(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
current_password: ks_wallet::WalletPassword,
replacement_password: ks_wallet::WalletPassword,
) -> ks_core::Result<crate::WalletPasswordRotationSummary> {
let before = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"password lifecycle scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let changed = match manager.change_password(alias, current_password, replacement_password).await
{
std::result::Result::Ok(handle) => handle,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if before.alias() != changed.alias() || before.public_key() != changed.public_key() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_password_rotation_identity_changed",
"password rotation changed the native wallet public identity",
));
}
return std::result::Result::Ok(crate::WalletPasswordRotationSummary {
identity: changed.identity(),
});
}
/// Requires one supplied password to fail native wallet authentication.
pub async fn expect_wallet_password_rejected(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
rejected_password: ks_wallet::WalletPassword,
) -> ks_core::Result<crate::WalletPasswordRejectionSummary> {
let handle = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"password rejection scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rejection = match manager.unlock(alias, rejected_password).await {
std::result::Result::Ok(wallet) => {
let accepted_identity = wallet.identity();
wallet.lock();
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_rejected_password_accepted",
format!(
"password expected to be rejected authenticated wallet alias {} with public key {}",
accepted_identity.alias.as_str(),
accepted_identity.public_key
),
));
},
std::result::Result::Err(error) => error,
};
if rejection.code() != "wallet_native_authentication_failed" {
return std::result::Result::Err(rejection);
}
return std::result::Result::Ok(crate::WalletPasswordRejectionSummary {
identity: handle.identity(),
rejection_code: rejection.code().to_owned(),
});
}
/// Authenticates one native wallet and signs an explicit non-empty challenge.
pub async fn unlock_and_sign_wallet(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
password: ks_wallet::WalletPassword,
challenge: &[u8],
) -> ks_core::Result<crate::WalletSigningScenarioSummary> {
if challenge.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_signing_challenge_empty",
"wallet signing scenario requires a non-empty challenge",
));
}
let expected = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"wallet signing scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = match manager.unlock(alias, password).await {
std::result::Result::Ok(wallet) => wallet,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let identity = wallet.identity();
if &identity.alias != expected.alias() || identity.public_key.as_str() != expected.public_key()
{
wallet.lock();
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_unlocked_identity_mismatch",
"authenticated wallet identity differs from the inspected native wallet",
));
}
let signature = match wallet.sign_message(challenge) {
std::result::Result::Ok(signature) => signature,
std::result::Result::Err(error) => {
wallet.lock();
return std::result::Result::Err(error);
},
};
wallet.lock();
if signature.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_signature_empty",
"authenticated wallet produced an empty signature",
));
}
return std::result::Result::Ok(crate::WalletSigningScenarioSummary {
identity,
signature,
message_length: challenge.len(),
});
}