The Android client USES Web3j create wallet, import the purse are likely to produce OOM, related issue on making already mentioned: https://github.com/web3j/web3j/issues/299. This problem does not exist before Web3j 3.0, because the new Version of Web3j uses the SpongyCastle library to replace lambdaWorks library, which improves the efficiency, but has compatibility problems on Android.
The project code address: https://github.com/uncleleonfan/WalletOOM.git
Create wallet OOM solution
When creating a Wallet, creating a Full Wallet results in OOM:
public void onCreateFullWallet(View view) { String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/full"; File file = new File(filePath); file.mkdirs(); try { WalletUtils.generateFullNewWalletFile("a12345678", file); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (CipherException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}Copy the code
The Log is as follows:
"Caused by: java.lang.OutOfMemoryError: Failed to allocate a 1036 byte allocation with 16777216 free bytes and 48MB until OOM; failed due to fragmentation (required continguous free 16384 bytes for a new buffer where largest contiguous free 8192 bytes)",
"\tat org.spongycastle.util.Arrays.clone(Arrays.java:602)",
"\tat org.spongycastle.crypto.generators.SCrypt.SMix(SCrypt.java:126)",
"\tat org.spongycastle.crypto.generators.SCrypt.MFcrypt(SCrypt.java:87)",
"\tat org.spongycastle.crypto.generators.SCrypt.generate(SCrypt.java:66)",
"\tat org.web3j.crypto.Wallet.generateDerivedScryptKey(Wallet.java:136)",
"\tat org.web3j.crypto.Wallet.create(Wallet.java:74)",
"\tat org.web3j.crypto.Wallet.createStandard(Wallet.java:93)",
"\tat org.web3j.crypto.WalletUtils.generateWalletFile(WalletUtils.java:61)"
Copy the code
GenerateFullNewWalletFile createStandard will call to create inside the wallet, the use of N_STANDARD, P_STANDARD to configure encryption intensity, directly affect the need to use the memory size, and eventually lead to OOM.
public static WalletFile createStandard(String password, ECKeyPair ecKeyPair)
throws CipherException {
return create(password, ecKeyPair, N_STANDARD, P_STANDARD);
}
Copy the code
The solution is as simple as creating a Light Wallet:
public void onCreateLightWallet(View view) { String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/light"; File file = new File(filePath); file.mkdirs(); try { WalletUtils.generateLightNewWalletFile("a12345678", file); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (CipherException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}Copy the code
GenerateLightNewWalletFile createLight will call to create a light purse, use N_LIGHT P_LIGHT, they are relatively small, in the numerical so not OOM.
public static WalletFile createLight(String password, ECKeyPair ecKeyPair)
throws CipherException {
return create(password, ecKeyPair, N_LIGHT, P_LIGHT);
}
Copy the code
We can compare the size of N_STANDARD and P_STANDARD, N_LIGHT and P_LIGHT:
private static final int N_LIGHT = 1 << 12;
private static final int P_LIGHT = 6;
private static final int N_STANDARD = 1 << 18;
private static final int P_STANDARD = 1;
Copy the code
Import wallet OOM solution
If we import a light wallet, there will be no OOM, but if we import a non-light wallet, there will be OOM. For example, we use Imtoken to create a wallet and export the Keystore as follows:
{"address":"9a2e2419f3af050d4730f80e7a65b9f8deb5e16f","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"eaccea79c27 a91e307f24988186ef21a"},"ciphertext":"a163e532edf2d76beaee5c26fd2c2fab071a9cb37627aa185ac89e223e41ab97","kdf":"scrypt"," kdfparams":{"dklen":32,"n":65536,"p":1,"r":8,"salt":"6a847392a029553f4152dea7bb0b6fb0ac9eec29f55e572fe94603182f5ed7f1"}, "mac":"3fad2a31e18c611b10df84db9ae368ce2e189b5c35e9f111e40ca4b4bfb02491"},"id":"032c47c2-c7b7-46f8-a3f7-f526580f6f09","v ersion":3}Copy the code
It can be seen that n is 65536 and P is 1, while n of the light wallet is 1<<12, that is, 2 to the 12th power, 4096, so this is not a light wallet. We push the Keystore into the SD card as a JSON file and import it using Web3j:
Public void onImportWallet(View View) {try {// Push the keystore.json file in assets to String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/keystore.json"; File file = new File(filePath); WalletUtils.loadCredentials("a12345678", file); } catch (IOException e) { e.printStackTrace(); } catch (CipherException e) { e.printStackTrace(); }}Copy the code
Find also OOM:
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 1036 byte allocation with 13588800 free bytes and 12MB until OOM; failed due to fragmentation (required continguous free 16384 bytes for a new buffer where largest contiguous free 12288 bytes) at org.spongycastle.util.Arrays.clone(Arrays.java:602) at org.spongycastle.crypto.generators.SCrypt.SMix(SCrypt.java:126) at org.spongycastle.crypto.generators.SCrypt.MFcrypt(SCrypt.java:87) at org.spongycastle.crypto.generators.SCrypt.generate(SCrypt.java:66) at org.web3j.crypto.Wallet.generateDerivedScryptKey(Wallet.java:136) at org.web3j.crypto.Wallet.decrypt(Wallet.java:214) at org.web3j.crypto.WalletUtils.loadCredentials(WalletUtils.java:112)Copy the code
GenerateDerivedScryptKey is used to create the wallet in OOM.
private static byte[] generateDerivedScryptKey(
byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws CipherException {
return SCrypt.generate(password, salt, n, r, p, dkLen);
}
Copy the code
Create wallet can create a light wallet, import wallet can not let the user change a light wallet to import it. In this case, we have to switch back to the lambda library to codec the keystore. We can write a generateDerivedScryptKey method that replaces SpongyCastle’s SCrypt with Lambda’s SCrypt. We used MyWalletUtils and MyWallet to do this.
Public void onImportWallet(View View) {try {// Push the keystore.json file in assets to String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/keystore.json"; File file = new File(filePath); Credentials credentials = MyWalletUtils.loadCredentials("a12345678", file); Log.d(TAG, "address:" + credentials.getAddress()); } catch (IOException e) { e.printStackTrace(); } catch (CipherException e) { e.printStackTrace(); } } public class MyWalletUtils { public static Credentials loadCredentials(String password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); return Credentials.create(MyWallet.decrypt(password, walletFile)); } } public class MyWallet { private static final int CURRENT_VERSION = 3; private static final String CIPHER = "aes-128-ctr"; static final String AES_128_CTR = "pbkdf2"; static final String SCRYPT = "scrypt"; private static byte[] generateDerivedScryptKey( byte[] password, byte[] salt, int n, int r, int p, int dkLen) { try { return SCrypt.scrypt(password, salt, n, r, p, dkLen); } catch (GeneralSecurityException e) { e.printStackTrace(); } return null; }}Copy the code
After the above processing, OOM can be solved, but the user will wait a little longer. In addition, it is better to add the libscrpt. So library for Android platform, which can be found in jniLibs of this project.