Skip to content

Commit

Permalink
refactor: move H2C implementation to a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
dufkan committed Mar 26, 2024
1 parent fc00af0 commit 7318e46
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 66 deletions.
20 changes: 20 additions & 0 deletions applet/src/main/java/jcmint/Denomination.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package jcmint;

import javacard.framework.JCSystem;
import javacard.framework.Util;
import jcmint.jcmathlib.*;

public class Denomination {
public final BigNat secret;
public final byte[] partialKeys;

public Denomination(ResourceManager rm) {
secret = new BigNat((short) 32, JCSystem.MEMORY_TYPE_PERSISTENT, rm);
partialKeys = new byte[65 * Consts.MAX_PARTIES];
}

public void setup(short parties, byte[] secret, short secretOffset, byte[] partialKeys, short partialKeysOffset) {
this.secret.fromByteArray(secret, secretOffset, (short) 32);
Util.arrayCopyNonAtomic(partialKeys, partialKeysOffset, this.partialKeys, (short) 0, (short) (65 * parties));
}
}
49 changes: 49 additions & 0 deletions applet/src/main/java/jcmint/HashToCurve.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package jcmint;

import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.MessageDigest;
import jcmint.jcmathlib.*;

public class HashToCurve {
private final MessageDigest md = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
private final byte[] prefixBuffer = JCSystem.makeTransientByteArray((short) 36, JCSystem.CLEAR_ON_RESET);
private final byte[] ramArray = JCSystem.makeTransientByteArray((short) 32, JCSystem.CLEAR_ON_RESET);

public void hash(byte[] data, short offset, ECPoint output) {
Util.arrayFillNonAtomic(prefixBuffer, (short) 32, (short) 4, (byte) 0);
md.reset();
md.update(Consts.H2C_DOMAIN_SEPARATOR, (short) 0, (short) Consts.H2C_DOMAIN_SEPARATOR.length);
md.doFinal(data, offset, (short) 32, prefixBuffer, (short) 0);

for (short counter = 0; counter < (short) 256; ++counter) { // TODO consider increasing max number of iters
md.reset();
prefixBuffer[32] = (byte) (counter & 0xff);
md.doFinal(prefixBuffer, (short) 0, (short) prefixBuffer.length, ramArray, (short) 0);
if (output.fromX(ramArray, (short) 0, (short) 32))
break;
}
if (!output.isYEven())
output.negate();
}

public void hashPrecomputed(byte[] input, short inputOffset, byte[] result, short resultOffset, ECPoint output) {
Util.arrayFillNonAtomic(prefixBuffer, (short) 32, (short) 4, (byte) 0);
md.reset();
md.update(Consts.H2C_DOMAIN_SEPARATOR, (short) 0, (short) Consts.H2C_DOMAIN_SEPARATOR.length);
md.doFinal(input, inputOffset, (short) 32, prefixBuffer, (short) 0);

md.reset();
md.doFinal(prefixBuffer, (short) 0, (short) prefixBuffer.length, ramArray, (short) 0);

if (Util.arrayCompare(ramArray, (short) 0, result, (short) (resultOffset + 1), (short) 32) != 0) {
ISOException.throwIt(Consts.E_INVALID_PRECOMPUTE);
}

output.setW(result, resultOffset, (short) 65);

if (!output.isYEven())
output.negate();
}
}
108 changes: 42 additions & 66 deletions applet/src/main/java/jcmint/JCMint.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ public class JCMint extends Applet implements ExtendedLength {

private ResourceManager rm;
private ECCurve curve;
private final MessageDigest md = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
private final RandomData randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
private final MessageDigest md = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);

private byte index;
private byte parties;
private BigNat secret;
private byte[] partialKeys;
private Denomination[] denominations = new Denomination[1];

private ECPoint point1, point2;
private BigNat bn1, bn2;
private final byte[] prefixBuffer = JCSystem.makeTransientByteArray((short) 36, JCSystem.CLEAR_ON_RESET);
private final byte[] ramArray = JCSystem.makeTransientByteArray((short) 65, JCSystem.CLEAR_ON_RESET);
private final byte[] largeBuffer = JCSystem.makeTransientByteArray((short) 512, JCSystem.CLEAR_ON_RESET);
private HashToCurve h2c;

private final Ledger ledger = new Ledger();
private final byte[] verifying = new byte[(short) (32 + 65 + 65)]; // (x, C, H(x))
Expand Down Expand Up @@ -117,13 +116,17 @@ private void initialize() {

rm = new ResourceManager((short) 256);
curve = new ECCurve(SecP256k1.p, SecP256k1.a, SecP256k1.b, SecP256k1.G, SecP256k1.r, rm);
secret = new BigNat((short) 32, JCSystem.MEMORY_TYPE_PERSISTENT, rm);
partialKeys = new byte[65 * Consts.MAX_PARTIES];
h2c = new HashToCurve();

point1 = new ECPoint(curve);
point2 = new ECPoint(curve);
bn1 = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm);
bn2 = new BigNat((short) 32, JCSystem.MEMORY_TYPE_TRANSIENT_RESET, rm);

for (int i = 0; i < (short) denominations.length; ++i) {
denominations[i] = new Denomination(rm);
}

initialized = true;
}

Expand All @@ -134,14 +137,15 @@ private void setup(APDU apdu) {
if (parties < 1 || parties > Consts.MAX_PARTIES) {
ISOException.throwIt(Consts.E_INVALID_PARTY_COUNT);
}
secret.fromByteArray(apduBuffer, ISO7816.OFFSET_CDATA, (short) 32);
Util.arrayCopyNonAtomic(apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32), partialKeys, (short) 0, (short) (65 * parties));
for (short i = 0; i < (short) denominations.length; ++i) {
denominations[i].setup(parties, apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32));
}

ECPoint mintKey = point2;

mintKey.decode(partialKeys, (short) 0, (short) 65);
mintKey.decode(denominations[0].partialKeys, (short) 0, (short) 65);
for (short i = 1; i < parties; ++i) {
point1.decode(partialKeys, (short) (65 * i), (short) 65);
point1.decode(denominations[0].partialKeys, (short) (65 * i), (short) 65);
mintKey.add(point1);
}
ledger.reset();
Expand All @@ -151,61 +155,28 @@ private void setup(APDU apdu) {

private void issue(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
byte d = apduBuffer[ISO7816.OFFSET_P2];

point1.decode(apduBuffer, ISO7816.OFFSET_CDATA, (short) 65);
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);

apdu.setOutgoingAndSend((short) 0, point1.getW(apduBuffer, (short) 0));
}

private void hashToCurve(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();

h2c(apduBuffer, ISO7816.OFFSET_CDATA);
h2c.hash(apduBuffer, ISO7816.OFFSET_CDATA, point1);
apdu.setOutgoingAndSend((short) 0, point1.getW(apduBuffer, (short) 0));
}

private void h2c(byte[] data, short offset) {
Util.arrayFillNonAtomic(prefixBuffer, (short) 32, (short) 4, (byte) 0);
md.reset();
md.update(Consts.H2C_DOMAIN_SEPARATOR, (short) 0, (short) Consts.H2C_DOMAIN_SEPARATOR.length);
md.doFinal(data, offset, (short) 32, prefixBuffer, (short) 0);

for (short counter = 0; counter < (short) 256; ++counter) { // TODO consider increasing max number of iters
md.reset();
prefixBuffer[32] = (byte) (counter & 0xff);
md.doFinal(prefixBuffer, (short) 0, (short) prefixBuffer.length, ramArray, (short) 0);
if (point1.fromX(ramArray, (short) 0, (short) 32))
break;
}
if (!point1.isYEven())
point1.negate();
}

private void h2cPrecomputed(byte[] input, short inputOffset, byte[] result, short resultOffset) {
Util.arrayFillNonAtomic(prefixBuffer, (short) 32, (short) 4, (byte) 0);
md.reset();
md.update(Consts.H2C_DOMAIN_SEPARATOR, (short) 0, (short) Consts.H2C_DOMAIN_SEPARATOR.length);
md.doFinal(input, inputOffset, (short) 32, prefixBuffer, (short) 0);

md.reset();
md.doFinal(prefixBuffer, (short) 0, (short) prefixBuffer.length, ramArray, (short) 0);

if (Util.arrayCompare(ramArray, (short) 0, result, (short) (resultOffset + 1), (short) 32) != 0) {
ISOException.throwIt(Consts.E_INVALID_PRECOMPUTE);
}

point1.setW(result, resultOffset, (short) 65);

if (!point1.isYEven())
point1.negate();
}

private void verify(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
byte precomputed = apduBuffer[ISO7816.OFFSET_P1];
BigNat nonce = bn1;
BigNat tmp = bn2;
byte d = apduBuffer[ISO7816.OFFSET_P2];

if (ledger.contains(apduBuffer, ISO7816.OFFSET_CDATA))
ISOException.throwIt(Consts.E_ALREADY_SPENT);
Expand All @@ -215,16 +186,17 @@ private void verify(APDU apdu) {

// DLEQ X
if (precomputed == (byte) 1) {
h2cPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65));
h2c.hashPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65), point1);
} else {
h2c(apduBuffer, ISO7816.OFFSET_CDATA);
h2c.hash(apduBuffer, ISO7816.OFFSET_CDATA, point1);
}
md.reset();

point1.getW(verifying, (short) (32 + 65));
md.reset();
md.update(verifying, (short) (32 + 65), (short) 65);

// DLEQ Y
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);
point1.getW(apduBuffer, (short) 0);
point1.decode(verifying, (short) (32 + 65), (short) 65); // restore hashOutput
md.update(apduBuffer, (short) 0, (short) 65);
Expand All @@ -234,7 +206,7 @@ private void verify(APDU apdu) {
md.update(curve.G, (short) 0, (short) 65);

// DLEQ Q
md.update(partialKeys, (short) (index * 65), (short) 65);
md.update(denominations[d].partialKeys, (short) (index * 65), (short) 65);

randomData.nextBytes(ramArray, (short) 0, (short) 32);
nonce.fromByteArray(ramArray, (short) 0, (short) 32);
Expand All @@ -250,14 +222,14 @@ private void verify(APDU apdu) {
md.doFinal(ramArray, (short) 0, (short) 65, apduBuffer, (short) 65);

tmp.fromByteArray(apduBuffer, (short) 65, (short) 32);
tmp.modMult(secret, curve.rBN);
tmp.modMult(denominations[d].secret, curve.rBN);
tmp.modAdd(nonce, curve.rBN);
tmp.copyToByteArray(apduBuffer, (short) (65 + 32));

apdu.setOutgoingAndSend((short) 0, (short) (65 + 32 + 32));
}

private void finishVerify(byte[] token, short tokenOffset, byte[] proofs, short proofsOffset) {
private void finishVerify(byte d, byte[] token, short tokenOffset, byte[] proofs, short proofsOffset) {
BigNat e = bn1;
BigNat s = bn2;

Expand All @@ -273,7 +245,7 @@ private void finishVerify(byte[] token, short tokenOffset, byte[] proofs, short
md.update(verifying, (short) (32 + 65), (short) 65); // X
md.update(proofs, (short) (proofsOffset + i * (65 + 32 + 32)), (short) 65); // Y
md.update(curve.G, (short) 0, (short) curve.G.length); // P
md.update(partialKeys, (short) (65 * i), (short) 65); // Q
md.update(denominations[d].partialKeys, (short) (65 * i), (short) 65); // Q

// compute A
point2.decode(proofs, (short) (proofsOffset + i * (65 + 32 + 32)), (short) 65);
Expand All @@ -286,7 +258,7 @@ private void finishVerify(byte[] token, short tokenOffset, byte[] proofs, short
md.update(ramArray, (short) 0, (short) 65); // A

// compute B
point2.decode(partialKeys, (short) (65 * i), (short) 65);
point2.decode(denominations[d].partialKeys, (short) (65 * i), (short) 65);
point2.multiplication(e);
point2.negate();

Expand Down Expand Up @@ -316,17 +288,19 @@ private void finishVerify(byte[] token, short tokenOffset, byte[] proofs, short

private void swap(APDU apdu) {
loadExtendedApdu(apdu);
byte d = largeBuffer[ISO7816.OFFSET_P2];

finishVerify(largeBuffer, apdu.getOffsetCdata(), largeBuffer, (short) (apdu.getOffsetCdata() + 32 + 65 + 65));
finishVerify(d, largeBuffer, apdu.getOffsetCdata(), largeBuffer, (short) (apdu.getOffsetCdata() + 32 + 65 + 65));

point1.decode(largeBuffer, (short) (apdu.getOffsetCdata() + 32 + 65), (short) 65);
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);
apdu.setOutgoingAndSend((short) 0, point1.getW(apdu.getBuffer(), (short) 0));
}

private void swapSingle(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
byte precomputed = apduBuffer[ISO7816.OFFSET_P1];
byte d = apduBuffer[ISO7816.OFFSET_P2];

if (parties != 1)
ISOException.throwIt(Consts.E_INVALID_PARTY_COUNT);
Expand All @@ -335,33 +309,35 @@ private void swapSingle(APDU apdu) {
ISOException.throwIt(Consts.E_ALREADY_SPENT);

if (precomputed == (byte) 1) {
h2cPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65 + 65));
h2c.hashPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65 + 65), point1);
} else {
h2c(apduBuffer, ISO7816.OFFSET_CDATA);
h2c.hash(apduBuffer, ISO7816.OFFSET_CDATA, point1);
}
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);

point1.getW(ramArray, (short) 0);
if (Util.arrayCompare(apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32), ramArray, (short) 0, (short) 65) != 0)
ISOException.throwIt(Consts.E_VERIFICATION_FAILED_TOKEN);

ledger.append(apduBuffer, ISO7816.OFFSET_CDATA);
point1.decode(apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65), (short) 65);
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);
apdu.setOutgoingAndSend((short) 0, point1.getW(apduBuffer, (short) 0));
}

private void redeem(APDU apdu) {
loadExtendedApdu(apdu);
byte d = largeBuffer[ISO7816.OFFSET_P2];

finishVerify(largeBuffer, apdu.getOffsetCdata(), largeBuffer, (short) (apdu.getOffsetCdata() + 32 + 65));
finishVerify(d, largeBuffer, apdu.getOffsetCdata(), largeBuffer, (short) (apdu.getOffsetCdata() + 32 + 65));

apdu.setOutgoing();
}

private void redeemSingle(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
byte precomputed = apduBuffer[ISO7816.OFFSET_P1];
byte d = apduBuffer[ISO7816.OFFSET_P2];

if (parties != 1)
ISOException.throwIt(Consts.E_INVALID_PARTY_COUNT);
Expand All @@ -370,11 +346,11 @@ private void redeemSingle(APDU apdu) {
ISOException.throwIt(Consts.E_ALREADY_SPENT);

if (precomputed == (byte) 1) {
h2cPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65));
h2c.hashPrecomputed(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32 + 65), point1);
} else {
h2c(apduBuffer, ISO7816.OFFSET_CDATA);
h2c.hash(apduBuffer, ISO7816.OFFSET_CDATA, point1);
}
point1.multiplication(secret);
point1.multiplication(denominations[d].secret);

point1.getW(ramArray, (short) 0);
if (Util.arrayCompare(apduBuffer, (short) (ISO7816.OFFSET_CDATA + 32), ramArray, (short) 0, (short) 65) != 0)
Expand Down

0 comments on commit 7318e46

Please sign in to comment.