5 changed files with 388 additions and 29 deletions
@@ -3,9 +3,13 @@ package im.status.keycard;
import javacard.framework.*;
import javacard.security.*;
import static javacard.framework.ISO7816.OFFSET_P2;
public class CashApplet extends Applet {
private static final short SIGN_OUT_OFF = ISO7816.OFFSET_CDATA + MessageDigest.LENGTH_SHA_256;
private static final byte TLV_PUB_DATA = (byte) 0x82;
private static final byte SIGN_P2_ECDSA = 0x00;
private static final byte SIGN_P2_SCHNORR = 0x01;
private KeyPair keypair;
private ECPublicKey publicKey;
@@ -15,6 +19,7 @@ public class CashApplet extends Applet {
private SECP256k1 secp256k1;
private Signature signature;
private boolean schnorrInitialized;
/**
* Invoked during applet installation. Creates an instance of this class. The installation parameters are passed in
@@ -40,7 +45,7 @@ public class CashApplet extends Applet {
*/
public CashApplet(byte[] bArray, short bOffset, byte bLength) {
crypto = new Crypto();
secp256k1 = new SECP256k1();
secp256k1 = new SECP256k1(crypto);
keypair = new KeyPair(KeyPair.ALG_EC_FP, SECP256k1.SECP256K1_KEY_SIZE);
publicKey = (ECPublicKey) keypair.getPublic();
@@ -51,6 +56,7 @@ public class CashApplet extends Applet {
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
signature.init(privateKey, Signature.MODE_SIGN);
schnorrInitialized = false;
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
@@ -91,6 +97,11 @@ public class CashApplet extends Applet {
}
private void selectApplet(APDU apdu) {
if (!schnorrInitialized) {
secp256k1.initSchnorr();
schnorrInitialized = true;
}
byte[] apduBuffer = apdu.getBuffer();
short off = 0;
@@ -120,6 +131,20 @@ public class CashApplet extends Applet {
private void sign(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
boolean schnorr;
switch(apduBuffer[OFFSET_P2]) {
case SIGN_P2_ECDSA:
schnorr = false;
break;
case SIGN_P2_SCHNORR:
schnorr = true;
break;
default:
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
return;
}
apduBuffer[SIGN_OUT_OFF] = KeycardApplet.TLV_SIGNATURE_TEMPLATE;
apduBuffer[(short) (SIGN_OUT_OFF + 3)] = KeycardApplet.TLV_PUB_KEY;
short outLen = apduBuffer[(short) (SIGN_OUT_OFF + 4)] = Crypto.KEY_PUB_SIZE;
@@ -129,8 +154,12 @@ public class CashApplet extends Applet {
outLen += 5;
short sigOff = (short) (SIGN_OUT_OFF + outLen);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);
if (schnorr) {
outLen += secp256k1.signSchnorr(privateKey, apduBuffer, (short) (SIGN_OUT_OFF + 5), apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
} else {
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);
}
apduBuffer[(short) (SIGN_OUT_OFF + 1)] = (byte) 0x81;
apduBuffer[(short) (SIGN_OUT_OFF + 2)] = (byte) (outLen - 3);
+49 -15
View File
@@ -36,7 +36,7 @@ public class Crypto {
private Signature hmacSHA512;
private HMACKey hmacKey;
private byte[] hmacBlock;
protected byte[] hmacBlock;
Crypto() {
random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
@@ -131,7 +131,7 @@ public class Crypto {
sOff++;
if (ret == -1 || ucmp256(sig, sOff, MAX_S, (short) 0) > 0) {
sub256(S_SUB, (short) 0, sig, sOff, sig, sOff);
subBig(S_SUB, (short) 0, sig, sOff, sig, sOff, KEY_SECRET_SIZE);
}
return ret;
@@ -150,7 +150,7 @@ public class Crypto {
* @param out the output buffer
* @param outOff the offset in the output buffer
*/
private void hmacSHA512(byte[] key, short keyOff, short keyLen, byte[] in, short inOff, short inLen, byte[] out, short outOff) {
void hmacSHA512(byte[] key, short keyOff, short keyLen, byte[] in, short inOff, short inLen, byte[] out, short outOff) {
if (hmacSHA512 != null) {
hmacKey.setKey(key, keyOff, keyLen);
hmacSHA512.init(hmacKey, Signature.MODE_SIGN);
@@ -186,9 +186,9 @@ public class Crypto {
* @param out the output buffer
* @param outOff the offset in the output buffer
*/
private void addm256(byte[] a, short aOff, byte[] b, short bOff, byte[] n, short nOff, byte[] out, short outOff) {
if ((add256(a, aOff, b, bOff, out, outOff) != 0) || (ucmp256(out, outOff, n, nOff) > 0)) {
sub256(out, outOff, n, nOff, out, outOff);
void addm256(byte[] a, short aOff, byte[] b, short bOff, byte[] n, short nOff, byte[] out, short outOff) {
if ((addBig(a, aOff, b, bOff, out, outOff, KEY_SECRET_SIZE) != 0) || (ucmp256(out, outOff, n, nOff) > 0)) {
subBig(out, outOff, n, nOff, out, outOff, KEY_SECRET_SIZE);
}
}
@@ -201,7 +201,7 @@ public class Crypto {
* @param bOff the offset of the b operand
* @return the comparison result
*/
private short ucmp256(byte[] a, short aOff, byte[] b, short bOff) {
short ucmp256(byte[] a, short aOff, byte[] b, short bOff) {
short ai, bi;
for (short i = 0 ; i < 32; i++) {
ai = (short)(a[(short)(aOff + i)] & 0x00ff);
@@ -222,7 +222,7 @@ public class Crypto {
* @param aOff the offset of the a operand
* @return true if a is 0, false otherwise
*/
private boolean isZero256(byte[] a, short aOff) {
boolean isZero256(byte[] a, short aOff) {
boolean isZero = true;
for (short i = 0; i < (byte) 32; i++) {
@@ -236,7 +236,7 @@ public class Crypto {
}
/**
* Addition of two 256-bit numbers.
* Addition of two big numbers.
*
* @param a the a operand
* @param aOff the offset of the a operand
@@ -244,20 +244,21 @@ public class Crypto {
* @param bOff the offset of the b operand
* @param out the output buffer
* @param outOff the offset in the output buffer
* @param i the size of number in bytes
* @return the carry of the addition
*/
private short add256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) {
short addBig(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff, short i) {
short outI = 0;
for (short i = 31 ; i >= 0 ; i--) {
for (i--; i >= 0; i--) {
outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) + (short)(b[(short)(bOff + i)] & 0xFF) + outI);
out[(short)(outOff + i)] = (byte)outI ;
out[(short)(outOff + i)] = (byte)outI;
outI = (short)(outI >> 8);
}
return outI;
}
/**
* Subtraction of two 256-bit numbers.
* Subtraction of two big numbers.
*
* @param a the a operand
* @param aOff the offset of the a operand
@@ -265,12 +266,13 @@ public class Crypto {
* @param bOff the offset of the b operand
* @param out the output buffer
* @param outOff the offset in the output buffer
* @param i the size of number in bytes
* @return the carry of the subtraction
*/
private short sub256(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff) {
short subBig(byte[] a, short aOff, byte[] b, short bOff, byte[] out, short outOff, short i) {
short outI = 0;
for (short i = 31 ; i >= 0 ; i--) {
for (i--; i >= 0; i--) {
outI = (short) ((short)(a[(short)(aOff + i)] & 0xFF) - (short)(b[(short)(bOff + i)] & 0xFF) - outI);
out[(short)(outOff + i)] = (byte)outI ;
outI = (short)(((outI >> 8) != 0) ? 1 : 0);
@@ -278,4 +280,36 @@ public class Crypto {
return outI;
}
/**
* Addition of two big numbers of different size. A must be larger than B and the result will be
* the size of A
*
* @param a the a operand
* @param aOff the offset of the a operand
* @param aLen the length of a
* @param b the b operand
* @param bOff the offset of the b operand
* @param bLen the length of b
* @param out the output buffer
* @param outOff the offset in the output buffer
* @return the carry of the addition
*/
short addBig(byte[] a, short aOff, short aLen, byte[] b, short bOff, short bLen, byte[] out, short outOff) {
short outI = 0;
short diff = (short) (aLen - bLen);
for (aLen--; aLen >= diff; aLen--) {
outI = (short) ((short)(a[(short)(aOff + aLen)] & 0xFF) + (short)(b[(short)(bOff + (aLen - diff))] & 0xFF) + outI);
out[(short)(outOff + aLen)] = (byte) outI;
outI = (short)(outI >> 8);
}
for (; aLen >= 0; aLen--) {
outI = (short) ((short)(a[(short)(aOff + aLen)] & 0xFF) + outI);
out[(short)(outOff + aLen)] = (byte)outI;
outI = (short)(outI >> 8);
}
return outI;
}
}
@@ -2,9 +2,9 @@ package im.status.keycard;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;
import static javacard.framework.ISO7816.OFFSET_P1;
import static javacard.framework.ISO7816.OFFSET_P2;
/**
* The applet's main class. All incoming commands a processed by this class.
@@ -68,6 +68,9 @@ public class KeycardApplet extends Applet {
static final byte SIGN_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
static final byte SIGN_P1_PINLESS = 0x03;
static final byte SIGN_P2_ECDSA = 0x00;
static final byte SIGN_P2_SCHNORR = 0x01;
static final byte EXPORT_KEY_P1_CURRENT = 0x00;
static final byte EXPORT_KEY_P1_DERIVE = 0x01;
static final byte EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
@@ -167,7 +170,7 @@ public class KeycardApplet extends Applet {
*/
public KeycardApplet(byte[] bArray, short bOffset, byte bLength) {
crypto = new Crypto();
secp256k1 = new SECP256k1();
secp256k1 = new SECP256k1(crypto);
uid = new byte[UID_LENGTH];
crypto.random.generateData(uid, (short) 0, UID_LENGTH);
@@ -340,6 +343,8 @@ public class KeycardApplet extends Applet {
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), PUK_LENGTH);
JCSystem.commitTransaction();
secp256k1.initSchnorr();
} else {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
@@ -1104,6 +1109,20 @@ public class KeycardApplet extends Applet {
return;
}
boolean schnorr;
switch(apduBuffer[OFFSET_P2]) {
case SIGN_P2_ECDSA:
schnorr = false;
break;
case SIGN_P2_SCHNORR:
schnorr = true;
break;
default:
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
return;
}
short len;
if (usePinless && !secureChannel.isOpen()) {
@@ -1148,10 +1167,13 @@ public class KeycardApplet extends Applet {
outLen += 5;
short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen);
signature.init(signingKey, Signature.MODE_SIGN);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);
if (schnorr) {
outLen += secp256k1.signSchnorr(signingKey, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5), apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
} else {
signature.init(signingKey, Signature.MODE_SIGN);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);
}
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81;
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3);
@@ -1212,7 +1234,7 @@ public class KeycardApplet extends Applet {
boolean publicOnly;
switch (apduBuffer[ISO7816.OFFSET_P2]) {
switch (apduBuffer[OFFSET_P2]) {
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
publicOnly = false;
break;
+224 -4
View File
@@ -1,9 +1,14 @@
package im.status.keycard;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.ECKey;
import javacard.security.ECPrivateKey;
import javacard.security.KeyAgreement;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.RSAPublicKey;
import javacardx.crypto.Cipher;
/**
* Utility methods to work with the SECP256k1 curve. This class is not meant to be instantiated, but its init method
@@ -49,22 +54,66 @@ public class SECP256k1 {
static final byte SECP256K1_K = (byte)0x01;
static final short SECP256K1_KEY_SIZE = 256;
static final short SECP256K1_BYTE_SIZE = (short) (SECP256K1_KEY_SIZE / 8);
static final byte TLV_SCHNORR_SIGNATURE = (byte) 0x8f;
static final short SCHNORR_MULT_KEY_SIZE = KeyBuilder.LENGTH_RSA_736;
static final short SCHNORR_COMPONENT_SIZE = (short) (SCHNORR_MULT_KEY_SIZE / 8);
static final short MULT_OUT_SIZE = (short) 64;
static final short SCHNORR_K_OUT_OFF = (short) 0;
static final short SCHNORR_E_OUT_OFF = (short) (SECP256K1_BYTE_SIZE + SCHNORR_K_OUT_OFF);
static final short SCHNORR_D_OUT_OFF = (short) (SCHNORR_COMPONENT_SIZE + SCHNORR_E_OUT_OFF);
static final short SCHNORR_RES_OUT_OFF = (short) (SCHNORR_COMPONENT_SIZE + SCHNORR_D_OUT_OFF);
static final short SCHNORR_E_32_OFF = (short) (SCHNORR_COMPONENT_SIZE - SECP256K1_BYTE_SIZE + SCHNORR_E_OUT_OFF);
static final short SCHNORR_D_32_OFF = (short) (SCHNORR_COMPONENT_SIZE - SECP256K1_BYTE_SIZE + SCHNORR_D_OUT_OFF);
static final short SCHNORR_RES_32_OFF = (short) (SCHNORR_COMPONENT_SIZE - SECP256K1_BYTE_SIZE + SCHNORR_RES_OUT_OFF);
static final short SCHNORR_RES_64_OFF = (short) (SCHNORR_COMPONENT_SIZE - MULT_OUT_SIZE + SCHNORR_RES_OUT_OFF);
static final short TMP_LEN = (short) (SECP256K1_BYTE_SIZE + (SCHNORR_COMPONENT_SIZE * 3));
private static final byte ALG_EC_SVDP_DH_PLAIN_XY = 6; // constant from JavaCard 3.0.5
private static final short MOD_DIGIT_LEN = 8;
private static final short MOD_DDIGIT_LEN = 16;
private static final short MOD_DIGIT_MASK = 0xff;
private static final short MOD_DDIGIT_MASK = 0x7fff;
private KeyAgreement ecPointMultiplier;
private Crypto crypto;
ECPrivateKey tmpECPrivateKey;
private KeyPair multPair;
private RSAPublicKey pow2;
private Cipher multCipher;
static final byte[] CONST_TWO = { 0x02 };
private byte[] tmp;
/**
* Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time.
*/
SECP256k1() {
SECP256k1(Crypto crypto) {
this.crypto = crypto;
this.ecPointMultiplier = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false);
this.tmpECPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256K1_KEY_SIZE, false);
setCurveParameters(tmpECPrivateKey);
}
void initSchnorr() {
this.tmp = JCSystem.makeTransientByteArray(TMP_LEN, JCSystem.CLEAR_ON_RESET);
multPair = new KeyPair(KeyPair.ALG_RSA_CRT, SCHNORR_MULT_KEY_SIZE);
multPair.genKeyPair();
pow2 = (RSAPublicKey) multPair.getPublic();
pow2.setExponent(CONST_TWO, (short) 0, (short) CONST_TWO.length);
multCipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
multCipher.init(pow2, Cipher.MODE_ENCRYPT);
}
/**
* Sets the SECP256k1 curve parameters to the given ECKey (public or private).
*
@@ -92,7 +141,6 @@ public class SECP256k1 {
return multiplyPoint(privateKey, SECP256K1_G, (short) 0, (short) SECP256K1_G.length, pubOut, pubOff);
}
/**
* Derives the public key from the given private key and outputs it in the pubOut buffer. This is done by multiplying
* the private key by the G point of the curve.
@@ -103,7 +151,7 @@ public class SECP256k1 {
* @return the length of the public key
*/
short derivePublicKey(byte[] privateKey, short privOff, byte[] pubOut, short pubOff) {
tmpECPrivateKey.setS(privateKey, privOff, (short)(SECP256K1_KEY_SIZE/8));
tmpECPrivateKey.setS(privateKey, privOff, SECP256K1_BYTE_SIZE);
return derivePublicKey(tmpECPrivateKey, pubOut, pubOff);
}
@@ -123,4 +171,176 @@ public class SECP256k1 {
ecPointMultiplier.init(privateKey);
return ecPointMultiplier.generateSecret(point, pointOff, pointLen, out, outOff);
}
short signSchnorr(ECPrivateKey privKey, byte[] pubKey, short pubOff, byte[] data, short dataOff, short dataLen, byte[] output, short outOff) {
output[outOff++] = TLV_SCHNORR_SIGNATURE;
output[outOff++] = (byte) (Crypto.KEY_PUB_SIZE + SECP256K1_BYTE_SIZE);
crypto.random.generateData(tmp, SCHNORR_K_OUT_OFF, SECP256K1_BYTE_SIZE);
Util.arrayFillNonAtomic(tmp, SCHNORR_E_OUT_OFF, (short)(TMP_LEN - SCHNORR_E_OUT_OFF), (byte) 0x00);
derivePublicKey(tmp, SCHNORR_K_OUT_OFF, output, outOff);
crypto.sha256.update(output, outOff, Crypto.KEY_PUB_SIZE);
crypto.sha256.update(pubKey, pubOff, Crypto.KEY_PUB_SIZE);
crypto.sha256.doFinal(data, dataOff, dataLen, tmp, SCHNORR_E_32_OFF);
privKey.getS(tmp, SCHNORR_D_32_OFF);
tmp[(short)(SCHNORR_RES_32_OFF - 1)] = (byte) crypto.addBig(tmp, SCHNORR_E_32_OFF, tmp, SCHNORR_D_32_OFF, tmp, SCHNORR_RES_32_OFF, SECP256K1_BYTE_SIZE);
multCipher.doFinal(tmp, SCHNORR_RES_OUT_OFF, SCHNORR_COMPONENT_SIZE, tmp, SCHNORR_RES_OUT_OFF);
multCipher.doFinal(tmp, SCHNORR_D_OUT_OFF, SCHNORR_COMPONENT_SIZE, tmp, SCHNORR_D_OUT_OFF);
crypto.subBig(tmp, SCHNORR_RES_OUT_OFF, tmp, SCHNORR_D_OUT_OFF, tmp, SCHNORR_RES_OUT_OFF, SCHNORR_COMPONENT_SIZE);
multCipher.doFinal(tmp, SCHNORR_E_OUT_OFF, SCHNORR_COMPONENT_SIZE, tmp, SCHNORR_E_OUT_OFF);
crypto.subBig(tmp, SCHNORR_RES_OUT_OFF, tmp, SCHNORR_E_OUT_OFF, tmp, SCHNORR_RES_OUT_OFF, SCHNORR_COMPONENT_SIZE);
divideResBy2();
crypto.addBig(tmp, SCHNORR_RES_64_OFF, MULT_OUT_SIZE, tmp, SCHNORR_K_OUT_OFF, SECP256K1_BYTE_SIZE, tmp, SCHNORR_RES_64_OFF);
secp256k1Mod(tmp, SCHNORR_RES_64_OFF);
Util.arrayCopyNonAtomic(tmp, SCHNORR_RES_32_OFF, output, (short) (outOff + Crypto.KEY_PUB_SIZE), SECP256K1_BYTE_SIZE);
return (short) (2 + Crypto.KEY_PUB_SIZE + SECP256K1_BYTE_SIZE);
}
private void divideResBy2() {
short res, res2;
for (short i = (short) (SCHNORR_COMPONENT_SIZE - 1); i >= (short) (SCHNORR_COMPONENT_SIZE - MULT_OUT_SIZE - 1); i--) {
res = (short) ((short) (tmp[(short)(SCHNORR_RES_OUT_OFF + i)] & 0xff) >> 1);
res2 = (short) ((short) (tmp[(short)(SCHNORR_RES_OUT_OFF + i - 1)] & 0xff) << 7);
tmp[(short)(SCHNORR_RES_OUT_OFF + i)] = (byte) ((short) (res | res2));
}
}
private void secp256k1Mod(byte[] value, short offset) {
short divisorShift = (short) (MULT_OUT_SIZE - SECP256K1_R.length);
short divisionRound = 0;
short firstDivisorDigit = (short) (SECP256K1_R[(short) 0] & MOD_DIGIT_MASK);
short divisorBitShift = (short) (highestBit((short) (firstDivisorDigit + 1)) - 1);
byte secondDivisorDigit = SECP256K1_R[(short) 1];
byte thirdDivisorDigit = SECP256K1_R[(short) 2];
short dividendDigits, divisorDigit;
short dividendBitShift, bitShift;
short multiple;
while (divisorShift >= 0) {
while (!shiftLesser(value, offset, divisorShift, (short) (divisionRound > 0 ? divisionRound - 1 : 0))) {
dividendDigits = divisionRound == 0 ? 0 : (short) ((short) (value[(short) (offset + divisionRound - 1)]) << MOD_DIGIT_LEN);
dividendDigits |= (short) (value[(short)(offset + divisionRound)] & MOD_DIGIT_MASK);
if (dividendDigits < 0) {
dividendDigits = (short) ((dividendDigits >>> 1) & MOD_DDIGIT_MASK);
divisorDigit = (short) ((firstDivisorDigit >>> 1) & MOD_DDIGIT_MASK);
} else {
dividendBitShift = (short) (highestBit(dividendDigits) - 1);
bitShift = dividendBitShift <= divisorBitShift ? dividendBitShift : divisorBitShift;
dividendDigits = shiftBits(dividendDigits, divisionRound < (short) (MULT_OUT_SIZE - 1) ? value[(short) (offset + divisionRound + 1)] : 0, divisionRound < (short) (SECP256K1_R.length - 2) ? value[(short) (offset + divisionRound + 2)] : 0, bitShift);
divisorDigit = shiftBits(firstDivisorDigit, secondDivisorDigit, thirdDivisorDigit, bitShift);
}
multiple = (short) (dividendDigits / (short) (divisorDigit + 1));
if (multiple < 1) {
multiple = 1;
}
timesMinus(value, offset, divisorShift, multiple);
}
divisionRound++;
divisorShift--;
}
}
private void timesMinus(byte[] value, short offset, short shift, short mult) {
short accu = 0;
short subtractionResult;
short i = (short) (MULT_OUT_SIZE - 1 - shift);
short j = (short) (SECP256K1_R.length - 1);
for (; i >= 0 && j >= 0; i--, j--) {
accu = (short) (accu + (short) (mult * (SECP256K1_R[j] & MOD_DIGIT_MASK)));
subtractionResult = (short) ((value[(short)(offset + i)] & MOD_DIGIT_MASK) - (accu & MOD_DIGIT_MASK));
value[(short)(offset + i)] = (byte) (subtractionResult & MOD_DIGIT_MASK);
accu = (short) ((accu >> MOD_DIGIT_LEN) & MOD_DIGIT_MASK);
if (subtractionResult < 0) {
accu++;
}
}
while (i >= 0 && accu != 0) {
subtractionResult = (short) ((value[(short)(offset + i)] & MOD_DIGIT_MASK) - (accu & MOD_DIGIT_MASK));
value[(short)(offset + i)] = (byte) (subtractionResult & MOD_DIGIT_MASK);
accu = (short) ((accu >> MOD_DIGIT_LEN) & MOD_DIGIT_MASK);
if (subtractionResult < 0) {
accu++;
}
i--;
}
}
private static short highestBit(short x) {
for (short i = 0; i < MOD_DDIGIT_LEN; i++) {
if (x < 0) {
return i;
}
x <<= 1;
}
return MOD_DDIGIT_LEN;
}
private static short shiftBits(short high, byte middle, byte low, short shift) {
high <<= shift;
byte mask = (byte) (MOD_DIGIT_MASK << (shift >= MOD_DIGIT_LEN ? 0 : MOD_DIGIT_LEN - shift));
short bits = (short) ((short) (middle & mask) & MOD_DIGIT_MASK);
if (shift > MOD_DIGIT_LEN) {
bits <<= shift - MOD_DIGIT_LEN;
} else {
bits >>>= MOD_DIGIT_LEN - shift;
}
high |= bits;
if (shift <= MOD_DIGIT_LEN) {
return high;
}
mask = (byte) (MOD_DIGIT_MASK << MOD_DDIGIT_LEN - shift);
bits = (short) ((((short) (low & mask) & MOD_DIGIT_MASK) >> MOD_DDIGIT_LEN - shift));
high |= bits;
return high;
}
private static boolean shiftLesser(byte[] value, short offset, short shift, short start) {
short j;
j = (short) (SECP256K1_R.length + shift - MULT_OUT_SIZE + start);
short valShort, divisorShort;
for (short i = start; i < MULT_OUT_SIZE; i++, j++) {
valShort = (short) (value[(short)(i + offset)] & MOD_DIGIT_MASK);
if (j >= 0 && j < SECP256K1_R.length) {
divisorShort = (short) (SECP256K1_R[j] & MOD_DIGIT_MASK);
}
else {
divisorShort = 0;
}
if (valShort < divisorShort) {
return true; // CTO
}
if (valShort > divisorShort) {
return false;
}
}
return false;
}
}
@@ -17,6 +17,8 @@ import org.bitcoinj.crypto.HDKeyDerivation;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.*;
import org.web3j.crypto.*;
@@ -1006,6 +1008,18 @@ public class KeycardTest {
response = cmdSet.sign(hash);
verifySignResp(data, response);
//TODO: Integrate in SDK!
// START SCHNORR
APDUCommand sign = secureChannel.protectedCommand(0x80, 0xC0, 0x00, 0x01, hash);
long time = System.currentTimeMillis();
response = secureChannel.transmit(sdkChannel, sign);
System.out.print("Schnorr time: ");
System.out.println(System.currentTimeMillis() - time);
response.checkOK();
verifySchnorr(hash, response.getData());
// END SCHNORR
// Sign and derive
String currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData()).toString();
String updatedPath = new KeyPath(currentPath + "/2").toString();
@@ -1048,6 +1062,34 @@ public class KeycardTest {
assertEquals(0x6A88, response.getSw());
}
private void verifySchnorr(byte[] m, byte[] sig) throws Exception {
byte[] p = extractPublicKeyFromSignature(sig);
byte[] rawSig = extractSignature(sig);
byte[] r = Arrays.copyOfRange(rawSig, 2, 67);
byte[] rawS = Arrays.copyOfRange(rawSig, 67, rawSig.length);
System.out.println("p = " + Hex.toHexString(p));
System.out.println("r = " + Hex.toHexString(r));
System.out.println("s = " + Hex.toHexString(rawS));
MessageDigest dg = MessageDigest.getInstance("SHA256");
dg.update(r);
dg.update(p);
dg.update(m);
BigInteger e = new BigInteger(1, dg.digest());
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint P = ecSpec.getCurve().decodePoint(p);
ECPoint G = ecSpec.getG();
BigInteger s = new BigInteger(1, rawS);
ECPoint R = G.multiply(s).subtract(P.multiply(e));
System.out.println("R = " + Hex.toHexString(R.getEncoded(false)));
assertTrue(R.equals(ecSpec.getCurve().decodePoint(r)));
}
private void verifySignResp(byte[] data, APDUResponse response) throws Exception {
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
assertEquals(0x9000, response.getSw());
@@ -1375,6 +1417,18 @@ public class KeycardTest {
response = cashCmdSet.sign(hash);
verifySignResp(data, response);
//TODO: Integrate in SDK!
// START SCHNORR
APDUCommand sign = new APDUCommand(0x80, 0xC0, 0x00, 0x01, hash);
long time = System.currentTimeMillis();
response = sdkChannel.send(sign);
System.out.print("Schnorr time: ");
System.out.println(System.currentTimeMillis() - time);
response.checkOK();
verifySchnorr(hash, response.getData());
// END SCHNORR
}
@Test