leapwallet / cosmos-metamask-snap

Leap Metamask Cosmos Snap for making cosmos transactions
https://leapwallet.io
Other
8 stars 7 forks source link

signArbirary signatures are not valid #74

Closed HS-Joe closed 7 months ago

HS-Joe commented 8 months ago

When using signArbitrary with the snap, the generated sig cannot be verified, though using Leap wallet's browser extension signArbitrary it works.

How to reproduce

import {signArbitrary} from "@leapwallet/cosmos-snap-provider";
import * as secp from '@noble/secp256k1';
import { sha256 } from '@noble/hashes/sha256';

const data = {key: "value"};
const accountData = await window.ethereum.request({
        method: 'wallet_invokeSnap',
        params: {
          snapId: "npm:@leapwallet/metamask-cosmos-snap",
          request: {
            method: 'getKey',
            params: 'cosmoshub-4',
          },
        },
      });

const signature = await signArbitrary('cosmoshub-4', accountData.address, data);

const signDoc = createSignDocWith(data); // use getADR36SignDoc from cosmos-snap-provider/src/cosmjs-offline-signer.ts#L114

const valid = secp.verify(
    Buffer.from(signature).toString('hex'),
         Buffer.from(sha256(signDoc).toString('hex'),
         Buffer.from(accountData.pubKey).toString('hex')
);

console.log(valid); // ouputs false

Could you guys have a look, and maybe write a unit test to help?

leapsamvel commented 7 months ago

hey @HS-Joe you can use the below snippet to validate it.

const sortObject = (obj: any): any  => {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (Array.isArray(obj)) {
    return obj.map(sortObject);
  }
  const sortedKeys = Object.keys(obj).sort();
  const result: Record<string, any> = {};

  for (const key of sortedKeys) {
    result[key] = sortObject(obj[key]);
  }

  return result;
}

/**
 *
 * @param signDoc
 */
const serializeStdSignDoc = (signDoc: StdSignDoc) => {
  const json = JSON.stringify(sortObject(signDoc));
  return new TextEncoder().encode(json);
}

function base64ToArrayBuffer(base64: any) {
  var binaryString = atob(base64);
  var bytes = new Uint8Array(binaryString.length);
  for (var i = 0; i < binaryString.length; i++) {
      bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
}

  const getADR36SignDoc  = (data: any, signer: string) => {
    let isADR36WithString = false;
  let b64Data = '';
  if (typeof data === 'string') {
    b64Data = Buffer.from(data).toString('base64');
    isADR36WithString = true;
  } else {
    b64Data = Buffer.from(data).toString('base64');
  }

  const sortedSignDoc =  {
    chain_id: 'cosmoshub-4',
    account_number: '0',
    sequence: '0',
    fee: {
      gas: '0',
      amount: [],
    },
    memo: '',
    msgs: [
      {
        type: 'sign/MsgSignData',
        value: {
          signer: signer,
          b64Data,
        },
      },
    ],
  }
  return sortedSignDoc;
  }

  const validate = async () => {

const data:any = { key: "value" };

const accountData = await window.ethereum.request({
        method: 'wallet_invokeSnap',
        params: {
          snapId: "npm:@leapwallet/metamask-cosmos-snap",
          request: {
            method: 'getKey',
            params: { chainId: 'cosmoshub-4' }
          },
        },
      });

const base64Data:string = btoa(JSON.stringify(data));
const sign = await signArbitrary('cosmoshub-4', accountData.address, base64Data, { enableExtraEntropy: false });
const signDoc = getADR36SignDoc(base64Data, accountData.address); 
const hashedSerializedSignDoc = sha256(serializeStdSignDoc(signDoc))

const pubKey = new Uint8Array(Object.values(accountData.pubkey));
const valid = secp.verify(
        base64ToArrayBuffer(sign.signature),
        hashedSerializedSignDoc,
        pubKey);
  console.log(valid);
}

I will expose appropriate methods from provider, so that you can avoid having them in your codebase

HS-Joe commented 7 months ago

Thanks @leapsamvel I confirm it works!