Skip to content

chore: refactor alias setting in AccountCreateTransaction #2318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@
* <p>
* Demonstrates different methods of creating Hedera accounts with various key configurations.
* Shows how to create accounts with and without aliases using different key types.
* <p>
* Reference: HIP-583 Expand alias support in CryptoCreate and CryptoTransfer Transactions
* https://hips.hedera.com/hip/hip-583
*/
public class CreateAccountWithAliasExample {

// UTIL VARIABLES BELOW
private static final int TOTAL_ACCOUNT_CREATION_METHODS = 3;

// CONFIG VARIABLES BELOW

/*
* See .env.sample in the examples folder root for how to specify values below
* or set environment variables with the same names.
Expand Down Expand Up @@ -53,9 +50,10 @@ public class CreateAccountWithAliasExample {
* for example via VM options: -Dorg.slf4j.simpleLogger.log.org.hiero=trace
*/
private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT");

/**
* Creates a Hedera account with an alias using an ECDSA key.
* This demonstrates creating an account where both the account key and alias
* are derived from the same ECDSA key.
*
* @param client The Hedera network client used to execute the transaction
* @throws Exception If there's an error during account creation
Expand All @@ -66,13 +64,17 @@ public static void createAccountWithAlias(Client client) throws Exception {
* Create an ECDSA private key.
*/
PrivateKey privateKey = PrivateKey.generateECDSA();
System.out.println("\n--- Creating account with ECDSA key and derived alias ---");
System.out.println("ECDSA private key: " + privateKey);

/*
* Step 2:
* Extract the ECDSA public key and generate EVM address.
*/
PublicKey publicKey = privateKey.getPublicKey();
EvmAddress evmAddress = publicKey.toEvmAddress();
System.out.println("ECDSA public key: " + publicKey);
System.out.println("EVM address: " + evmAddress);

/*
* Step 3:
Expand All @@ -92,16 +94,15 @@ public static void createAccountWithAlias(Client client) throws Exception {
* Query the account information to verify details.
*/
AccountInfo info = new AccountInfoQuery().setAccountId(accountId).execute(client);

/*
* Step 5:
* Print the EVM address to confirm it matches the contract account ID.
*/
System.out.println("Created account ID: " + accountId);
System.out.println("Account key: " + info.key);
System.out.println("Initial EVM address: " + evmAddress + " is the same as " + info.contractAccountId);
}

/**
* Creates a Hedera account with both ED25519 and ECDSA keys.
* This demonstrates setting a separate account key (ED25519) and
* ECDSA key for alias derivation.
*
* @param client The Hedera network client used to execute the transaction
* @throws Exception If there's an error during account creation
Expand All @@ -113,12 +114,16 @@ public static void createAccountWithBothKeys(Client client) throws Exception {
*/
PrivateKey ed25519Key = PrivateKey.generateED25519();
PrivateKey ecdsaKey = PrivateKey.generateECDSA();
System.out.println("\n--- Creating account with ED25519 account key and ECDSA alias key ---");
System.out.println("ED25519 key: " + ed25519Key);
System.out.println("ECDSA key: " + ecdsaKey);

/*
* Step 2:
* Derive the EVM address from the ECDSA public key.
*/
EvmAddress evmAddress = ecdsaKey.getPublicKey().toEvmAddress();
System.out.println("EVM address: " + evmAddress);

/*
* Step 3:
Expand All @@ -140,17 +145,14 @@ public static void createAccountWithBothKeys(Client client) throws Exception {
* Query the account information to verify details.
*/
AccountInfo info = new AccountInfoQuery().setAccountId(accountId).execute(client);

/*
* Step 5:
* Print key and EVM address details for verification.
*/
System.out.println("Created account ID: " + accountId);
System.out.println("Account's key: " + info.key + " is the same as " + ed25519Key.getPublicKey());
System.out.println("Initial EVM address: " + evmAddress + " is the same as " + info.contractAccountId);
}

/**
* Creates a Hedera account without an alias.
* This demonstrates creating an account with a key but no EVM address alias.
*
* @param client The Hedera network client used to execute the transaction
* @throws Exception If there's an error during account creation
Expand All @@ -161,6 +163,8 @@ public static void createAccountWithoutAlias(Client client) throws Exception {
* Create a new ECDSA private key.
*/
PrivateKey privateKey = PrivateKey.generateECDSA();
System.out.println("\n--- Creating account without alias ---");
System.out.println("ECDSA key: " + privateKey);

/*
* Step 2:
Expand All @@ -180,13 +184,109 @@ public static void createAccountWithoutAlias(Client client) throws Exception {
* Query the account information to verify details.
*/
AccountInfo info = new AccountInfoQuery().setAccountId(accountId).execute(client);
System.out.println("Created account ID: " + accountId);
System.out.println("Account's key: " + info.key + " is the same as " + privateKey.getPublicKey());
System.out.println("Account has no alias: " + isZeroAddress(Hex.decode(info.contractAccountId)));
}

/**
* Creates a Hedera account with an alias derived from ECDSA public key.
* This demonstrates creating an account with the public key directly
* rather than the private key.
*
* @param client The Hedera network client used to execute the transaction
* @throws Exception If there's an error during account creation
*/
public static void createAccountWithPublicKeyAlias(Client client) throws Exception {
/*
* Step 1:
* Create an ECDSA private key and derive the public key.
*/
PrivateKey privateKey = PrivateKey.generateECDSA();
PublicKey publicKey = privateKey.getPublicKey();
System.out.println("\n--- Creating account with public ECDSA key alias ---");
System.out.println("ECDSA private key: " + privateKey);
System.out.println("ECDSA public key: " + publicKey);

/*
* Step 2:
* Generate the EVM address from the public key.
*/
EvmAddress evmAddress = publicKey.toEvmAddress();
System.out.println("EVM address: " + evmAddress);

/*
* Step 3:
* Create an account with the public key as an alias.
* The transaction must be signed with the corresponding private key.
*/
AccountId accountId = new AccountCreateTransaction()
.setKeyWithAlias(publicKey)
.freezeWith(client)
.sign(privateKey)
.execute(client)
.getReceipt(client)
.accountId;

/*
* Step 4:
* Query the account information to verify details.
*/
AccountInfo info = new AccountInfoQuery().setAccountId(accountId).execute(client);
System.out.println("Created account ID: " + accountId);
System.out.println("Account key: " + info.key);
System.out.println("Initial EVM address: " + evmAddress + " is the same as " + info.contractAccountId);
}

/**
* Creates a Hedera account with ED25519 account key and ECDSA public key for alias.
* This demonstrates using public key for alias rather than private key.
*
* @param client The Hedera network client used to execute the transaction
* @throws Exception If there's an error during account creation
*/
public static void createAccountWithSeparatePublicKeyAlias(Client client) throws Exception {
/*
* Step 1:
* Generate ED25519 account key and ECDSA key pair for alias.
*/
PrivateKey accountKey = PrivateKey.generateED25519();
PrivateKey aliasPrivateKey = PrivateKey.generateECDSA();
PublicKey aliasPublicKey = aliasPrivateKey.getPublicKey();
System.out.println("\n--- Creating account with ED25519 key and separate ECDSA public key alias ---");
System.out.println("Account key (ED25519): " + accountKey);
System.out.println("Alias private key (ECDSA): " + aliasPrivateKey);
System.out.println("Alias public key (ECDSA): " + aliasPublicKey);

/*
* Step 2:
* Derive the EVM address from the ECDSA public key.
*/
EvmAddress evmAddress = aliasPublicKey.toEvmAddress();
System.out.println("EVM address: " + evmAddress);

/*
* Step 3:
* Create an account with separate keys.
* The transaction must be signed with both the account key and the alias key.
*/
AccountId accountId = new AccountCreateTransaction()
.setKeyWithAlias(accountKey, aliasPublicKey)
.freezeWith(client)
.sign(accountKey)
.sign(aliasPrivateKey)
.execute(client)
.getReceipt(client)
.accountId;

/*
* Step 4:
* Print key and alias details for verification.
* Query the account information to verify details.
*/
System.out.println("Account's key: " + info.key + " is the same as " + privateKey.getPublicKey());
System.out.println("Account has no alias: " + isZeroAddress(Hex.decode(info.contractAccountId)));
AccountInfo info = new AccountInfoQuery().setAccountId(accountId).execute(client);
System.out.println("Created account ID: " + accountId);
System.out.println("Account's key: " + info.key + " is the same as " + accountKey.getPublicKey());
System.out.println("Initial EVM address: " + evmAddress + " is the same as " + info.contractAccountId);
}

/**
Expand Down Expand Up @@ -226,6 +326,8 @@ public static void main(String[] args) throws Exception {
createAccountWithAlias(client);
createAccountWithBothKeys(client);
createAccountWithoutAlias(client);
createAccountWithPublicKeyAlias(client);
createAccountWithSeparatePublicKeyAlias(client);

/*
* Clean up:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,14 @@ public AccountCreateTransaction setKey(Key key) {
* {@link AccountCreateTransaction#setKey(Key)} +
* {@link AccountCreateTransaction#setAlias(EvmAddress)}
*
* @param privateKeyECDSA
* @param key
* @return this
*/
public AccountCreateTransaction setKeyWithAlias(PrivateKey privateKeyECDSA) {
Objects.requireNonNull(privateKeyECDSA);
public AccountCreateTransaction setKeyWithAlias(Key key) {
Objects.requireNonNull(key);
requireNotFrozen();
if (privateKeyECDSA.isECDSA()) {
this.key = privateKeyECDSA;
EvmAddress evmAddress = privateKeyECDSA.getPublicKey().toEvmAddress();
Objects.requireNonNull(evmAddress);
this.alias = evmAddress;
} else {
throw new BadKeyException("Private key is not ECDSA");
}
this.key = key;
this.alias = extractAlias(key);
return this;
}

Expand All @@ -138,20 +132,14 @@ public AccountCreateTransaction setKeyWithAlias(PrivateKey privateKeyECDSA) {
* A user must sign the transaction with both keys for this flow to be successful.
*
* @param key
* @param privateKeyECDSA
* @param ecdsaKey
* @return this
*/
public AccountCreateTransaction setKeyWithAlias(Key key, PrivateKey privateKeyECDSA) {
public AccountCreateTransaction setKeyWithAlias(Key key, Key ecdsaKey) {
Objects.requireNonNull(key);
requireNotFrozen();
this.key = key;
if (privateKeyECDSA.isECDSA()) {
EvmAddress evmAddress = privateKeyECDSA.getPublicKey().toEvmAddress();
Objects.requireNonNull(evmAddress);
this.alias = evmAddress;
} else {
throw new BadKeyException("Private key is not ECDSA");
}
this.alias = extractAlias(ecdsaKey);
return this;
}

Expand Down Expand Up @@ -545,6 +533,18 @@ void initFromTransactionBody() {
alias = EvmAddress.fromAliasBytes(body.getAlias());
}

private EvmAddress extractAlias(Key key) {
var isPrivateEcdsaKey = key instanceof PrivateKeyECDSA;
var isPublicEcdsaKey = key instanceof PublicKeyECDSA;
if (isPrivateEcdsaKey) {
return ((PrivateKeyECDSA) key).getPublicKey().toEvmAddress();
} else if (isPublicEcdsaKey) {
return ((PublicKeyECDSA) key).toEvmAddress();
} else {
throw new BadKeyException("Private key is not ECDSA");
}
}

@Override
MethodDescriptor<com.hedera.hashgraph.sdk.proto.Transaction, TransactionResponse> getMethodDescriptor() {
return CryptoServiceGrpc.getCreateAccountMethod();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,63 @@ void createAccountUsingSetKeyWithAliasWithED25519KeyShouldThrowAnException() thr
}
}

@Test
@DisplayName(
"Can create account with ECDSA public key using setKeyWithAlias, account should have public key as key and derived alias")
void createAccountUsingSetKeyWithAliasWithPublicKeyShouldHavePublicKeyAndDerivedAlias() throws Exception {
try (var testEnv = new IntegrationTestEnv(1)) {
var privateKey = PrivateKey.generateECDSA();
var publicKey = privateKey.getPublicKey();
var evmAddress = publicKey.toEvmAddress();

var accountId = new AccountCreateTransaction()
.setKeyWithAlias(publicKey)
.freezeWith(testEnv.client)
.sign(privateKey)
.execute(testEnv.client)
.getReceipt(testEnv.client)
.accountId;

assertThat(accountId).isNotNull();

var info = new AccountInfoQuery().setAccountId(accountId).execute(testEnv.client);

assertThat(info.accountId).isNotNull();
assertThat(info.key).isEqualTo(publicKey);
assertThat(info.contractAccountId).hasToString(evmAddress.toString());
}
}

@Test
@DisplayName(
"Can create account with ED25519 key and ECDSA public key for alias, account should have ED25519 key and derived alias")
void createAccountUsingSetKeyWithAliasWithED25519KeyAndPublicECDSAKeyShouldHaveED25519KeyAndDerivedAlias()
throws Exception {
try (var testEnv = new IntegrationTestEnv(1)) {
var accountKey = PrivateKey.generateED25519();
var aliasPrivateKey = PrivateKey.generateECDSA();
var aliasPublicKey = aliasPrivateKey.getPublicKey();
var evmAddress = aliasPublicKey.toEvmAddress();

var accountId = new AccountCreateTransaction()
.setKeyWithAlias(accountKey, aliasPublicKey)
.freezeWith(testEnv.client)
.sign(accountKey)
.sign(aliasPrivateKey)
.execute(testEnv.client)
.getReceipt(testEnv.client)
.accountId;

assertThat(accountId).isNotNull();

var info = new AccountInfoQuery().setAccountId(accountId).execute(testEnv.client);

assertThat(info.accountId).isNotNull();
assertThat(info.key).isEqualTo(accountKey.getPublicKey());
assertThat(info.contractAccountId).hasToString(evmAddress.toString());
}
}

private boolean isLongZeroAddress(byte[] address) {
for (int i = 0; i < 12; i++) {
if (address[i] != 0) {
Expand Down