12

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

Lucas Yamamoto
  • 185
  • 1
  • 8

4 Answers4

19

OK,

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 :)

Considerations:

  • 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.

Woodstock
  • 338
  • 3
  • 9
  • 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.

    – Alex Coventry Aug 27 '19 at 20:14
  • I did exactly the same, but my ethaddress generating during signing is different every single time. Any idea why ? – user2805885 Dec 20 '19 at 07:28
  • Do you mean ecrecover returns a different address every time? Or, do you mean it’s one of two possible addresses? – Woodstock Dec 20 '19 at 08:13
  • ecrecover gives different address every single time. – user2805885 Dec 20 '19 at 09:37
  • can enyone elaborate 6 and 7 point ? HSM wont return r s and v value and i am doing exactly -> https://github.com/ethereumjs/ethereumjs-util/blob/599ba5b1c7043a7e155e6032c50d7a01fc63aaf1/src/signature.ts#L15 . Only difference is the sign part is inside hsm – user2805885 Dec 20 '19 at 09:39
  • @user2805885 if ecrecover gives a different address every time, you are not signing with the same key each time. Check that you are using the same key. – Woodstock Dec 20 '19 at 10:42
  • I am signing with same key. But i suspect that i am missing something about this calculation. Can you give more about this? I am not very clear about what you have said. what is id here? if (s > curve.n / 2) id = id ^ 1; // Invert id if s of signature is over half the n – user2805885 Dec 23 '19 at 06:23
  • 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 Dec 23 '19 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. – user2805885 Dec 24 '19 at 02:19
  • Awesome! Feels great to succeed! – Woodstock Dec 24 '19 at 09:39
  • 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 – Albert Renshaw May 15 '20 at 23:19
  • Back again. It's crucial to know: R and S values can't have leading zeros. – Albert Renshaw Jun 09 '21 at 23:06
6

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 }
hurb
  • 391
  • 3
  • 10
  • 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 Aug 11 '22 at 05:10
5

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

Wazen Shbair
  • 53
  • 1
  • 5
0

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

a9911b
  • 31
  • 2