I’d like to use an HSM to sign my Hedera transactions but I get an error. In order to create a repeatable example I tried this
package com.managination;
import com.hedera.hashgraph.sdk.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import java.util.concurrent.TimeoutException;
public class HederaTransactionWithHSM {
public static void main(String[] args) throws PrecheckStatusException, TimeoutException, CryptoException, ReceiptStatusException {
new HederaTransactionWithHSM().testSigner();
}
public void testSigner() throws PrecheckStatusException, TimeoutException, CryptoException, ReceiptStatusException {
PrivateKey fundedPrivateKey = PrivateKey.fromStringED25519(MY_PRIVATE_KEY);
Client client = Client.forTestnet();
AccountId fundedAccountId = AccountId.fromString("0.0.3672483");
client.setOperator(fundedAccountId, fundedPrivateKey);
PrivateKey emptyPrivateKey = PrivateKey.generateED25519();
PublicKey publicKey = emptyPrivateKey.getPublicKey();
TransactionResponse newAccountCreateTX = new AccountCreateTransaction()
.setKey(publicKey)
.setInitialBalance(Hbar.from(0))
.execute(client);
client.setOperator(newAccountCreateTX.getReceipt(client).accountId, emptyPrivateKey);
// Create a transaction that needs to be signed with the fundedPrivateKey
assert client.getOperatorAccountId() != null;
TransferTransaction transaction = new TransferTransaction()
.addHbarTransfer(client.getOperatorAccountId(), Hbar.from(10))
.addHbarTransfer(fundedAccountId, Hbar.from(-10))
.setTransactionId(TransactionId.generate(fundedAccountId))
.freezeWith(client)
// .sign(fundedPrivateKey)
.sign(emptyPrivateKey);
// Get the transaction hash
byte[] transactionHash = transaction.getTransactionHash();
// Add the signature to the transaction
transaction.addSignature(fundedPrivateKey.getPublicKey(), this.sign(transactionHash, fundedPrivateKey));
// Execute the transaction
TransactionResponse response = transaction.execute(client);
TransactionId transactionId = response.transactionId;
System.out.println("Transaction ID: " + transactionId);
}
public byte[] sign(byte[] message, PrivateKey hederaPrivateKey) throws CryptoException {
Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(hederaPrivateKey.toBytes(), 0);
// Obtain the corresponding public key from the private key
Ed25519PublicKeyParameters publicKeyParameters = privateKeyParameters.generatePublicKey();
// Create an AsymmetricCipherKeyPair from the private and public key parameters
AsymmetricCipherKeyPair keyPair = new AsymmetricCipherKeyPair(publicKeyParameters, privateKeyParameters);
// Extract the keys
AsymmetricKeyParameter privateKey = keyPair.getPrivate(); // private key
AsymmetricKeyParameter publicKey = keyPair.getPublic(); // public key
// sign a simple message
Signer signer = new Ed25519Signer();
signer.init(true, privateKey);
signer.update(message, 0, message.length);
byte[] signature = signer.generateSignature(); // Signature!!!
// now let's verify the signature
signer.init(false, publicKey);
signer.update(message, 0, message.length);
boolean isVerified = signer.verifySignature(signature); // should be true!!
System.out.println("Signature of Verification Result: " + isVerified);
return signature;
}
}
I know this works because if I replace the my custom signing function with the default Hedera function the transaction is executed just fine.