Compare commits

...
Author SHA1 Message Date
Vitaliy Vlasov 73c41d1a3d Add exportKeyWithPath() fn 2019-12-27 16:32:51 +02:00
Vitaliy Vlasov 29fe98beb3 Add signPinless 2019-12-05 13:36:21 +02:00
Roman Volosovskyi 79a7576ee1 fix master key address exporting 2019-11-27 09:57:50 +02:00
Roman Volosovskyi c9fbb9a3f3 Return master key address and public key
Before this commit `Keycard.generateAndLoadKey` and
`Keyacrd.getKeys` methods returned wallet root key address and public
key in "address" and "public-key" fields correspondingly. This caused
discrepancy in the way how keys were used for on-device and keycard
accounts in Status app. For on-device accounts master key address
was used as account's deterministic id, when keycard accounts used
wallet root key address for this purpose.
2019-11-19 11:12:46 +02:00
WoosangKim 2d5893d562 add thread to avoid main thread freezing 2019-11-07 11:14:31 +01:00
Dmitry Novotochinov c97d629a7d update docs 2019-10-03 15:33:24 +03:00
Dmitry Novotochinov 34a76aeef9 2.5.12 2019-09-30 15:22:49 +03:00
Dmitry Novotochinov 3638d7ee42 add more data to getKeys() output 2019-09-30 15:22:26 +03:00
Dmitry Novotochinov 1e0f6e30c8 2.5.11 2019-09-05 18:50:43 +03:00
Dmitry Novotochinov 8fe4af0da8 derive root key at m/44'/60'/0'/0 2019-09-05 18:50:35 +03:00
Dmitry Novotochinov 8027427b1a 2.5.10 2019-09-05 12:19:40 +03:00
Dmitry Novotochinov 2718a08879 export root path key 2019-09-05 12:19:29 +03:00
Dmitry Novotochinov 0b1b319e8e 2.5.9 2019-09-05 11:21:40 +03:00
Dmitry Novotochinov d6bac19ca0 add root address and public key 2019-09-05 11:21:19 +03:00
Dmitry Novotochinov 3febebfba1 2.5.8 2019-08-20 23:01:11 +03:00
Dmitry Novotochinov da4eb7bdbe add root address and public key to generateAndLoadKey() response 2019-08-20 23:01:11 +03:00
Dmitry Novotochinov edb34edbfb 2.5.7 2019-08-02 13:57:31 +03:00
Dmitry Novotochinov a891033668 add whisper-address to getKeys() response 2019-08-02 13:57:13 +03:00
Dmitry Novotochinov 33653e7c57 2.5.6 2019-08-01 20:44:26 +03:00
Dmitry Novotochinov 48f645145d add wallet-public-key to generate keys response 2019-08-01 20:43:15 +03:00
4 changed files with 250 additions and 83 deletions
@@ -148,13 +148,17 @@ public class RNStatusKeycardModule extends ReactContextBaseJavaModule implements
@ReactMethod
public void saveMnemonic(final String mnemonic, final String pairing, final String pin, final Promise promise) {
try {
smartCard.saveMnemonic(mnemonic, pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.saveMnemonic(mnemonic, pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
@@ -173,25 +177,48 @@ public class RNStatusKeycardModule extends ReactContextBaseJavaModule implements
@ReactMethod
public void deriveKey(final String path, final String pairing, final String pin, final Promise promise) {
try {
smartCard.deriveKey(path, pairing, pin);
promise.resolve(path);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.deriveKey(path, pairing, pin);
promise.resolve(path);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void exportKey(final String pairing, final String pin, final Promise promise) {
try {
promise.resolve(smartCard.exportKey(pairing, pin));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
promise.resolve(smartCard.exportKey(pairing, pin));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void exportKeyWithPath(final String pairing, final String pin, final String path, final Promise promise) {
new Thread(new Runnable() {
public void run() {
try {
promise.resolve(smartCard.exportKeyWithPath(pairing, pin, path));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void getKeys(final String pairing, final String pin, final Promise promise) {
new Thread(new Runnable() {
@@ -220,6 +247,20 @@ public class RNStatusKeycardModule extends ReactContextBaseJavaModule implements
}).start();
}
@ReactMethod
public void signPinless(final String hash, final Promise promise) {
new Thread(new Runnable() {
public void run() {
try {
promise.resolve(smartCard.signPinless(hash));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void installApplet(final Promise promise) {
final ReactContext ctx = this.reactContext;
@@ -254,96 +295,127 @@ public class RNStatusKeycardModule extends ReactContextBaseJavaModule implements
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void verifyPin(final String pairing, final String pin, final Promise promise) {
try {
promise.resolve(smartCard.verifyPin(pairing, pin));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
promise.resolve(smartCard.verifyPin(pairing, pin));
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void changePin(final String pairing, final String currentPin, final String newPin, final Promise promise) {
try {
smartCard.changePin(pairing, currentPin, newPin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.changePin(pairing, currentPin, newPin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void unblockPin(final String pairing, final String puk, final String newPin, final Promise promise) {
try {
smartCard.unblockPin(pairing, puk, newPin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.unblockPin(pairing, puk, newPin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void unpair(final String pairing, final String pin, final Promise promise) {
try {
smartCard.unpair(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.unpair(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void delete(final Promise promise) {
try {
smartCard.delete();
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.delete();
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void removeKey(final String pairing, final String pin, final Promise promise) {
try {
smartCard.removeKey(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.removeKey(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void removeKeyWithUnpair(final String pairing, final String pin, final Promise promise) {
try {
smartCard.removeKeyWithUnpair(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.removeKeyWithUnpair(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
@ReactMethod
public void unpairAndDelete(final String pairing, final String pin, final Promise promise) {
try {
smartCard.unpairAndDelete(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
new Thread(new Runnable() {
public void run() {
try {
smartCard.unpairAndDelete(pairing, pin);
promise.resolve(true);
} catch (IOException | APDUException e) {
Log.d(TAG, e.getMessage());
promise.reject(e);
}
}
}).start();
}
}
}
@@ -31,6 +31,7 @@ import im.status.keycard.android.NFCCardManager;
import im.status.keycard.applet.ApplicationStatus;
import im.status.keycard.applet.BIP32KeyPair;
import im.status.keycard.applet.Mnemonic;
import im.status.keycard.applet.CashCommandSet;
import im.status.keycard.applet.KeycardCommandSet;
import im.status.keycard.applet.Pairing;
import im.status.keycard.applet.ApplicationInfo;
@@ -48,6 +49,8 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
private static final String TAG = "SmartCard";
private Boolean started = false;
private static final String MASTER_PATH = "m";
private static final String ROOT_PATH = "m/44'/60'/0'/0";
private static final String WALLET_PATH = "m/44'/60'/0'/0/0";
private static final String WHISPER_PATH = "m/43'/60'/1581'/0'/0";
private static final String ENCRYPTION_PATH = "m/43'/60'/1581'/1'/0";
@@ -296,6 +299,24 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
return Hex.toHexString(key);
}
public String exportKeyWithPath(final String pairingBase64, final String pin, final String path) throws IOException, APDUException {
KeycardCommandSet cmdSet = new KeycardCommandSet(this.cardChannel);
cmdSet.select().checkOK();
Pairing pairing = new Pairing(pairingBase64);
cmdSet.setPairing(pairing);
cmdSet.autoOpenSecureChannel();
Log.i(TAG, "secure channel opened");
cmdSet.verifyPIN(pin).checkOK();
Log.i(TAG, "pin verified");
byte[] key = BIP32KeyPair.fromTLV(cmdSet.exportKey(path, false, true).checkOK().getData()).getPublicKey();
return Hex.toHexString(key);
}
public WritableMap getKeys(final String pairingBase64, final String pin) throws IOException, APDUException {
KeycardCommandSet cmdSet = new KeycardCommandSet(this.cardChannel);
cmdSet.select().checkOK();
@@ -309,6 +330,12 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
cmdSet.verifyPIN(pin).checkOK();
Log.i(TAG, "pin verified");
byte[] tlvMaster = cmdSet.exportKey(MASTER_PATH, true, true).checkOK().getData();
BIP32KeyPair masterPair = BIP32KeyPair.fromTLV(tlvMaster);
byte[] tlvRoot = cmdSet.exportKey(ROOT_PATH, true, true).checkOK().getData();
BIP32KeyPair keyPair = BIP32KeyPair.fromTLV(tlvRoot);
byte[] tlv = cmdSet.exportKey(WALLET_PATH, true, true).checkOK().getData();
BIP32KeyPair walletKeyPair = BIP32KeyPair.fromTLV(tlv);
@@ -318,11 +345,21 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
byte[] tlv3 = cmdSet.exportKey(ENCRYPTION_PATH, false, false).checkOK().getData();
BIP32KeyPair encryptionKeyPair = BIP32KeyPair.fromTLV(tlv3);
ApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());
WritableMap data = Arguments.createMap();
data.putString("address", Hex.toHexString(masterPair.toEthereumAddress()));
data.putString("public-key", Hex.toHexString(masterPair.getPublicKey()));
data.putString("wallet-root-address", Hex.toHexString(keyPair.toEthereumAddress()));
data.putString("wallet-root-public-key", Hex.toHexString(keyPair.getPublicKey()));
data.putString("wallet-address", Hex.toHexString(walletKeyPair.toEthereumAddress()));
data.putString("wallet-public-key", Hex.toHexString(walletKeyPair.getPublicKey()));
data.putString("whisper-address", Hex.toHexString(whisperKeyPair.toEthereumAddress()));
data.putString("whisper-public-key", Hex.toHexString(whisperKeyPair.getPublicKey()));
data.putString("whisper-private-key", Hex.toHexString(whisperKeyPair.getPrivateKey()));
data.putString("encryption-public-key", Hex.toHexString(encryptionKeyPair.getPublicKey()));
data.putString("instance-uid", Hex.toHexString(info.getInstanceUID()));
data.putString("key-uid", Hex.toHexString(info.getKeyUID()));
return data;
}
@@ -346,6 +383,12 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
cmdSet.loadKey(keyPair);
log("keypair loaded to card");
cmdSet.deriveKey(ROOT_PATH).checkOK();
Log.i(TAG, "Derived " + ROOT_PATH);
byte[] tlvRoot = cmdSet.exportCurrentKey(true).checkOK().getData();
BIP32KeyPair rootKeyPair = BIP32KeyPair.fromTLV(tlvRoot);
cmdSet.deriveKey(WALLET_PATH).checkOK();
Log.i(TAG, "Derived " + WALLET_PATH);
@@ -367,7 +410,12 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
ApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());
WritableMap data = Arguments.createMap();
data.putString("address", Hex.toHexString(keyPair.toEthereumAddress()));
data.putString("public-key", Hex.toHexString(keyPair.getPublicKey()));
data.putString("wallet-root-address", Hex.toHexString(rootKeyPair.toEthereumAddress()));
data.putString("wallet-root-public-key", Hex.toHexString(rootKeyPair.getPublicKey()));
data.putString("wallet-address", Hex.toHexString(walletKeyPair.toEthereumAddress()));
data.putString("wallet-public-key", Hex.toHexString(walletKeyPair.getPublicKey()));
data.putString("whisper-address", Hex.toHexString(whisperKeyPair.toEthereumAddress()));
data.putString("whisper-public-key", Hex.toHexString(whisperKeyPair.getPublicKey()));
data.putString("whisper-private-key", Hex.toHexString(whisperKeyPair.getPrivateKey()));
@@ -527,4 +575,27 @@ public class SmartCard extends BroadcastReceiver implements CardListener {
return sig;
}
public String signPinless(final String message) throws IOException, APDUException {
CashCommandSet cmdSet = new CashCommandSet(this.cardChannel);
cmdSet.select().checkOK();
byte[] hash = Hex.decode(message);
RecoverableSignature signature = new RecoverableSignature(hash, cmdSet.sign(hash).checkOK().getData());
Log.i(TAG, "Signed hash: " + Hex.toHexString(hash));
Log.i(TAG, "Recovery ID: " + signature.getRecId());
Log.i(TAG, "R: " + Hex.toHexString(signature.getR()));
Log.i(TAG, "S: " + Hex.toHexString(signature.getS()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(signature.getR());
out.write(signature.getS());
out.write(signature.getRecId());
String sig = Hex.toHexString(out.toByteArray());
Log.i(TAG, "Signature: " + sig);
return sig;
}
}
+29 -5
View File
@@ -124,7 +124,12 @@ Keycard.generateAndLoadKey(mnemonic, pairing, pin).then(data => console.log(data
`data` object returned:
```javascript
{"wallet-address": "9726cbc67d170307dd80af6416ebe844e7b8eb1c",
{"address": "a89a57f4d3241e6a123ea332241d6f03790075b4",
"public-key": "04cccc3998d0e0b8d56b64fad4d1f025914b8cb72558810c74dd34454fcd6907f6f7429a0726dceec9b93c9060103ff8b2e7daa1cb9a4dd62b7ae1ba2232709555",
"wallet-root-address": "b19a57f4d3241e6a123ea332241d6f03790075b4",
"wallet-root-public-key": "0427cc3998d0e0b8d56b64fad4d1f025914b8cb72558810c74dd34454fcd6907f6f7429a0726dceec9b93c9060103ff8b2e7daa1cb9a4dd62b7ae1ba2232709555",
"wallet-address": "9726cbc67d170307dd80af6416ebe844e7b8eb1c",
"wallet-public-key": "04065670509d295cb8330e02a688eafe83dbbb317486062482725ba1036dba396d635e91afd9a9e7087c0dfeaccf30d2004d092ed250d62b5c75f8bb4c9326d409",
"whisper-address": "438e576b638bff08b2872dd708cf0240811d79af",
"whisper-public-key": "04add221cb97dde8afbf3be27b0bfb3b6842071cb1052abfc3c34d45eba944dc10dcba5d4823fe69148ae17b12ed459237124365c2f46c2b46be9537ce6efa93c8",
"whisper-private-key": "073d77b952b3b92ba66947df53d03b23bc4cc8cf10cbba1060fe25a569c8ee6b",
@@ -133,6 +138,14 @@ Keycard.generateAndLoadKey(mnemonic, pairing, pin).then(data => console.log(data
"key-uid":"a88d46499e5690c6ad637e243e83cf51be3e2c67e48324b2b2def3e6a0492576"}
```
`address` is an address of master key `m`
`public-key` is a public key of master key `m`
`wallet-root-address` is an address of root key `m/44'/60'/0'/0`
`wallet-root-public-key` is public key of root key `m/44'/60'/0'/0`
`wallet-address` is ethereum address of key with derivation path `m/44'/60'/0'/0/0`
`whisper-address` is ethereum address of key with derivation path `m/43'/60'/1581'/0'/0`
@@ -151,12 +164,23 @@ Keycard.getKeys(pairing, pin).then(data => console.log(data));
`data` object contains:
```javascript
{"encryption-public-key": "04d36d64bea374b917bc097646cb4e81061c7b0ab0872207480b886e66fb52d53774e303e2aa07a1107776f312b94663566765a8a75cf0a92b0851017b28b356a1",
{"address": "a89a57f4d3241e6a123ea332241d6f03790075b4",
"public-key": "04cccc3998d0e0b8d56b64fad4d1f025914b8cb72558810c74dd34454fcd6907f6f7429a0726dceec9b93c9060103ff8b2e7daa1cb9a4dd62b7ae1ba2232709555",
"wallet-root-address": "b19a57f4d3241e6a123ea332241d6f03790075b4",
"wallet-root-public-key": "0427cc3998d0e0b8d56b64fad4d1f025914b8cb72558810c74dd34454fcd6907f6f7429a0726dceec9b93c9060103ff8b2e7daa1cb9a4dd62b7ae1ba2232709555",
"wallet-address": "9726cbc67d170307dd80af6416ebe844e7b8eb1c",
"wallet-public-key": "04065670509d295cb8330e02a688eafe83dbbb317486062482725ba1036dba396d635e91afd9a9e7087c0dfeaccf30d2004d092ed250d62b5c75f8bb4c9326d409",
"whisper-address": "438e576b638bff08b2872dd708cf0240811d79af",
"whisper-public-key": "04add221cb97dde8afbf3be27b0bfb3b6842071cb1052abfc3c34d45eba944dc10dcba5d4823fe69148ae17b12ed459237124365c2f46c2b46be9537ce6efa93c8",
"whisper-private-key":"073d77b952b3b92ba66947df53d03b23bc4cc8cf10cbba1060fe25a569c8ee6b",
"wallet-address":"9726cbc67d170307dd80af6416ebe844e7b8eb1c"}
"whisper-private-key": "073d77b952b3b92ba66947df53d03b23bc4cc8cf10cbba1060fe25a569c8ee6b",
"encryption-public-key": "04d36d64bea374b917bc097646cb4e81061c7b0ab0872207480b886e66fb52d53774e303e2aa07a1107776f312b94663566765a8a75cf0a92b0851017b28b356a1",
"instance-uid": "21c0ce19aa9a26efc02fd32078c08527",
"key-uid":"a88d46499e5690c6ad637e243e83cf51be3e2c67e48324b2b2def3e6a0492576"}
```
Response is identical to `generateAndLoadKey`.
Please refer to `generateAndLoadKey` response for detailed description.
### Sign
```javascript
const pairing = "AFFdkP01GywuaJRQkGDq+OyPHBE9nECEDDCfXhpfaxlo";
@@ -259,4 +283,4 @@ Keycard.installApplet().then(() => console.log("applet installed"));
```
### Keycard CLI
You can also interact with keycard (installing and removing applet, getting card info, etc) using [keycard cli](https://github.com/status-im/keycard-cli). You'll need a USB reader for that.
You can also interact with keycard (installing and removing applet, getting card info, etc) using [keycard cli](https://github.com/status-im/keycard-cli). You'll need a USB reader for that.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "react-native-status-keycard",
"homepage": "https://keycard.status.im/",
"version": "2.5.5",
"version": "2.5.16",
"description": "React Native library to interact with Status Keycard using NFC connection (Android only)",
"main": "index.js",
"scripts": {