Does somebody knows how to properly use AWS CloudHSM to sign ethereum transactions (Keccak256)?

  • 2
    yup I've done it with colleagues - will try and post later.
    – Woodstock
    Commented Jul 25, 2019 at 14:04
  • 1
    @Woodstock I'm interested in AWS CloudHSM if you have some links it will be helpful.
    – Ismael
    Commented Jul 26, 2019 at 17:56
  • 1
    OK created an answer
    – Woodstock
    Commented Jul 28, 2019 at 8:50

4 Answers 4



so when we (me and the ShaneT) implemented hardware signing (private key stored exclusively on AWS CloudHSM) there were a number of things we learned. Hopefully this gives you a guide on how you can achieve it. Unfortunately I can't directly share the code as it's my firm's IP.

Here is the actual tx we first made using AWS CloudHSM on mainnet :)


  • Most HSM products support a proprietary interface and a generic interface called PKCS#11. PKCS#11 is just a fancy term for a standard way to talk to a HSM.

  • AWS supports both prop and PKCS#11 interaction, you should use PKCS#11.

  • Keccak256 is irrelevant with regard to HSM, it's the fact that AWS supports the SECP256k1 curve that allows it to sign ETH transactions.

  • ECDSA (elliptic curve digital signature algo) is bigger than ETH or BTC. i.e. you may hit hurdles where AWS CloudHSM is returning valid ECDSA signatures, however they are invalid Ethereum transactions, such as if the HSM returns a sig with a high S value.

  • There exists many libs in Web3, however JS is the predominant language, don't fight it :)

Approximate guide to implementation:

  • Follow this guide meticulously in order to setup the infrastructure/HSM. If you skip even a little you are gonna get rekt. p.s. I used Amazon Linux 2, it's easier.

  • Use this (Graphene) lib for JS PKCS#11, I tried them all this is the one to use imo.

  • If you can use ecrecover with an r, s, and v value that results in the calculation of the correct Eth address you've done it.

Steps to Signature:

1) Open connection to HSM create a public private key pair using SECP256k1.

2) Retrieve the public key for the pair and calculate the corresponding ETH address.

3) You now have a Eth address for which the HSM owns the private key.

4) Create a tx like so:

  const txParams = {
    nonce: '0x' + nonce.toString(16),
    gasPrice: '0x09184e72a00',
    gasLimit: '0x27100',
    to: '0x4D8519890C77217A352d3cC978B0b74165154421', 
    value: web3.utils.toHex(web3.utils.toWei('0.01', 'ether')),
    chainId: 4

5) With a handle to the private key request signature, remember you're signing a hash of the payload:

  const sign = session.createSign('ECDSA', yourPrivateKey);
  const sig = sign.once(msgHash);

6) Remember, r and s are the first and second 32 bytes of the ECDSA sig, v is a calculated value and ethereum bound concept representing chainID.

  const rs = {
    r: sig.slice(0, 32),
    s: sig.slice(32, 64)

7) CRITICAL: Due to EIP-2 you must loop until you achieve a ECDSA sig with an s value on the right side of the curve, or else it will be a bad Eth tx.

i.e. where s < curve.n/2

if (s > curve.n / 2) id = id ^ 1; // Invert id if s of signature is over half the n

8) Assuming all is implemented correctly you should be able to use ecrecover to check the validity of the HSM created sig.

if (ethUtil.ecrecover(msgHash, 27, rsvOdd.r, rsvOdd.s).toString('hex') === yourETHPublicKeyString)

If that's all good, then you've done it! You can now submit your tx and it should be mined.

This sounds tricky but it's totally doable.

Questions, just shout.

  • 2
    Rather than looping until you get an s which satisfies EIP-2, you can just take s = curve.n - s, and flip the v value. From EIP-2 "Allowing transactions with any s value with 0 < s < secp256k1n, as is currently the case, opens a transaction malleability concern, as one can take any transaction, flip the s value from s to secp256k1n - s, flip the v value (27 -> 28, 28 -> 27), and the resulting signature would still be valid." The point of EIP-2 is to mandate one of these two valid signatures. Commented Aug 27, 2019 at 20:14
  • I did exactly the same, but my ethaddress generating during signing is different every single time. Any idea why ? Commented Dec 20, 2019 at 7:28
  • 1
    So, R and S are the outputs of the ECDSA signature process. Sometimes the S value can randomly be on the 'wrong side' on the curve. This is not wrong from an ECDSA POV, but is from an EIP POV. So you must loop until you get an S value which is not 'high'. How are you calculating V? @user2805885 Rather than looping until you get an s which satisfies EIP-2, you can just take s = curve.n - s, and flip the v value.
    – Woodstock
    Commented Dec 23, 2019 at 17:34
  • 1
    Wow. Thanks alot for the input. I was able to resolve the issue.I am using Gemalto SafeNet Luna PCIe HSM and it worked. Commented Dec 24, 2019 at 2:19
  • 1
    For future readers trying to ensure S is below the halfn of the curve (for Ethereum's curve, secp256k1), its value (curve.n/2) is 7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 Commented May 15, 2020 at 23:19

I published a medium article about this integration.

Please also see my working code on github. This integration uses AWS KMS (Cloud HSM behind the scenes).

One thing that some of the other answers do not mention is that you don't have to keep looping through signatures to find a valid one. You can invert the result if you're on the "wrong" side of the curve. See example below.

let secp256k1N = new BN("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); // max value on the curve
let secp256k1halfN = secp256k1N.div(new BN(2)); // half of the curve
// Because of EIP-2 not all elliptic curve signatures are accepted
// the value of s needs to be SMALLER than half of the curve
// i.e. we need to flip s if it's greater than half of the curve
if (s.gt(secp256k1halfN)) {
    console.log("s is on the wrong side of the curve... flipping - tempsig: " + tempsig + " length: " + tempsig.length);
    // According to EIP2 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
    // if s < half the curve we need to invert it 
    // s = curve.n - s
    s = secp256k1N.sub(s);
    console.log("new s: " + s.toString(10));
    return { r, s }
// if s is less than half of the curve, we're on the "good" side of the curve, we can just return
return { r, s }
  • 1
    Hi, thank you for this. If someone is looking to verify this signature in solidity, using (r+s+v) please note that the return of { r , s } on this function is not necessarily will be equal to 32 bytes each, you still need to add leading zeros until it became 32 bytes each.
    – Lonewarp
    Commented Aug 11, 2022 at 5:10

I had implemented a solution based @Woodstock steps using SoftHSM and Graphene. The source code is available in https://github.com/wshbair/HSM2ETH

Hope this will help

  • Did you try this with AWS Cloud HSM that uses Cavium? Commented Mar 17, 2020 at 14:56
  • Not yet, but I think as the AWS CloudHSM is accessed via PKCS#11 it should work. Commented Mar 18, 2020 at 0:22
  • @WazenShbair exactly! nice work, clean code.
    – Woodstock
    Commented Apr 28, 2020 at 8:37
  • 1
    I have updated my implementation by a complete implementation for creating, signing, and building Tx. Also, it has a UI to make it easier to manage. [github.com/wshbair/HSM2ETH ](github.com/wshbair/HSM2ETH ) Commented Nov 1, 2021 at 8:48

This article outlines a method that allows for the signing of an Ethereum transaction using a private key managed by AWS CloudHSM : https://jonathanokz.medium.com/secure-an-ethereum-wallet-with-a-kms-provider-2914bd1e4341

Additionally, this npm repository provides a ready-to-use solution : https://github.com/JonathanOkz/web3-kms-signer

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.