In order to add an offchain worker with using AccountId20 as the runtime account type I did following things.

  1. add associated type type AuthorityId: AppCrypto<Self::Public, Self::Signature>; in the config traitof an OCW pallet, and add traitbond CreateSignedTransaction<Call<Self>> to the config.

  2. Implement CreateSignedTransaction<LocalCall> for Runtime like the substrate offchain worker tutorial says.

  3. create a crypto module, and rewrite the sign and verify method (where I used sp_io::crypto::ecdsa_sign):

    pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"demo");
    pub mod crypto {
        use super::KEY_TYPE;
        use sp_runtime::traits::Verify;
        use sp_runtime::{
            app_crypto::{app_crypto, ecdsa},
        use fp_account::{
            EthereumSignature, EthereumSigner
        use sp_io::crypto::ecdsa_sign;
        use sp_io::hashing::keccak_256;
        use fp_account::AccountId20;
        app_crypto!(ecdsa, KEY_TYPE);
        impl frame_system::offchain::AppCrypto<EthereumSigner, EthereumSignature> for TestAuthId {
            type RuntimeAppPublic = Public; 
            type GenericSignature = ecdsa::Signature; //struct Signature(pub [u8; 65]);
            type GenericPublic = ecdsa::Public;       //struct Public(pub [u8; 32]) 
            fn sign(payload: &[u8], public: EthereumSigner) -> Option<EthereumSignature> { 
                let m = keccak_256(payload);
                let ECDSAPublic: ecdsa::Public = public.into(); // compressed one
                match ecdsa_sign(KEY_TYPE, &ECDSAPublic, &m) {
                    None => return None,
                    Some(e) => {
                        log::info!("[{:?}]: singature.", &e);
                        return Some(e.into())
            fn verify(payload: &[u8], public: EthereumSigner, signature: EthereumSignature) -> bool { 
                let accountId: &AccountId20 = &public.into();
                let result = signature.verify(payload, accountId);
                log::info!("[{:?}]: verify result.", &result);

Turns out I've got Badproof error: (offchain call) Error submitting a transaction to the pool: Transaction pool error: Invalid transaction validity: InvalidTransaction::BadProof from checking the transaction. The error is caused by that the verify method of EthereumSignature from trait sp_runtime::traits::Verify returns false:

    fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId20) -> bool {
        let m = keccak_256(msg.get());
        log::info!("[{:?}]: failed to verify0, signature", self.0);
        match sp_io::crypto::secp256k1_ecdsa_recover(self.0.as_ref(), &m) {
            Ok(pubkey) => { 
                // return false

I guess my assumption that sp_io::crypto::secp256k1_ecdsa_recover is the reverse of sp_io::crypto::ecdsa_sign was wrong, or did I do something else wrong?

And what is the correct way to add an offchain worker in Frontier with AccountId20?

  • Were you able to solve it? I'm having a similar issue
    – Discardo
    Commented Jan 31 at 8:15
  • @Discardo Yes, I've solved it by using sp_io::crypto::ecdsa_sign_prehashed to correspond sp_io::crypto::secp256k1_ecdsa_recover, but I modified the EthereumSigner this way pub struct EthereumSigner([u8; 20], Option<ecdsa::Public>) and impled all the From and Into traits neccessary.
    – xiao liang
    Commented Jan 31 at 14:25
  • @Discardo before using sp_io::crypto::ecdsa_sign_prehashed, I encryptoed my payload message with keccak_256 as what the verify method of EthereumSignature does
    – xiao liang
    Commented Jan 31 at 14:33
  • Unfortunately, I'm still getting the BadProof error even after using the ecdsa_sign_prehashed in the the sign method of the AppCrypto trait
    – Discardo
    Commented Feb 3 at 13:04
  • Do I need to make some changes in the verify method too?
    – Discardo
    Commented Feb 3 at 13:05


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.