One easy way to do this without needing to change the pallet code too much, as you said, is using signed extensions. Here's one that does a similar thing:
https://github.com/paritytech/substrate/pull/12129/files#diff-103bae13a15813f338cb0bb67e77f3687c2c811fe75b2defd1cdc3d2afd6aa17R487
As for custom origin, you can also do that, but it would require some changes to the pallet code:
// in `trait Config`
type CustomSignedOrigin: EnsureOrigin<Self::AccountId>;
Then, in all of the calls that you want to be restricted:
// remove this
let who = ensure_signed(origin)?;
// add this:
let who = T::CustomSignedOrigin::ensure_origin(origin)?;
Finally, in the top level runtime, you want an implementation of EnsureOrigin
that acts similar to EnsureSigned
but filters out certain accounts.
The main advantage of the second approach is that the overhead will only be felt if the transaction is related to pallet-democracy
, whereas in the former approach, the signed extension is checked against all transaction. Although, if you implement it correctly, the first thing that you will do in the signed extension is to check if the transaction's call is from democracy, and if not, you early exist. In my opinion, this is a very negligible overhead.