Skip to content

Commit b7c680e

Browse files
Merge pull request #484 from KeystoneHQ/feat/okx_multicoin
Feat/okx multicoin
2 parents 96e93f3 + 852e03f commit b7c680e

17 files changed

Lines changed: 236 additions & 37 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"java.configuration.updateBuildConfiguration": "automatic"
3+
}

app/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ dependencies {
185185
implementation 'androidx.core:core:1.6.0'
186186
compileOnly files('libs/cvos.jar')
187187
implementation 'co.nstant.in:cbor:0.9'
188-
implementation 'com.github.KeystoneHQ:hummingbird:0.6.0'
188+
implementation 'com.github.KeystoneHQ:hummingbird:0.6.2'
189189
implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
190190
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
191191
implementation 'androidx.navigation:navigation-fragment:2.2.0-rc04'
@@ -219,6 +219,10 @@ dependencies {
219219

220220
}
221221

222+
configurations.all {
223+
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
224+
}
225+
222226
preBuild {
223227
doLast {
224228
def imlFile = file(project.name + ".iml")

app/src/main/java/com/keystone/cold/remove_wallet_mode/ui/fragment/connect_wallet/SyncFragment.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ public void setupWalletUI(Wallet wallet) {
180180
R.drawable.ic_coin_btc,
181181
R.drawable.ic_coin_eth,
182182
R.drawable.ic_coin_okb,
183+
R.drawable.ic_coin_dash,
184+
R.drawable.ic_coin_bch,
185+
R.drawable.ic_coin_trx,
186+
R.drawable.ic_coin_ltc,
183187
R.drawable.ic_coin_bnb,
184188
R.drawable.ic_coin_arb,
185189
R.drawable.ic_coin_avax,

app/src/main/java/com/keystone/cold/remove_wallet_mode/ui/fragment/connect_wallet/config/WalletConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public enum WalletConfig {
2828
BITKEEP_ONLY_BTC(Wallet.BITKEEP.getWalletId(), new String[]{Coins.BTC.coinId()}, false, false, true, false),
2929
KEYSTONE(Wallet.KEYSTONE.getWalletId(), new String[]{Coins.BTC.coinId(), Coins.ETH.coinId()}, false, false, true, true),
3030
ETERNL(Wallet.ETERNL.getWalletId(), new String[]{Coins.ADA.coinId()}, false, false, true, false),
31-
OKX(Wallet.OKX.getWalletId(), new String[]{Coins.BTC.coinId(), Coins.ETH.coinId()}, false, false, true, true),
31+
OKX(Wallet.OKX.getWalletId(), new String[]{Coins.BTC.coinId(), Coins.ETH.coinId(),Coins.DASH.coinId(),Coins.BCH.coinId(),Coins.TRON.coinId(),Coins.LTC.coinId()}, false, false, true, true),
3232
DEFAULT("default", new String[]{""}, false, false, true, true),
3333
;
3434

app/src/main/java/com/keystone/cold/remove_wallet_mode/ui/fragment/main/scanner/processor/URProcessor.java

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import com.sparrowwallet.hummingbird.registry.solana.SolNFTItem;
4949
import com.sparrowwallet.hummingbird.registry.solana.SolSignRequest;
5050
import com.sparrowwallet.hummingbird.registry.sui.SuiSignRequest;
51+
import com.sparrowwallet.hummingbird.registry.KeystoneSignRequest;
52+
5153

5254
import org.json.JSONException;
5355
import org.json.JSONObject;
@@ -92,7 +94,9 @@ public Destination run(ScanResult r) throws BaseException {
9294
return new QRHardwareCallProcessor().run(r.resolve());
9395
} else if (r.getType().equals(ScanResultTypes.UR_CARDANO_SIGN_REQUEST)) {
9496
return new CardanoSignRequestProcessor().run(r.resolve());
95-
} else {
97+
} else if (r.getType().equals(ScanResultTypes.UR_KEYSTONE_SIGN_REQUEST)){
98+
return new KeystoneSignRequestProcessor().run(r.resolve());
99+
}else {
96100
throw UnimplementedException.newInstance();
97101
}
98102
}
@@ -101,8 +105,94 @@ interface URResolver {
101105
Destination run(Object object) throws BaseException;
102106
}
103107

104-
private static class ETHSignRequestProcessor implements URResolver {
108+
private static class KeystoneSignRequestProcessor implements URResolver {
109+
110+
111+
@Override
112+
public Destination run(Object object) throws BaseException {
113+
KeystoneSignRequest keystoneSignRequest = (KeystoneSignRequest) object;
114+
String origin = keystoneSignRequest.getOrigin();
115+
String hexString = Hex.toHexString(keystoneSignRequest.getSignData());
116+
JSONObject json = tryDecodeAsJson(hexString);
105117

118+
if (json == null) {
119+
json = tryDecodeAsProtobuf(hexString);
120+
}
121+
if (json != null) {
122+
Destination destination = decodeAndProcess(json);
123+
if (destination == null) {
124+
throw UnknownQrCodeException.newInstance();
125+
}
126+
return destination;
127+
} else {
128+
throw UnknownQrCodeException.newInstance();
129+
}
130+
}
131+
132+
133+
private JSONObject tryDecodeAsJson(String hex) {
134+
try {
135+
return new JSONObject(new String(Hex.decode(hex)));
136+
} catch (Exception ignored) {
137+
}
138+
return null;
139+
}
140+
141+
private JSONObject tryDecodeAsProtobuf(String hex) {
142+
JSONObject object;
143+
hex = ZipUtil.unzip(hex);
144+
object = new ProtoParser(Hex.decode(hex)).parseToJson();
145+
return object;
146+
}
147+
148+
private Destination decodeAndProcess(JSONObject object) throws BaseException {
149+
Destination destination = checkWebAuth(object);
150+
if (destination != null) return destination;
151+
if (object.has("TransactionType")) {
152+
Bundle bundle = new Bundle();
153+
bundle.putString(BundleKeys.SIGN_DATA_KEY, object.toString());
154+
return new Destination(R.id.action_to_rippleConfirmTransactionFragment, bundle);
155+
}
156+
if (object.optString("type").equals("TYPE_SIGN_TX")) {
157+
return handleSign(object);
158+
}
159+
throw UnknownQrCodeException.newInstance();
160+
}
161+
162+
private Destination checkWebAuth(JSONObject object) {
163+
try {
164+
JSONObject webAuth = object.optJSONObject("data");
165+
if (webAuth != null && webAuth.optString("type").equals("webAuth")) {
166+
String data = webAuth.getString("data");
167+
Bundle bundle = new Bundle();
168+
bundle.putString(BundleKeys.WEB_AUTH_DATA_KEY, data);
169+
return new Destination(R.id.action_to_webAuthResultFragment, bundle);
170+
}
171+
} catch (JSONException e) {
172+
e.printStackTrace();
173+
}
174+
return null;
175+
}
176+
177+
private Destination handleSign(JSONObject object)
178+
throws BaseException {
179+
String xfp = new GetMasterFingerprintCallable().call();
180+
if (!object.optString("xfp").toUpperCase().equals(xfp.toUpperCase())){
181+
throw XfpNotMatchException.newInstance();
182+
}
183+
try {
184+
Bundle bundle = new Bundle();
185+
bundle.putString(BundleKeys.SIGN_DATA_KEY, object.getJSONObject("signTx").toString());
186+
return new Destination(R.id.action_to_keystoneConfirmTransactionFragment, bundle);
187+
} catch (JSONException e) {
188+
e.printStackTrace();
189+
return null;
190+
}
191+
}
192+
}
193+
194+
195+
private static class ETHSignRequestProcessor implements URResolver {
106196
@Override
107197
public Destination run(Object object) throws BaseException {
108198
EthSignRequest ethSignRequest = (EthSignRequest) object;
@@ -548,7 +638,7 @@ private Destination checkWebAuth(JSONObject object) {
548638
private Destination handleSign(JSONObject object)
549639
throws BaseException {
550640
String xfp = new GetMasterFingerprintCallable().call();
551-
if (!object.optString("xfp").equals(xfp)) {
641+
if (!object.optString("xfp").toUpperCase().equals(xfp.toUpperCase())) {
552642
throw XfpNotMatchException.newInstance();
553643
}
554644
try {

app/src/main/java/com/keystone/cold/remove_wallet_mode/ui/fragment/main/tx/keystone/KeystoneConfirmTransactionFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected void setupView() {
6464

6565
@Override
6666
protected void onSignSuccess() {
67-
String signatureURString = viewModel.getSignatureUR();
67+
String signatureURString = viewModel.getSignatureUR();
6868
Bundle data = new Bundle();
6969
data.putString(BundleKeys.SIGNATURE_UR_KEY, signatureURString);
7070
data.putString(BundleKeys.COIN_CODE_KEY, viewModel.getCoinCode());

app/src/main/java/com/keystone/cold/remove_wallet_mode/viewmodel/sync_viewmodel/OKXWalletViewModel.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public class OKXWalletViewModel extends AndroidViewModel {
2626
public static final String BTCNestedSegwitPath = "M/49'/0'/0'";
2727
public static final String BTCNativeSegwitPath = "M/84'/0'/0'";
2828

29+
30+
public static final String DashPath = "M/44'/5'/0'";
31+
public static final String BtcCashPath = "M/44'/145'/0'";
32+
public static final String TronPath = "M/44'/195'/0'";
33+
public static final String LitcoinPath = "M/49'/2'/0'";
34+
2935
private final String[] btcPaths = {BTCLegacyPath, BTCNestedSegwitPath, BTCNativeSegwitPath};
3036

3137
private List<String> openedCoins = new ArrayList<>();
@@ -52,6 +58,10 @@ public void setOpenedCoins(List<String> openedCoins) {
5258
this.openedCoins.add(Coins.ETH.coinId());
5359
this.openedCoins.add(Coins.BTC.coinId());
5460
}
61+
this.openedCoins.add(Coins.DASH.coinId());
62+
this.openedCoins.add(Coins.BCH.coinId());
63+
this.openedCoins.add(Coins.TRON.coinId());
64+
this.openedCoins.add(Coins.LTC.coinId());
5565
}
5666

5767
private CryptoMultiAccounts generateCryptoMultiAccounts() {
@@ -63,6 +73,26 @@ private CryptoMultiAccounts generateCryptoMultiAccounts() {
6373
if (openedCoins.contains(Coins.BTC.coinId())) {
6474
cryptoHDKeyList.addAll(generateCryptoHDKeysForBitcoin());
6575
}
76+
77+
if (openedCoins.contains(Coins.DASH.coinId())) {
78+
CryptoHDKey dash = generateCryptoHDKeyForOtherCoin(DashPath, 5);
79+
cryptoHDKeyList.add(dash);
80+
}
81+
82+
if (openedCoins.contains(Coins.BCH.coinId())) {
83+
CryptoHDKey bch = generateCryptoHDKeyForOtherCoin(BtcCashPath, 145);
84+
cryptoHDKeyList.add(bch);
85+
}
86+
87+
if (openedCoins.contains(Coins.TRON.coinId())) {
88+
CryptoHDKey tron = generateCryptoHDKeyForOtherCoin(TronPath,195);
89+
cryptoHDKeyList.add(tron);
90+
}
91+
92+
if (openedCoins.contains(Coins.LTC.coinId())) {
93+
CryptoHDKey litcoin = generateCryptoHDKeyForOtherCoin(LitcoinPath,2);
94+
cryptoHDKeyList.add(litcoin);
95+
}
6696
return new CryptoMultiAccounts(masterFingerprint, cryptoHDKeyList, DeviceInfoUtil.getDeviceType(), DeviceInfoUtil.getDeviceId());
6797
}
6898

@@ -98,4 +128,8 @@ private List<CryptoHDKey> generateCryptoHDKeysForBitcoin() {
98128
private CryptoHDKey generateCryptoHDKeyForBitcoin(String path) {
99129
return URRegistryHelper.generateCryptoHDKey(path, 0);
100130
}
131+
132+
private CryptoHDKey generateCryptoHDKeyForOtherCoin(String path, int type) {
133+
return URRegistryHelper.generateCryptoHDKey(path, type);
134+
}
101135
}

app/src/main/java/com/keystone/cold/remove_wallet_mode/viewmodel/tx/KeystoneTxViewModel.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@
8585
import java.util.concurrent.ExecutorService;
8686
import java.util.concurrent.Executors;
8787
import java.util.stream.Stream;
88-
88+
import com.sparrowwallet.hummingbird.registry.KeystoneSignature;
89+
import java.nio.ByteBuffer;
90+
import java.util.UUID;
8991
public class KeystoneTxViewModel extends BaseTxViewModel<TxEntity> {
9092
private static final String TAG = "KeystoneTxViewModel";
9193
private static final String BTC_SEGWIT_PATH = "M/49'/0'/0'/";
@@ -118,7 +120,6 @@ public void parseTxData(Bundle bundle) {
118120
try {
119121
String json = bundle.getString(BundleKeys.SIGN_DATA_KEY);
120122
JSONObject object = new JSONObject(json);
121-
Log.i(TAG, "object = " + object.toString(4));
122123
transaction = AbsTx.newInstance(object);
123124
if (transaction == null) {
124125
observableException.postValue(new InvalidTransactionException(MainApplication.getApplication().getString(R.string.incorrect_tx_data), "invalid transaction"));
@@ -437,9 +438,37 @@ public void handleSignMessage() {
437438

438439
}
439440

441+
442+
443+
private static byte[] hexToBytes(String s) {
444+
int len = s.length();
445+
byte[] data = new byte[len / 2];
446+
for (int i = 0; i < len; i += 2) {
447+
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
448+
+ Character.digit(s.charAt(i+1), 16));
449+
}
450+
return data;
451+
}
452+
440453
@Override
441454
public String getSignatureUR() {
442-
return observableTransaction.getValue().getSignResult();
455+
KeystoneSignature keystoneSignature;
456+
String signatureStr = observableTransaction.getValue().getSignResult();
457+
String requestIdStr = observableTransaction.getValue().getSignId();
458+
byte[] signature = hexToBytes(signatureStr);
459+
if (signatureStr == null) {
460+
keystoneSignature = new KeystoneSignature(signature);
461+
} else {
462+
UUID uuid = UUID.fromString(requestIdStr);
463+
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
464+
byteBuffer.putLong(uuid.getMostSignificantBits());
465+
byteBuffer.putLong(uuid.getLeastSignificantBits());
466+
byte[] requestId = byteBuffer.array();
467+
keystoneSignature = new KeystoneSignature(signature);
468+
}
469+
String urString = keystoneSignature.toUR().toString();
470+
471+
return urString;
443472
}
444473

445474
void signTransaction(Signer... signer) {

app/src/main/java/com/keystone/cold/ui/fragment/main/AssetFragment.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,11 +506,13 @@ public void handleScanResult(ScanResult result) throws Exception {
506506
handleArweaveSignRequest(result);
507507
} else if (result.getType().equals(ScanResultTypes.UR_EVM_SIGN_REQUEST)) {
508508
handleEvmSignRequest(result);
509+
509510
} else {
510511
throw new UnknowQrCodeException("unknown transaction!");
511512
}
512513
}
513514

515+
514516
private void handleURBytes(ScanResult result) throws JSONException, UnExpectedQRException {
515517
JSONObject object = new JSONObject(new String((byte[]) result.resolve(), StandardCharsets.UTF_8));
516518
JSONObject webAuth = object.optJSONObject("data");

app/src/main/java/com/keystone/cold/ui/fragment/main/scan/scanner/ScanResultTypes.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import com.sparrowwallet.hummingbird.registry.solana.SolNFTItem;
1919
import com.sparrowwallet.hummingbird.registry.solana.SolSignRequest;
2020
import com.sparrowwallet.hummingbird.registry.sui.SuiSignRequest;
21-
21+
import com.sparrowwallet.hummingbird.registry.KeystoneSignRequest;
2222
import org.spongycastle.util.encoders.Hex;
2323

2424
import java.util.List;
@@ -46,7 +46,9 @@ public enum ScanResultTypes {
4646
UR_COSMOS_SIGN_REQUEST,
4747
UR_QR_HARDWARE_CALL,
4848
UR_CARDANO_SIGN_REQUEST,
49-
UR_EVM_SIGN_REQUEST;
49+
UR_EVM_SIGN_REQUEST,
50+
51+
UR_KEYSTONE_SIGN_REQUEST;
5052

5153

5254
public boolean isType(String text) {
@@ -88,6 +90,8 @@ public boolean isType(UR ur) {
8890
return decodeResult instanceof QRHardwareCall;
8991
case UR_CARDANO_SIGN_REQUEST:
9092
return decodeResult instanceof CardanoSignRequest;
93+
case UR_KEYSTONE_SIGN_REQUEST:
94+
return decodeResult instanceof KeystoneSignRequest;
9195
default:
9296
return false;
9397
}
@@ -138,6 +142,8 @@ public Object resolveURHex(String hex) {
138142
return QRHardwareCall.fromCbor(dataItem);
139143
case UR_CARDANO_SIGN_REQUEST:
140144
return CardanoSignRequest.fromCbor(dataItem);
145+
case UR_KEYSTONE_SIGN_REQUEST:
146+
return KeystoneSignRequest.fromCbor(dataItem);
141147
default:
142148
return null;
143149
}
@@ -179,6 +185,8 @@ public static ScanResultTypes fromUR(UR ur) throws UnsupportedURException {
179185
return UR_QR_HARDWARE_CALL;
180186
case "cardano-sign-request":
181187
return UR_CARDANO_SIGN_REQUEST;
188+
case "keystone-sign-request":
189+
return UR_KEYSTONE_SIGN_REQUEST;
182190
default:
183191
throw new UnsupportedURException(MainApplication.getApplication().getString(R.string.invalid_qr_code_hint), "unsupported ur type: " + ur.getType());
184192
}

0 commit comments

Comments
 (0)