diff --git a/.gitignore b/.gitignore
index 98f4c142..a37bf85c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,10 @@
bin/
gen/
out/
+.cxx/
+ostsdk/src/main/jniLibs/
+app/src/obj/
+ostsdk/src/obj/
# Gradle files
.gradle/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 631f6fd8..4fef5c1a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# OST Wallet SDK Changelog
+## Version 2.3.8
+### Changes:
+* Reduced recovery key generation time substantially by leveraging on NDK.
+
+### Bug Fix:
+* In OstWalletSDK UI workflows progress bar crashes in background.
+
## Version 2.3.7
### Bug Fix:
* Inaccurate error is thrown when application runs out of memory during recover device workflow.
diff --git a/README.md b/README.md
index d55cfefc..99186fd4 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ compileOptions {
```
dependencies {
- implementation 'com.ost:ost-wallet-sdk-android:2.3.7'
+ implementation 'com.ost:ost-wallet-sdk-android:2.3.8'
...
...
...
diff --git a/Samples/customloader/src/OstMockLoaderFragment.java b/Samples/customloader/src/OstMockLoaderFragment.java
index 747d6e15..4c3854d5 100644
--- a/Samples/customloader/src/OstMockLoaderFragment.java
+++ b/Samples/customloader/src/OstMockLoaderFragment.java
@@ -3,15 +3,15 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
import com.ost.ostwallet.R;
import com.ost.walletsdk.ui.loader.OstLoaderFragment;
diff --git a/Samples/customloader/src/OstMockLoaderManager.java b/Samples/customloader/src/OstMockLoaderManager.java
index dc385824..b4deb69c 100644
--- a/Samples/customloader/src/OstMockLoaderManager.java
+++ b/Samples/customloader/src/OstMockLoaderManager.java
@@ -22,6 +22,12 @@ public OstLoaderFragment getLoader(OstWorkflowContext.WORKFLOW_TYPE workflowType
@Override
public boolean waitForFinalization(OstWorkflowContext.WORKFLOW_TYPE workflowType) {
+ if (OstWorkflowContext.WORKFLOW_TYPE.ACTIVATE_USER.equals(workflowType)) {
+ return false;
+ }
+ if (OstWorkflowContext.WORKFLOW_TYPE.EXECUTE_TRANSACTION.equals(workflowType)) {
+ return false;
+ }
return true;
}
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 5fa958a2..4ebe9932 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@ buildscript {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.2.1'
+ classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'io.fabric.tools:gradle:1.+'
// NOTE: Do not place your application dependencies here; they belong
diff --git a/documentation/OstWalletUI.md b/documentation/OstWalletUI.md
index ecdb80f3..30eab877 100644
--- a/documentation/OstWalletUI.md
+++ b/documentation/OstWalletUI.md
@@ -50,6 +50,13 @@ try {
OstWalletUI.setThemeConfig(themeConfig)
```
+### Get Theme Config
+Get currently applied theme config from sdk.
+
+```java
+OstWalletUI.getThemeConfig()
+```
+
### Set Content Config
Content for OstWalletUI can be initialized by calling `setContentConfig` API.
diff --git a/documentation/ThemeConfig.md b/documentation/ThemeConfig.md
index 679600c9..a23fb509 100644
--- a/documentation/ThemeConfig.md
+++ b/documentation/ThemeConfig.md
@@ -120,6 +120,7 @@ The following UI components properties supported by navigation bar:
| ---------------------- | :-----------------------------: |
| bar logo | nav_bar_logo_image.asset_name |
| bar tint color | navigation_bar.tint_color |
+| bar title color | navigation_bar_header.tint_color |
| close icon tint color | icons.close.tint_color |
| close icon tint color | icons.back.tint_color |
@@ -132,6 +133,36 @@ The following UI components properties supported by navigation bar:
| empty_color | hex value(String) |
| filled_color | hex value(String) |
+### Cell Separator
+
+ The following UI components properties supported by cell separator:
+
+| Configuration Keys | Type |
+| -------------------- | :---------------- |
+| color | hex value(String) |
+
+ ### Link
+
+ The following UI components properties supported by link:
+
+| Configuration Keys | Type |
+| -------------------- | :---------------- |
+| size | number |
+| color | hex value(String) |
+| system_font_weight | string |
+| alignment | string |
+
+ ### status
+
+The following UI components properties supported by status:
+
+| Configuration Keys | Type |
+| -------------------- | :---------------- |
+| size | number |
+| color | hex value(String) |
+| system_font_weight | string |
+| alignment | string |
+
## UI Components
![copy-framework-file](images/NavBar.png)
@@ -140,4 +171,4 @@ The following UI components properties supported by navigation bar:
![copy-framework-file](images/Card.png)
-![copy-framework-file](images/TextField.png)
\ No newline at end of file
+![copy-framework-file](images/TextField.png)
diff --git a/documentation/images/TextField.png b/documentation/images/TextField.png
new file mode 100644
index 00000000..4d89a911
Binary files /dev/null and b/documentation/images/TextField.png differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7550890f..c579a232 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,16 +1,6 @@
-#
-# Copyright 2019 OST.com Inc
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-
-#Mon Feb 25 12:36:41 IST 2019
+#Tue Dec 17 03:00:32 IST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/ostsdk/build.gradle b/ostsdk/build.gradle
index 8e1b9985..255eeae1 100644
--- a/ostsdk/build.gradle
+++ b/ostsdk/build.gradle
@@ -1,3 +1,5 @@
+import org.apache.tools.ant.taskdefs.condition.Os
+
/*
* Copyright 2019 OST.com Inc
*
@@ -57,6 +59,36 @@ android {
lintOptions {
abortOnError false
}
+
+ task ndkBuild(type: Exec) {
+ def rootDir = project.rootDir
+ def localProperties = new File(rootDir, "local.properties")
+ Properties properties = new Properties()
+ localProperties.withInputStream { instr ->
+ properties.load(instr)
+ }
+ def ndkDir = properties.getProperty('ndk.dir')
+
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ commandLine ndkDir + '\\ndk-build.cmd',
+ 'NDK_LIBS_OUT=main/jniLibs',
+ 'NDK_PROJECT_PATH=' + rootDir + '\\ostsdk\\src',
+ 'APP_BUILD_SCRIPT=jni/Android.mk',
+ '-C',
+ file('src').absolutePath
+ } else {
+ commandLine ndkDir + '/ndk-build',
+ 'NDK_LIBS_OUT=main/jniLibs',
+ 'NDK_PROJECT_PATH=' + rootDir + '/ostsdk/src',
+ 'APP_BUILD_SCRIPT=jni/Android.mk',
+ '-C',
+ file('src').absolutePath
+ }
+ }
+
+ tasks.withType(JavaCompile) {
+ compileTask -> compileTask.dependsOn ndkBuild
+ }
}
dependencies {
diff --git a/ostsdk/gradle.properties b/ostsdk/gradle.properties
index 78d1a1a6..fa8adff0 100644
--- a/ostsdk/gradle.properties
+++ b/ostsdk/gradle.properties
@@ -22,9 +22,9 @@
# org.gradle.parallel=true
#Increase version when publishing.
-VERSION_NAME=2.3.7
+VERSION_NAME=2.3.8
#Increase version code when publishing.
-VERSION_CODE=49
+VERSION_CODE=53
#Everything else.
GROUP=com.ost
diff --git a/ostsdk/src/jni/Android.mk b/ostsdk/src/jni/Android.mk
new file mode 100644
index 00000000..e543ab46
--- /dev/null
+++ b/ostsdk/src/jni/Android.mk
@@ -0,0 +1,16 @@
+
+LOCAL_MODULE := libscrypt
+LOCAL_MODULE_FILENAME := libscrypt
+LOCAL_PATH := $(NDK_PROJECT_PATH)
+
+LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/scrypt/c/*.c)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/scrypt/include/
+
+LOCAL_CFLAGS := -std=c99 -Wall -O2
+
+LOCAL_LDFLAGS := -shared
+LOCAL_CFLAGS += -DHAVE_CONFIG_H -I $(LOCAL_PATH)/scrypt/include
+CC := arm-linux-androideabi-gcc
+LOCAL_CFLAGS += --sysroot=$(SYSROOT)
+
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/ostsdk/src/main/assets/ost-theme-config.json b/ostsdk/src/main/assets/ost-theme-config.json
index 255bd3c4..1f1e4a1b 100644
--- a/ostsdk/src/main/assets/ost-theme-config.json
+++ b/ostsdk/src/main/assets/ost-theme-config.json
@@ -1,5 +1,4 @@
{
-
"nav_bar_logo_image": {
"asset_name": "ost_nav_bar_logo"
},
@@ -68,6 +67,28 @@
"system_font_weight": "medium"
},
+ "navigation_bar": {
+ "tint_color": "#ffffff"
+ },
+
+ "navigation_bar_header": {
+ "tint_color": "#438bad"
+ },
+
+ "icons": {
+ "close": {
+ "tint_color": "#438bad"
+ },
+ "back":{
+ "tint_color": "#438bad"
+ }
+ },
+
+ "pin_input": {
+ "empty_color": "#c7c7cc",
+ "filled_color": "#438bad"
+ },
+
"edit_text": {
"size": 15,
"color": "#101010",
@@ -77,25 +98,26 @@
"placeholder": {
"size": 15,
"color": "#888888",
+ "alignment": "left",
"system_font_weight": "regular"
}
},
- "navigation_bar": {
- "tint_color": "#ffffff"
+ "cell_separator": {
+ "color": "#DBDBDB"
},
- "icons": {
- "close": {
- "tint_color": "#438bad"
- },
- "back":{
- "tint_color": "#438bad"
- }
+ "link": {
+ "size": 15,
+ "color": "#007aff",
+ "system_font_weight": "medium",
+ "alignment": "left"
},
- "pin_input": {
- "empty_color": "#c7c7cc",
- "filled_color": "#438bad"
+ "status": {
+ "size": 15,
+ "color": "#0F9D58",
+ "system_font_weight": "regular",
+ "alignment": "left"
}
}
\ No newline at end of file
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/codec/Base64.java b/ostsdk/src/main/java/com/ost/walletsdk/codec/Base64.java
new file mode 100644
index 00000000..33d7791d
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/codec/Base64.java
@@ -0,0 +1,157 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.codec;
+
+import java.util.Arrays;
+
+/**
+ * High-performance base64 codec based on the algorithm used in Mikael Grev's MiG Base64.
+ * This implementation is designed to handle base64 without line splitting and with
+ * optional padding. Alternative character tables may be supplied to the {@code encode}
+ * and {@code decode} methods to implement modified base64 schemes.
+ *
+ * Decoding assumes correct input, the caller is responsible for ensuring that the input
+ * contains no invalid characters.
+ *
+ * @author Will Glozer
+ */
+public class Base64 {
+ private static final char[] encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final int[] decode = new int[128];
+ private static final char pad = '=';
+
+ static {
+ Arrays.fill(decode, -1);
+ for (int i = 0; i < encode.length; i++) {
+ decode[encode[i]] = i;
+ }
+ decode[pad] = 0;
+ }
+
+ /**
+ * Decode base64 chars to bytes.
+ *
+ * @param chars Chars to encode.
+ *
+ * @return Decoded bytes.
+ */
+ public static byte[] decode(char[] chars) {
+ return decode(chars, decode, pad);
+ }
+
+ /**
+ * Encode bytes to base64 chars, with padding.
+ *
+ * @param bytes Bytes to encode.
+ *
+ * @return Encoded chars.
+ */
+ public static char[] encode(byte[] bytes) {
+ return encode(bytes, encode, pad);
+ }
+
+ /**
+ * Encode bytes to base64 chars, with optional padding.
+ *
+ * @param bytes Bytes to encode.
+ * @param padded Add padding to output.
+ *
+ * @return Encoded chars.
+ */
+ public static char[] encode(byte[] bytes, boolean padded) {
+ return encode(bytes, encode, padded ? pad : 0);
+ }
+
+ /**
+ * Decode base64 chars to bytes using the supplied decode table and padding
+ * character.
+ *
+ * @param src Base64 encoded data.
+ * @param table Decode table.
+ * @param pad Padding character.
+ *
+ * @return Decoded bytes.
+ */
+ public static byte[] decode(char[] src, int[] table, char pad) {
+ int len = src.length;
+
+ if (len == 0) return new byte[0];
+
+ int padCount = (src[len - 1] == pad ? (src[len - 2] == pad ? 2 : 1) : 0);
+ int bytes = (len * 6 >> 3) - padCount;
+ int blocks = (bytes / 3) * 3;
+
+ byte[] dst = new byte[bytes];
+ int si = 0, di = 0;
+
+ while (di < blocks) {
+ int n = table[src[si++]] << 18 | table[src[si++]] << 12 | table[src[si++]] << 6 | table[src[si++]];
+ dst[di++] = (byte) (n >> 16);
+ dst[di++] = (byte) (n >> 8);
+ dst[di++] = (byte) n;
+ }
+
+ if (di < bytes) {
+ int n = 0;
+ switch (len - si) {
+ case 4: n |= table[src[si+3]];
+ case 3: n |= table[src[si+2]] << 6;
+ case 2: n |= table[src[si+1]] << 12;
+ case 1: n |= table[src[si]] << 18;
+ }
+ for (int r = 16; di < bytes; r -= 8) {
+ dst[di++] = (byte) (n >> r);
+ }
+ }
+
+ return dst;
+ }
+
+ /**
+ * Encode bytes to base64 chars using the supplied encode table and with
+ * optional padding.
+ *
+ * @param src Bytes to encode.
+ * @param table Encoding table.
+ * @param pad Padding character, or 0 for no padding.
+ *
+ * @return Encoded chars.
+ */
+ public static char[] encode(byte[] src, char[] table, char pad) {
+ int len = src.length;
+
+ if (len == 0) return new char[0];
+
+ int blocks = (len / 3) * 3;
+ int chars = ((len - 1) / 3 + 1) << 2;
+ int tail = len - blocks;
+ if (pad == 0 && tail > 0) chars -= 3 - tail;
+
+ char[] dst = new char[chars];
+ int si = 0, di = 0;
+
+ while (si < blocks) {
+ int n = (src[si++] & 0xff) << 16 | (src[si++] & 0xff) << 8 | (src[si++] & 0xff);
+ dst[di++] = table[(n >>> 18) & 0x3f];
+ dst[di++] = table[(n >>> 12) & 0x3f];
+ dst[di++] = table[(n >>> 6) & 0x3f];
+ dst[di++] = table[n & 0x3f];
+ }
+
+ if (tail > 0) {
+ int n = (src[si] & 0xff) << 10;
+ if (tail == 2) n |= (src[++si] & 0xff) << 2;
+
+ dst[di++] = table[(n >>> 12) & 0x3f];
+ dst[di++] = table[(n >>> 6) & 0x3f];
+ if (tail == 2) dst[di++] = table[n & 0x3f];
+
+ if (pad != 0) {
+ if (tail == 1) dst[di++] = pad;
+ dst[di] = pad;
+ }
+ }
+
+ return dst;
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/crypto/PBKDF.java b/ostsdk/src/main/java/com/ost/walletsdk/crypto/PBKDF.java
new file mode 100644
index 00000000..41dc92a7
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/crypto/PBKDF.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.crypto;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.GeneralSecurityException;
+import static java.lang.System.arraycopy;
+
+/**
+ * An implementation of the Password-Based Key Derivation Function as specified
+ * in RFC 2898.
+ *
+ * @author Will Glozer
+ */
+public class PBKDF {
+ /**
+ * Implementation of PBKDF2 (RFC2898).
+ *
+ * @param alg HMAC algorithm to use.
+ * @param P Password.
+ * @param S Salt.
+ * @param c Iteration count.
+ * @param dkLen Intended length, in octets, of the derived key.
+ *
+ * @return The derived key.
+ *
+ * @throws GeneralSecurityException
+ */
+ public static byte[] pbkdf2(String alg, byte[] P, byte[] S, int c, int dkLen) throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(alg);
+ mac.init(new SecretKeySpec(P, alg));
+ byte[] DK = new byte[dkLen];
+ pbkdf2(mac, S, c, DK, dkLen);
+ return DK;
+ }
+
+ /**
+ * Implementation of PBKDF2 (RFC2898).
+ *
+ * @param mac Pre-initialized {@link Mac} instance to use.
+ * @param S Salt.
+ * @param c Iteration count.
+ * @param DK Byte array that derived key will be placed in.
+ * @param dkLen Intended length, in octets, of the derived key.
+ *
+ * @throws GeneralSecurityException
+ */
+ public static void pbkdf2(Mac mac, byte[] S, int c, byte[] DK, int dkLen) throws GeneralSecurityException {
+ int hLen = mac.getMacLength();
+
+ if (dkLen > (Math.pow(2, 32) - 1) * hLen) {
+ throw new GeneralSecurityException("Requested key length too long");
+ }
+
+ byte[] U = new byte[hLen];
+ byte[] T = new byte[hLen];
+ byte[] block1 = new byte[S.length + 4];
+
+ int l = (int) Math.ceil((double) dkLen / hLen);
+ int r = dkLen - (l - 1) * hLen;
+
+ arraycopy(S, 0, block1, 0, S.length);
+
+ for (int i = 1; i <= l; i++) {
+ block1[S.length + 0] = (byte) (i >> 24 & 0xff);
+ block1[S.length + 1] = (byte) (i >> 16 & 0xff);
+ block1[S.length + 2] = (byte) (i >> 8 & 0xff);
+ block1[S.length + 3] = (byte) (i >> 0 & 0xff);
+
+ mac.update(block1);
+ mac.doFinal(U, 0);
+ arraycopy(U, 0, T, 0, hLen);
+
+ for (int j = 1; j < c; j++) {
+ mac.update(U);
+ mac.doFinal(U, 0);
+
+ for (int k = 0; k < hLen; k++) {
+ T[k] ^= U[k];
+ }
+ }
+
+ arraycopy(T, 0, DK, (i - 1) * hLen, (i == l ? r : hLen));
+ }
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCrypt.java b/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCrypt.java
new file mode 100644
index 00000000..579fd8aa
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCrypt.java
@@ -0,0 +1,222 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.crypto;
+
+import android.util.Log;
+
+import com.ost.walletsdk.jni.*;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.GeneralSecurityException;
+
+import static java.lang.Integer.MAX_VALUE;
+import static java.lang.System.arraycopy;
+
+/**
+ * An implementation of the scrypt
+ * key derivation function. This class will attempt to load a native library
+ * containing the optimized C implementation from
+ * http://www.tarsnap.com/scrypt.html and
+ * fall back to the pure Java version if that fails.
+ *
+ * @author Will Glozer
+ */
+public class SCrypt {
+ private static final boolean native_library_loaded;
+
+ static {
+ LibraryLoader loader = LibraryLoaders.loader();
+ native_library_loaded = loader.load("scrypt", true);
+ }
+
+ /**
+ * Implementation of the scrypt KDF.
+ * Calls the native implementation {@link #scryptN} when the native library was successfully
+ * loaded, otherwise calls {@link #scryptJ}.
+ *
+ * @param passwd Password.
+ * @param salt Salt.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ * @param dkLen Intended length of the derived key.
+ *
+ * @return The derived key.
+ *
+ * @throws GeneralSecurityException when HMAC_SHA256 is not available.
+ */
+ public static byte[] scrypt(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException {
+ if ( native_library_loaded ) {
+ Log.i("OstSCrypt", "Using Native SCrypt method :D");
+ return scryptN(passwd, salt, N, r, p, dkLen);
+ } else {
+ Log.i("OstSCrypt", "Using Java SCrypt method :(");
+ return scryptJ(passwd, salt, N, r, p, dkLen);
+ }
+ }
+
+ /**
+ * Native C implementation of the scrypt KDF using
+ * the code from http://www.tarsnap.com/scrypt.html.
+ *
+ * @param passwd Password.
+ * @param salt Salt.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ * @param dkLen Intended length of the derived key.
+ *
+ * @return The derived key.
+ */
+ public static native byte[] scryptN(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen);
+
+ /**
+ * Pure Java implementation of the scrypt KDF.
+ *
+ * @param passwd Password.
+ * @param salt Salt.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ * @param dkLen Intended length of the derived key.
+ *
+ * @return The derived key.
+ *
+ * @throws GeneralSecurityException when HMAC_SHA256 is not available.
+ */
+ public static byte[] scryptJ(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException {
+ if (N < 2 || (N & (N - 1)) != 0) throw new IllegalArgumentException("N must be a power of 2 greater than 1");
+
+ if (N > MAX_VALUE / 128 / r) throw new IllegalArgumentException("Parameter N is too large");
+ if (r > MAX_VALUE / 128 / p) throw new IllegalArgumentException("Parameter r is too large");
+
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(passwd, "HmacSHA256"));
+
+ byte[] DK = new byte[dkLen];
+
+ byte[] B = new byte[128 * r * p];
+ byte[] XY = new byte[256 * r];
+ byte[] V = new byte[128 * r * N];
+ int i;
+
+ PBKDF.pbkdf2(mac, salt, 1, B, p * 128 * r);
+
+ for (i = 0; i < p; i++) {
+ smix(B, i * 128 * r, r, N, V, XY);
+ }
+
+ PBKDF.pbkdf2(mac, B, 1, DK, dkLen);
+
+ return DK;
+ }
+
+ public static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) {
+ int Xi = 0;
+ int Yi = 128 * r;
+ int i;
+
+ arraycopy(B, Bi, XY, Xi, 128 * r);
+
+ for (i = 0; i < N; i++) {
+ arraycopy(XY, Xi, V, i * (128 * r), 128 * r);
+ blockmix_salsa8(XY, Xi, Yi, r);
+ }
+
+ for (i = 0; i < N; i++) {
+ int j = integerify(XY, Xi, r) & (N - 1);
+ blockxor(V, j * (128 * r), XY, Xi, 128 * r);
+ blockmix_salsa8(XY, Xi, Yi, r);
+ }
+
+ arraycopy(XY, Xi, B, Bi, 128 * r);
+ }
+
+ public static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) {
+ byte[] X = new byte[64];
+ int i;
+
+ arraycopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64);
+
+ for (i = 0; i < 2 * r; i++) {
+ blockxor(BY, i * 64, X, 0, 64);
+ salsa20_8(X);
+ arraycopy(X, 0, BY, Yi + (i * 64), 64);
+ }
+
+ for (i = 0; i < r; i++) {
+ arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64);
+ }
+
+ for (i = 0; i < r; i++) {
+ arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64);
+ }
+ }
+
+ public static int R(int a, int b) {
+ return (a << b) | (a >>> (32 - b));
+ }
+
+ public static void salsa20_8(byte[] B) {
+ int[] B32 = new int[16];
+ int[] x = new int[16];
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ B32[i] = (B[i * 4 + 0] & 0xff) << 0;
+ B32[i] |= (B[i * 4 + 1] & 0xff) << 8;
+ B32[i] |= (B[i * 4 + 2] & 0xff) << 16;
+ B32[i] |= (B[i * 4 + 3] & 0xff) << 24;
+ }
+
+ arraycopy(B32, 0, x, 0, 16);
+
+ for (i = 8; i > 0; i -= 2) {
+ x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9);
+ x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18);
+ x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9);
+ x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18);
+ x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9);
+ x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18);
+ x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9);
+ x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18);
+ x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9);
+ x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18);
+ x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9);
+ x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18);
+ x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9);
+ x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18);
+ x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9);
+ x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18);
+ }
+
+ for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i];
+
+ for (i = 0; i < 16; i++) {
+ B[i * 4 + 0] = (byte) (B32[i] >> 0 & 0xff);
+ B[i * 4 + 1] = (byte) (B32[i] >> 8 & 0xff);
+ B[i * 4 + 2] = (byte) (B32[i] >> 16 & 0xff);
+ B[i * 4 + 3] = (byte) (B32[i] >> 24 & 0xff);
+ }
+ }
+
+ public static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) {
+ for (int i = 0; i < len; i++) {
+ D[Di + i] ^= S[Si + i];
+ }
+ }
+
+ public static int integerify(byte[] B, int Bi, int r) {
+ int n;
+
+ Bi += (2 * r - 1) * 64;
+
+ n = (B[Bi + 0] & 0xff) << 0;
+ n |= (B[Bi + 1] & 0xff) << 8;
+ n |= (B[Bi + 2] & 0xff) << 16;
+ n |= (B[Bi + 3] & 0xff) << 24;
+
+ return n;
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCryptUtil.java b/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCryptUtil.java
new file mode 100644
index 00000000..8cdb50c3
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/crypto/SCryptUtil.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.crypto;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+
+import static com.ost.walletsdk.codec.Base64.*;
+
+/**
+ * Simple {@link SCrypt} interface for hashing passwords using the
+ * scrypt key derivation function
+ * and comparing a plain text password to a hashed one. The hashed output is an
+ * extended implementation of the Modular Crypt Format that also includes the scrypt
+ * algorithm parameters.
+ *
+ * Format: $s0$PARAMS$SALT$KEY
.
+ *
+ *
s0
identifies version 0 of the scrypt format, using a 128-bit salt and 256-bit derived key.
+ *
+ * @author Will Glozer
+ */
+public class SCryptUtil {
+ /**
+ * Hash the supplied plaintext password and generate output in the format described
+ * in {@link SCryptUtil}.
+ *
+ * @param passwd Password.
+ * @param N CPU cost parameter.
+ * @param r Memory cost parameter.
+ * @param p Parallelization parameter.
+ *
+ * @return The hashed password.
+ */
+ public static String scrypt(String passwd, int N, int r, int p) {
+ try {
+ byte[] salt = new byte[16];
+ SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);
+
+ byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
+
+ String params = Long.toString(log2(N) << 16L | r << 8 | p, 16);
+
+ StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
+ sb.append("$s0$").append(params).append('$');
+ sb.append(encode(salt)).append('$');
+ sb.append(encode(derived));
+
+ return sb.toString();
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("JVM doesn't support UTF-8?");
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
+ }
+ }
+
+ /**
+ * Compare the supplied plaintext password to a hashed password.
+ *
+ * @param passwd Plaintext password.
+ * @param hashed scrypt hashed password.
+ *
+ * @return true if passwd matches hashed value.
+ */
+ public static boolean check(String passwd, String hashed) {
+ try {
+ String[] parts = hashed.split("\\$");
+
+ if (parts.length != 5 || !parts[1].equals("s0")) {
+ throw new IllegalArgumentException("Invalid hashed value");
+ }
+
+ long params = Long.parseLong(parts[2], 16);
+ byte[] salt = decode(parts[3].toCharArray());
+ byte[] derived0 = decode(parts[4].toCharArray());
+
+ int N = (int) Math.pow(2, params >> 16 & 0xffff);
+ int r = (int) params >> 8 & 0xff;
+ int p = (int) params & 0xff;
+
+ byte[] derived1 = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
+
+ if (derived0.length != derived1.length) return false;
+
+ int result = 0;
+ for (int i = 0; i < derived0.length; i++) {
+ result |= derived0[i] ^ derived1[i];
+ }
+ return result == 0;
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("JVM doesn't support UTF-8?");
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("JVM doesn't support SHA1PRNG or HMAC_SHA256?");
+ }
+ }
+
+ private static int log2(int n) {
+ int log = 0;
+ if ((n & 0xffff0000 ) != 0) { n >>>= 16; log = 16; }
+ if (n >= 256) { n >>>= 8; log += 8; }
+ if (n >= 16 ) { n >>>= 4; log += 4; }
+ if (n >= 4 ) { n >>>= 2; log += 2; }
+ return log + (n >>> 1);
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/InternalKeyManager.java b/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/InternalKeyManager.java
index fecbe8c1..ca26f55b 100644
--- a/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/InternalKeyManager.java
+++ b/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/InternalKeyManager.java
@@ -13,6 +13,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.ost.walletsdk.crypto.SCrypt;
import com.ost.walletsdk.OstConfigs;
import com.ost.walletsdk.OstSdk;
import com.ost.walletsdk.ecKeyInteracts.impls.OstAndroidSecureStorage;
@@ -30,7 +31,8 @@
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
-import com.ost.walletsdk.utils.scrypt.SCrypt;
+
+
import org.web3j.crypto.Bip32ECKeyPair;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
@@ -165,17 +167,48 @@ String signBytesWithApiSigner(byte[] dataToSign) {
OstSecureKey osk = null;
byte[] key = null;
+ byte[] dataToDecrypt = null;
ECKeyPair ecKeyPair;
try {
osk = metaRepository.getByKey(apiKeyId);
- key = OstAndroidSecureStorage.getInstance(OstSdk.getContext(), mUserId).decrypt(osk.getData());
+ dataToDecrypt = osk.getData();
+
+ key = OstAndroidSecureStorage.getInstance(OstSdk.getContext(), mUserId).decrypt( dataToDecrypt );
+
ecKeyPair = ECKeyPair.create(key);
//Sign the data
Sign.SignatureData signatureData = Sign.signPrefixedMessage(dataToSign, ecKeyPair);
return signatureDataToString(signatureData);
- } catch (Exception ex) {
- Log.e(TAG, "m_s_ikm_sbwps: Unexpected Exception", ex);
- return null;
+ } catch (Throwable th) {
+ OstError ostError = OstError.SdkError("m_s_ikm_sbwps_1", th);
+
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.apiKeyAddress", apiKeyAddress);
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.deviceKeyAddress", mKeyMetaStruct.getDeviceAddress() );
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.apiKeyId", apiKeyId);
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.mUserId", mUserId);
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.isKeyPairGenerated", String.valueOf(OstAndroidSecureStorage.isKeyPairGenerated));
+
+ String isKeyNull = "true";
+ String keyLength = "0";
+ if ( null != key ) {
+ isKeyNull = "false";
+ keyLength = String.valueOf( key.length );
+ }
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.isKeyNull", isKeyNull );
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.keyLength", keyLength );
+
+
+ String isDataToDecryptNull = "true";
+ String dataToDecryptLength = "0";
+ if ( null != dataToDecrypt ) {
+ isDataToDecryptNull = "false";
+ dataToDecryptLength = String.valueOf( dataToDecrypt.length );
+ }
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.isDataToDecryptNull", isDataToDecryptNull );
+ ostError.addErrorInfo("m_s_ikm_sbwps_1.dataToDecryptLength", dataToDecryptLength );
+
+ Log.e(TAG, "m_s_ikm_sbwps_1: Unexpected Exception", th);
+ throw ostError;
} finally {
clearBytes(key);
ecKeyPair = null;
@@ -666,8 +699,27 @@ private ECKeyPair createRecoveryKey(UserPassphrase userPassphrase, byte[] salt)
} catch (OutOfMemoryError error) {
throw new OstError("c_ikm_crk_2", ErrorCode.OUT_OF_MEMORY_ERROR);
} catch (Throwable th) {
- //Suppress Error.
- throw new OstError("c_ikm_crk_1", ErrorCode.RECOVERY_KEY_GENERATION_FAILED);
+ OstError ostError = OstError.SdkError("c_ikm_crk_1", th);
+
+ String passphraseLength = "null";
+ if (null != passphrase) {
+ passphraseLength = String.valueOf(passphrase.length);
+ }
+ ostError.addErrorInfo("c_ikm_crk_1.passphrase.length", passphraseLength);
+
+ String seedLength = "null";
+ if (null != seed) {
+ seedLength = String.valueOf(seed.length);
+ }
+ ostError.addErrorInfo("c_ikm_crk_1.seed.length", seedLength );
+
+ String saltLength = "null";
+ if (null != salt) {
+ saltLength = String.valueOf(salt.length);
+ }
+ ostError.addErrorInfo("c_ikm_crk_1.salt.length", saltLength );
+
+ throw ostError;
} finally {
//Clear the seed.
clearBytes(seed);
@@ -712,32 +764,63 @@ String signDataWithRecoveryKey(String hexStringToSign, UserPassphrase passphrase
}
ECKeyPair ecKeyPair = null;
+ String recoveryOwnerAddress = null;
+ String expectedRecoveryOwnerAddress = null;
+ Sign.SignatureData signatureData = null;
try {
OstUser ostUser = OstUser.getById(mUserId);
- String recoveryOwnerAddress = ostUser.getRecoveryOwnerAddress();
+ recoveryOwnerAddress = ostUser.getRecoveryOwnerAddress();
// Generate ecKeyPair.
ecKeyPair = createRecoveryKey(passphrase, salt);
// Validate recoveryOwnerAddress.
- String expectedRecoveryOwnerAddress = Credentials.create(ecKeyPair).getAddress();
+ expectedRecoveryOwnerAddress = Credentials.create(ecKeyPair).getAddress();
if ( !expectedRecoveryOwnerAddress.equalsIgnoreCase(recoveryOwnerAddress) ) {
// Note that user passphrase is invalid.
userPassphraseInvalidated();
- //Do not throw. Just return null.
- return null;
+ throw new OstError("m_s_ikm_sdwrk_2", ErrorCode.INVALID_USER_PASSPHRASE);
}
// Note that user passphrase is valid.
userPassphraseValidated();
// Sign the data.
- Sign.SignatureData signatureData = Sign.signMessage(Numeric.hexStringToByteArray(hexStringToSign), ecKeyPair, false);
+ signatureData = Sign.signMessage(Numeric.hexStringToByteArray(hexStringToSign), ecKeyPair, false);
return Numeric.toHexString(signatureData.getR()) + Numeric.cleanHexPrefix(Numeric.toHexString(signatureData.getS())) + String.format("%02x", (signatureData.getV()));
- } catch (OstError ostError) {
- throw ostError;
} catch (Throwable th) {
- //Supress it.
- throw new OstError("c_ikm_sdwrk_1", ErrorCode.FAILED_TO_SIGN_DATA);
+ OstError ostError = OstError.SdkError("m_s_ikm_sdwrk_1", th);
+
+ if (null == recoveryOwnerAddress) {
+ recoveryOwnerAddress = "null";
+ }
+ ostError.addErrorInfo("m_s_ikm_sdwrk_1.recoveryOwnerAddress", recoveryOwnerAddress);
+
+ if (null == expectedRecoveryOwnerAddress) {
+ expectedRecoveryOwnerAddress = "null";
+ }
+ ostError.addErrorInfo("m_s_ikm_sdwrk_1.generatedRecoveryOwnerAddress", expectedRecoveryOwnerAddress);
+
+ String saltLength = "null";
+ if (null != salt){
+ saltLength = String.valueOf(salt.length);
+ }
+ ostError.addErrorInfo("m_s_ikm_sdwrk_1.salt.length", saltLength);
+
+ String signatureString = "null";
+ if (null != signatureData) {
+ String rLength = "null";
+ if (null != signatureData.getR()) {
+ rLength = String.valueOf(signatureData.getR().length);
+ }
+ String sLength = "null";
+ if (null != signatureData.getS()) {
+ sLength = String.valueOf(signatureData.getS().length);
+ }
+ signatureString = String.format("R.%s S.%s", rLength, sLength);
+ }
+ ostError.addErrorInfo("m_s_ikm_sdwrk_1.signatureData.length", signatureString);
+
+ throw ostError;
} finally {
if ( null == ecKeyPair ) {
clearBytes(salt);
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/impls/OstAndroidSecureStorage.java b/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/impls/OstAndroidSecureStorage.java
index 520827b6..0a9ea3d2 100644
--- a/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/impls/OstAndroidSecureStorage.java
+++ b/ostsdk/src/main/java/com/ost/walletsdk/ecKeyInteracts/impls/OstAndroidSecureStorage.java
@@ -20,6 +20,7 @@
import com.ost.walletsdk.annotations.NonNull;
import com.ost.walletsdk.ecKeyInteracts.OstSecureStorage;
+import com.ost.walletsdk.workflows.errors.OstError;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
@@ -47,6 +48,7 @@ public class OstAndroidSecureStorage implements OstSecureStorage {
private static final String RSA = "RSA";
private final Context mContext;
private String mKeyAlias;
+ public static boolean isKeyPairGenerated = false;
private KeyStore mKeyStore;
@@ -57,11 +59,19 @@ private OstAndroidSecureStorage(@NonNull Context context, @NonNull String keyAli
mKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
mKeyStore.load(null);
- if (null == getKey()) {
+ KeyPair keyPair = null;
+ try {
+ keyPair = getKey();
+ } catch (OstError err) {
+ //This is expected. Ignore it.
+ }
+
+ if (null == keyPair) {
generateKey();
}
} catch (Exception ex) {
Log.e(TAG, "Exception faced while build object" + ex.getMessage(), ex.getCause());
+ throw OstError.SdkError("oass_constructor_1", ex);
}
}
@@ -87,10 +97,11 @@ public byte[] decrypt(byte[] data) {
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ASYMMETRIC);
cipher.init(Cipher.DECRYPT_MODE, Objects.requireNonNull(getKey()).getPrivate());
return cipher.doFinal(data);
- } catch (Exception ex) {
- Log.e(TAG, "Exception faced while decryption " + ex.getMessage(), ex.getCause());
+ } catch (Throwable th) {
+ OstError ostError = OstError.SdkError("oass_d_1", th);
+ Log.e(TAG, "Exception faced while decryption " + th.getMessage(), th.getCause());
+ throw ostError;
}
- return null;
}
private void generateKey() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
@@ -103,7 +114,7 @@ private void generateKey() throws NoSuchProviderException, NoSuchAlgorithmExcept
}
keyPairGenerator.initialize(algorithmParameterSpec);
keyPairGenerator.genKeyPair();
-
+ isKeyPairGenerated = true;
}
@RequiresApi(Build.VERSION_CODES.M)
@@ -132,17 +143,33 @@ private KeyPairGeneratorSpec initGeneratorWithKeyPairGeneratorSpec() {
}
private KeyPair getKey() {
+ PrivateKey privateKey = null;
+ Certificate keyStoreCertificate = null;
+ PublicKey publicKey = null;
try {
- PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(mKeyAlias, null);
- Certificate keyStoreCertificate = mKeyStore.getCertificate(mKeyAlias);
- PublicKey publicKey = (null == keyStoreCertificate ? null : keyStoreCertificate.getPublicKey());
- if (null == privateKey || null == publicKey) {
- return null;
+ privateKey = (PrivateKey) mKeyStore.getKey(mKeyAlias, null);
+ keyStoreCertificate = mKeyStore.getCertificate(mKeyAlias);
+ publicKey = (null == keyStoreCertificate ? null : keyStoreCertificate.getPublicKey());
+
+ if (null == privateKey || null == keyStoreCertificate || null == publicKey) {
+ throw new Error("Failed to get private key from key store");
}
return new KeyPair(publicKey, privateKey);
- } catch (Exception ex) {
+ } catch (Throwable th) {
+ OstError ostError = OstError.SdkError("oass_kp_gk_1", th);
+ ostError.addErrorInfo("oass_kp_gk_1.key_alias", mKeyAlias);
+
+ String isPrivateKeyNull = null == privateKey? "true": "false";
+ ostError.addErrorInfo("oass_kp_gk_1.isPrivateKeyNull", isPrivateKeyNull);
+
+ String isPublicKeyNull = null == publicKey? "true": "false";
+ ostError.addErrorInfo("oass_kp_gk_1.isPublicKeyNull", isPublicKeyNull);
+
+ String isKeyStoreCertificateNull = null == keyStoreCertificate? "true": "false";
+ ostError.addErrorInfo("oass_kp_gk_1.isKeyStoreCertificateNull", isKeyStoreCertificateNull);
+
Log.d(TAG, "Exception faced in getId ");
+ throw ostError;
}
- return null;
}
}
\ No newline at end of file
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/jni/JarLibraryLoader.java b/ostsdk/src/main/java/com/ost/walletsdk/jni/JarLibraryLoader.java
new file mode 100644
index 00000000..fecb8f7d
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/jni/JarLibraryLoader.java
@@ -0,0 +1,145 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.jni;
+
+import java.io.*;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * A native library loader that will extract and load a shared library contained in a jar.
+ * This loader will attempt to detect the {@link Platform platform} (CPU architecture and OS)
+ * it is running on and load the appropriate shared library.
+ *
+ * Given a library path and name this loader looks for a native library with path
+ * [libraryPath]/[arch]/[os]/lib[name].[ext]
+ *
+ * @author Will Glozer
+ */
+public class JarLibraryLoader implements LibraryLoader {
+ private final CodeSource codeSource;
+ private final String libraryPath;
+
+ /**
+ * Initialize a new instance that looks for shared libraries located in the same jar
+ * as this class and with a path starting with {@code lib}.
+ */
+ public JarLibraryLoader() {
+ this(JarLibraryLoader.class.getProtectionDomain().getCodeSource(), "lib");
+ }
+
+ /**
+ * Initialize a new instance that looks for shared libraries located in the specified
+ * directory of the supplied code source.
+ *
+ * @param codeSource Code source containing shared libraries.
+ * @param libraryPath Path prefix of shared libraries.
+ */
+ public JarLibraryLoader(CodeSource codeSource, String libraryPath) {
+ this.codeSource = codeSource;
+ this.libraryPath = libraryPath;
+ }
+
+ /**
+ * Load a shared library, and optionally verify the jar signatures.
+ *
+ * @param name Name of the library to load.
+ * @param verify Verify the jar file if signed.
+ *
+ * @return true if the library was successfully loaded.
+ */
+ public boolean load(String name, boolean verify) {
+ boolean loaded = false;
+
+ try {
+ Platform platform = Platform.detect();
+ JarFile jar = new JarFile(codeSource.getLocation().getPath(), verify);
+ try {
+ for (String path : libCandidates(platform, name)) {
+ JarEntry entry = jar.getJarEntry(path);
+ if (entry == null) continue;
+
+ File lib = extract(name, jar.getInputStream(entry));
+ System.load(lib.getAbsolutePath());
+ lib.delete();
+
+ loaded = true;
+ break;
+ }
+ } finally {
+ jar.close();
+ }
+ } catch (Throwable e) {
+ loaded = false;
+ }
+
+ return loaded;
+ }
+
+ /**
+ * Extract a jar entry to a temp file.
+ *
+ * @param name Name prefix for temp file.
+ * @param is Jar entry input stream.
+ *
+ * @return A temporary file.
+ *
+ * @throws IOException when an IO error occurs.
+ */
+ private static File extract(String name, InputStream is) throws IOException {
+ byte[] buf = new byte[4096];
+ int len;
+
+ File lib = File.createTempFile(name, "lib");
+ FileOutputStream os = new FileOutputStream(lib);
+
+ try {
+ while ((len = is.read(buf)) > 0) {
+ os.write(buf, 0, len);
+ }
+ } catch (IOException e) {
+ lib.delete();
+ throw e;
+ } finally {
+ os.close();
+ is.close();
+ }
+
+ return lib;
+ }
+
+ /**
+ * Generate a list of candidate libraries for the supplied library name and suitable
+ * for the current platform.
+ *
+ * @param platform Current platform.
+ * @param name Library name.
+ *
+ * @return List of potential library names.
+ */
+ private Listos.arch
and os.name
system properties.
+ *
+ * @author Will Glozer
+ */
+public class Platform {
+ public enum Arch {
+ x86 ("x86|i386"),
+ x86_64("x86_64|amd64");
+
+ Pattern pattern;
+
+ Arch(String pattern) {
+ this.pattern = Pattern.compile("\\A" + pattern + "\\Z", CASE_INSENSITIVE);
+ }
+ }
+
+ public enum OS {
+ darwin ("darwin|mac os x"),
+ freebsd("freebsd"),
+ linux ("linux");
+
+ Pattern pattern;
+
+ OS(String pattern) {
+ this.pattern = Pattern.compile("\\A" + pattern + "\\Z", CASE_INSENSITIVE);
+ }
+ }
+
+ public final Arch arch;
+ public final OS os;
+
+ private Platform(Arch arch, OS os) {
+ this.arch = arch;
+ this.os = os;
+ }
+
+ /**
+ * Attempt to detect the current platform.
+ *
+ * @return The current platform.
+ *
+ * @throws UnsupportedPlatformException if the platform cannot be detected.
+ */
+ public static Platform detect() throws UnsupportedPlatformException {
+ String osArch = getProperty("os.arch");
+ String osName = getProperty("os.name");
+
+ for (Arch arch : Arch.values()) {
+ if (arch.pattern.matcher(osArch).matches()) {
+ for (OS os : OS.values()) {
+ if (os.pattern.matcher(osName).matches()) {
+ return new Platform(arch, os);
+ }
+ }
+ }
+ }
+
+ String msg = String.format("Unsupported platform %s %s", osArch, osName);
+ throw new UnsupportedPlatformException(msg);
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/jni/SysLibraryLoader.java b/ostsdk/src/main/java/com/ost/walletsdk/jni/SysLibraryLoader.java
new file mode 100644
index 00000000..7f911e21
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/jni/SysLibraryLoader.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.jni;
+
+/**
+ * A native library loader that simply invokes {@link System#loadLibrary}. The shared
+ * library path and filename are platform specific.
+ *
+ * @author Will Glozer
+ */
+public class SysLibraryLoader implements LibraryLoader {
+ /**
+ * Load a shared library.
+ *
+ * @param name Name of the library to load.
+ * @param verify Ignored, no verification is done.
+ *
+ * @return true if the library was successfully loaded.
+ */
+ public boolean load(String name, boolean verify) {
+ boolean loaded;
+
+ try {
+ System.loadLibrary(name);
+ loaded = true;
+ } catch (Throwable e) {
+ loaded = false;
+ }
+
+ return loaded;
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/jni/UnsupportedPlatformException.java b/ostsdk/src/main/java/com/ost/walletsdk/jni/UnsupportedPlatformException.java
new file mode 100644
index 00000000..a45a9b9d
--- /dev/null
+++ b/ostsdk/src/main/java/com/ost/walletsdk/jni/UnsupportedPlatformException.java
@@ -0,0 +1,14 @@
+// Copyright (C) 2011 - Will Glozer. All rights reserved.
+
+package com.ost.walletsdk.jni;
+
+/**
+ * Exception thrown when the current platform cannot be detected.
+ *
+ * @author Will Glozer
+ */
+public class UnsupportedPlatformException extends RuntimeException {
+ public UnsupportedPlatformException(String s) {
+ super(s);
+ }
+}
diff --git a/ostsdk/src/main/java/com/ost/walletsdk/network/OstJsonApi.java b/ostsdk/src/main/java/com/ost/walletsdk/network/OstJsonApi.java
index 632422b9..ec146515 100644
--- a/ostsdk/src/main/java/com/ost/walletsdk/network/OstJsonApi.java
+++ b/ostsdk/src/main/java/com/ost/walletsdk/network/OstJsonApi.java
@@ -61,7 +61,7 @@ private static void execGetBalance(@NonNull String userId, @NonNull OstJsonApiCa
if ( err instanceof OstError ) {
error = (OstError) err;
} else {
- error = new OstError("ojsonapi_egb_2", ErrorCode.SDK_ERROR);
+ error = OstError.SdkError("ojsonapi_egb_1", err);
}
sendErrorCallback(callback, error, response);
}
@@ -98,7 +98,7 @@ private static void execGetPricePoints(@NonNull String userId, @NonNull OstJsonA
if ( err instanceof OstError ) {
error = (OstError) err;
} else {
- error = new OstError("ojsonapi_egb_2", ErrorCode.SDK_ERROR);
+ error = OstError.SdkError("ojsonapi_egpp_1", err);
}
sendErrorCallback(callback, error, response);
}
@@ -160,8 +160,7 @@ private static void execGetBalanceWithPricePoints(@NonNull String userId, @NonNu
if ( err instanceof OstError ) {
error = (OstError) err;
} else {
-
- error = new OstError("ojsonapi_egbwpp_4", ErrorCode.SDK_ERROR);
+ error = OstError.SdkError("ojsonapi_egbpp_1", err);
}
sendErrorCallback(callback, error, response);
}
@@ -199,7 +198,7 @@ private static void execGetTransactions(@NonNull String userId, Map