near_primitives/
signable_message.rs1use borsh::{BorshDeserialize, BorshSerialize};
2use near_crypto::{Signature, Signer};
3use near_primitives_core::hash::hash;
4use near_primitives_core::types::AccountId;
5use near_schema_checker_lib::ProtocolSchema;
6
7const MIN_ON_CHAIN_DISCRIMINANT: u32 = 1 << 30;
19const MAX_ON_CHAIN_DISCRIMINANT: u32 = (1 << 31) - 1;
20const MIN_OFF_CHAIN_DISCRIMINANT: u32 = 1 << 31;
21const MAX_OFF_CHAIN_DISCRIMINANT: u32 = u32::MAX;
22
23const NEP_366_META_TRANSACTIONS: u32 = 366;
25
26#[derive(
36 Debug,
37 Clone,
38 Copy,
39 PartialEq,
40 Eq,
41 PartialOrd,
42 Ord,
43 Hash,
44 BorshSerialize,
45 BorshDeserialize,
46 serde::Serialize,
47 serde::Deserialize,
48 ProtocolSchema,
49)]
50pub struct MessageDiscriminant {
51 discriminant: u32,
53}
54
55#[derive(BorshSerialize)]
61pub struct SignableMessage<'a, T> {
62 pub discriminant: MessageDiscriminant,
63 pub msg: &'a T,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[non_exhaustive]
68pub enum SignableMessageType {
69 DelegateAction,
71}
72
73#[derive(thiserror::Error, Debug)]
74#[non_exhaustive]
75pub enum ReadDiscriminantError {
76 #[error("does not fit any known categories")]
77 UnknownMessageType,
78 #[error("NEP {0} does not have a known on-chain use")]
79 UnknownOnChainNep(u32),
80 #[error("NEP {0} does not have a known off-chain use")]
81 UnknownOffChainNep(u32),
82 #[error("discriminant is in the range for transactions")]
83 TransactionFound,
84}
85
86#[derive(thiserror::Error, Debug)]
87#[non_exhaustive]
88pub enum CreateDiscriminantError {
89 #[error("nep number {0} is too big")]
90 NepTooLarge(u32),
91}
92
93impl<'a, T: BorshSerialize> SignableMessage<'a, T> {
94 pub fn new(msg: &'a T, ty: SignableMessageType) -> Self {
95 let discriminant = ty.into();
96 Self { discriminant, msg }
97 }
98
99 pub fn sign(&self, signer: &Signer) -> Signature {
100 let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
101 let hash = hash(&bytes);
102 signer.sign(hash.as_bytes())
103 }
104}
105
106impl MessageDiscriminant {
107 pub fn new_on_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
114 if nep > MAX_ON_CHAIN_DISCRIMINANT - MIN_ON_CHAIN_DISCRIMINANT {
116 Err(CreateDiscriminantError::NepTooLarge(nep))
117 } else {
118 Ok(Self {
119 discriminant: MIN_ON_CHAIN_DISCRIMINANT + nep,
121 })
122 }
123 }
124
125 pub fn new_off_chain(nep: u32) -> Result<Self, CreateDiscriminantError> {
132 if nep > MAX_OFF_CHAIN_DISCRIMINANT - MIN_OFF_CHAIN_DISCRIMINANT {
134 Err(CreateDiscriminantError::NepTooLarge(nep))
135 } else {
136 Ok(Self {
137 discriminant: MIN_OFF_CHAIN_DISCRIMINANT + nep,
139 })
140 }
141 }
142
143 pub fn raw_discriminant(&self) -> u32 {
145 self.discriminant
146 }
147
148 pub fn is_transaction(&self) -> bool {
150 self.discriminant >= AccountId::MIN_LEN as u32
156 && self.discriminant <= AccountId::MAX_LEN as u32
157 }
158
159 pub fn on_chain_nep(&self) -> Option<u32> {
162 if self.discriminant < MIN_ON_CHAIN_DISCRIMINANT
163 || self.discriminant > MAX_ON_CHAIN_DISCRIMINANT
164 {
165 None
166 } else {
167 let nep = self.discriminant - MIN_ON_CHAIN_DISCRIMINANT;
169 Some(nep)
170 }
171 }
172
173 #[allow(clippy::absurd_extreme_comparisons)]
180 pub fn off_chain_nep(&self) -> Option<u32> {
181 if self.discriminant < MIN_OFF_CHAIN_DISCRIMINANT
182 || self.discriminant > MAX_OFF_CHAIN_DISCRIMINANT
183 {
184 None
185 } else {
186 let nep = self.discriminant - MIN_OFF_CHAIN_DISCRIMINANT;
188 Some(nep)
189 }
190 }
191}
192
193impl TryFrom<MessageDiscriminant> for SignableMessageType {
194 type Error = ReadDiscriminantError;
195
196 fn try_from(discriminant: MessageDiscriminant) -> Result<Self, Self::Error> {
197 if discriminant.is_transaction() {
198 Err(Self::Error::TransactionFound)
199 } else if let Some(nep) = discriminant.on_chain_nep() {
200 match nep {
201 NEP_366_META_TRANSACTIONS => Ok(Self::DelegateAction),
202 _ => Err(Self::Error::UnknownOnChainNep(nep)),
203 }
204 } else if let Some(nep) = discriminant.off_chain_nep() {
205 Err(Self::Error::UnknownOffChainNep(nep))
206 } else {
207 Err(Self::Error::UnknownMessageType)
208 }
209 }
210}
211
212impl From<SignableMessageType> for MessageDiscriminant {
213 fn from(ty: SignableMessageType) -> Self {
214 match ty {
216 SignableMessageType::DelegateAction => {
217 MessageDiscriminant::new_on_chain(NEP_366_META_TRANSACTIONS).unwrap()
218 }
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use near_crypto::{InMemorySigner, PublicKey};
226
227 use super::*;
228 use crate::action::delegate::{DelegateAction, SignedDelegateAction};
229
230 #[test]
232 fn nep_366_ok() {
233 let sender_id: AccountId = "alice.near".parse().unwrap();
234 let receiver_id: AccountId = "bob.near".parse().unwrap();
235 let signer = InMemorySigner::test_signer(&sender_id);
236
237 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
238 let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
239 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
240
241 assert!(signed.verify());
242 }
243
244 #[test]
246 fn nep_366_wrong_nep() {
247 let sender_id: AccountId = "alice.near".parse().unwrap();
248 let receiver_id: AccountId = "bob.near".parse().unwrap();
249 let signer = InMemorySigner::test_signer(&sender_id);
250
251 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
252 let wrong_nep = 777;
253 let signable = SignableMessage {
254 discriminant: MessageDiscriminant::new_on_chain(wrong_nep).unwrap(),
255 msg: &delegate_action,
256 };
257 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
258
259 assert!(!signed.verify());
260 }
261
262 #[test]
264 fn nep_366_wrong_msg_type() {
265 let sender_id: AccountId = "alice.near".parse().unwrap();
266 let receiver_id: AccountId = "bob.near".parse().unwrap();
267 let signer = InMemorySigner::test_signer(&sender_id);
268
269 let delegate_action = delegate_action(sender_id, receiver_id, signer.public_key());
270 let correct_nep = 366;
271 let wrong_discriminant = MessageDiscriminant::new_off_chain(correct_nep).unwrap();
273 let signable = SignableMessage { discriminant: wrong_discriminant, msg: &delegate_action };
274 let signed = SignedDelegateAction { signature: signable.sign(&signer), delegate_action };
275
276 assert!(!signed.verify());
277 }
278
279 fn delegate_action(
280 sender_id: AccountId,
281 receiver_id: AccountId,
282 public_key: PublicKey,
283 ) -> DelegateAction {
284 let delegate_action = DelegateAction {
285 sender_id,
286 receiver_id,
287 actions: vec![],
288 nonce: 0,
289 max_block_height: 1000,
290 public_key,
291 };
292 delegate_action
293 }
294}