I'm currently undertaking a course on Blockchain cryptography. And our first project is to transfer tokens from one wallet to another using client / server application. All mock of course.

This project is using the prescribed https://www.npmjs.com/package/ethereum-cryptography

The theory is this as I understand it:

Client side

  • generate public and private key
  • Create what I call an action object - {operation: "transfer", from (public key), to (public key), amount}
  • Hash the action
  • Sign the hash with private key to create a signature
  • Post the {action, signature} to the server

Server side

  • Verify the action using the signature and public key
  • Perform the transfer between wallets (if valid)

I'm pretty sure that this is correct.

Now what I had to do required quite a bit of investigation and work arounds and it just feels like it should have been simpler. I'd like to know if there is a better way. Here is what I had to do:

Client side

  • Client side - generate public and private key
  • Create what I call an action - {operation: "transfer", from (public key), to (public key), amount}
  • Hash the action with keccak256(utf8ToBytes(JSON.stringify(action)))
  • Sign the hash with private key to create a Signature using secp256k1.sign(actionHash, privateKey)
  • work around -> Because the returned Signature contains bigints (which seemingly don't serialize), I had to use https://www.npmjs.com/package/json-bigint to stringify it
  • Post the {action, signature} to the server

Server side

  • work around -> Use json-bigint with { useNativeBigInt: true } option to parse the signature (for BigInt(..) in the next step)
  • Whoops, I missed a detail previously. The (typed) signature of verify is:
    verify: (signature: Hex | {
        r: bigint;
        s: bigint;
    }, msgHash: Hex, publicKey: Hex, opts?: import("./abstract/weierstrass.js").VerOpts | undefined) => boolean;

and as I discuss below, I couldn't get the Hex version of signature to work and despite it just seeming to take a {r: bigInt, s: bigInt}, verify complained that I need to pass a Signature object.

class Signature {
    constructor({r, s, recovery}) {
        // const {r, s, recovery} = SigLikeObj;
        this.r = BigInt(r);
        this.s = BigInt(s);
        this.recovery = recovery;
  • Verify it with secp256k1.verify(signatureFixed, actionHash, publicKey)
  • Perform transfer

The json-bigint requirement is a detail, but the need to create a class Signature seems wrong.

I saw that the signature in verify() can be a Hex though I couldn't see a way to create this.

I tried this on the Server:

    const signatureObj = JSONbig.parse(signatureJson);
    const signatureHex = toHex(new Uint8Array(signatureObj))
    const isValid = secp256k1.verify(signatureHex, actionHash, publicKey);

But verification fails this way.

Am I missing something - is there an easier way to handle the class Signature or to make a Hex out of it?

  • $\begingroup$ This site is dedicated to cryptography, so general programming questions are off-topic. I said "general" because specific areas such as side-channel prevention, algorithm implementation optimization, testings for robustness are exceptions and are on-topic here. As this particular question doesn't pertain to any such aspects or reasonable extensions to it, please do not add new answers here - new questions should be asked on StackOverflow. $\endgroup$
    – DannyNiu
    Commented Jun 11 at 2:13
  • $\begingroup$ I kinda see your point DannyNiu. However this is significantly about the crypto side of things and probably wouldn't get a look in over there. And I'd probably get a comment suggesting I ask on crypto.stackexchange.com :) $\endgroup$
    – HankCa
    Commented Jun 13 at 8:13
  • $\begingroup$ Surely. I added the comment to remind people that this question already have an accepted answer. So unless newer answer significantly add meaningful content to here, people should refrain from posting them. $\endgroup$
    – DannyNiu
    Commented Jun 13 at 9:00

2 Answers 2


Doh! I updated the question with more detail of the error and then that action highlighted that I completely missed something so obvious.

I just needed to do this for the signature problem:

    const signatureObj = JSONbig.parse(signatureJson);
    const signatureObj2 = { r: signatureObj.r, s: signatureObj.s }
    const isValid = secp256k1.verify(signatureObj2, actionHash, publicKey);

I must admit I've solved a lot of issues by trying to explain the problem on StackExchange. Most often I work it out before posting the question. Oh well, I hope I've helped others.


I ran into the same issue as you and I found another way around. You can use the JSON.stringify callback to map the stringify the same happens with JSON.parse.

For stringify the signature you can just do as follows:

const signature = JSON.stringify(signature, (_, value) => (typeof value === "bigint" ? value.toString() : value))

For parsing the signature:

const signatureObj = JSON.parse(signature, (_, value) => (typeof value === "string" ? BigInt(value) : value))

Hope it helps someone running the issue.


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.