SDK Algorand for Mobile

Hello everyone,
I have notice that only in JS SDK a user can sign transaction offline and then use external algod to propagate the signed TX.
See: https://developer.algorand.org/docs/javascript-sdk#node-example-sign

What SDK do you use in your Algorand Wallet? We would to create a simple algorand client that can:

  1. Create accout
  2. Create and sign a transaction (offline for security reason!!)
  3. And the transmit transaction to public algo node

Is it correct that in GO, JAVA and PYTHON SDK can’t sign offline, but only using and external kmd?

Thank you for your answer.
Please let me know.
Enrico

All the SDKs can sign transaction offline and provide the ability to generate accounts that are not managed by the kmd of a node. A new set of docs will be available this week on the developer site that cover using all of the sdks to do offline transactions. What language are you developing in?

This is the problem! In Android we use Java (for Android) for iOS Swift

Can we use Algorand Java SDK in Android App?

1 Like

Here is an example on how to sign offline with the Java SDK: https://github.com/algorand/java-algorand-sdk/blob/e388dfcf0de7835bdf81ed6ef69d2340031c377d/examples/src/main/java/com/algorand/algosdk/example/Main.java#L108

Here is a java example of doing both unsigned and signed in Java. I have not tried with Android:

package com.algorand.javatest;

import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;

import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.algod.client.AlgodClient;
import com.algorand.algosdk.algod.client.api.AlgodApi;
import com.algorand.algosdk.algod.client.auth.ApiKeyAuth;
import com.algorand.algosdk.algod.client.model.TransactionID;
import com.algorand.algosdk.algod.client.model.TransactionParams;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.crypto.Digest;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.Encoder;

public class SaveTransactionOffline {
public AlgodApi algodApiInstance = null;

// utility function to connect to a node
private AlgodApi connectToNetwork(){

    // Initialize an algod client
    final String ALGOD_API_ADDR = <algod-address>;
    final String ALGOD_API_TOKEN = <algod-token>;

    AlgodClient client = (AlgodClient) new AlgodClient().setBasePath(ALGOD_API_ADDR);
    ApiKeyAuth api_key = (ApiKeyAuth) client.getAuthentication("api_key");
    api_key.setApiKey(ALGOD_API_TOKEN);
    algodApiInstance = new AlgodApi(client);   
    return algodApiInstance;
}
// utility function to wait on a transaction to be confirmed    
public void waitForConfirmation( String txID ) throws Exception{
    if( algodApiInstance == null ) connectToNetwork();
    while(true) {
        try {
            //Check the pending tranactions
            com.algorand.algosdk.algod.client.model.Transaction pendingInfo = algodApiInstance.pendingTransactionInformation(txID);
            if (pendingInfo.getRound() != null && pendingInfo.getRound().longValue() > 0) {
                //Got the completed Transaction
                System.out.println("Transaction " + pendingInfo.getTx() + " confirmed in round " + pendingInfo.getRound().longValue());
                break;
            } 
            algodApiInstance.waitForBlock(BigInteger.valueOf( algodApiInstance.getStatus().getLastRound().longValue() +1 ) );
        } catch (Exception e) {
            throw( e );
        }
    }

}
public void writeUnsignedTransaction(){

    // connect to node
    if( algodApiInstance == null ) connectToNetwork();

    final String DEST_ADDR = <transaction-reciever>;
    final String SRC_ADDR = <transaction-sender>;

    try { 
        // Get suggested parameters from the node
        TransactionParams params = algodApiInstance.transactionParams();                     
        BigInteger firstRound = params.getLastRound();
        String genId = params.getGenesisID();
        Digest genesisHash = new Digest(params.getGenesishashb64());

        // create transaction
        BigInteger amount = BigInteger.valueOf(200000);
        BigInteger lastRound = firstRound.add(BigInteger.valueOf(1000));  
        Transaction tx = new Transaction(new Address(SRC_ADDR),  
                BigInteger.valueOf(1000), firstRound, lastRound, 
                null, amount, new Address(DEST_ADDR), genId, genesisHash);
        // save as signed even though it has not been
        SignedTransaction stx = new SignedTransaction();
        stx.tx = tx;  
        // Save transaction to a file 
        Files.write(Paths.get("./unsigned.txn"), Encoder.encodeToMsgPack(stx));
        System.out.println("Transaction written to a file");
    } catch (Exception e) { 
        System.out.println("Save Exception: " + e); 
    }

}
public void readUnsignedTransaction(){

    try {
        // connect to node
        if( algodApiInstance == null ) connectToNetwork();

        // read transaction from file
        SignedTransaction decodedTransaction = Encoder.decodeFromMsgPack(
            Files.readAllBytes(Paths.get("./unsigned.txn")), SignedTransaction.class);            
        Transaction tx = decodedTransaction.tx;           

        // recover account    
        String SRC_ACCOUNT = <25-word-passphrase>;
        Account src = new Account(SRC_ACCOUNT);

        // sign transaction
        SignedTransaction signedTx = src.signTransaction(tx);
        byte[] encodedTxBytes = Encoder.encodeToMsgPack(signedTx);

        // submit the encoded transaction to the network
        TransactionID id = algodApiInstance.rawTransaction(encodedTxBytes);
        System.out.println("Successfully sent tx with id: " + id);
        waitForConfirmation(id.getTxId());

    } catch (Exception e) {
        System.out.println("Submit Exception: " + e); 
    }


}
public void writeSignedTransaction(){

    // connect to node
    if( algodApiInstance == null ) connectToNetwork();

    final String DEST_ADDR = <transaction-reciever>;
    final String SRC_ADDR = <transaction-sender>;;

    try { 

        // Get suggested parameters from the node
        TransactionParams params = algodApiInstance.transactionParams();
        BigInteger firstRound = params.getLastRound();
        String genId = params.getGenesisID();
        Digest genesisHash = new Digest(params.getGenesishashb64());

        // create transaction 
        BigInteger amount = BigInteger.valueOf(200000);
        BigInteger lastRound = firstRound.add(BigInteger.valueOf(1000));  
        Transaction tx = new Transaction(new Address(SRC_ADDR),  
                BigInteger.valueOf(1000), firstRound, lastRound, 
                null, amount, new Address(DEST_ADDR), genId, genesisHash);

        // recover account    
        String SRC_ACCOUNT = <25-word-passphrase>;                    
        Account src = new Account(SRC_ACCOUNT);

        // sign transaction
        SignedTransaction signedTx = src.signTransaction(tx);                    

        // save signed transaction to  a file 
        Files.write(Paths.get("./signed.txn"), Encoder.encodeToMsgPack(signedTx));
    } catch (Exception e) { 
        System.out.println("Save Exception: " + e); 
    }

}

public void readSignedTransaction(){

    try {
        // connect to a node
        if( algodApiInstance == null ) connectToNetwork();

        //Read the transaction from a file 
        SignedTransaction decodedSignedTransaction = Encoder.decodeFromMsgPack(
            Files.readAllBytes(Paths.get("./signed.txn")), SignedTransaction.class);   
        System.out.println("Signed transaction with txid: " + decodedSignedTransaction.transactionID);           

        // Msgpack encode the signed transaction
        byte[] encodedTxBytes = Encoder.encodeToMsgPack(decodedSignedTransaction);

        //submit the encoded transaction to the network
        TransactionID id = algodApiInstance.rawTransaction(encodedTxBytes);
        System.out.println("Successfully sent tx with id: " + id); 
        waitForConfirmation(id.getTxId());

    } catch (Exception e) {
        System.out.println("Submit Exception: " + e); 
    }


}
public static void main(String args[]) throws Exception {
    SaveTransactionOffline mn = new SaveTransactionOffline();
    mn.writeUnsignedTransaction();
    mn.readUnsignedTransaction();

    //mn.writeSignedTransaction();
    //mn.readSignedTransaction();

}

}

2 Likes

Every time I try to create a new instance of the Account class like this new Account() in an android environment, I get the following error
java.security.NoSuchAlgorithmException: Ed25519 KeyPairGenerator not available
I have tried adding BouncyCastle as a provider by doing this
Security.addProvider(new BouncyCastleProvider());
But I still get the same error,

I also get
java.security.NoSuchAlgorithmException: SHA-512/256 MessageDigest not available
when i try to pass in the 25 words mnemonic as a String to the constructor of the Account class,
please any help or reply is appreciated.
Thanks
@Vytek @JasonW

Thanks, this issue has already been resolved

What did you have to do to fix the issue?

i am also getting same error
java.security.NoSuchAlgorithmException: Ed25519 KeyPairGenerator not available

Thanks
@JasonW @jesulonimi

You need to register a crypto provider such as BouncyCastle. Scroll down to section 6.0 for details: https://www.bouncycastle.org/specifications.html

@will i am following your link but still getting the error. Please check the below code. I am consuming this code for android purpose

   Security.addProvider(BouncyCastleProvider())

    // "BC" is the name of the BouncyCastle provider
    val keyGen = KeyGenerator.getInstance("DES", "BC")
    keyGen.init(SecureRandom())
    key = keyGen.generateKey()
    encrypt = Cipher.getInstance("DES/CBC/PKCS5Padding")

    encrypt.init(Cipher.ENCRYPT_MODE, key)
    val bOut = ByteArrayOutputStream()
    val cOut = CipherOutputStream(bOut, encrypt)

    val myAccount = Account()

Can this be the issue?

Security.removeProvider("BC");
Security.insertProviderAt(new BouncyCastleProvider(), 0);

What i did above is pretty simple, the problem is that android ships with an outdated version of BouncyCastle, and has been added as a Provider, what we have to do is remove that provider and add our own version of BouncyCastle as a new Provider. Please note that the two lines of code above must be added before any other code that uses public or private Key Cryptographic algorithm.

1 Like

Thank you so much @rfustino with this code I am able to generate my keys. For the reference for other android developers please check the below code.

Security.removeProvider(“BC”)
Security.insertProviderAt(BouncyCastleProvider(), 0)
val myAccount = Account()

Great to hear! @shashank you might want to add these lines too right after the insert. And I give jesulonimi credit as this is how he solved it. Also, some error checking would be good to add.

    Preformatted textString providerName = "BC";
    if (Security.getProvider(providerName) == null)
    {
        Log.d("algoDebug",providerName + " provider not installed");
    }
    else
    {
        Log.d("algoDebug",providerName + " is installed.");
    }
1 Like

Thanks a lot @rfustino, a beginner tutorial explaining more on this can now be found here

1 Like