1#![warn(missing_docs)]
58
59use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
60use codec::Encode;
61use sp_runtime::{
62 app_crypto::RuntimeAppPublic,
63 traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One},
64 RuntimeDebug,
65};
66
67pub struct ForAll {}
69pub struct ForAny {}
71
72pub struct SubmitTransaction<T: SendTransactionTypes<OverarchingCall>, OverarchingCall> {
79 _phantom: core::marker::PhantomData<(T, OverarchingCall)>,
80}
81
82impl<T, LocalCall> SubmitTransaction<T, LocalCall>
83where
84 T: SendTransactionTypes<LocalCall>,
85{
86 pub fn submit_transaction(
88 call: <T as SendTransactionTypes<LocalCall>>::OverarchingCall,
89 signature: Option<<T::Extrinsic as ExtrinsicT>::SignaturePayload>,
90 ) -> Result<(), ()> {
91 let xt = T::Extrinsic::new(call, signature).ok_or(())?;
92 sp_io::offchain::submit_transaction(xt.encode())
93 }
94
95 pub fn submit_unsigned_transaction(
97 call: <T as SendTransactionTypes<LocalCall>>::OverarchingCall,
98 ) -> Result<(), ()> {
99 SubmitTransaction::<T, LocalCall>::submit_transaction(call, None)
100 }
101}
102
103#[derive(RuntimeDebug)]
116pub struct Signer<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X = ForAny> {
117 accounts: Option<Vec<T::Public>>,
118 _phantom: core::marker::PhantomData<(X, C)>,
119}
120
121impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Default for Signer<T, C, X> {
122 fn default() -> Self {
123 Self { accounts: Default::default(), _phantom: Default::default() }
124 }
125}
126
127impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>, X> Signer<T, C, X> {
128 pub fn all_accounts() -> Signer<T, C, ForAll> {
130 Default::default()
131 }
132
133 pub fn any_account() -> Signer<T, C, ForAny> {
135 Default::default()
136 }
137
138 pub fn with_filter(mut self, accounts: Vec<T::Public>) -> Self {
144 self.accounts = Some(accounts);
145 self
146 }
147
148 pub fn can_sign(&self) -> bool {
150 self.accounts_from_keys().count() > 0
151 }
152
153 pub fn accounts_from_keys<'a>(&'a self) -> Box<dyn Iterator<Item = Account<T>> + 'a> {
158 let keystore_accounts = Self::keystore_accounts();
159 match self.accounts {
160 None => Box::new(keystore_accounts),
161 Some(ref keys) => {
162 let keystore_lookup: BTreeSet<<T as SigningTypes>::Public> =
163 keystore_accounts.map(|account| account.public).collect();
164
165 Box::new(
166 keys.iter()
167 .enumerate()
168 .map(|(index, key)| {
169 let account_id = key.clone().into_account();
170 Account::new(index, account_id, key.clone())
171 })
172 .filter(move |account| keystore_lookup.contains(&account.public)),
173 )
174 },
175 }
176 }
177
178 pub fn keystore_accounts() -> impl Iterator<Item = Account<T>> {
180 C::RuntimeAppPublic::all().into_iter().enumerate().map(|(index, key)| {
181 let generic_public = C::GenericPublic::from(key);
182 let public: T::Public = generic_public.into();
183 let account_id = public.clone().into_account();
184 Account::new(index, account_id, public)
185 })
186 }
187}
188
189impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAll> {
190 fn for_all<F, R>(&self, f: F) -> Vec<(Account<T>, R)>
191 where
192 F: Fn(&Account<T>) -> Option<R>,
193 {
194 let accounts = self.accounts_from_keys();
195 accounts
196 .into_iter()
197 .filter_map(|account| f(&account).map(|res| (account, res)))
198 .collect()
199 }
200}
201
202impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> Signer<T, C, ForAny> {
203 fn for_any<F, R>(&self, f: F) -> Option<(Account<T>, R)>
204 where
205 F: Fn(&Account<T>) -> Option<R>,
206 {
207 let accounts = self.accounts_from_keys();
208 for account in accounts.into_iter() {
209 let res = f(&account);
210 if let Some(res) = res {
211 return Some((account, res))
212 }
213 }
214 None
215 }
216}
217
218impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
219 for Signer<T, C, ForAll>
220{
221 type SignatureData = Vec<(Account<T>, T::Signature)>;
222
223 fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
224 self.for_all(|account| C::sign(message, account.public.clone()))
225 }
226
227 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
228 where
229 F: Fn(&Account<T>) -> TPayload,
230 TPayload: SignedPayload<T>,
231 {
232 self.for_all(|account| f(account).sign::<C>())
233 }
234}
235
236impl<T: SigningTypes, C: AppCrypto<T::Public, T::Signature>> SignMessage<T>
237 for Signer<T, C, ForAny>
238{
239 type SignatureData = Option<(Account<T>, T::Signature)>;
240
241 fn sign_message(&self, message: &[u8]) -> Self::SignatureData {
242 self.for_any(|account| C::sign(message, account.public.clone()))
243 }
244
245 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
246 where
247 F: Fn(&Account<T>) -> TPayload,
248 TPayload: SignedPayload<T>,
249 {
250 self.for_any(|account| f(account).sign::<C>())
251 }
252}
253
254impl<
255 T: CreateSignedTransaction<LocalCall> + SigningTypes,
256 C: AppCrypto<T::Public, T::Signature>,
257 LocalCall,
258 > SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAny>
259{
260 type Result = Option<(Account<T>, Result<(), ()>)>;
261
262 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
263 self.for_any(|account| {
264 let call = f(account);
265 self.send_single_signed_transaction(account, call)
266 })
267 }
268}
269
270impl<
271 T: SigningTypes + CreateSignedTransaction<LocalCall>,
272 C: AppCrypto<T::Public, T::Signature>,
273 LocalCall,
274 > SendSignedTransaction<T, C, LocalCall> for Signer<T, C, ForAll>
275{
276 type Result = Vec<(Account<T>, Result<(), ()>)>;
277
278 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result {
279 self.for_all(|account| {
280 let call = f(account);
281 self.send_single_signed_transaction(account, call)
282 })
283 }
284}
285
286impl<
287 T: SigningTypes + SendTransactionTypes<LocalCall>,
288 C: AppCrypto<T::Public, T::Signature>,
289 LocalCall,
290 > SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAny>
291{
292 type Result = Option<(Account<T>, Result<(), ()>)>;
293
294 fn send_unsigned_transaction<TPayload, F>(
295 &self,
296 f: F,
297 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
298 ) -> Self::Result
299 where
300 F: Fn(&Account<T>) -> TPayload,
301 TPayload: SignedPayload<T>,
302 {
303 self.for_any(|account| {
304 let payload = f(account);
305 let signature = payload.sign::<C>()?;
306 let call = f2(payload, signature);
307 self.submit_unsigned_transaction(call)
308 })
309 }
310}
311
312impl<
313 T: SigningTypes + SendTransactionTypes<LocalCall>,
314 C: AppCrypto<T::Public, T::Signature>,
315 LocalCall,
316 > SendUnsignedTransaction<T, LocalCall> for Signer<T, C, ForAll>
317{
318 type Result = Vec<(Account<T>, Result<(), ()>)>;
319
320 fn send_unsigned_transaction<TPayload, F>(
321 &self,
322 f: F,
323 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
324 ) -> Self::Result
325 where
326 F: Fn(&Account<T>) -> TPayload,
327 TPayload: SignedPayload<T>,
328 {
329 self.for_all(|account| {
330 let payload = f(account);
331 let signature = payload.sign::<C>()?;
332 let call = f2(payload, signature);
333 self.submit_unsigned_transaction(call)
334 })
335 }
336}
337
338#[derive(RuntimeDebug, PartialEq)]
340pub struct Account<T: SigningTypes> {
341 pub index: usize,
343 pub id: T::AccountId,
345 pub public: T::Public,
347}
348
349impl<T: SigningTypes> Account<T> {
350 pub fn new(index: usize, id: T::AccountId, public: T::Public) -> Self {
352 Self { index, id, public }
353 }
354}
355
356impl<T: SigningTypes> Clone for Account<T>
357where
358 T::AccountId: Clone,
359 T::Public: Clone,
360{
361 fn clone(&self) -> Self {
362 Self { index: self.index, id: self.id.clone(), public: self.public.clone() }
363 }
364}
365
366pub trait AppCrypto<Public, Signature> {
392 type RuntimeAppPublic: RuntimeAppPublic;
394
395 type GenericPublic: From<Self::RuntimeAppPublic>
397 + Into<Self::RuntimeAppPublic>
398 + TryFrom<Public>
399 + Into<Public>;
400
401 type GenericSignature: From<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
403 + Into<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>
404 + TryFrom<Signature>
405 + Into<Signature>;
406
407 fn sign(payload: &[u8], public: Public) -> Option<Signature> {
409 let p: Self::GenericPublic = public.try_into().ok()?;
410 let x = Into::<Self::RuntimeAppPublic>::into(p);
411 x.sign(&payload)
412 .map(|x| {
413 let sig: Self::GenericSignature = x.into();
414 sig
415 })
416 .map(Into::into)
417 }
418
419 fn verify(payload: &[u8], public: Public, signature: Signature) -> bool {
421 let p: Self::GenericPublic = match public.try_into() {
422 Ok(a) => a,
423 _ => return false,
424 };
425 let x = Into::<Self::RuntimeAppPublic>::into(p);
426 let signature: Self::GenericSignature = match signature.try_into() {
427 Ok(a) => a,
428 _ => return false,
429 };
430 let signature =
431 Into::<<Self::RuntimeAppPublic as RuntimeAppPublic>::Signature>::into(signature);
432
433 x.verify(&payload, &signature)
434 }
435}
436
437pub trait SigningTypes: crate::Config {
444 type Public: Clone
449 + PartialEq
450 + IdentifyAccount<AccountId = Self::AccountId>
451 + core::fmt::Debug
452 + codec::Codec
453 + Ord
454 + scale_info::TypeInfo;
455
456 type Signature: Clone + PartialEq + core::fmt::Debug + codec::Codec + scale_info::TypeInfo;
458}
459
460pub trait SendTransactionTypes<LocalCall> {
462 type Extrinsic: ExtrinsicT<Call = Self::OverarchingCall> + codec::Encode;
464 type OverarchingCall: From<LocalCall> + codec::Encode;
468}
469
470pub trait CreateSignedTransaction<LocalCall>:
478 SendTransactionTypes<LocalCall> + SigningTypes
479{
480 fn create_transaction<C: AppCrypto<Self::Public, Self::Signature>>(
487 call: Self::OverarchingCall,
488 public: Self::Public,
489 account: Self::AccountId,
490 nonce: Self::Nonce,
491 ) -> Option<(Self::OverarchingCall, <Self::Extrinsic as ExtrinsicT>::SignaturePayload)>;
492}
493
494pub trait SignMessage<T: SigningTypes> {
496 type SignatureData;
500
501 fn sign_message(&self, message: &[u8]) -> Self::SignatureData;
506
507 fn sign<TPayload, F>(&self, f: F) -> Self::SignatureData
512 where
513 F: Fn(&Account<T>) -> TPayload,
514 TPayload: SignedPayload<T>;
515}
516
517pub trait SendSignedTransaction<
519 T: SigningTypes + CreateSignedTransaction<LocalCall>,
520 C: AppCrypto<T::Public, T::Signature>,
521 LocalCall,
522>
523{
524 type Result;
528
529 fn send_signed_transaction(&self, f: impl Fn(&Account<T>) -> LocalCall) -> Self::Result;
536
537 fn send_single_signed_transaction(
539 &self,
540 account: &Account<T>,
541 call: LocalCall,
542 ) -> Option<Result<(), ()>> {
543 let mut account_data = crate::Account::<T>::get(&account.id);
544 log::debug!(
545 target: "runtime::offchain",
546 "Creating signed transaction from account: {:?} (nonce: {:?})",
547 account.id,
548 account_data.nonce,
549 );
550 let (call, signature) = T::create_transaction::<C>(
551 call.into(),
552 account.public.clone(),
553 account.id.clone(),
554 account_data.nonce,
555 )?;
556 let res = SubmitTransaction::<T, LocalCall>::submit_transaction(call, Some(signature));
557
558 if res.is_ok() {
559 account_data.nonce += One::one();
562 crate::Account::<T>::insert(&account.id, account_data);
563 }
564
565 Some(res)
566 }
567}
568
569pub trait SendUnsignedTransaction<T: SigningTypes + SendTransactionTypes<LocalCall>, LocalCall> {
571 type Result;
575
576 fn send_unsigned_transaction<TPayload, F>(
583 &self,
584 f: F,
585 f2: impl Fn(TPayload, T::Signature) -> LocalCall,
586 ) -> Self::Result
587 where
588 F: Fn(&Account<T>) -> TPayload,
589 TPayload: SignedPayload<T>;
590
591 fn submit_unsigned_transaction(&self, call: LocalCall) -> Option<Result<(), ()>> {
593 Some(SubmitTransaction::<T, LocalCall>::submit_unsigned_transaction(call.into()))
594 }
595}
596
597pub trait SignedPayload<T: SigningTypes>: Encode {
599 fn public(&self) -> T::Public;
602
603 fn sign<C: AppCrypto<T::Public, T::Signature>>(&self) -> Option<T::Signature> {
607 self.using_encoded(|payload| C::sign(payload, self.public()))
608 }
609
610 fn verify<C: AppCrypto<T::Public, T::Signature>>(&self, signature: T::Signature) -> bool {
614 self.using_encoded(|payload| C::verify(payload, self.public(), signature))
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::mock::{RuntimeCall, Test as TestRuntime, CALL};
622 use codec::Decode;
623 use sp_core::offchain::{testing, TransactionPoolExt};
624 use sp_runtime::testing::{TestSignature, TestXt, UintAuthorityId};
625
626 impl SigningTypes for TestRuntime {
627 type Public = UintAuthorityId;
628 type Signature = TestSignature;
629 }
630
631 type Extrinsic = TestXt<RuntimeCall, ()>;
632
633 impl SendTransactionTypes<RuntimeCall> for TestRuntime {
634 type Extrinsic = Extrinsic;
635 type OverarchingCall = RuntimeCall;
636 }
637
638 #[derive(codec::Encode, codec::Decode)]
639 struct SimplePayload {
640 pub public: UintAuthorityId,
641 pub data: Vec<u8>,
642 }
643
644 impl SignedPayload<TestRuntime> for SimplePayload {
645 fn public(&self) -> UintAuthorityId {
646 self.public.clone()
647 }
648 }
649
650 struct DummyAppCrypto;
651 impl AppCrypto<UintAuthorityId, TestSignature> for DummyAppCrypto {
656 type RuntimeAppPublic = UintAuthorityId;
657 type GenericPublic = UintAuthorityId;
658 type GenericSignature = TestSignature;
659 }
660
661 fn assert_account(next: Option<(Account<TestRuntime>, Result<(), ()>)>, index: usize, id: u64) {
662 assert_eq!(next, Some((Account { index, id, public: id.into() }, Ok(()))));
663 }
664
665 #[test]
666 fn should_send_unsigned_with_signed_payload_with_all_accounts() {
667 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
668
669 let mut t = sp_io::TestExternalities::default();
670 t.register_extension(TransactionPoolExt::new(pool));
671
672 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
674
675 t.execute_with(|| {
676 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
678 .send_unsigned_transaction(
679 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
680 |_payload, _signature| CALL.clone(),
681 );
682
683 let mut res = result.into_iter();
685 assert_account(res.next(), 0, 0xf0);
686 assert_account(res.next(), 1, 0xf1);
687 assert_account(res.next(), 2, 0xf2);
688 assert_eq!(res.next(), None);
689
690 let tx1 = pool_state.write().transactions.pop().unwrap();
692 let _tx2 = pool_state.write().transactions.pop().unwrap();
693 let _tx3 = pool_state.write().transactions.pop().unwrap();
694 assert!(pool_state.read().transactions.is_empty());
695 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
696 assert_eq!(tx1.signature, None);
697 });
698 }
699
700 #[test]
701 fn should_send_unsigned_with_signed_payload_with_any_account() {
702 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
703
704 let mut t = sp_io::TestExternalities::default();
705 t.register_extension(TransactionPoolExt::new(pool));
706
707 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
709
710 t.execute_with(|| {
711 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
713 .send_unsigned_transaction(
714 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
715 |_payload, _signature| CALL.clone(),
716 );
717
718 let mut res = result.into_iter();
720 assert_account(res.next(), 0, 0xf0);
721 assert_eq!(res.next(), None);
722
723 let tx1 = pool_state.write().transactions.pop().unwrap();
725 assert!(pool_state.read().transactions.is_empty());
726 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
727 assert_eq!(tx1.signature, None);
728 });
729 }
730
731 #[test]
732 fn should_send_unsigned_with_signed_payload_with_all_account_and_filter() {
733 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
734
735 let mut t = sp_io::TestExternalities::default();
736 t.register_extension(TransactionPoolExt::new(pool));
737
738 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
740
741 t.execute_with(|| {
742 let result = Signer::<TestRuntime, DummyAppCrypto>::all_accounts()
744 .with_filter(vec![0xf2.into(), 0xf1.into()])
745 .send_unsigned_transaction(
746 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
747 |_payload, _signature| CALL.clone(),
748 );
749
750 let mut res = result.into_iter();
752 assert_account(res.next(), 0, 0xf2);
753 assert_account(res.next(), 1, 0xf1);
754 assert_eq!(res.next(), None);
755
756 let tx1 = pool_state.write().transactions.pop().unwrap();
758 let _tx2 = pool_state.write().transactions.pop().unwrap();
759 assert!(pool_state.read().transactions.is_empty());
760 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
761 assert_eq!(tx1.signature, None);
762 });
763 }
764
765 #[test]
766 fn should_send_unsigned_with_signed_payload_with_any_account_and_filter() {
767 let (pool, pool_state) = testing::TestTransactionPoolExt::new();
768
769 let mut t = sp_io::TestExternalities::default();
770 t.register_extension(TransactionPoolExt::new(pool));
771
772 UintAuthorityId::set_all_keys(vec![0xf0, 0xf1, 0xf2]);
774
775 t.execute_with(|| {
776 let result = Signer::<TestRuntime, DummyAppCrypto>::any_account()
778 .with_filter(vec![0xf2.into(), 0xf1.into()])
779 .send_unsigned_transaction(
780 |account| SimplePayload { data: vec![1, 2, 3], public: account.public.clone() },
781 |_payload, _signature| CALL.clone(),
782 );
783
784 let mut res = result.into_iter();
786 assert_account(res.next(), 0, 0xf2);
787 assert_eq!(res.next(), None);
788
789 let tx1 = pool_state.write().transactions.pop().unwrap();
791 assert!(pool_state.read().transactions.is_empty());
792 let tx1 = Extrinsic::decode(&mut &*tx1).unwrap();
793 assert_eq!(tx1.signature, None);
794 });
795 }
796}