unc_primitives/
signable_message.rs1use borsh::{BorshDeserialize, BorshSerialize};
2use unc_crypto::{Signature, Signer};
3use unc_primitives_core::hash::hash;
4use unc_primitives_core::types::AccountId;
5
6const MIN_ON_CHAIN_DISCRIMINANT: u32 = 1 << 30;
17const MAX_ON_CHAIN_DISCRIMINANT: u32 = (1 << 31) - 1;
18const MIN_OFF_CHAIN_DISCRIMINANT: u32 = 1 << 31;
19const MAX_OFF_CHAIN_DISCRIMINANT: u32 = u32::MAX;
20
21const UIP_META_TRANSACTIONS: u32 = 366;
22
23#[derive(
33 Debug,
34 Clone,
35 Copy,
36 PartialEq,
37 Eq,
38 PartialOrd,
39 Ord,
40 Hash,
41 BorshSerialize,
42 BorshDeserialize,
43 serde::Serialize,
44 serde::Deserialize,
45)]
46pub struct MessageDiscriminant {
47 discriminant: u32,
49}
50
51#[derive(BorshSerialize)]
57pub struct SignableMessage<'a, T> {
58 pub discriminant: MessageDiscriminant,
59 pub msg: &'a T,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[non_exhaustive]
64pub enum SignableMessageType {
65 DelegateAction,
67}
68
69#[derive(thiserror::Error, Debug)]
70#[non_exhaustive]
71pub enum ReadDiscriminantError {
72 #[error("does not fit any known categories")]
73 UnknownMessageType,
74 #[error("UIP {0} does not have a known on-chain use")]
75 UnknownOnChainNep(u32),
76 #[error("UIP {0} does not have a known off-chain use")]
77 UnknownOffChainNep(u32),
78 #[error("discriminant is in the range for transactions")]
79 TransactionFound,
80}
81
82#[derive(thiserror::Error, Debug)]
83#[non_exhaustive]
84pub enum CreateDiscriminantError {
85 #[error("nep number {0} is too big")]
86 NepTooLarge(u32),
87}
88
89impl<'a, T: BorshSerialize> SignableMessage<'a, T> {
90 pub fn new(msg: &'a T, ty: SignableMessageType) -> Self {
91 let discriminant = ty.into();
92 Self { discriminant, msg }
93 }
94
95 pub fn sign(&self, signer: &dyn Signer) -> Signature {
96 let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
97 let hash = hash(&bytes);
98 signer.sign(hash.as_bytes())
99 }
100}
101
102impl MessageDiscriminant {
103 pub fn new_on_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
109 if nep > MAX_ON_CHAIN_DISCRIMINANT - MIN_ON_CHAIN_DISCRIMINANT {
111 Err(CreateDiscriminantError::NepTooLarge(nep))
112 } else {
113 Ok(Self {
114 discriminant: MIN_ON_CHAIN_DISCRIMINANT + nep,
116 })
117 }
118 }
119
120 pub fn new_off_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
126 if nep > MAX_OFF_CHAIN_DISCRIMINANT - MIN_OFF_CHAIN_DISCRIMINANT {
128 Err(CreateDiscriminantError::NepTooLarge(nep))
129 } else {
130 Ok(Self {
131 discriminant: MIN_OFF_CHAIN_DISCRIMINANT + nep,
133 })
134 }
135 }
136
137 pub fn raw_discriminant(&self) -> u32 {
139 self.discriminant
140 }
141
142 pub fn is_transaction(&self) -> bool {
144 self.discriminant >= AccountId::MIN_LEN as u32
150 && self.discriminant <= AccountId::MAX_LEN as u32
151 }
152
153 pub fn on_chain_nep(&self) -> Option<u32> {
156 if self.discriminant < MIN_ON_CHAIN_DISCRIMINANT
157 || self.discriminant > MAX_ON_CHAIN_DISCRIMINANT
158 {
159 None
160 } else {
161 let nep = self.discriminant - MIN_ON_CHAIN_DISCRIMINANT;
163 Some(nep)
164 }
165 }
166
167 #[allow(clippy::absurd_extreme_comparisons)]
174 pub fn off_chain_nep(&self) -> Option<u32> {
175 if self.discriminant < MIN_OFF_CHAIN_DISCRIMINANT
176 || self.discriminant > MAX_OFF_CHAIN_DISCRIMINANT
177 {
178 None
179 } else {
180 let nep = self.discriminant - MIN_OFF_CHAIN_DISCRIMINANT;
182 Some(nep)
183 }
184 }
185}
186
187impl TryFrom<MessageDiscriminant> for SignableMessageType {
188 type Error = ReadDiscriminantError;
189
190 fn try_from(discriminant: MessageDiscriminant) -> Result<Self, Self::Error> {
191 if discriminant.is_transaction() {
192 Err(Self::Error::TransactionFound)
193 } else if let Some(nep) = discriminant.on_chain_nep() {
194 match nep {
195 UIP_META_TRANSACTIONS => Ok(Self::DelegateAction),
196 _ => Err(Self::Error::UnknownOnChainNep(nep)),
197 }
198 } else if let Some(nep) = discriminant.off_chain_nep() {
199 Err(Self::Error::UnknownOffChainNep(nep))
200 } else {
201 Err(Self::Error::UnknownMessageType)
202 }
203 }
204}
205
206impl From<SignableMessageType> for MessageDiscriminant {
207 fn from(ty: SignableMessageType) -> Self {
208 match ty {
210 SignableMessageType::DelegateAction => {
211 MessageDiscriminant::new_on_chain(UIP_META_TRANSACTIONS).unwrap()
212 }
213 }
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use unc_crypto::{InMemorySigner, KeyType, PublicKey};
220 use unc_primitives_core::account::id::AccountIdRef;
221
222 use super::*;
223 use crate::action::delegate::{DelegateAction, SignedDelegateAction};
224
225 fn create_user_test_signer(account_id: &AccountIdRef) -> InMemorySigner {
228 InMemorySigner::from_seed(account_id.to_owned(), KeyType::ED25519, account_id.as_str())
229 }
230
231 #[test]
233 fn nep_366_ok() {
234 let sender_id: AccountId = "alice.unc".parse().unwrap();
235 let receiver_id: AccountId = "bob.unc".parse().unwrap();
236 let signer = create_user_test_signer(&sender_id);
237
238 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
239 let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
240 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
241
242 assert!(signed.verify());
243 }
244
245 #[test]
247 fn nep_366_wrong_nep() {
248 let sender_id: AccountId = "alice.unc".parse().unwrap();
249 let receiver_id: AccountId = "bob.unc".parse().unwrap();
250 let signer = create_user_test_signer(&sender_id);
251
252 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
253 let wrong_nep = 777;
254 let signable = SignableMessage {
255 discriminant: MessageDiscriminant::new_on_chain(wrong_nep).unwrap(),
256 msg: &delegate_action,
257 };
258 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
259
260 assert!(!signed.verify());
261 }
262
263 #[test]
265 fn nep_366_wrong_msg_type() {
266 let sender_id: AccountId = "alice.unc".parse().unwrap();
267 let receiver_id: AccountId = "bob.unc".parse().unwrap();
268 let signer = create_user_test_signer(&sender_id);
269
270 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
271 let correct_nep = 366;
272 let wrong_discriminant = MessageDiscriminant::new_off_chain(correct_nep).unwrap();
274 let signable = SignableMessage { discriminant: wrong_discriminant, msg: &delegate_action };
275 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
276
277 assert!(!signed.verify());
278 }
279
280 fn delegate_action(
281 sender_id: AccountId,
282 receiver_id: AccountId,
283 public_key: PublicKey,
284 ) -> DelegateAction {
285 let delegate_action = DelegateAction {
286 sender_id,
287 receiver_id,
288 actions: vec![],
289 nonce: 0,
290 max_block_height: 1000,
291 public_key,
292 };
293 delegate_action
294 }
295}