Transaction signing with HSM6 and Web3 (Python)

Hello,

I’m a computer science student, I need to use HSM6 to sign transactions on Ethereum (on Rinkeby) in Python. Currently, I’m able to derive the keys according to the derivation path m/44’/60’/0’/0, but when I sign the transaction with the zymkey.client.sign() function to retrieve the VRS values to encode and send the transaction with the Web3 library, I get an error that tells me that the account has no funds. When I’m checking with the recover_transaction() function of the eth_account.account library before sending the transaction with web3, I get a different source address each time I run the program. Do you have any idea of a detail I might have missed?

Thanks,
Pedro

def ECDSA_Sign(Unsigned_Transaction: bytes, slot_key: int) -> Tuple[int, int, int]:
    sha256 = hashlib.sha256()  
    transaction_hash = Unsigned_Transaction.hash()    
    sha256.update(transaction_hash.hex().encode("utf-8"))
    
    flag = True
    while(flag):
       signed_tx, V_Data = zymkey.client.sign(transaction_hash.hex(), slot_key, True)
       S: int = int.from_bytes(signed_tx[32:64], "big")

       if(S < ((SECP256K1_SIZE)/2)):
           flag = False 

    R: int = int.from_bytes(signed_tx[0:32], "big")
    S: int = int.from_bytes(signed_tx[32:64], "big") 
    V: int = V_Data.value + 27
    return R, S, V
    
def Sign_Transaction(UTransaction, Slot_Key: int) -> SignedTransaction:
    r, s, v = ECDSA_Sign(UTransaction, Slot_Key)
    encoded_transaction = encode_transaction(UTransaction, vrs=(v, r, s))
    return SignedTransaction(
        rawTransaction=HexBytes(encoded_transaction),
        hash=HexBytes(UTransaction.hash()),
        r=r,
        s=s,
        v=v,
    )

if __name__ == "__main__":        
    web3: Web3 = Web3(Web3.HTTPProvider(URL))
    Eth_Addr: str = Web3.toChecksumAddress(pubkey_to_addr(zymkey.client.get_public_key(CHILD_SLOT)))
    print("Wallet address :", Eth_Addr)
    
    unsigned_transaction = Create_Transaction(web3, Eth_Addr, ADDRESS_1, 0.0001, 2000000)
    signed_transaction = Sign_Transaction(unsigned_transaction, CHILD_SLOT) 

    RecoverAdd = eth_account.account.Account.recover_transaction(signed_transaction.rawTransaction)          
    print("Transaction from " + RecoverAdd + " to " + ADDRESS_1)

    tx_hash = web3.eth.sendRawTransaction(signed_transaction.rawTransaction)
    print("\nTransaction Hash :\n", web3.toHex(tx_hash))

The steps to prepare a transaction to be signed (per ethereum):

  • RLP encode your raw transaction(the order of the attributes matter)

  • Keccak Hash the RLP encoded transaction

I’m unsure if your Create_Transaction() function creates a rlp serialized transaction so double check that.

Next you need to keccak hash it instead of a sha256 hash.

Then you can sign the transaction with our zymkey.client.sign().

Double check that the web3.eth.sendRawTransaction() is sending a rlp encoded transaction as well.

A full tutorial on signing a ethereum transaction with our module can be found here:
https://docs.zymbit.com/tutorials/digital-wallet/ethereum-signing-example/

Hi Eric,

Thanks for your help, my problem is solved :slight_smile:

Pedro