use alloc::vec::Vec;
use miden_objects::account::auth::PublicKeyCommitment;
use miden_objects::account::{AccountCode, AccountComponent, StorageMap, StorageSlot};
use miden_objects::{AccountError, Word};
use crate::account::components::ecdsa_k256_keccak_acl_library;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthEcdsaK256KeccakAclConfig {
pub auth_trigger_procedures: Vec<Word>,
pub allow_unauthorized_output_notes: bool,
pub allow_unauthorized_input_notes: bool,
}
impl AuthEcdsaK256KeccakAclConfig {
pub fn new() -> Self {
Self {
auth_trigger_procedures: vec![],
allow_unauthorized_output_notes: false,
allow_unauthorized_input_notes: false,
}
}
pub fn with_auth_trigger_procedures(mut self, procedures: Vec<Word>) -> Self {
self.auth_trigger_procedures = procedures;
self
}
pub fn with_allow_unauthorized_output_notes(mut self, allow: bool) -> Self {
self.allow_unauthorized_output_notes = allow;
self
}
pub fn with_allow_unauthorized_input_notes(mut self, allow: bool) -> Self {
self.allow_unauthorized_input_notes = allow;
self
}
}
impl Default for AuthEcdsaK256KeccakAclConfig {
fn default() -> Self {
Self::new()
}
}
pub struct AuthEcdsaK256KeccakAcl {
pub_key: PublicKeyCommitment,
config: AuthEcdsaK256KeccakAclConfig,
}
impl AuthEcdsaK256KeccakAcl {
pub fn new(
pub_key: PublicKeyCommitment,
config: AuthEcdsaK256KeccakAclConfig,
) -> Result<Self, AccountError> {
let max_procedures = AccountCode::MAX_NUM_PROCEDURES;
if config.auth_trigger_procedures.len() > max_procedures {
return Err(AccountError::other(format!(
"Cannot track more than {max_procedures} procedures (account limit)"
)));
}
Ok(Self { pub_key, config })
}
}
impl From<AuthEcdsaK256KeccakAcl> for AccountComponent {
fn from(ecdsa: AuthEcdsaK256KeccakAcl) -> Self {
let mut storage_slots = Vec::with_capacity(3);
storage_slots.push(StorageSlot::Value(ecdsa.pub_key.into()));
let num_procs = ecdsa.config.auth_trigger_procedures.len() as u32;
storage_slots.push(StorageSlot::Value(Word::from([
num_procs,
u32::from(ecdsa.config.allow_unauthorized_output_notes),
u32::from(ecdsa.config.allow_unauthorized_input_notes),
0,
])));
let map_entries = ecdsa
.config
.auth_trigger_procedures
.iter()
.enumerate()
.map(|(i, proc_root)| (Word::from([i as u32, 0, 0, 0]), *proc_root));
storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap()));
AccountComponent::new(ecdsa_k256_keccak_acl_library(), storage_slots)
.expect(
"ACL auth component should satisfy the requirements of a valid account component",
)
.with_supports_all_types()
}
}
#[cfg(test)]
mod tests {
use miden_objects::Word;
use miden_objects::account::AccountBuilder;
use super::*;
use crate::account::components::WellKnownComponent;
use crate::account::wallets::BasicWallet;
struct AclTestConfig {
with_procedures: bool,
allow_unauthorized_output_notes: bool,
allow_unauthorized_input_notes: bool,
expected_slot_1: Word,
}
fn get_basic_wallet_procedures() -> Vec<Word> {
let procedures: Vec<Word> = WellKnownComponent::BasicWallet.procedure_digests().collect();
assert_eq!(procedures.len(), 2);
procedures
}
fn test_acl_component(config: AclTestConfig) {
let public_key = PublicKeyCommitment::from(Word::empty());
let mut acl_config = AuthEcdsaK256KeccakAclConfig::new()
.with_allow_unauthorized_output_notes(config.allow_unauthorized_output_notes)
.with_allow_unauthorized_input_notes(config.allow_unauthorized_input_notes);
let auth_trigger_procedures = if config.with_procedures {
let procedures = get_basic_wallet_procedures();
acl_config = acl_config.with_auth_trigger_procedures(procedures.clone());
procedures
} else {
vec![]
};
let component =
AuthEcdsaK256KeccakAcl::new(public_key, acl_config).expect("component creation failed");
let account = AccountBuilder::new([0; 32])
.with_auth_component(component)
.with_component(BasicWallet)
.build()
.expect("account building failed");
let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
assert_eq!(public_key_slot, public_key.into());
let slot_1 = account.storage().get_item(1).expect("storage slot 1 access failed");
assert_eq!(slot_1, config.expected_slot_1);
if config.with_procedures {
for (i, expected_proc_root) in auth_trigger_procedures.iter().enumerate() {
let proc_root = account
.storage()
.get_map_item(2, Word::from([i as u32, 0, 0, 0]))
.expect("storage map access failed");
assert_eq!(proc_root, *expected_proc_root);
}
} else {
let proc_root = account
.storage()
.get_map_item(2, Word::empty())
.expect("storage map access failed");
assert_eq!(proc_root, Word::empty());
}
}
#[test]
fn test_ecdsa_k256_keccak_acl_no_procedures() {
test_acl_component(AclTestConfig {
with_procedures: false,
allow_unauthorized_output_notes: false,
allow_unauthorized_input_notes: false,
expected_slot_1: Word::empty(), });
}
#[test]
fn test_ecdsa_k256_keccak_acl_with_two_procedures() {
test_acl_component(AclTestConfig {
with_procedures: true,
allow_unauthorized_output_notes: false,
allow_unauthorized_input_notes: false,
expected_slot_1: Word::from([2u32, 0, 0, 0]),
});
}
#[test]
fn test_ecdsa_k256_keccak_acl_with_allow_unauthorized_output_notes() {
test_acl_component(AclTestConfig {
with_procedures: false,
allow_unauthorized_output_notes: true,
allow_unauthorized_input_notes: false,
expected_slot_1: Word::from([0u32, 1, 0, 0]),
});
}
#[test]
fn test_ecdsa_k256_keccak_acl_with_procedures_and_allow_unauthorized_output_notes() {
test_acl_component(AclTestConfig {
with_procedures: true,
allow_unauthorized_output_notes: true,
allow_unauthorized_input_notes: false,
expected_slot_1: Word::from([2u32, 1, 0, 0]),
});
}
#[test]
fn test_ecdsa_k256_keccak_acl_with_allow_unauthorized_input_notes() {
test_acl_component(AclTestConfig {
with_procedures: false,
allow_unauthorized_output_notes: false,
allow_unauthorized_input_notes: true,
expected_slot_1: Word::from([0u32, 0, 1, 0]),
});
}
#[test]
fn test_ecdsa_k256_keccak_acl_with_both_allow_flags() {
test_acl_component(AclTestConfig {
with_procedures: true,
allow_unauthorized_output_notes: true,
allow_unauthorized_input_notes: true,
expected_slot_1: Word::from([2u32, 1, 1, 0]),
});
}
}