Android Web 3 j OOM解決

7999 ワード

AndroidクライアントでWeb 3 jを使用して財布を作成したり、財布をインポートしたりするとOOMが発生する可能性があります.関連issueはGithubですでに言及されています.https://github.com/web3j/web3j/issues/299 .この問題はWeb 3 j 3.0以前にはありませんでしたが、新版のWeb 3 jはspongycastleライブラリをlambdaworksライブラリに置き換えたため、効率的に速度が向上しましたが、Android側の互換性の問題があります.
本プロジェクトのコードアドレス:https://github.com/uncleleonfan/WalletOOM.git
ウォレットOOM解決の作成
ウォレットを作成するときにFull Walletを作成すると、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();
    }
}

ログは以下の通り.
"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)"

GenerateFullNewWalletFileではcreateStandardを呼び出して財布を作成し、N_を使用します.STANDARD,P_STANDARDは暗号化強度を構成し、使用するメモリサイズに直接影響し、最終的にOOMの発生を招く.
public static WalletFile createStandard(String password, ECKeyPair ecKeyPair)
        throws CipherException {
    return create(password, ecKeyPair, N_STANDARD, P_STANDARD);
}

解決方法は非常に簡単で、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();
    }

}

G e n erateLightNewWalletFileはcreateLightを呼び出して軽財布を作成し、N_を使用します.LIGHT,P_LIGHT、彼らは数値的に比較的小さいので、OOMはできません.
public static WalletFile createLight(String password, ECKeyPair ecKeyPair)
        throws CipherException {
    return create(password, ecKeyPair, N_LIGHT, P_LIGHT);
}

N_と比較してみましょうSTANDARDとP_STANDARD, N_LIGHTと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;

ウォレットOOMソリューションのインポート
ライトウォレットをインポートすると、OOMは生成されませんが、インポートがライトウォレットでない場合、OOMが生成される可能性があります.たとえば、Impokenを使用して財布を作成し、Keystoreをエクスポートします.Keystoreは次のようになります.
{"address":"9a2e2419f3af050d4730f80e7a65b9f8deb5e16f","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"eaccea79c27a91e307f24988186ef21a"},"ciphertext":"a163e532edf2d76beaee5c26fd2c2fab071a9cb37627aa185ac89e223e41ab97","kdf":"scrypt","kdfparams":{"dklen":32,"n":65536,"p":1,"r":8,"salt":"6a847392a029553f4152dea7bb0b6fb0ac9eec29f55e572fe94603182f5ed7f1"},"mac":"3fad2a31e18c611b10df84db9ae368ce2e189b5c35e9f111e40ca4b4bfb02491"},"id":"032c47c2-c7b7-46f8-a3f7-f526580f6f09","version":3}

ここで、nは65536、pは1であり、軽財布のnは1<12、すなわち2の12次方、4096であるため、これは軽財布ではないことがわかる.このKeystoreをjsonファイルとしてSDカードにpushし、Web 3 jを使用してインポートします.
public void onImportWallet(View view) {
    try {
        //    assets    keystore.json       SD  
        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();
    }
}

同じ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)

ロゴを見ると、ここでは財布を作成するOOMと同じで、generateDerivedScryptKeyを最後に呼び出した後に発生したことがわかります.
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);
}

ウォレットを作成するとライトウォレットを作成できますが、ウォレットをインポートすると、ユーザーがライトウォレットを交換してインポートすることはできません.ここでは、keystoreの符号化を完了するためにlambdaライブラリを交換するしかありません.すなわち、spongycastleのSCryptをlambdaのSCryptに変更するgenerateDerivedScryptKeyメソッドを自分で書くことができます.MyWalletUtilsとMyWalletを使用して、このタスクを共同で完了します.
public void onImportWallet(View view) {
    try {
        //    assets    keystore.json       SD  
        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;
    }
}

以上のように処理することでOOMを解決することができるが、ユーザの待ち時間は少し長くなる、またAndroidプラットフォームのlibscrptを追加することが望ましい.soライブラリ、皆さんは本プロジェクトのjniLibsで見つけることができます.