use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use miden_processor::FutureMaybeSend;
use miden_protocol::account::auth::{AuthSecretKey, PublicKey, PublicKeyCommitment, Signature};
use miden_protocol::crypto::SequentialCommit;
use miden_protocol::transaction::TransactionSummary;
use miden_protocol::{Felt, Hasher, Word};
use crate::errors::AuthenticationError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone)]
pub enum SigningInputs {
TransactionSummary(Box<TransactionSummary>),
Arbitrary(Vec<Felt>),
Blind(Word),
}
impl SequentialCommit for SigningInputs {
type Commitment = Word;
fn to_elements(&self) -> Vec<Felt> {
match self {
SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_elements(),
SigningInputs::Arbitrary(elements) => elements.clone(),
SigningInputs::Blind(word) => word.as_elements().to_vec(),
}
}
fn to_commitment(&self) -> Self::Commitment {
match self {
SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_commitment(),
SigningInputs::Arbitrary(elements) => Hasher::hash_elements(elements),
SigningInputs::Blind(word) => *word,
}
}
}
impl SigningInputs {
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
}
pub fn to_elements(&self) -> Vec<Felt> {
<Self as SequentialCommit>::to_elements(self)
}
}
impl Serializable for SigningInputs {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
SigningInputs::TransactionSummary(tx_summary) => {
target.write_u8(0);
tx_summary.as_ref().write_into(target);
},
SigningInputs::Arbitrary(elements) => {
target.write_u8(1);
elements.write_into(target);
},
SigningInputs::Blind(word) => {
target.write_u8(2);
word.write_into(target);
},
}
}
}
impl Deserializable for SigningInputs {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let discriminant = source.read_u8()?;
match discriminant {
0 => {
let tx_summary: TransactionSummary = source.read()?;
Ok(SigningInputs::TransactionSummary(Box::new(tx_summary)))
},
1 => {
let elements: Vec<Felt> = source.read()?;
Ok(SigningInputs::Arbitrary(elements))
},
2 => {
let word: Word = source.read()?;
Ok(SigningInputs::Blind(word))
},
other => Err(DeserializationError::InvalidValue(format!(
"invalid SigningInputs variant: {other}"
))),
}
}
}
pub trait TransactionAuthenticator {
fn get_signature(
&self,
pub_key_commitment: PublicKeyCommitment,
signing_inputs: &SigningInputs,
) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>>;
fn get_public_key(
&self,
pub_key_commitment: PublicKeyCommitment,
) -> impl FutureMaybeSend<Option<Arc<PublicKey>>>;
}
#[derive(Debug, Clone, Copy)]
pub struct UnreachableAuth {
_protect: core::marker::PhantomData<u8>,
}
impl TransactionAuthenticator for UnreachableAuth {
#[allow(clippy::manual_async_fn)]
fn get_signature(
&self,
_pub_key_commitment: PublicKeyCommitment,
_signing_inputs: &SigningInputs,
) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
async { unreachable!("Type `UnreachableAuth` must not be instantiated") }
}
fn get_public_key(
&self,
_pub_key_commitment: PublicKeyCommitment,
) -> impl FutureMaybeSend<Option<Arc<PublicKey>>> {
async { unreachable!("Type `UnreachableAuth` must not be instantiated") }
}
}
#[derive(Clone, Debug)]
pub struct BasicAuthenticator {
keys: BTreeMap<PublicKeyCommitment, (AuthSecretKey, Arc<PublicKey>)>,
}
impl BasicAuthenticator {
pub fn new(keys: &[AuthSecretKey]) -> Self {
let mut key_map = BTreeMap::new();
for secret_key in keys {
let pub_key = secret_key.public_key();
key_map.insert(pub_key.to_commitment(), (secret_key.clone(), pub_key.into()));
}
BasicAuthenticator { keys: key_map }
}
pub fn from_key_pairs(keys: &[(AuthSecretKey, PublicKey)]) -> Self {
let mut key_map = BTreeMap::new();
for (secret_key, public_key) in keys {
key_map.insert(
public_key.to_commitment(),
(secret_key.clone(), public_key.clone().into()),
);
}
BasicAuthenticator { keys: key_map }
}
pub fn keys(&self) -> &BTreeMap<PublicKeyCommitment, (AuthSecretKey, Arc<PublicKey>)> {
&self.keys
}
}
impl TransactionAuthenticator for BasicAuthenticator {
fn get_signature(
&self,
pub_key_commitment: PublicKeyCommitment,
signing_inputs: &SigningInputs,
) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
let message = signing_inputs.to_commitment();
async move {
match self.keys.get(&pub_key_commitment) {
Some((auth_key, _)) => Ok(auth_key.sign(message)),
None => Err(AuthenticationError::UnknownPublicKey(pub_key_commitment)),
}
}
}
fn get_public_key(
&self,
pub_key_commitment: PublicKeyCommitment,
) -> impl FutureMaybeSend<Option<Arc<PublicKey>>> {
async move { self.keys.get(&pub_key_commitment).map(|(_, pub_key)| pub_key.clone()) }
}
}
impl TransactionAuthenticator for () {
#[allow(clippy::manual_async_fn)]
fn get_signature(
&self,
_pub_key_commitment: PublicKeyCommitment,
_signing_inputs: &SigningInputs,
) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
async {
Err(AuthenticationError::RejectedSignature(
"default authenticator cannot provide signatures".to_string(),
))
}
}
fn get_public_key(
&self,
_pub_key_commitment: PublicKeyCommitment,
) -> impl FutureMaybeSend<Option<Arc<PublicKey>>> {
async { None }
}
}
#[cfg(test)]
mod test {
use miden_protocol::account::auth::AuthSecretKey;
use miden_protocol::utils::serde::{Deserializable, Serializable};
use miden_protocol::{Felt, Word};
use super::SigningInputs;
#[test]
fn serialize_auth_key() {
let auth_key = AuthSecretKey::new_falcon512_poseidon2();
let serialized = auth_key.to_bytes();
let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap();
assert_eq!(auth_key, deserialized);
}
#[test]
fn serialize_deserialize_signing_inputs_arbitrary() {
let elements = vec![
Felt::new(0),
Felt::new(1),
Felt::new(2),
Felt::new(3),
Felt::new(4),
Felt::new(5),
Felt::new(6),
Felt::new(7),
];
let inputs = SigningInputs::Arbitrary(elements.clone());
let bytes = inputs.to_bytes();
let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
match decoded {
SigningInputs::Arbitrary(decoded_elements) => {
assert_eq!(decoded_elements, elements);
},
_ => panic!("expected Arbitrary variant"),
}
}
#[test]
fn serialize_deserialize_signing_inputs_blind() {
let word = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
let inputs = SigningInputs::Blind(word);
let bytes = inputs.to_bytes();
let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
match decoded {
SigningInputs::Blind(w) => {
assert_eq!(w, word);
},
_ => panic!("expected Blind variant"),
}
}
}