Create an account using the Java SDK

I have some confusion when using java sdk to register an account.
The following is the test class of the SDK. I can’t understand what values ​​​​the data and signer need to pass in, public keys or other values, and where these values ​​​​are obtained

 @Test
    public void testCreateTransferWithRegisterData() {
        try {
            RegisterDataTransaction tx = TransactionFactory.newRegisterData()
                    .data(Data.from(new byte[]{1, 2, 3, 4, 5}))
                    .sender(AccountAddress.from("3JwD2Wm3nMbsowCwb1iGEpnt47UQgdrtnq2qT6opJc3z2AgCrc"))
                    .nonce(Nonce.from(78910))
                    .expiry(Expiry.from(123456))
                    .signer(TransactionSigner.from(
                            SignerEntry.from(Index.from(0), Index.from(0),
                                    ED25519SecretKey.from("7100071c835a0a35e86dccba7ee9d10b89e36d1e596771cdc8ee36a17f7abbf2")),
                            SignerEntry.from(Index.from(0), Index.from(1),
                                    ED25519SecretKey.from("cd20ea0127cddf77cf2c20a18ec4516a99528a72e642ac7deb92131a9d108ae9"))
                    ))
                    .build();
            val transferWithRegisterData = tx.getPayload();

            assertEquals(UInt64.from(568), transferWithRegisterData.header.getMaxEnergyCost());
            assertEquals(8, transferWithRegisterData.getBytes().length);
            val blockItem = transferWithRegisterData.toBlockItem();
            assertEquals(Hash.from("0ad44be061cbdfc22fdcf14e2cd48d7c34d543ea60cb9cf5298cb40d89c25d83"), blockItem.getHash());
            assertEquals(blockItem.getHash(), BlockItem.fromVersionedBytes(ByteBuffer.wrap(blockItem.getVersionedBytes())).getHash());
            assertArrayEquals(TestUtils.EXPECTED_BLOCK_ITEM_TRANSFER_WITH_REGISTER_DATA_VERSIONED_BYTES, TestUtils.signedByteArrayToUnsigned(blockItem.getVersionedBytes()));
        } catch (TransactionCreationException e) {
            fail("Unexpected error: " + e.getMessage());
        }
    }

Hi sandy,

This test case does not have to do with creating an account - it’s a “register data” transaction.

The RegisterData transaction that is used in this example is a transaction that records some (arbitrary) data on the chain. This is typically used for notarization: by recording a hash of some data, you can establish that the data was created by the time of the transaction. In this case, the data (here the byte array {1,2,3,4,5}) can be any array of bytes up to 256 bytes in length.

The signer needs to be initialized with the secret key(s) for the account that is sending the transaction. In this example, there are two keys, but typically accounts only have one key (so you would just provide one key with indexes 0 and 0). The Java SDK does not really provide much support for key management. You can export private keys from the concordium wallets that you can then use for signing.

To actually create an account is a bit complicated. First, you need an identity object. To create an identity object, you need to create an identity issuance request and submit it to an identity provider. The user then has to complete the identity issuance flow (specific to the identity provider), which results in the creation of an identity object.

You can then use the identity object to create a CredentialDeploymentTransaction, which can be submitted to the chain to create an account. (See here for an example.) I recommend looking over the concordium-android-wallet-example in depth to see how the identity issuance and account creation process works.

Hope this helps
– Thomas

OK, thank you for your answer. I have other questions. What data does this identity object contain? When I create an address, I call a separate service of our exchange and calculate the address offline through the public key. In this process, I can only get the address and public key information, but I can’t get the private key. Can I complete the registration of this address in this way? The following is my code for generating an address offline.

  public String generate(String publicKey) throws DecoderException {

        byte[] publicBytes = Hex.decodeHex(publicKey);

        byte[] input = new byte[33];
        input[0] = (byte)1;
        System.arraycopy(publicBytes, 0, input, 1, 32);
        byte[] hash1 = Hash.sha256(input);
        byte[] hash2 = Hash.sha256(hash1);
        byte[] result = new byte[33 + 4];
        System.arraycopy(input, 0, result, 0 ,33);
        System.arraycopy(hash2, 0, result, 33 ,4);

        return Base58.encode(result);

    }

The identity object consists of:

  • A “pre-identity object”, which is the information sent by the account holder to the identity provider. This includes various cryptographic data that allows identity revocation authorities to discover the identity associated with accounts created using the identity object.
  • A list of attributes associated with the identity (e.g. name, nationality, date of birth) that are certified by the identity provider.
  • The identity provider’s signature on the identity.

I’m a bit unclear about what you mean by “create an address”. When an account is created on Concordium, its address is calculated by hashing (SHA256) the registration id (technically, this is an element of the G1 group of the BLS curve) of the credential used to create the account. This gives the canonical address of the account, but the account can also be referred to by any address that only differs from the canonical address in the last 3 bytes (these are “aliases” for the account).

I believe that your generate function computes the Base58-checked representation of the account address from the hexadecimal encoding of its address (i.e. the SHA256 hash of the credential registration id).

         TransferWithMemoTransaction tx = TransactionFactory.newTransferWithMemo()
                    .memo(Memo.from(new byte[]{1, 2, 3, 4, 5}))
                    .receiver(AccountAddress.from("3hYXYEPuGyhFcVRhSk2cVgKBhzVcAryjPskYk4SecpwGnoHhuM"))
                    .amount(CCDAmount.fromMicro(17))
                    .sender(AccountAddress.from("3JwD2Wm3nMbsowCwb1iGEpnt47UQgdrtnq2qT6opJc3z2AgCrc"))
                    .nonce(Nonce.from(78910))
                    .expiry(Expiry.from(123456))
                    .signer(TransactionSigner.from(
                            SignerEntry.from(Index.from(0), Index.from(0),
                                    ED25519SecretKey.from("7100071c835a0a35e86dccba7ee9d10b89e36d1e596771cdc8ee36a17f7abbf2")),
                            SignerEntry.from(Index.from(0), Index.from(1),
                                    ED25519SecretKey.from("cd20ea0127cddf77cf2c20a18ec4516a99528a72e642ac7deb92131a9d108ae9"))
                    ))
                    .build();

When I used this method in the SDK to test transfer, he reported such an error to me. Can you help me find out the reason?

[2024-08-01 08:05:29.211] - [  http-nio-8080-exec-1 o.a.c.c.C.[.[.[.[dispatcherServlet]:182] - [ERROR] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.ExceptionInInitializerError] with root cause
java.lang.RuntimeException: FAILED LOADING LIB
        at com.concordium.sdk.crypto.NativeResolver.loadLib(NativeResolver.java:31)
        at com.concordium.sdk.crypto.ed25519.ED25519.<clinit>(ED25519.java:12)
        at com.concordium.sdk.crypto.ed25519.ED25519SecretKey.sign(ED25519SecretKey.java:58)
        at com.concordium.sdk.transactions.TransactionSignerImpl.sign(TransactionSignerImpl.java:43)
        at com.concordium.sdk.transactions.Payload.signWith(Payload.java:84)
        at com.concordium.sdk.transactions.AccountTransaction.<init>(AccountTransaction.java:56)
        at com.concordium.sdk.transactions.TransferWithMemoTransaction.<init>(TransferWithMemoTransaction.java:20)
        at com.concordium.sdk.transactions.TransferWithMemoTransaction.from(TransferWithMemoTransaction.java:50)
        at com.concordium.sdk.transactions.TransferWithMemoTransaction$TransferWithMemoTransactionBuilder.build(TransferWithMemoTransaction.java:41)
        at com.bitmart.chain.service.impl.DefaultChainService.testTransfer(DefaultChainService.java:276)
        at com.bitmart.chain.service.impl.DefaultChainService$$FastClassBySpringCGLIB$$971f7e45.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)

Hi sandy, can you please show how you added the SDK to your project?

Please also specify on what platform (OS, CPU architecture) you are running the code


I added the compiled jar package to the project, and then loaded the jar package through the gradle configuration, but I skipped the test in the process of compiling the jar package. I used the macos system locally, and then deployed the project to the server for testing.

If you have the SDK source code and want to add SNAPSHOT versions to your project, it’s better to follow the build&installation guide from the SDK Readme:

  1. Run make from the root of this repository.
  2. Run mvn install from the root of the concordium-sdk folder.

Then you can import the SDK in your Gradle project from mavenLocal repository:
implementation("com.concordium.sdk:concordium-sdk:7.1.0-SNAPSHOT")
Having mavenLocal() added to your allprojects repositories.

Please, try importing the library this way and check if the native library is loading as expected

Also, if your server is running on different OS or CPU architecture, the locally built snapshot may not work because it only contains the native library built for your Mac.
If this is the case, try importing the latest version from
Maven Central: com.concordium.sdk:concordium-sdk (sonatype.com)

OK, I’ll try again. I’ve done this before, but it didn’t work.

When I import the latest version, it throws the following error when I deploy it. Is there any missing snapshot you uploaded?

#9 80.60   Required by:
#9 80.60       project : > com.concordium.sdk:concordium-sdk:7.1.0
#9 80.60    > Could not resolve org.semver4j:semver4j:2.0.1.
#9 80.60       > inconsistent module metadata found. Descriptor: org.semver4j:semver4j:${revision} Errors: bad version: expected='2.0.1' found='${revision}'
#9 80.60    > Could not resolve org.semver4j:semver4j:2.0.1.
#9 80.60       > inconsistent module metadata found. Descriptor: org.semver4j:semver4j:${revision} Errors: bad version: expected='2.0.1' found='${revision}'
#9 80.60    > Could not resolve org.semver4j:semver4j:2.0.1.
#9 80.60       > Could not get resource 'https://repo.spring.io/libs-milestone/org/semver4j/semver4j/2.0.1/semver4j-2.0.1.pom'.
#9 80.60          > Could not HEAD 'https://repo.spring.io/libs-milestone/org/semver4j/semver4j/2.0.1/semver4j-2.0.1.pom'. Received status code 401 from server: 
#9 80.60    > Could not resolve org.semver4j:semver4j:2.0.1.
#9 80.60       > inconsistent module metadata found. Descriptor: org.semver4j:semver4j:${revision} Errors: bad version: expected='2.0.1' found='${revision}'

You have an issue with repo.spring.io repository. If it is possible, remove it from your project repositories or make Gradle not use it for loading Maven Central dependencies

threw exception [Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class com.concordium.sdk.crypto.ed25519.ED25519] with root cause
java.lang.NoClassDefFoundError: Could not initialize class com.concordium.sdk.crypto.ed25519.ED25519

When I deployed to the server calling the signature method I got this error
Other interfaces are normal

 [ERROR] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: /tmp/bd6234dfda826723081772539542018libcrypto_jni.so.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by /tmp/bd6234dfda826723081772539542018libcrypto_jni.so.so)] with root cause
java.lang.UnsatisfiedLinkError: /tmp/bd6234dfda826723081772539542018libcrypto_jni.so.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by /tmp/bd6234dfda826723081772539542018libcrypto_jni.so.so)
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
        at java.lang.Runtime.load0(Runtime.java:809)
        at java.lang.System.load(System.java:1086)
        at com.concordium.sdk.crypto.NativeResolver.loadLib(NativeResolver.java:39)
        at com.concordium.sdk.crypto.ed25519.ED25519.<clinit>(ED25519.java:12)
        at com.concordium.sdk.crypto.ed25519.ED25519SecretKey.sign(ED25519SecretKey.java:58)
        at com.concordium.sdk.transactions.TransactionSignerImpl.sign(TransactionSignerImpl.java:43)

This error was thrown when I called it for the first time. The above error was thrown for the second time. I am not sure which link went wrong, or if there is any strong requirement for the environment version.

Something is wrong with loading the native library included to the SDK.
Your server is running on Linux on x86-64 CPU, am I right?

Yes, are there any requirements for the version?

I don’t think so, at least the SDK runs on my Windows x86-64 laptop. I’ll try to find someone who knows more about the native bindings and will get back to you.

OK, thank you for your support

So the native library fails to load because the OS on your server has outdated glibc (GNU C library).
If you run Ubuntu, it needs to be updated to 22.04.