polkadot-js / common

Utilities and base libraries for use across polkadot-js for Polkadot and Substrate. Includes base libraries, crypto helpers and cross-environment helpers.
Apache License 2.0
254 stars 146 forks source link

Verify a signed transaction payload offline for ecdsa #1825

Closed drhanlondon closed 1 week ago

drhanlondon commented 1 year ago

Hello, @jacogr

Thanks for your continuous help.

I am building a custodial wallet with AWS KMS for Polkadot blockchain (with ecdsa scheme). Unfortunately AWS KMS supports only secp256k1 curve, neither sr25519 nor ed25519. This research is motivated from AWS blockchain research (https://aws.amazon.com/blogs/database/part1-use-aws-kms-to-securely-manage-ethereum-accounts/) where a private key is created and stored HSM (Hardware Security Module), a private key never leaves HSM and a transaction payload is signed offline in HSM.

I generated a signed transaction payload offline by following the instruction (https://wiki.polkadot.network/docs/build-transaction-construction) with AWS KMS, and submitted it to Westend testnet. But I got this error:

  2023-05-02 21:38:41        API/INIT: RPC methods not decorated: mmr_root, mmr_verifyProof, mmr_verifyProofStateless
  2023-05-02 21:38:41        API/INIT: westend/9401: Not decorating runtime apis without matching versions: ParachainHost/4 (1/2/3 known)
  2023-05-02 21:38:41        RPC-CORE: submitExtrinsic(extrinsic: Extrinsic): Hash:: 1010: Invalid Transaction: Transaction has a bad signature
  /home/drhan/cmk-based-substrate-account-typescript/node_modules/@polkadot/rpc-provider/cjs/coder/index.js:24
          throw new error_js_1.default(`${code}: ${message}${formatErrorData(data)}`, code, data);
                ^
  RpcError: 1010: Invalid Transaction: Transaction has a bad signature
      at checkError (/home/drhan/cmk-based-substrate-account-typescript/node_modules/@polkadot/rpc-provider/cjs/coder/index.js:24:15)
      at RpcCoder.decodeResponse (/home/drhan/cmk-based-substrate-account-typescript/node_modules/@polkadot/rpc-provider/cjs/coder/index.js:42:9)
      at WsProvider.<anonymous> (/home/drhan/cmk-based-substrate-account-typescript/node_modules/@polkadot/rpc-provider/cjs/ws/index.js:145:93)
      at WebSocket.<anonymous> (/home/drhan/cmk-based-substrate-account-typescript/node_modules/@polkadot/rpc-provider/cjs/ws/index.js:134:96)
      at callListener (/home/drhan/cmk-based-substrate-account-typescript/node_modules/ws/lib/event-target.js:290:14)
      at WebSocket.onMessage (/home/drhan/cmk-based-substrate-account-typescript/node_modules/ws/lib/event-target.js:209:9)
      at WebSocket.emit (node:events:513:28)
      at WebSocket.emit (node:domain:489:12)
      at Receiver.receiverOnMessage (/home/drhan/cmk-based-substrate-account-typescript/node_modules/ws/lib/websocket.js:1184:20)
      at Receiver.emit (node:events:513:28)

Before I submitted it, I verified a signature, which is a part of a signed transaction payload, twice with the two functions below:

  1. signatureVerify (https://github.com/polkadot-js/common/blob/e5cb0ba2b4a6b5817626cc964b4f66334f2410e4/packages/util-crypto/src/signature/verify.ts#L90)
  2. secp256k1Verify (https://github.com/polkadot-js/common/blob/e5cb0ba2b4a6b5817626cc964b4f66334f2410e4/packages/util-crypto/src/secp256k1/verify.ts#L16)

The signature was successfully verified as a valid signature. I have carefully counted a nonce. I look for the reason which has caused the error above. I would be very grateful if you could advise me on what I have to check further.

Thanks

jacogr commented 1 year ago

Those are exceptionally difficult to track. Generally it is due to -

  1. The incorrect payload gets signed (i.e. the signature is made of data that are both included in the extrinsic and data not included in the extrinsic, e.g. genesisHash is an example of those)
  2. The data in the payload may be correct, but possibly it has additional data, e.g. a length prefix (it gets signed without)
  3. Invalid payload-only data may be included, for instance if your genesisHash mismatches, you would get the same error
  4. Address or signature format part of the transaction is incorrect, i.e. MultiSignature would expect a leading byte indicating the type of crypto used
  5. Transaction is incorrectly constructed

Overall on the node side it is difficult to "give more exact info", since it really could be anything missing or mismatching.

TL;DR Generating the signature is "easy", making sure the right data is signed is slightly more problematic

drhanlondon commented 1 year ago

Thanks a lot for your comment.

To create unsigned shown on https://wiki.polkadot.network/docs/build-transaction-construction

    const unsigned = methods.balances.transferKeepAlive(
      {
        dest: "15vrtLsCQFG3qRYUcaEeeEih4JwepocNJHkpsrqojqnZPc2y",
        value: 5000000000,
      },
      {
        address: "121X5bEgTZcGQx5NZjwuTjqqKoiG8B2wEAvrUFjuw24ZGZf2",
        blockHash: "0x1fc7493f3c1e9ac758a183839906475f8363aafb1b1d3e910fe16fab4ae1b582",
        blockNumber: 4302222,
        genesisHash: "0xe3777fa922cafbff200cadeaea1a76bd7898ad5b89f7848999058b50e715f636",
        metadataRpc, // must import from client RPC call state_getMetadata
        nonce: 2,
        specVersion: 1019,
        tip: 0,
        eraPeriod: 64, // number of blocks from checkpoint that transaction is valid
        transactionVersion: 1,
      },
      {
        metadataRpc,
        registry, // Type registry
      }
    );

Instead of passing all required information above manually, I used the idea siggested on https://github.com/paritytech/txwrapper-core/blob/main/packages/txwrapper-examples/polkadot/src/polkadot.ts

  export function rpcToLocalNode(
      method: string,
      params: any[] = []
  ): Promise<any> {
      return fetch('http://0.0.0.0:9933', {
          body: JSON.stringify({
              id: 1,
              jsonrpc: '2.0',
              method,
              params,
          }),
          headers: {
              'Content-Type': 'application/json',
              connection: 'keep-alive',
          },
          method: 'POST',
      })
          .then((response) => response.json())
          .then(({ error, result }) => {
              if (error) {
                  throw new Error(
                      `${error.code} ${error.message}: ${JSON.stringify(error.data)}`
                  );
              }

              return result;
          });
  }

Then I replaced 'http://0.0.0.0:9933' by 'https://westend-rpc.polkadot.io:443'

I would be grateful if you could advise me on whether this idea is risky or wrong.

TarikGul commented 1 year ago

@drhanlondon If you could please direct any txwrapper-core questions to https://github.com/paritytech/txwrapper-core.

The polkadot wiki is seperate from polkadot-js.

drhanlondon commented 1 year ago

Hi, @TarikGul

I see. I have created a new issue https://github.com/paritytech/txwrapper-core/issues/296

Thanks

drhanlondon commented 1 year ago

Hello, @jacogr

Could you check my last comment on https://github.com/paritytech/txwrapper-core/issues/296, please?

I have a question about the function signatureVerify. I would be grateful if you could give me advice on why the function returned "false" value.

Thanks for your continuous help.

drhanlondon commented 1 year ago

Hello @jacogr

I look for your advice on verification on the function signatureVerify.

I have also posted this question with additional Subkey question on StackExchange (https://substrate.stackexchange.com/questions/8441/sign-a-transaction-offline-for-westend-testnet-and-verification-of-the-transacti)

I would be very grateful if you could advise me.

Thanks.

TarikGul commented 1 year ago

The above via signatureVerify is explained and solved in the following comment: https://github.com/paritytech/txwrapper-core/issues/296#issuecomment-1542174700

I think this is good to close @drhanlondon

TarikGul commented 1 week ago

Closing as this is solved

polkadot-js-bot commented 5 days ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue if you think you have a related problem or query.