use borsh::{BorshDeserialize, BorshSerialize};
use unc_crypto::{Signature, Signer};
use unc_primitives_core::hash::hash;
use unc_primitives_core::types::AccountId;
const MIN_ON_CHAIN_DISCRIMINANT: u32 = 1 << 30;
const MAX_ON_CHAIN_DISCRIMINANT: u32 = (1 << 31) - 1;
const MIN_OFF_CHAIN_DISCRIMINANT: u32 = 1 << 31;
const MAX_OFF_CHAIN_DISCRIMINANT: u32 = u32::MAX;
const UIP_META_TRANSACTIONS: u32 = 366;
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
serde::Serialize,
serde::Deserialize,
)]
pub struct MessageDiscriminant {
discriminant: u32,
}
#[derive(BorshSerialize)]
pub struct SignableMessage<'a, T> {
pub discriminant: MessageDiscriminant,
pub msg: &'a T,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum SignableMessageType {
DelegateAction,
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ReadDiscriminantError {
#[error("does not fit any known categories")]
UnknownMessageType,
#[error("UIP {0} does not have a known on-chain use")]
UnknownOnChainNep(u32),
#[error("UIP {0} does not have a known off-chain use")]
UnknownOffChainNep(u32),
#[error("discriminant is in the range for transactions")]
TransactionFound,
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum CreateDiscriminantError {
#[error("nep number {0} is too big")]
NepTooLarge(u32),
}
impl<'a, T: BorshSerialize> SignableMessage<'a, T> {
pub fn new(msg: &'a T, ty: SignableMessageType) -> Self {
let discriminant = ty.into();
Self { discriminant, msg }
}
pub fn sign(&self, signer: &dyn Signer) -> Signature {
let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
let hash = hash(&bytes);
signer.sign(hash.as_bytes())
}
}
impl MessageDiscriminant {
pub fn new_on_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
if nep > MAX_ON_CHAIN_DISCRIMINANT - MIN_ON_CHAIN_DISCRIMINANT {
Err(CreateDiscriminantError::NepTooLarge(nep))
} else {
Ok(Self {
discriminant: MIN_ON_CHAIN_DISCRIMINANT + nep,
})
}
}
pub fn new_off_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
if nep > MAX_OFF_CHAIN_DISCRIMINANT - MIN_OFF_CHAIN_DISCRIMINANT {
Err(CreateDiscriminantError::NepTooLarge(nep))
} else {
Ok(Self {
discriminant: MIN_OFF_CHAIN_DISCRIMINANT + nep,
})
}
}
pub fn raw_discriminant(&self) -> u32 {
self.discriminant
}
pub fn is_transaction(&self) -> bool {
self.discriminant >= AccountId::MIN_LEN as u32
&& self.discriminant <= AccountId::MAX_LEN as u32
}
pub fn on_chain_nep(&self) -> Option<u32> {
if self.discriminant < MIN_ON_CHAIN_DISCRIMINANT
|| self.discriminant > MAX_ON_CHAIN_DISCRIMINANT
{
None
} else {
let nep = self.discriminant - MIN_ON_CHAIN_DISCRIMINANT;
Some(nep)
}
}
#[allow(clippy::absurd_extreme_comparisons)]
pub fn off_chain_nep(&self) -> Option<u32> {
if self.discriminant < MIN_OFF_CHAIN_DISCRIMINANT
|| self.discriminant > MAX_OFF_CHAIN_DISCRIMINANT
{
None
} else {
let nep = self.discriminant - MIN_OFF_CHAIN_DISCRIMINANT;
Some(nep)
}
}
}
impl TryFrom<MessageDiscriminant> for SignableMessageType {
type Error = ReadDiscriminantError;
fn try_from(discriminant: MessageDiscriminant) -> Result<Self, Self::Error> {
if discriminant.is_transaction() {
Err(Self::Error::TransactionFound)
} else if let Some(nep) = discriminant.on_chain_nep() {
match nep {
UIP_META_TRANSACTIONS => Ok(Self::DelegateAction),
_ => Err(Self::Error::UnknownOnChainNep(nep)),
}
} else if let Some(nep) = discriminant.off_chain_nep() {
Err(Self::Error::UnknownOffChainNep(nep))
} else {
Err(Self::Error::UnknownMessageType)
}
}
}
impl From<SignableMessageType> for MessageDiscriminant {
fn from(ty: SignableMessageType) -> Self {
match ty {
SignableMessageType::DelegateAction => {
MessageDiscriminant::new_on_chain(UIP_META_TRANSACTIONS).unwrap()
}
}
}
}
#[cfg(test)]
mod tests {
use unc_crypto::{InMemorySigner, KeyType, PublicKey};
use unc_primitives_core::account::id::AccountIdRef;
use super::*;
use crate::action::delegate::{DelegateAction, SignedDelegateAction};
fn create_user_test_signer(account_id: &AccountIdRef) -> InMemorySigner {
InMemorySigner::from_seed(account_id.to_owned(), KeyType::ED25519, account_id.as_str())
}
#[test]
fn nep_366_ok() {
let sender_id: AccountId = "alice.unc".parse().unwrap();
let receiver_id: AccountId = "bob.unc".parse().unwrap();
let signer = create_user_test_signer(&sender_id);
let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
assert!(signed.verify());
}
#[test]
fn nep_366_wrong_nep() {
let sender_id: AccountId = "alice.unc".parse().unwrap();
let receiver_id: AccountId = "bob.unc".parse().unwrap();
let signer = create_user_test_signer(&sender_id);
let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
let wrong_nep = 777;
let signable = SignableMessage {
discriminant: MessageDiscriminant::new_on_chain(wrong_nep).unwrap(),
msg: &delegate_action,
};
let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
assert!(!signed.verify());
}
#[test]
fn nep_366_wrong_msg_type() {
let sender_id: AccountId = "alice.unc".parse().unwrap();
let receiver_id: AccountId = "bob.unc".parse().unwrap();
let signer = create_user_test_signer(&sender_id);
let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
let correct_nep = 366;
let wrong_discriminant = MessageDiscriminant::new_off_chain(correct_nep).unwrap();
let signable = SignableMessage { discriminant: wrong_discriminant, msg: &delegate_action };
let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
assert!(!signed.verify());
}
fn delegate_action(
sender_id: AccountId,
receiver_id: AccountId,
public_key: PublicKey,
) -> DelegateAction {
let delegate_action = DelegateAction {
sender_id,
receiver_id,
actions: vec![],
nonce: 0,
max_block_height: 1000,
public_key,
};
delegate_action
}
}