specify assisted key derivation

This commit is contained in:
Michele Balistreri 2017-10-19 16:37:14 +03:00
parent dc2af3b4fb
commit 3a32731061
12 changed files with 73 additions and 3035 deletions

View File

@ -26,9 +26,10 @@ The PIN can be changed by the user after authentication.
## Keys & Signature
The application allows loading a replacing of a single EC keyset, defined on the SECP256k1 curve. This keyset is used
to sign transactions. When the applet is first installed, no keyset is available so signing will fail. It is necessary
to first load the keyset in order for the application to be fully operational.
The application allows loading a replacing of a single EC keyset, defined on the SECP256k1 curve. The keyset can contain
a 32-byte chain code to further derive keys according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
specifications. This keyset is used to sign transactions. When the applet is first installed, no keyset is available so
signing will fail. It is necessary to first load the keyset in order for the application to be fully operational.
Signing of transactions is done by uploading the data in blocks no larger than 255 bytes (including the overhead caused
by the Secure Channel). Segmentation must be handled at the application protocol.
@ -69,7 +70,7 @@ Response Data format:
- Tag 0xC0 = PIN retry count (1 byte)
- Tag 0xC1 = PUK retry count (1 byte)
- Tag 0xC2 = 0 if key is not initialized, 1 otherwise
- Tag 0xC3 = 1 if public key derivation is hw optimized, 0 otherwise
- Tag 0xC3 = 1 if public key derivation is supported, 0 otherwise
### VERIFY PIN
@ -125,7 +126,8 @@ always returns 0x63C0, even if the PUK is inserted correctly. In this case the w
* P1 = key type
* P2 = 0x00
* Data = the key data
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid, 0x6A81 if public key
derivation is not supported and the public key is omitted
* Preconditions: Secure Channel must be opened, user PIN must be verified
P1:
@ -137,12 +139,13 @@ Data:
If P1 is 0x01 or 0x02
- Tag 0xA1 = keypair template
- Tag 0x80 = ECC public key component (can be omitted)
- Tag 0x80 = ECC public key component (can be omitted if public key derivation is supported)
- Tag 0x81 = ECC private key component
- Tag 0x82 = chain code (if P1=0x02)
If P1 is 0x03 a 64 byte sequence generated according to the BIP39 specifications is expected. The master key will be
generated according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications.
generated according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. This
is only supported if the hardware supports public key derivation.
This command is used to load or replace the keypair used for signing on the card. This command always aborts open
signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.
@ -151,14 +154,37 @@ signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command
* CLA = 0x80
* INS = 0xD1
* P1 = 0x00
* P2 = 0x00
* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid
* P1 = derivation options
* P2 = assisted derivation phase
* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used. On assisted
derivation contains a public key when P2 = 0x02.
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public key derivation is not supported and
bit 0 of P1 is set, 0x6A86 if P2 = 0x01 and bit 0 of P1 is not set.
* Response Data = On assisted derivation and P2 = 0x01 the key derivation template. Empty otherwise.
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
This command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
specifications. The generated key is used for all subsequent SIGN sessions.
specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent
SIGN sessions. Because JavaCard does not offer native EC point multiplication before version 3.0.5, there is an
alternative mode of operation where the public key is partially derived off-card and loaded back on card. In this mode
of operation only 1 derivation step at the time can be completed and as such during assisted derivation the data can
contain a single 32-bit integer, instead of a sequence.
P1:
* bit 0 = if 0 derive autonomously (only works if public key derivation is supported), if 1 do assisted derivation
* bit 1-6 = reserved
* bit 7 = if 0 derive from master keys, if 1 derive from current keys
P2:
* 0x00 = data is 32 a sequence of 32-bit integers
* 0x01 = data is a public key
Response Data format:
- Tag 0xA3 = key derivation template
- Tag 0x83 = ECC Public Key X component
- Tag 0x30 = ECDSA Signature
- Tag 0x02 = R value
- Tag 0x02 = S value
### GENERATE MNEMONIC

File diff suppressed because it is too large Load Diff

View File

@ -1,229 +0,0 @@
package im.status.wallet;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.MessageDigest;
import javacard.security.RSAPublicKey;
import javacardx.crypto.Cipher;
/**
*
* @author Petr Svenda
*/
public class Bignat_Helper {
private final ECConfig ecc;
/**
* Helper flag which signalizes that code is executed inside simulator
* (during tests). Is used to address simulator specific behaviour workaround
* if required.
*/
byte[] helper_BN_array1 = null;
byte[] helper_BN_array2 = null;
/**
* Number of pre-allocated helper arrays
*/
public static final byte NUM_HELPER_ARRAYS = 2;
byte[] fnc_deep_resize_tmp = null;
public byte[] fnc_mult_resultArray1 = null;
byte[] fnc_mult_resultArray2 = null;
byte[] tmp_array_short = null;
byte[] fnc_same_value_array1 = null;
byte[] fnc_same_value_hash = null;
// These Bignats helper_BN_? are allocated
Bignat helper_BN_A;
Bignat helper_BN_B;
Bignat helper_BN_C;
Bignat helper_BN_D;
Bignat helper_BN_E;
Bignat helper_BN_F;
// These Bignats are just pointing to some helper_BN_? so reasonable naming is preserved yet no need to actually allocated whole Bignat object
Bignat fnc_mod_exp_modBN;
Bignat fnc_mod_add_tmp;
Bignat fnc_mod_sub_tmp;
Bignat fnc_mod_sub_tmpOther;
Bignat fnc_mod_sub_tmpThis;
Bignat fnc_mod_mult_tmpThis;
Bignat fnc_mult_mod_tmpThis;
Bignat fnc_mult_mod_tmp_x;
Bignat fnc_mult_mod_tmp_mod;
Bignat fnc_divide_tmpThis;
Bignat fnc_exponentiation_i;
Bignat fnc_exponentiation_tmp;
Bignat fnc_sqrt_p_1;
Bignat fnc_sqrt_Q;
Bignat fnc_sqrt_S;
Bignat fnc_sqrt_tmp;
Bignat fnc_sqrt_exp;
Bignat fnc_sqrt_z;
Bignat fnc_mod_minus_2;
Bignat fnc_negate_tmp;
Bignat fnc_int_add_tmpMag;
Bignat fnc_int_multiply_mod;
Bignat fnc_int_multiply_tmpThis;
Bignat fnc_int_divide_tmpThis;
RSAPublicKey fnc_NmodE_pubKey;
Cipher fnc_NmodE_cipher;
public Bignat ONE;
public Bignat TWO;
public Bignat THREE;
// Helper objects for fast multiplication of two large numbers (without modulo)
KeyPair fnc_mult_keypair = null;
RSAPublicKey fnc_mult_pubkey_pow2 = null;
Cipher fnc_mult_cipher = null;
MessageDigest hashEngine;
static byte[] CONST_ONE = {0x01};
static byte[] CONST_TWO = {0x02};
public Bignat_Helper(ECConfig ecCfg) {
ecc = ecCfg;
tmp_array_short = ecc.memAlloc.allocateByteArray((short) 2, JCSystem.MEMORY_TYPE_TRANSIENT_RESET); // only 2b RAM for faster add(short)
fnc_NmodE_cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
fnc_NmodE_pubKey = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, ecc.MODULO_RSA_ENGINE_MAX_LENGTH_BITS, false);
// Multiplication speedup engines and arrays used by Bignat.mult_RSATrick()
helper_BN_array1 = ecc.memAlloc.allocateByteArray((short) (ecc.MULT_RSA_ENGINE_MAX_LENGTH_BITS / 8), ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_array1));
helper_BN_array2 = ecc.memAlloc.allocateByteArray((short) (ecc.MULT_RSA_ENGINE_MAX_LENGTH_BITS / 8), ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_array2));
fnc_deep_resize_tmp = helper_BN_array1;
fnc_mult_resultArray1 = helper_BN_array1;
fnc_mult_resultArray2 = helper_BN_array2;
fnc_same_value_array1 = helper_BN_array1;
fnc_same_value_hash = helper_BN_array2;
helper_BN_A = new Bignat(ecc.MAX_BIGNAT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_A), ecc);
helper_BN_B = new Bignat(ecc.MAX_BIGNAT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_B), ecc);
helper_BN_C = new Bignat(ecc.MAX_BIGNAT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_C), ecc);
helper_BN_D = new Bignat(ecc.MAX_BIGNAT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_D), ecc);
helper_BN_E = new Bignat(ecc.MAX_BIGNAT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_E), ecc);
helper_BN_F = new Bignat((short) (ecc.MAX_BIGNAT_SIZE + 2), ecc.memAlloc.getAllocatorType(ObjectAllocator.BNH_helper_BN_F), ecc); // +2 is to correct for infrequent RSA result with two or more leading zeroes
// BN below are just reassigned allocated helper_BN_? so that same helper_BN_? is not used in parallel (checked by lock() unlock())
fnc_mod_add_tmp = helper_BN_A;
fnc_mod_sub_tmpThis = helper_BN_A;
fnc_mod_sub_tmp = helper_BN_B;
fnc_mod_sub_tmpOther = helper_BN_C;
fnc_mult_mod_tmpThis = helper_BN_A;
fnc_mult_mod_tmp_mod = helper_BN_B;
fnc_mult_mod_tmp_x = helper_BN_C;
fnc_exponentiation_tmp = helper_BN_A;
fnc_exponentiation_i = helper_BN_B;
fnc_mod_minus_2 = helper_BN_B;
fnc_negate_tmp = helper_BN_B;
fnc_sqrt_S = helper_BN_A;
fnc_sqrt_exp = helper_BN_A;
fnc_sqrt_p_1 = helper_BN_B;
fnc_sqrt_Q = helper_BN_C;
fnc_sqrt_tmp = helper_BN_D;
fnc_sqrt_z = helper_BN_E;
fnc_mod_mult_tmpThis = helper_BN_E; // mod_mult is called from fnc_sqrt => requires helper_BN_E not being locked in fnc_sqrt when mod_mult is called
fnc_divide_tmpThis = helper_BN_E; // divide is called from fnc_sqrt => requires helper_BN_E not being locked in fnc_sqrt when divide is called
fnc_mod_exp_modBN = helper_BN_F; // mod_exp is called from fnc_sqrt => requires helper_BN_F not being locked in fnc_sqrt when mod_exp is called
fnc_int_add_tmpMag = helper_BN_A;
fnc_int_multiply_mod = helper_BN_A;
fnc_int_multiply_tmpThis = helper_BN_B;
fnc_int_divide_tmpThis = helper_BN_A;
// Allocate BN constants always in EEPROM (only reading)
ONE = new Bignat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, ecc);
ONE.one();
TWO = new Bignat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, ecc);
TWO.two();
THREE = new Bignat((short) 1, JCSystem.MEMORY_TYPE_PERSISTENT, ecc);
THREE.three();
// Speedup for fast multiplication
fnc_mult_keypair = new KeyPair(KeyPair.ALG_RSA_CRT, ecc.MULT_RSA_ENGINE_MAX_LENGTH_BITS);
fnc_mult_keypair.genKeyPair();
fnc_mult_pubkey_pow2 = (RSAPublicKey) fnc_mult_keypair.getPublic();
//mult_privkey_pow2 = (RSAPrivateCrtKey) mult_keypair.getPrivate();
fnc_mult_pubkey_pow2.setExponent(CONST_TWO, (short) 0, (short) CONST_TWO.length);
fnc_mult_cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
hashEngine = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
ecc.FLAG_FAST_MULT_VIA_RSA = false; // set true only if succesfully allocated and tested below
try { // Subsequent code may fail on some real (e.g., Infineon CJTOP80K) cards - catch exception
fnc_mult_cipher.init(fnc_mult_pubkey_pow2, Cipher.MODE_ENCRYPT);
// Try operation - if doesn't work, exception SW_CANTALLOCATE_BIGNAT is emitted
Util.arrayFillNonAtomic(fnc_mult_resultArray1, (short) 0, (short) fnc_mult_resultArray1.length, (byte) 6);
fnc_mult_cipher.doFinal(fnc_mult_resultArray1, (short) 0, (short) fnc_mult_resultArray1.length, fnc_mult_resultArray1, (short) 0);
ecc.FLAG_FAST_MULT_VIA_RSA = true;
} catch (Exception ignored) {} // discard exception
}
/**
* Erase all values stored in helper objects
*/
void erase() {
helper_BN_A.erase();
helper_BN_B.erase();
helper_BN_C.erase();
helper_BN_D.erase();
helper_BN_E.erase();
helper_BN_F.erase();
Util.arrayFillNonAtomic(tmp_array_short, (short) 0, (short) tmp_array_short.length, (byte) 0);
Util.arrayFillNonAtomic(helper_BN_array1, (short) 0, (short) helper_BN_array1.length, (byte) 0);
Util.arrayFillNonAtomic(helper_BN_array2, (short) 0, (short) helper_BN_array2.length, (byte) 0);
}
/**
* Unlocks all helper objects
*/
public void unlockAll() {
if (helper_BN_A.isLocked()) {
helper_BN_A.unlock();
}
if (helper_BN_B.isLocked()) {
helper_BN_B.unlock();
}
if (helper_BN_C.isLocked()) {
helper_BN_C.unlock();
}
if (helper_BN_D.isLocked()) {
helper_BN_D.unlock();
}
if (helper_BN_E.isLocked()) {
helper_BN_E.unlock();
}
if (helper_BN_F.isLocked()) {
helper_BN_F.unlock();
}
}
}

View File

@ -1,142 +0,0 @@
package im.status.wallet;
import javacard.framework.ISOException;
/**
* Configure itself to proper lengths and other parameters according to intended length of ECC
* @author Petr Svenda
*/
public class ECConfig {
/**
* The size of speedup engine used for fast modulo exponent computation
* (must be larger than biggest Bignat used)
*/
public short MODULO_RSA_ENGINE_MAX_LENGTH_BITS = (short) 512;
/**
* The size of speedup engine used for fast multiplication of large numbers
* Must be larger than 2x biggest Bignat used
*/
public short MULT_RSA_ENGINE_MAX_LENGTH_BITS = (short) 768;
/**
* The size of largest integer used in computations
*/
public short MAX_BIGNAT_SIZE = (short) 65; // ((short) (MODULO_ENGINE_MAX_LENGTH_BITS / 8) + 1);
/**
* The size of largest ECC point used
*/
public short MAX_POINT_SIZE = (short) 64;
/**
* The size of single coordinate of the largest ECC point used
*/
public short MAX_COORD_SIZE = (short) 32; // MAX_POINT_SIZE / 2
/**
* If true, fast multiplication of large numbers via RSA engine can be used.
* Is set automatically after successful allocation of required engines
*/
public boolean FLAG_FAST_MULT_VIA_RSA = false;
/**
* Threshold length in bits of an operand after which speedup with RSA multiplication is used.
* Schoolbook multiplication is used for shorter operands
*/
public short FAST_MULT_VIA_RSA_TRESHOLD_LENGTH = (short) 16;
/**
* I true, fast multiplication of ECPoints via KeyAgreement can be used
* Is set automatically after successful allocation of required engines
*/
public boolean FLAG_FAST_EC_MULT_VIA_KA = false;
/**
* Object responsible for easy management of target placement (RAM/EEPROM) fro allocated objects
*/
public ObjectAllocator memAlloc = null;
/**
* Helper structure containing all preallocated objects necessary for Bignat operations
*/
public Bignat_Helper bnh = null;
/**
* Helper structure containing all preallocated objects necessary for ECPoint operations
*/
public ECPoint_Helper ech = null;
/**
* Creates new control structure for requested bit length with all preallocated arrays and engines
* @param maxECLength maximum length of ECPoint objects supported. The provided value is used to
* initialize properly underlying arrays and engines.
*/
public ECConfig(short maxECLength) {
// Set proper lengths and other internal settings based on required ECC length
if (maxECLength <= (short) 256) {
setECC256Config();
}
else if (maxECLength <= (short) 384) {
setECC384Config();
}
else if (maxECLength <= (short) 512) {
setECC512Config();
}
else {
ISOException.throwIt(ReturnCodes.SW_ECPOINT_INVALIDLENGTH);
}
//locker.setLockingActive(false); // if required, locking can be disabled
memAlloc = new ObjectAllocator();
memAlloc.setAllAllocatorsRAM();
//if required, memory for helper objects and arrays can be in persistent memory to save RAM (or some tradeoff)
//ObjectAllocator.setAllAllocatorsEEPROM(); //ObjectAllocator.setAllocatorsTradeoff();
// Allocate helper objects for BN and EC
bnh = new Bignat_Helper(this);
ech = new ECPoint_Helper(this);
}
void reset() {
FLAG_FAST_MULT_VIA_RSA = false;
FLAG_FAST_EC_MULT_VIA_KA = false;
}
public void setECC256Config() {
reset();
MODULO_RSA_ENGINE_MAX_LENGTH_BITS = (short) 512;
MULT_RSA_ENGINE_MAX_LENGTH_BITS = (short) 768;
MAX_POINT_SIZE = (short) 64;
computeDerivedLengths();
}
public void setECC384Config() {
reset();
MODULO_RSA_ENGINE_MAX_LENGTH_BITS = (short) 768;
MULT_RSA_ENGINE_MAX_LENGTH_BITS = (short) 1024;
MAX_POINT_SIZE = (short) 96;
computeDerivedLengths();
}
public void setECC512Config() {
reset();
MODULO_RSA_ENGINE_MAX_LENGTH_BITS = (short) 1024;
MULT_RSA_ENGINE_MAX_LENGTH_BITS = (short) 1280;
MAX_POINT_SIZE = (short) 128;
computeDerivedLengths();
}
public void setECC521Config() {
reset();
MODULO_RSA_ENGINE_MAX_LENGTH_BITS = (short) 1280;
MULT_RSA_ENGINE_MAX_LENGTH_BITS = (short) 1280;
MAX_POINT_SIZE = (short) 129;
computeDerivedLengths();
}
private void computeDerivedLengths() {
MAX_BIGNAT_SIZE = (short) ((short) (MODULO_RSA_ENGINE_MAX_LENGTH_BITS / 8) + 1);
MAX_COORD_SIZE = (short) (MAX_POINT_SIZE / 2);
}
/**
* Unlocks all logically locked arrays and objects. Useful as recovery after premature end of some operation (e.g., due to exception)
* when some objects remains locked.
*/
void unlockAll() {
bnh.unlockAll();
ech.unlockAll();
}
}

View File

@ -1,195 +0,0 @@
package im.status.wallet;
import javacard.framework.Util;
import javacard.security.ECPrivateKey;
import javacard.security.ECPublicKey;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
/**
*
* @author Vasilios Mavroudis and Petr Svenda
*/
public class ECCurve {
public final short KEY_LENGTH; //Bits
public final short POINT_SIZE; //Bytes
public final short COORD_SIZE; //Bytes
//Parameters
public byte[] p = null;
public byte[] a = null;
public byte[] b = null;
public byte[] G = null;
public byte[] r = null;
public Bignat pBN;
public Bignat aBN;
public Bignat bBN;
public KeyPair disposable_pair;
public ECPrivateKey disposable_priv;
/**
* Creates new curve object from provided parameters. Either copy of provided
* arrays is performed (bCopyArgs == true, input arrays can be reused later for other
* purposes) or arguments are directly stored (bCopyArgs == false, usable for fixed static arrays) .
* @param bCopyArgs if true, copy of arguments is created, otherwise reference is directly stored
* @param p_arr array with p
* @param a_arr array with a
* @param b_arr array with b
* @param G_arr array with base point G
* @param r_arr array with r
*/
public ECCurve(boolean bCopyArgs, byte[] p_arr, byte[] a_arr, byte[] b_arr, byte[] G_arr, byte[] r_arr, ECConfig ecc) {
//ECCurve_initialize(p_arr, a_arr, b_arr, G_arr, r_arr);
this.KEY_LENGTH = (short) (p_arr.length * 8);
this.POINT_SIZE = (short) G_arr.length;
this.COORD_SIZE = (short) ((short) (G_arr.length - 1) / 2);
if (bCopyArgs) {
// Copy curve parameters into newly allocated arrays in EEPROM (will be only read, not written later => good performance even when in EEPROM)
this.p = new byte[(short) p_arr.length];
this.a = new byte[(short) a_arr.length];
this.b = new byte[(short) b_arr.length];
this.G = new byte[(short) G_arr.length];
this.r = new byte[(short) r_arr.length];
Util.arrayCopyNonAtomic(p_arr, (short) 0, p, (short) 0, (short) p.length);
Util.arrayCopyNonAtomic(a_arr, (short) 0, a, (short) 0, (short) a.length);
Util.arrayCopyNonAtomic(b_arr, (short) 0, b, (short) 0, (short) b.length);
Util.arrayCopyNonAtomic(G_arr, (short) 0, G, (short) 0, (short) G.length);
Util.arrayCopyNonAtomic(r_arr, (short) 0, r, (short) 0, (short) r.length);
}
else {
// No allocation, store directly provided arrays
this.p = p_arr;
this.a = a_arr;
this.b = b_arr;
this.G = G_arr;
this.r = r_arr;
}
// We will not modify values of p/a/b during the lifetime of curve => allocate helper bignats directly from the array
this.pBN = new Bignat(this.p, ecc);
this.aBN = new Bignat(this.a, ecc);
this.bBN = new Bignat(this.b, ecc);
this.disposable_pair = this.newKeyPair(null);
this.disposable_priv = (ECPrivateKey) this.disposable_pair.getPrivate();
}
/**
* Refresh critical information stored in RAM for performance reasons after a card reset (RAM was cleared).
*/
public void updateAfterReset() {
this.pBN.from_byte_array(this.p);
this.aBN.from_byte_array(this.a);
this.bBN.from_byte_array(this.b);
}
/**
* Creates a new keyPair based on this curve parameters. KeyPair object is reused if provided. Fresh keyPair value is generated.
* @param existingKeyPair existing KeyPair object which is reused if required. If null, new KeyPair is allocated
* @return new or existing object with fresh key pair value
*/
KeyPair newKeyPair(KeyPair existingKeyPair) {
ECPrivateKey privKey;
ECPublicKey pubKey;
if (existingKeyPair == null) { // Allocate if not supplied
existingKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KEY_LENGTH);
}
// Some implementation wil not return valid pub key until ecKeyPair.genKeyPair() is called
// Other implementation will fail with exception if same is called => try catch and drop any exception
try {
pubKey = (ECPublicKey) existingKeyPair.getPublic();
if (pubKey == null) {
existingKeyPair.genKeyPair();
}
} catch (Exception e) {
} // intentionally do nothing
privKey = (ECPrivateKey) existingKeyPair.getPrivate();
pubKey = (ECPublicKey) existingKeyPair.getPublic();
// Set required values
privKey.setFieldFP(p, (short) 0, (short) p.length);
privKey.setA(a, (short) 0, (short) a.length);
privKey.setB(b, (short) 0, (short) b.length);
privKey.setG(G, (short) 0, (short) G.length);
privKey.setR(r, (short) 0, (short) r.length);
privKey.setK((short) 1);
pubKey.setFieldFP(p, (short) 0, (short) p.length);
pubKey.setA(a, (short) 0, (short) a.length);
pubKey.setB(b, (short) 0, (short) b.length);
pubKey.setG(G, (short) 0, (short) G.length);
pubKey.setR(r, (short) 0, (short) r.length);
pubKey.setK((short) 1);
existingKeyPair.genKeyPair();
return existingKeyPair;
}
public KeyPair newKeyPair_legacy(KeyPair existingKeyPair) {
ECPrivateKey privKey;
ECPublicKey pubKey;
if (existingKeyPair == null) {
// We need to create required objects
privKey = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KEY_LENGTH, false);
pubKey = (ECPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KEY_LENGTH, false);
}
else {
// Obtain from object
privKey = (ECPrivateKey) existingKeyPair.getPrivate();
pubKey = (ECPublicKey) existingKeyPair.getPublic();
}
// Set required values
privKey.setFieldFP(p, (short) 0, (short) p.length);
privKey.setA(a, (short) 0, (short) a.length);
privKey.setB(b, (short) 0, (short) b.length);
privKey.setG(G, (short) 0, (short) G.length);
privKey.setR(r, (short) 0, (short) r.length);
pubKey.setFieldFP(p, (short) 0, (short) p.length);
pubKey.setA(a, (short) 0, (short) a.length);
pubKey.setB(b, (short) 0, (short) b.length);
pubKey.setG(G, (short) 0, (short) G.length);
pubKey.setR(r, (short) 0, (short) r.length);
if (existingKeyPair == null) { // Allocate if not supplied
existingKeyPair = new KeyPair(pubKey, privKey);
}
existingKeyPair.genKeyPair();
return existingKeyPair;
}
/**
* Converts provided Bignat into temporary EC private key object. No new
* allocation is performed, returned ECPrivateKey is overwritten by next call.
* @param bn Bignat with new value
* @return ECPrivateKey initialized with provided Bignat
*/
public ECPrivateKey bignatAsPrivateKey(Bignat bn) {
disposable_priv.setS(bn.as_byte_array(), (short) 0, bn.length());
return disposable_priv;
}
/**
* Set new G for this curve. Also updates all dependent key values.
* @param newG buffer with new G
* @param newGOffset start offset within newG
* @param newGLen length of new G
*/
public void setG(byte[] newG, short newGOffset, short newGLen) {
Util.arrayCopyNonAtomic(newG, newGOffset, G, (short) 0, newGLen);
this.disposable_pair = this.newKeyPair(this.disposable_pair);
this.disposable_priv = (ECPrivateKey) this.disposable_pair.getPrivate();
this.disposable_priv.setG(newG, newGOffset, newGLen);
}
}

View File

@ -1,395 +0,0 @@
package im.status.wallet;
import javacard.framework.ISOException;
import javacard.framework.Util;
import javacard.security.ECPrivateKey;
import javacard.security.ECPublicKey;
import javacard.security.KeyPair;
import javacard.security.Signature;
/**
*
* @author Vasilios Mavroudis and Petr Svenda
*/
public class ECPoint {
private final ECConfig ecc;
private ECPublicKey thePoint;
private KeyPair thePointKeyPair;
private final ECCurve theCurve;
/**
* Creates new ECPoint object for provided {@code curve}. Random initial point value is generated.
* The point will use helper structures from provided ECPoint_Helper object.
* @param curve point's elliptic curve
* @param ecc object with preallocated helper objects and memory arrays
*/
public ECPoint(ECCurve curve, ECConfig ecc) {
this.theCurve = curve;
this.ecc = ecc;
updatePointObjects();
}
/**
* Returns length of this point in bytes.
*
* @return
*/
public short length() {
return (short) (thePoint.getSize() / 8);
}
/**
* Properly updates all point values in case of a change of an underlying curve.
* New random point value is generated.
*/
public final void updatePointObjects() {
this.thePointKeyPair = this.theCurve.newKeyPair(this.thePointKeyPair);
this.thePoint = (ECPublicKey) thePointKeyPair.getPublic();
}
/**
* Generates new random point value.
*/
public void randomize(){
if (this.thePointKeyPair == null) {
this.thePointKeyPair = this.theCurve.newKeyPair(this.thePointKeyPair);
this.thePoint = (ECPublicKey) thePointKeyPair.getPublic();
}
else {
this.thePointKeyPair.genKeyPair();
}
}
/**
* Copy value of provided point into this. This and other point must have
* curve with same parameters, only length is checked.
* @param other point to be copied
*/
public void copy(ECPoint other) {
if (this.length() != other.length()) {
ISOException.throwIt(ReturnCodes.SW_ECPOINT_INVALIDLENGTH);
}
short len = other.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
this.setW(ecc.ech.uncompressed_point_arr1, (short) 0, len);
}
/**
* Set this point value (parameter W) from array with value encoded as per ANSI X9.62.
* The uncompressed form is always supported. If underlying native JavaCard implementation
* of {@code ECPublickKey} supports compressed points, then this method accepts also compressed points.
* @param buffer array with serialized point
* @param offset start offset within input array
* @param length length of point
*/
public void setW(byte[] buffer, short offset, short length) {
this.thePoint.setW(buffer, offset, length);
}
/**
* Returns current value of this point.
* @param buffer memory array where to store serailized point value
* @param offset start offset for output serialized point
* @return length of serialized point (number of bytes)
*/
public short getW(byte[] buffer, short offset) {
return thePoint.getW(buffer, offset);
}
/**
* Returns this point value as ECPublicKey object. No copy of point is made
* before return, so change of returned object will also change this point value.
* @return point as ECPublicKey object
*/
public ECPublicKey asPublicKey() {
return this.thePoint;
}
/**
* Returns curve associated with this point. No copy of curve is made
* before return, so change of returned object will also change curve for
* this point.
*
* @return curve as ECCurve object
*/
public ECCurve getCurve() {
return theCurve;
}
/**
* Returns the X coordinate of this point in uncompressed form.
* @param buffer output array for X coordinate
* @param offset start offset within output array
* @return length of X coordinate (in bytes)
*/
public short getX(byte[] buffer, short offset) {
thePoint.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
Util.arrayCopyNonAtomic(ecc.ech.uncompressed_point_arr1, (short) 1, buffer, offset, this.theCurve.COORD_SIZE);
return this.theCurve.COORD_SIZE;
}
/**
* Returns the Y coordinate of this point in uncompressed form.
*
* @param buffer output array for Y coordinate
* @param offset start offset within output array
* @return length of Y coordinate (in bytes)
*/
public short getY(byte[] buffer, short offset) {
thePoint.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
Util.arrayCopyNonAtomic(ecc.ech.uncompressed_point_arr1, (short)(1 + this.theCurve.COORD_SIZE), buffer, offset, this.theCurve.COORD_SIZE);
return this.theCurve.COORD_SIZE;
}
/**
* Returns the Y coordinate of this point in form of Bignat object.
*
* @param yCopy Bignat object which will be set with value of this point
*/
public void getY(Bignat yCopy) {
yCopy.set_size(this.getY(yCopy.as_byte_array(), (short) 0));
}
/**
* Doubles the current value of this point.
*/
public void makeDouble() {
// doubling via add sometimes causes exception inside KeyAgreement engine
// this.add(this);
// Use bit slower, but more robust version via multiplication by 2
this.multiplication(ecc.bnh.TWO);
}
/**
* Adds this (P) and provided (Q) point. Stores a resulting value into this point.
* @param other point to be added to this.
*/
public void add(ECPoint other) {
this.thePoint.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
ecc.ech.fnc_add_x_p.lock();
ecc.ech.fnc_add_x_p.set_size(this.theCurve.COORD_SIZE);
ecc.ech.fnc_add_x_p.from_byte_array(this.theCurve.COORD_SIZE, (short) 0, ecc.ech.uncompressed_point_arr1, (short) 1);
ecc.ech.fnc_add_y_p.lock();
ecc.ech.fnc_add_y_p.set_size(this.theCurve.COORD_SIZE);
ecc.ech.fnc_add_y_p.from_byte_array(this.theCurve.COORD_SIZE, (short) 0, ecc.ech.uncompressed_point_arr1, (short) (1 + this.theCurve.COORD_SIZE));
// l = (y_q-y_p)/(x_q-x_p))
// x_r = l^2 - x_p -x_q
// y_r = l(x_p-x_r)-y_p
// P+Q=R
ecc.ech.fnc_add_nominator.lock();
ecc.ech.fnc_add_denominator.lock();
if (this == other) {
//lambda = (3(x_p^2)+a)/(2y_p)
//(3(x_p^2)+a)
ecc.ech.fnc_add_nominator.clone(ecc.ech.fnc_add_x_p);
ecc.ech.fnc_add_nominator.mod_exp(ecc.bnh.TWO, this.theCurve.pBN);
ecc.ech.fnc_add_nominator.mod_mult(ecc.ech.fnc_add_nominator, ecc.bnh.THREE, this.theCurve.pBN);
ecc.ech.fnc_add_nominator.mod_add(this.theCurve.aBN, this.theCurve.pBN);
// (2y_p)
ecc.ech.fnc_add_denominator.clone(ecc.ech.fnc_add_y_p);
ecc.ech.fnc_add_denominator.mod_mult(ecc.ech.fnc_add_y_p, ecc.bnh.TWO, this.theCurve.pBN);
ecc.ech.fnc_add_denominator.mod_inv(this.theCurve.pBN);
} else {
// lambda=(y_q-y_p)/(x_q-x_p) mod p
other.thePoint.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
ecc.ech.fnc_add_x_q.lock();
ecc.ech.fnc_add_x_q.set_size(this.theCurve.COORD_SIZE);
ecc.ech.fnc_add_x_q.from_byte_array(other.theCurve.COORD_SIZE, (short) 0, ecc.ech.uncompressed_point_arr1, (short) 1);
ecc.ech.fnc_add_nominator.set_size(this.theCurve.COORD_SIZE);
ecc.ech.fnc_add_nominator.from_byte_array(this.theCurve.COORD_SIZE, (short) 0, ecc.ech.uncompressed_point_arr1, (short) (1 + this.theCurve.COORD_SIZE));
ecc.ech.fnc_add_nominator.mod(this.theCurve.pBN);
ecc.ech.fnc_add_nominator.mod_sub(ecc.ech.fnc_add_y_p, this.theCurve.pBN);
// (x_q-x_p)
ecc.ech.fnc_add_denominator.clone(ecc.ech.fnc_add_x_q);
ecc.ech.fnc_add_denominator.mod(this.theCurve.pBN);
ecc.ech.fnc_add_denominator.mod_sub(ecc.ech.fnc_add_x_p, this.theCurve.pBN);
ecc.ech.fnc_add_denominator.mod_inv(this.theCurve.pBN);
}
ecc.ech.fnc_add_lambda.lock();
ecc.ech.fnc_add_lambda.resize_to_max(false);
ecc.ech.fnc_add_lambda.zero();
ecc.ech.fnc_add_lambda.mod_mult(ecc.ech.fnc_add_nominator, ecc.ech.fnc_add_denominator, this.theCurve.pBN);
ecc.ech.fnc_add_nominator.unlock();
ecc.ech.fnc_add_denominator.unlock();
// (x_p,y_p)+(x_q,y_q)=(x_r,y_r)
// lambda=(y_q-y_p)/(x_q-x_p)
//x_r=lambda^2-x_p-x_q
ecc.ech.fnc_add_x_r.lock();
if (this == other) {
short len = this.multiplication_x(ecc.bnh.TWO, ecc.ech.fnc_add_x_r.as_byte_array(), (short) 0);
ecc.ech.fnc_add_x_r.set_size(len);
} else {
ecc.ech.fnc_add_x_r.clone(ecc.ech.fnc_add_lambda);
//m_occ.ecHelper.fnc_add_x_r.mod_exp(occ.bnHelper.TWO, this.TheCurve.pBN);
ecc.ech.fnc_add_x_r.mod_exp2(this.theCurve.pBN);
ecc.ech.fnc_add_x_r.mod_sub(ecc.ech.fnc_add_x_p, this.theCurve.pBN);
ecc.ech.fnc_add_x_r.mod_sub(ecc.ech.fnc_add_x_q, this.theCurve.pBN);
ecc.ech.fnc_add_x_q.unlock();
}
//y_r=lambda(x_p-x_r)-y_p
ecc.ech.fnc_add_y_r.lock();
ecc.ech.fnc_add_y_r.clone(ecc.ech.fnc_add_x_p);
ecc.ech.fnc_add_x_p.unlock();
ecc.ech.fnc_add_y_r.mod_sub(ecc.ech.fnc_add_x_r, this.theCurve.pBN);
ecc.ech.fnc_add_y_r.mod_mult(ecc.ech.fnc_add_y_r, ecc.ech.fnc_add_lambda, this.theCurve.pBN);
ecc.ech.fnc_add_lambda.unlock();
ecc.ech.fnc_add_y_r.mod_sub(ecc.ech.fnc_add_y_p, this.theCurve.pBN);
ecc.ech.fnc_add_y_p.unlock();
ecc.ech.uncompressed_point_arr1[0] = (byte)0x04;
// If x_r.length() and y_r.length() is smaller than this.TheCurve.COORD_SIZE due to leading zeroes which were shrinked before, then we must add these back
ecc.ech.fnc_add_x_r.prepend_zeros(this.theCurve.COORD_SIZE, ecc.ech.uncompressed_point_arr1, (short) 1);
ecc.ech.fnc_add_x_r.unlock();
ecc.ech.fnc_add_y_r.prepend_zeros(this.theCurve.COORD_SIZE, ecc.ech.uncompressed_point_arr1, (short) (1 + this.theCurve.COORD_SIZE));
ecc.ech.fnc_add_y_r.unlock();
this.setW(ecc.ech.uncompressed_point_arr1, (short) 0, this.theCurve.POINT_SIZE);
}
/**
* Multiply value of this point by provided scalar. Stores the result into
* this point.
*
* @param scalar value of scalar for multiplication
*/
public void multiplication(byte[] scalar, short scalarOffset, short scalarLen) {
ecc.ech.fnc_multiplication_scalar.lock();
ecc.ech.fnc_multiplication_scalar.set_size(scalarLen);
ecc.ech.fnc_multiplication_scalar.from_byte_array(scalarLen, (short) 0, scalar, scalarOffset);
multiplication(ecc.ech.fnc_multiplication_scalar);
ecc.ech.fnc_multiplication_scalar.unlock();
}
/**
* Multiply value of this point by provided scalar. Stores the result into this point.
* @param scalar value of scalar for multiplication
*/
public void multiplication(Bignat scalar) {
ecc.ech.fnc_multiplication_x.lock();
short len = this.multiplication_x(scalar, ecc.ech.fnc_multiplication_x.as_byte_array(), (short) 0);
ecc.ech.fnc_multiplication_x.set_size(len);
//Y^2 = X^3 + XA + B = x(x^2+A)+B
ecc.ech.fnc_multiplication_y_sq.lock();
ecc.ech.fnc_multiplication_y_sq.clone(ecc.ech.fnc_multiplication_x);
ecc.ech.fnc_multiplication_y_sq.mod_exp(ecc.bnh.TWO, this.theCurve.pBN);
ecc.ech.fnc_multiplication_y_sq.mod_add(this.theCurve.aBN, this.theCurve.pBN);
ecc.ech.fnc_multiplication_y_sq.mod_mult(ecc.ech.fnc_multiplication_y_sq, ecc.ech.fnc_multiplication_x, this.theCurve.pBN);
ecc.ech.fnc_multiplication_y_sq.mod_add(this.theCurve.bBN, this.theCurve.pBN);
ecc.ech.fnc_multiplication_y1.lock();
ecc.ech.fnc_multiplication_y1.clone(ecc.ech.fnc_multiplication_y_sq);
ecc.ech.fnc_multiplication_y_sq.unlock();
ecc.ech.fnc_multiplication_y1.sqrt_FP(this.theCurve.pBN);
// Construct public key with <x, y_1>
ecc.ech.uncompressed_point_arr1[0] = 0x04;
ecc.ech.fnc_multiplication_x.prepend_zeros(this.theCurve.COORD_SIZE, ecc.ech.uncompressed_point_arr1, (short) 1);
ecc.ech.fnc_multiplication_x.unlock();
ecc.ech.fnc_multiplication_y1.prepend_zeros(this.theCurve.COORD_SIZE, ecc.ech.uncompressed_point_arr1, (short) (1 + theCurve.COORD_SIZE));
this.setW(ecc.ech.uncompressed_point_arr1, (short) 0, theCurve.POINT_SIZE); //So that we can convert to pub key
// Check if public point <x, y_1> corresponds to the "secret" (i.e., our scalar)
if (!SignVerifyECDSA(this.theCurve.bignatAsPrivateKey(scalar), this.asPublicKey(), this.ecc.ech.fnc_SignVerifyECDSA_signEngine, ecc.bnh.fnc_mult_resultArray1)) { //If verification fails, then pick the <x, y_2>
ecc.ech.fnc_multiplication_y2.lock();
ecc.ech.fnc_multiplication_y2.clone(this.theCurve.pBN); //y_2 = p - y_1
ecc.ech.fnc_multiplication_y2.mod_sub(ecc.ech.fnc_multiplication_y1, this.theCurve.pBN);
ecc.ech.fnc_multiplication_y2.copy_to_buffer(ecc.ech.uncompressed_point_arr1, (short) (1 + theCurve.COORD_SIZE));
ecc.ech.fnc_multiplication_y2.unlock();
}
ecc.ech.fnc_multiplication_y1.unlock();
this.setW(ecc.ech.uncompressed_point_arr1, (short)0, theCurve.POINT_SIZE);
}
/**
* Multiplies this point value with provided scalar and stores result into provided array.
* No modification of this point is performed.
* @param scalar value of scalar for multiplication
* @param outBuffer output array for resulting value
* @param outBufferOffset offset within output array
* @return length of resulting value (in bytes)
*/
public short multiplication_x(Bignat scalar, byte[] outBuffer, short outBufferOffset) {
return multiplication_x_KA(scalar, outBuffer, outBufferOffset);
}
/**
* Multiplies this point value with provided scalar and stores result into
* provided array. No modification of this point is performed.
* Native KeyAgreement engine is used.
*
* @param scalar value of scalar for multiplication
* @param outBuffer output array for resulting value
* @param outBufferOffset offset within output array
* @return length of resulting value (in bytes)
*/
private short multiplication_x_KA(Bignat scalar, byte[] outBuffer, short outBufferOffset) {
// NOTE: potential problem on real cards (j2e) - when small scalar is used (e.g., Bignat.TWO), operation sometimes freezes
theCurve.disposable_priv.setS(scalar.as_byte_array(), (short) 0, scalar.length());
ecc.ech.fnc_multiplication_x_keyAgreement.init(theCurve.disposable_priv);
short len = this.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
len = ecc.ech.fnc_multiplication_x_keyAgreement.generateSecret(ecc.ech.uncompressed_point_arr1, (short) 0, len, outBuffer, outBufferOffset);
// Return always length of whole coordinate X instead of len - some real cards returns shorter value equal to SHA-1 output size although PLAIN results is filled into buffer (GD60)
return this.theCurve.COORD_SIZE;
}
/**
* Computes negation of this point.
*/
public void negate() {
// Operation will dump point into uncompressed_point_arr, negate Y and restore back
ecc.ech.fnc_negate_yBN.lock();
thePoint.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
ecc.ech.fnc_negate_yBN.set_size(this.theCurve.COORD_SIZE);
ecc.ech.fnc_negate_yBN.from_byte_array(this.theCurve.COORD_SIZE, (short) 0, ecc.ech.uncompressed_point_arr1, (short) (1 + this.theCurve.COORD_SIZE));
ecc.ech.fnc_negate_yBN.mod_negate(this.theCurve.pBN);
// Restore whole point back
ecc.ech.fnc_negate_yBN.prepend_zeros(this.theCurve.COORD_SIZE, ecc.ech.uncompressed_point_arr1, (short) (1 + this.theCurve.COORD_SIZE));
ecc.ech.fnc_negate_yBN.unlock();
this.setW(ecc.ech.uncompressed_point_arr1, (short) 0, this.theCurve.POINT_SIZE);
}
/**
* Compares this and provided point for equality. The comparison is made using hash of both values to prevent leak of position of mismatching byte.
* @param other second point for comparison
* @return true if both point are exactly equal (same length, same value), false otherwise
*/
public boolean isEqual(ECPoint other) {
boolean bResult = false;
if (this.length() != other.length()) {
return false;
}
else {
// The comparison is made with hash of point values instead of directly values.
// This way, offset of first mismatching byte is not leaked via timing side-channel.
// Additionally, only single array is required for storage of plain point values thus saving some RAM.
short len = this.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
ecc.bnh.hashEngine.doFinal(ecc.ech.uncompressed_point_arr1, (short) 0, len, ecc.ech.hashArray, (short) 0);
len = other.getW(ecc.ech.uncompressed_point_arr1, (short) 0);
len = ecc.bnh.hashEngine.doFinal(ecc.ech.uncompressed_point_arr1, (short) 0, len, ecc.ech.uncompressed_point_arr1, (short) 0);
bResult = Util.arrayCompare(ecc.ech.hashArray, (short) 0, ecc.ech.uncompressed_point_arr1, (short) 0, len) == 0;
}
return bResult;
}
static byte[] msg = {(byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x03};
public static boolean SignVerifyECDSA(ECPrivateKey privateKey, ECPublicKey publicKey, Signature signEngine, byte[] tmpSignArray) {
signEngine.init(privateKey, Signature.MODE_SIGN);
short signLen = signEngine.sign(msg, (short) 0, (short) msg.length, tmpSignArray, (short) 0);
signEngine.init(publicKey, Signature.MODE_VERIFY);
return signEngine.verify(msg, (short) 0, (short) msg.length, tmpSignArray, (short) 0, signLen);
}
}

View File

@ -1,133 +0,0 @@
package im.status.wallet;
import javacard.framework.Util;
import javacard.security.KeyAgreement;
import javacard.security.Signature;
/**
*
* @author Petr Svenda
*/
public class ECPoint_Helper {
// Selected constants missing from older JC API specs
public static final byte KeyAgreement_ALG_EC_SVDP_DH_PLAIN = (byte) 3;
public static final byte Signature_ALG_ECDSA_SHA_256 = (byte) 33;
private final ECConfig ecc;
byte[] uncompressed_point_arr1 = null;
byte[] hashArray = null;
public static final byte NUM_HELPER_ARRAYS = 2;
// These Bignats helperEC_BN_? are allocated
Bignat helperEC_BN_A;
Bignat helperEC_BN_B;
Bignat helperEC_BN_C;
Bignat helperEC_BN_D;
Bignat helperEC_BN_E;
Bignat helperEC_BN_F;
// These Bignats are just pointing to some helperEC_BN_? so reasonable naming is preserved yet no need to actually allocated whole Bignat object
Bignat fnc_add_x_r; // frequent write
Bignat fnc_add_y_r; // frequent write
Bignat fnc_add_x_p; // one init, then just read
Bignat fnc_add_y_p; // one init, then just read
Bignat fnc_add_x_q; // one init, then just read
Bignat fnc_add_lambda; // write mod_mul (but only final result)
Bignat fnc_add_nominator; // frequent write
Bignat fnc_add_denominator; // frequent write
Bignat fnc_multiplication_x; // result write
Bignat fnc_multiplication_y_sq; // frequent write
Bignat fnc_multiplication_scalar; // write once, read
Bignat fnc_multiplication_y1; // mostly just read, write inside sqrt_FP
Bignat fnc_multiplication_y2; // mostly just read, result write
Bignat fnc_negate_yBN; // mostly just read, result write
KeyAgreement fnc_multiplication_x_keyAgreement;
Signature fnc_SignVerifyECDSA_signEngine;
public ECPoint_Helper(ECConfig ecCfg) {
this.ecc = ecCfg;
helperEC_BN_A = new Bignat(ecc.MAX_POINT_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_A), ecc);
helperEC_BN_B = new Bignat(ecc.MAX_COORD_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_B), ecc);
helperEC_BN_C = new Bignat(ecc.MAX_COORD_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_C), ecc);
helperEC_BN_D = new Bignat(ecc.MAX_COORD_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_D), ecc);
helperEC_BN_E = new Bignat(ecc.MAX_COORD_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_E), ecc);
helperEC_BN_F = new Bignat(ecc.MAX_COORD_SIZE, ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_helperEC_BN_F), ecc);
// Important: assignment of helper BNs is made according to two criterions:
// 1. Correctness: same BN must not be assigned to overlapping operations (guarded by lock/unlock)
// 2. Memory tradeoff: we like to put as few BNs into RAM as possible. So most frequently used BNs for write should be in RAM
// and at the same time we like to have as few BNs in RAM as possible.
// So think twice before changing the assignments!
fnc_add_x_r = helperEC_BN_B;
fnc_add_y_r = helperEC_BN_C;
fnc_add_x_p = helperEC_BN_D;
fnc_add_y_p = helperEC_BN_E;
fnc_add_x_q = helperEC_BN_F;
fnc_add_nominator = helperEC_BN_B;
fnc_add_denominator = helperEC_BN_C;
fnc_add_lambda = helperEC_BN_A;
fnc_multiplication_scalar = helperEC_BN_F;
fnc_multiplication_x = helperEC_BN_B;
fnc_multiplication_y_sq = helperEC_BN_C;
fnc_multiplication_y1 = helperEC_BN_D;
fnc_multiplication_y2 = helperEC_BN_B;
fnc_negate_yBN = helperEC_BN_C;
uncompressed_point_arr1 = ecc.memAlloc.allocateByteArray((short) (ecc.MAX_POINT_SIZE + 1), ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_uncompressed_point_arr1));
hashArray = ecc.memAlloc.allocateByteArray(ecc.bnh.hashEngine.getLength(), ecc.memAlloc.getAllocatorType(ObjectAllocator.ECPH_hashArray));
ecc.FLAG_FAST_EC_MULT_VIA_KA = false; // set true only if succesfully allocated and tested below
try {
fnc_multiplication_x_keyAgreement = KeyAgreement.getInstance(KeyAgreement_ALG_EC_SVDP_DH_PLAIN, false);
fnc_SignVerifyECDSA_signEngine = Signature.getInstance(Signature_ALG_ECDSA_SHA_256, false);
ecc.FLAG_FAST_EC_MULT_VIA_KA = true;
}
catch (Exception ignored) {} // Discard any exception
}
/**
* Erase all values stored in helper objects
*/
void erase() {
helperEC_BN_A.erase();
helperEC_BN_B.erase();
helperEC_BN_C.erase();
helperEC_BN_D.erase();
helperEC_BN_E.erase();
helperEC_BN_F.erase();
Util.arrayFillNonAtomic(uncompressed_point_arr1, (short) 0, (short) uncompressed_point_arr1.length, (byte) 0);
}
/**
* Unlocks all helper objects
*/
public void unlockAll() {
if (helperEC_BN_A.isLocked()) {
helperEC_BN_A.unlock();
}
if (helperEC_BN_B.isLocked()) {
helperEC_BN_B.unlock();
}
if (helperEC_BN_C.isLocked()) {
helperEC_BN_C.unlock();
}
if (helperEC_BN_D.isLocked()) {
helperEC_BN_D.unlock();
}
if (helperEC_BN_E.isLocked()) {
helperEC_BN_E.unlock();
}
if (helperEC_BN_F.isLocked()) {
helperEC_BN_F.unlock();
}
}
}

View File

@ -1,142 +0,0 @@
package im.status.wallet;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
/**
* The control point for unified allocation of arrays and objects with customable
* specification of allocator type (RAM/EEPROM) for particular array. Allows for
* quick personalization and optimization of memory use when compiling for cards
* with more/less available memory.
*
* @author Petr Svenda
*/
public class ObjectAllocator {
short allocatedInRAM = 0;
short allocatedInEEPROM = 0;
byte[] ALLOCATOR_TYPE_ARRAY = null;
public static final byte BNH_helper_BN_array1 = 0;
public static final byte BNH_helper_BN_array2 = 1;
public static final byte BNH_helper_BN_A = 2;
public static final byte BNH_helper_BN_B = 3;
public static final byte BNH_helper_BN_C = 4;
public static final byte BNH_helper_BN_D = 5;
public static final byte BNH_helper_BN_E = 6;
public static final byte BNH_helper_BN_F = 7;
public static final byte ECPH_helperEC_BN_A = 8;
public static final byte ECPH_helperEC_BN_B = 9;
public static final byte ECPH_helperEC_BN_C = 10;
public static final byte ECPH_helperEC_BN_D = 11;
public static final byte ECPH_helperEC_BN_E = 12;
public static final byte ECPH_helperEC_BN_F = 13;
public static final byte ECPH_uncompressed_point_arr1 = 14;
public static final byte ECPH_hashArray = 15;
public static final short ALLOCATOR_TYPE_ARRAY_LENGTH = (short) (ECPH_hashArray + 1);
/**
* Creates new allocator control object, resets performance counters
*/
public ObjectAllocator() {
ALLOCATOR_TYPE_ARRAY = new byte[ALLOCATOR_TYPE_ARRAY_LENGTH];
setAllAllocatorsRAM();
resetAllocatorCounters();
}
/**
* All type of allocator for all object as EEPROM
*/
public final void setAllAllocatorsEEPROM() {
Util.arrayFillNonAtomic(ALLOCATOR_TYPE_ARRAY, (short) 0, (short) ALLOCATOR_TYPE_ARRAY.length, JCSystem.MEMORY_TYPE_PERSISTENT);
}
/**
* All type of allocator for all object as RAM
*/
public void setAllAllocatorsRAM() {
Util.arrayFillNonAtomic(ALLOCATOR_TYPE_ARRAY, (short) 0, (short) ALLOCATOR_TYPE_ARRAY.length, JCSystem.MEMORY_TYPE_TRANSIENT_RESET);
}
/**
* All type of allocator for selected object as RAM (faster), rest EEPROM (saving RAM)
* The current settings is heuristically obtained from measurements of performance of Bignat and ECPoint operations
*/
public void setAllocatorsTradeoff() {
// Set initial allocators into EEPROM
setAllAllocatorsEEPROM();
// Put only the most perfromance relevant ones into RAM
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_array1] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_array2] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_A] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_C] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_D] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_E] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[BNH_helper_BN_F] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[ECPH_helperEC_BN_B] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[ECPH_helperEC_BN_C] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
ALLOCATOR_TYPE_ARRAY[ECPH_uncompressed_point_arr1] = JCSystem.MEMORY_TYPE_TRANSIENT_RESET;
}
/**
* Allocates new byte[] array with provided length either in RAM or EEPROM based on an allocator type.
* Method updates internal counters of bytes allocated with specific allocator. Use {@code getAllocatedInRAM()}
* or {@code getAllocatedInEEPROM} for counters readout.
* @param length length of array
* @param allocatorType type of allocator
* @return allocated array
*/
public byte[] allocateByteArray(short length, byte allocatorType) {
switch (allocatorType) {
case JCSystem.MEMORY_TYPE_PERSISTENT:
allocatedInEEPROM += length;
return new byte[length];
case JCSystem.MEMORY_TYPE_TRANSIENT_RESET:
allocatedInRAM += length;
return JCSystem.makeTransientByteArray(length, JCSystem.CLEAR_ON_RESET);
case JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT:
allocatedInRAM += length;
return JCSystem.makeTransientByteArray(length, JCSystem.CLEAR_ON_DESELECT);
}
return null;
}
/**
* Returns pre-set allocator type for provided object identified by unique objectAllocatorID
* @param objectAllocatorID unique id of target object
* @return allocator type
*/
public byte getAllocatorType(short objectAllocatorID) {
if (objectAllocatorID >= 0 && objectAllocatorID <= (short) ALLOCATOR_TYPE_ARRAY.length) {
return ALLOCATOR_TYPE_ARRAY[objectAllocatorID];
} else {
ISOException.throwIt(ReturnCodes.SW_ALLOCATOR_INVALIDOBJID);
return -1;
}
}
/**
* Returns number of bytes allocated in RAM via {@code allocateByteArray()} since last reset of counters.
* @return number of bytes allocated in RAM via this control object
*/
public short getAllocatedInRAM() {
return allocatedInRAM;
}
/**
* Returns number of bytes allocated in EEPROM via {@code allocateByteArray()}
* since last reset of counters.
*
* @return number of bytes allocated in EEPROM via this control object
*/
public short getAllocatedInEEPROM() {
return allocatedInEEPROM;
}
/**
* Resets counters of allocated bytes in RAM and EEPROM
*/
public final void resetAllocatorCounters() {
allocatedInRAM = 0;
allocatedInEEPROM = 0;
}
}

View File

@ -1,18 +0,0 @@
package im.status.wallet;
/**
*
* @author Vasilios Mavroudis and Petr Svenda
*/
public class ReturnCodes {
// Custom error response codes
public static final short SW_BIGNAT_RESIZETOLONGER = (short) 0x7000;
public static final short SW_BIGNAT_REALLOCATIONNOTALLOWED = (short) 0x7001;
public static final short SW_BIGNAT_MODULOTOOLARGE = (short) 0x7002;
public static final short SW_BIGNAT_INVALIDCOPYOTHER = (short) 0x7003;
public static final short SW_BIGNAT_INVALIDRESIZE = (short) 0x7004;
public static final short SW_LOCK_ALREADYLOCKED = (short) 0x7005;
public static final short SW_LOCK_NOTLOCKED = (short) 0x7006;
public static final short SW_ECPOINT_INVALIDLENGTH = (short) 0x700a;
public static final short SW_ALLOCATOR_INVALIDOBJID = (short) 0x700c;
}

View File

@ -1,5 +1,7 @@
package im.status.wallet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.security.CryptoException;
import javacard.security.ECKey;
import javacard.security.ECPrivateKey;
@ -47,19 +49,16 @@ public class SECP256k1 {
private static final byte ALG_EC_SVDP_DH_PLAIN_XY = 6; // constant from JavaCard 3.0.5
private static KeyAgreement ecPointMultiplier;
private static ECConfig ecConfig;
private static ECCurve ecCurve;
private static ECPoint ecPoint;
private static KeyAgreement ecPointMultiplierX;
static void init() {
try {
ecPointMultiplier = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false);
} catch(CryptoException e) {
ecConfig = new ECConfig((short) 256);
ecCurve = new ECCurve(false, SECP256K1_FP, SECP256K1_A, SECP256K1_B, SECP256K1_G, SECP256K1_R, ecConfig);
ecPoint = new ECPoint(ecCurve, ecConfig);
ecPointMultiplier = null;
}
ecPointMultiplierX = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false);
}
static void setCurveParameters(ECKey key) {
@ -75,23 +74,28 @@ public class SECP256k1 {
return multiplyPoint(privateKey, SECP256K1_G, (short) 0, (short) SECP256K1_G.length, pubOut, pubOff);
}
/*
* This works efficiently only if KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY is implemented. Otherwise performance will
* be very slow, around 5s for a single multiplication.
*/
static short multiplyPoint(ECPrivateKey privateKey, byte[] point, short pointOff, short pointLen, byte[] out, short outOff) {
if(ecPointMultiplier != null) {
ecPointMultiplier.init(privateKey);
return ecPointMultiplier.generateSecret(point, pointOff, pointLen, out, outOff);
} else {
ecPoint.setW(point, pointOff, pointLen);
short len = privateKey.getS(out, outOff);
ecPoint.multiplication(out, outOff, len);
return ecPoint.getW(out, outOff);
}
static short derivePublicX(ECPrivateKey privateKey, byte[] xOut, short xOff) {
return multiplyX(privateKey, SECP256K1_G, (short) 0, (short) SECP256K1_G.length, xOut, xOff);
}
static boolean hasFastECPointMultiplication() {
static short multiplyPoint(ECPrivateKey privateKey, byte[] point, short pointOff, short pointLen, byte[] out, short outOff) {
assetECPointMultiplicationSupport();
ecPointMultiplier.init(privateKey);
return ecPointMultiplier.generateSecret(point, pointOff, pointLen, out, outOff);
}
static short multiplyX(ECPrivateKey privateKey, byte[] point, short pointOff, short pointLen, byte[] out, short outOff) {
ecPointMultiplierX.init(privateKey);
return ecPointMultiplierX.generateSecret(point, pointOff, pointLen, out, outOff);
}
static boolean hasECPointMultiplication() {
return ecPointMultiplier != null;
}
static void assetECPointMultiplicationSupport() {
if(!hasECPointMultiplication()) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
}
}

View File

@ -47,7 +47,7 @@ public class WalletApplet extends Applet {
static final byte TLV_PIN_RETRY_COUNT = (byte) 0xC0;
static final byte TLV_PUK_RETRY_COUNT = (byte) 0xC1;
static final byte TLV_KEY_INITIALIZATION_STATUS = (byte) 0xC2;
static final byte TLV_FAST_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
private OwnerPIN pin;
private OwnerPIN puk;
@ -176,9 +176,9 @@ public class WalletApplet extends Applet {
apduBuffer[off++] = TLV_KEY_INITIALIZATION_STATUS;
apduBuffer[off++] = 1;
apduBuffer[off++] = privateKey.isInitialized() ? (byte) 0x01 : (byte) 0x00;
apduBuffer[off++] = TLV_FAST_PUBLIC_KEY_DERIVATION;
apduBuffer[off++] = TLV_PUBLIC_KEY_DERIVATION;
apduBuffer[off++] = 1;
apduBuffer[off++] = SECP256k1.hasFastECPointMultiplication() ? (byte) 0x01 : (byte) 0x00;
apduBuffer[off++] = SECP256k1.hasECPointMultiplication() ? (byte) 0x01 : (byte) 0x00;
short len = secureChannel.encryptAPDU(apduBuffer, (short) (off - SecureChannel.SC_OUT_OFFSET));
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
@ -276,6 +276,7 @@ public class WalletApplet extends Applet {
short chainOffset = (short)(privOffset + apduBuffer[(short)(privOffset + 1)] + 2);
if (apduBuffer[pubOffset] != TLV_PUB_KEY) {
SECP256k1.assetECPointMultiplicationSupport();
chainOffset = privOffset;
privOffset = pubOffset;
pubOffset = -1;
@ -322,6 +323,8 @@ public class WalletApplet extends Applet {
}
private void loadSeed(byte[] apduBuffer) {
SECP256k1.assetECPointMultiplicationSupport();
if (apduBuffer[ISO7816.OFFSET_LC] != SEED_SIZE) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -343,6 +346,8 @@ public class WalletApplet extends Applet {
}
private void deriveKey(APDU apdu) {
SECP256k1.assetECPointMultiplicationSupport();
apdu.setIncomingAndReceive();
if (!(secureChannel.isOpen() && pin.isValidated() && isExtended)) {
@ -359,6 +364,7 @@ public class WalletApplet extends Applet {
short chainEnd = (short) (ISO7816.OFFSET_CDATA + len);
resetKeys(apduBuffer, chainEnd);
signInProgress = false;
for (short i = ISO7816.OFFSET_CDATA; i < chainEnd; i += 4) {
Crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0);

View File

@ -120,7 +120,7 @@ public class WalletAppletTest {
cmdSet.openSecureChannel();
// Good case. Since the order of test execution is undefined, the test cannot know if the keys are initialized or not.
// Additionally, support for fast public key derivation is hw dependent.
// Additionally, support for public key derivation is hw dependent.
response = cmdSet.getStatus();
assertEquals(0x9000, response.getSW());
byte[] data = secureChannel.decryptAPDU(response.getData());