0

Can someone show me how to correctly sign an ether's unsigned transaction hash using elliptic npm package. Below is my attempt (with some random private key) and transaction should ideally fail with "only minter". Any help is appreciated, getting pretty desperate.

import * as elliptic from "elliptic"
import { ethers, Contract, JsonRpcProvider, Transaction, Signature } from 'ethers';


const chainId = 13

const ec = new elliptic.ec("secp256k1")

const privateKey = "c395c9afc567105569ccdc2a858d4659bb66eb6ba1530c2d0c3e88c071c15cba"
const publicKey = ec.keyFromPrivate(privateKey).getPublic(false, "hex").slice(2)
const address = `0x` + ethers.keccak256(`0x`+publicKey).slice(-40)
const checksumedAddress = ethers.getAddress(address)

const abi = [{
  "inputs": [
    {
      "internalType": "address",
      "name": "_target",
      "type": "address"
    },
    {
      "internalType": "uint256",
      "name": "amount",
      "type": "uint256"
    }
  ],
  "name": "mintAmount",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
}]

async function main() {
  const provider = new JsonRpcProvider("https://coston-api.flare.network/ext/C/rpc")
  const contract = new Contract('0xC06496FA0551bf4996fb5Df876cBcC6F1d836460', abi, provider)
  const action = 'mintAmount'
  const params = ["0x0000000000000000000000000000000000000000", 10]
  const method = contract.getFunction(action)
  const baseTx = await method.populateTransaction(...params)
  const nonce = await provider.getTransactionCount(checksumedAddress)

  const unsignedTx = {
    ...baseTx,
    nonce: nonce,
    chainId: chainId,
    gasLimit: ethers.toBeHex("10000000"),
    gasPrice: ethers.toBeHex("10000000"),
  }

  // external transaction signature
  const tx = Transaction.from(unsignedTx)
  const _signature = ec.sign(
    tx.unsignedHash,
    Buffer.from(privateKey, 'hex'),
    { canonical: true }
  )
  const signature = Signature.from({
    r: _signature.r.toString(),
    s: _signature.s.toString(),
    yParity: _signature.recoveryParam as 0 | 1
  })
  const x = ethers.recoverAddress(tx.unsignedHash, signature)
  if (x !== checksumedAddress) throw new Error("invalid signature")

  // sign transaction
  tx.signature
  const signedTx = Transaction.from({
    ...unsignedTx,
    from: checksumedAddress,
    signature: signature
  })

  // send transaction to node
  await provider.send('eth_sendRawTransaction', [signedTx.serialized])
}

main()

but I can't get the recovered address to match (I know address is derived correctly from the signing private key). I also tried replacing tx.unsignedHash with const actualUnsignedHash = ethers.hashMessage(tx.unsignedHash) in case transaction expects a signed message but it also doesn't work.

1 Answer 1

0

The reason was that ec.sign expected the unsigned hash to be a BN type. Because the type of inputs with prefixed 0x is just too vague.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.