Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65ab8d30bf | ||
|
|
392a661300 | ||
|
|
37dd4aca69 | ||
|
|
a1c2bc119d | ||
|
|
ab4afcb4cd | ||
|
|
abcc05f0a3 | ||
|
|
1b7b196901 | ||
|
|
f1522fac81 |
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user