use std::{num::NonZeroU64, ops::Not};
use indexmap::IndexSet;
use tari_crypto::ristretto::RistrettoPublicKey;
use tari_ootle_wallet_crypto::{memo::Memo, pay_to::PayTo};
use tari_template_lib_types::{ResourceAddress, crypto::UtxoTag};
use crate::Address;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct StealthSignerRequirement {
signer: Address,
public_nonce: RistrettoPublicKey,
}
impl StealthSignerRequirement {
pub fn new(signer: Address, public_nonce: RistrettoPublicKey) -> Self {
Self { signer, public_nonce }
}
pub fn signer(&self) -> &Address {
&self.signer
}
pub fn public_nonce(&self) -> &RistrettoPublicKey {
&self.public_nonce
}
}
#[derive(Debug, Clone)]
pub struct SignatureRequirements {
required_signers: IndexSet<StealthSignerRequirement>,
must_sign_with_account_key: bool,
seal_signer: Option<StealthSignerRequirement>,
}
impl SignatureRequirements {
pub fn new_must_sign_with_account_key(required_signers: IndexSet<StealthSignerRequirement>) -> Self {
Self {
required_signers,
must_sign_with_account_key: true,
seal_signer: None,
}
}
pub fn new_opt_with_seal_signer(
required_signers: IndexSet<StealthSignerRequirement>,
seal_signer: Option<StealthSignerRequirement>,
) -> Self {
Self {
required_signers,
must_sign_with_account_key: false,
seal_signer,
}
}
pub fn must_sign_with_account_key(&self) -> bool {
self.must_sign_with_account_key
}
pub fn can_sign_with_ephemeral_key(&self) -> bool {
!self.must_sign_with_account_key && self.required_signers.is_empty() && self.seal_signer.is_none()
}
pub fn seal_signer(&self) -> Option<&StealthSignerRequirement> {
self.must_sign_with_account_key
.not()
.then(|| self.seal_signer.as_ref().or_else(|| self.required_signers.first()))
.flatten()
}
pub fn other_signers(&self) -> impl Iterator<Item = &StealthSignerRequirement> {
let skip = usize::from(!self.must_sign_with_account_key && self.seal_signer.is_none());
self.required_signers.iter().skip(skip)
}
pub fn len(&self) -> usize {
self.required_signers.len()
}
pub fn is_empty(&self) -> bool {
self.required_signers.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct Output {
pub destination: Address,
pub amount: NonZeroU64,
pub resource_address: ResourceAddress,
pub resource_view_key: Option<RistrettoPublicKey>,
pub memo: Option<Memo>,
pub pay_to: PayTo,
pub utxo_tag: Option<UtxoTag>,
pub minimum_value_promise: u64,
}
impl Output {
pub fn new(destination: Address, resource_address: ResourceAddress, amount: NonZeroU64) -> Self {
Self {
destination,
amount,
resource_address,
resource_view_key: None,
memo: None,
pay_to: PayTo::default(),
utxo_tag: None,
minimum_value_promise: 0,
}
}
pub fn with_resource_view_key(mut self, resource_view_key: RistrettoPublicKey) -> Self {
self.resource_view_key = Some(resource_view_key);
self
}
pub fn with_memo(mut self, memo: Memo) -> Self {
self.memo = Some(memo);
self
}
pub fn with_memo_message<T: Into<Box<str>>>(self, message: T) -> Self {
self.with_memo(Memo::new_message(message).expect("Memo message too long"))
}
pub fn with_pay_to(mut self, pay_to: PayTo) -> Self {
self.pay_to = pay_to;
self
}
pub fn with_utxo_tag(mut self, utxo_tag: UtxoTag) -> Self {
self.utxo_tag = Some(utxo_tag);
self
}
}
#[cfg(test)]
mod tests {
use ootle_byte_type::ToByteType;
use tari_crypto::{
keys::{PublicKey, SecretKey},
ristretto::RistrettoSecretKey,
};
use tari_ootle_common_types::Network;
use super::*;
fn signer_from_seed(seed: u8) -> StealthSignerRequirement {
let secret = RistrettoSecretKey::from_uniform_bytes(&[seed; 64]).unwrap();
let pk = RistrettoPublicKey::from_secret_key(&secret);
let addr = Address::new(Network::LocalNet, pk.to_byte_type(), pk.to_byte_type());
StealthSignerRequirement::new(addr, pk)
}
mod signature_requirement_invariants {
use super::*;
#[test]
fn invariant1() {
let signer1 = signer_from_seed(1);
let signer2 = signer_from_seed(2);
let mut required_signers = IndexSet::new();
required_signers.insert(signer1.clone());
required_signers.insert(signer2.clone());
let spec = SignatureRequirements::new_must_sign_with_account_key(required_signers);
assert!(spec.must_sign_with_account_key());
assert!(!spec.can_sign_with_ephemeral_key());
assert_eq!(spec.seal_signer(), None);
let other_signers = spec.other_signers().collect::<Vec<_>>();
assert_eq!(other_signers, vec![&signer1, &signer2]);
}
#[test]
fn invariant2() {
let signer1 = signer_from_seed(1);
let signer2 = signer_from_seed(2);
let mut required_signers = IndexSet::new();
required_signers.insert(signer1.clone());
required_signers.insert(signer2.clone());
let spec = SignatureRequirements::new_opt_with_seal_signer(required_signers, None);
assert!(!spec.must_sign_with_account_key());
assert!(!spec.can_sign_with_ephemeral_key());
let seal_signer = spec.seal_signer();
assert_eq!(seal_signer, Some(&signer1));
let other_signers = spec.other_signers().collect::<Vec<_>>();
assert_eq!(other_signers, vec![&signer2]);
let signer3 = signer_from_seed(3);
let mut required_signers = IndexSet::new();
required_signers.insert(signer1.clone());
required_signers.insert(signer3.clone());
let spec = SignatureRequirements::new_opt_with_seal_signer(required_signers, Some(signer2.clone()));
assert!(!spec.must_sign_with_account_key());
assert!(!spec.can_sign_with_ephemeral_key());
let seal_signer = spec.seal_signer();
assert_eq!(seal_signer, Some(&signer2));
let other_signers = spec.other_signers().collect::<Vec<_>>();
assert_eq!(other_signers, vec![&signer1, &signer3]);
}
#[test]
fn invariant3() {
let spec = SignatureRequirements::new_opt_with_seal_signer(Default::default(), None);
assert!(!spec.must_sign_with_account_key());
assert!(spec.can_sign_with_ephemeral_key());
let seal_signer = spec.seal_signer();
assert_eq!(seal_signer, None);
assert_eq!(spec.other_signers().next(), None);
}
}
}