use alloc::vec::Vec;
use miden_protocol::account::component::{AccountComponentCode, AccountComponentMetadata};
use miden_protocol::account::{
Account,
AccountBuilder,
AccountComponent,
AccountComponentName,
AccountProcedureRoot,
AccountType,
};
use miden_protocol::errors::AccountError;
use crate::account::account_component_code;
use crate::account::auth::{
Approver,
ApproverSet,
AuthGuardedMultisig,
AuthGuardedMultisigConfig,
AuthMultisig,
AuthMultisigConfig,
AuthSingleSig,
GuardianConfig,
};
use crate::procedure_root;
mod note_creator;
pub use note_creator::NoteCreator;
account_component_code!(BASIC_WALLET_CODE, "miden-standards-wallets-basic-wallet.masp");
procedure_root!(
BASIC_WALLET_RECEIVE_ASSET,
BasicWallet::NAME,
BasicWallet::RECEIVE_ASSET_PROC_NAME,
BasicWallet::code()
);
procedure_root!(
BASIC_WALLET_MOVE_ASSET_TO_NOTE,
BasicWallet::NAME,
BasicWallet::MOVE_ASSET_TO_NOTE_PROC_NAME,
BasicWallet::code()
);
procedure_root!(
BASIC_WALLET_CREATE_NOTE,
BasicWallet::NAME,
BasicWallet::CREATE_NOTE_PROC_NAME,
BasicWallet::code()
);
pub struct BasicWallet;
impl BasicWallet {
pub const NAME: &'static str = "miden::standards::components::wallets::basic_wallet";
const RECEIVE_ASSET_PROC_NAME: &str = "receive_asset";
const MOVE_ASSET_TO_NOTE_PROC_NAME: &str = "move_asset_to_note";
const CREATE_NOTE_PROC_NAME: &str = "create_note";
pub const fn name() -> AccountComponentName {
AccountComponentName::from_static_str(Self::NAME)
}
pub fn code() -> &'static AccountComponentCode {
&BASIC_WALLET_CODE
}
pub fn receive_asset_root() -> AccountProcedureRoot {
*BASIC_WALLET_RECEIVE_ASSET
}
pub fn move_asset_to_note_root() -> AccountProcedureRoot {
*BASIC_WALLET_MOVE_ASSET_TO_NOTE
}
pub fn create_note_root() -> AccountProcedureRoot {
*BASIC_WALLET_CREATE_NOTE
}
pub fn component_metadata() -> AccountComponentMetadata {
AccountComponentMetadata::new(Self::NAME)
.with_description("Basic wallet component for receiving and sending assets")
}
}
impl From<BasicWallet> for AccountComponent {
fn from(_: BasicWallet) -> Self {
let metadata = BasicWallet::component_metadata();
AccountComponent::new(BasicWallet::code().clone(), vec![], metadata).expect(
"basic wallet component should satisfy the requirements of a valid account component",
)
}
}
pub fn create_basic_wallet(
init_seed: [u8; 32],
approver: Approver,
account_type: AccountType,
) -> Result<Account, AccountError> {
let auth_component: AccountComponent = AuthSingleSig::new(approver).into();
create_wallet(init_seed, auth_component, account_type)
}
pub fn create_multisig_wallet(
init_seed: [u8; 32],
approver_set: ApproverSet,
proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
account_type: AccountType,
) -> Result<Account, AccountError> {
let default_threshold = approver_set.threshold().get();
if account_type == AccountType::Private
&& proc_thresholds
.iter()
.any(|(_, proc_threshold)| *proc_threshold < default_threshold)
{
return Err(AccountError::other(
"private multisig wallets do not allow per-procedure thresholds below the default \
threshold, as a lower threshold would let a sub-quorum advance and withhold the \
private account state; use a guarded wallet to lower thresholds safely",
));
}
let config = AuthMultisigConfig::new(approver_set).with_proc_thresholds(proc_thresholds)?;
let auth_component: AccountComponent = AuthMultisig::new(config)?.into();
create_wallet(init_seed, auth_component, account_type)
}
pub fn create_guarded_wallet(
init_seed: [u8; 32],
approver_set: ApproverSet,
proc_thresholds: Vec<(AccountProcedureRoot, u32)>,
guardian: GuardianConfig,
account_type: AccountType,
) -> Result<Account, AccountError> {
let config = AuthGuardedMultisigConfig::new(approver_set, guardian)?
.with_proc_thresholds(proc_thresholds)?;
let auth_component: AccountComponent = AuthGuardedMultisig::new(config)?.into();
create_wallet(init_seed, auth_component, account_type)
}
fn create_wallet(
init_seed: [u8; 32],
auth_component: AccountComponent,
account_type: AccountType,
) -> Result<Account, AccountError> {
AccountBuilder::new(init_seed)
.account_type(account_type)
.with_auth_component(auth_component)
.with_component(BasicWallet)
.build()
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use miden_protocol::account::auth::{self, PublicKeyCommitment};
use miden_protocol::utils::serde::{Deserializable, Serializable};
use miden_protocol::{ONE, Word};
use super::{
Account,
AccountType,
Approver,
ApproverSet,
GuardianConfig,
create_basic_wallet,
create_guarded_wallet,
create_multisig_wallet,
};
use crate::account::wallets::BasicWallet;
fn approver(seed: u32) -> Approver {
Approver::new(
PublicKeyCommitment::from(Word::from([seed, seed, seed, seed])),
auth::AuthScheme::Falcon512Poseidon2,
)
}
#[test]
fn test_create_basic_wallet() -> anyhow::Result<()> {
create_basic_wallet([1; 32], approver(1), AccountType::Public)?;
Ok(())
}
#[test]
fn test_serialize_basic_wallet() -> anyhow::Result<()> {
let approver = Approver::new(
PublicKeyCommitment::from(Word::from([ONE; 4])),
auth::AuthScheme::EcdsaK256Keccak,
);
let wallet = create_basic_wallet([1; 32], approver, AccountType::Public)?;
let bytes = wallet.to_bytes();
let deserialized_wallet = Account::read_from_bytes(&bytes)?;
assert_eq!(wallet, deserialized_wallet);
Ok(())
}
#[test]
fn test_create_multisig_wallet_public_allows_lower_override() -> anyhow::Result<()> {
let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Public)?;
Ok(())
}
#[test]
fn test_create_multisig_wallet_private_no_override_succeeds() -> anyhow::Result<()> {
let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
create_multisig_wallet([1; 32], approver_set, vec![], AccountType::Private)?;
Ok(())
}
#[test]
fn test_create_multisig_wallet_private_higher_override_succeeds() -> anyhow::Result<()> {
let approver_set = ApproverSet::new(vec![approver(1), approver(2), approver(3)], 2)?;
let proc_thresholds = vec![(BasicWallet::move_asset_to_note_root(), 3)];
create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)?;
Ok(())
}
#[test]
fn test_create_multisig_wallet_private_lower_override_rejected() -> anyhow::Result<()> {
let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
let err =
create_multisig_wallet([1; 32], approver_set, proc_thresholds, AccountType::Private)
.expect_err("private multisig with a below-default threshold must be rejected");
assert!(
err.to_string()
.contains("do not allow per-procedure thresholds below the default threshold")
);
Ok(())
}
#[test]
fn test_create_guarded_wallet_private_override_allowed() -> anyhow::Result<()> {
let approver_set = ApproverSet::new(vec![approver(1), approver(2)], 2)?;
let proc_thresholds = vec![(BasicWallet::receive_asset_root(), 1)];
let guardian = GuardianConfig::new(approver(3));
create_guarded_wallet(
[1; 32],
approver_set,
proc_thresholds,
guardian,
AccountType::Private,
)?;
Ok(())
}
#[test]
fn get_faucet_procedures() {
let _receive_asset_root = BasicWallet::receive_asset_root();
let _move_asset_to_note_root = BasicWallet::move_asset_to_note_root();
}
}