diff --git a/src/main/java/im/status/wallet/WalletApplet.java b/src/main/java/im/status/wallet/WalletApplet.java index 6554855..03d675d 100644 --- a/src/main/java/im/status/wallet/WalletApplet.java +++ b/src/main/java/im/status/wallet/WalletApplet.java @@ -360,13 +360,13 @@ public class WalletApplet extends Applet { for (short i = GENERATE_MNEMONIC_TMP_OFF; i < entLen; i += 2) { short w = Util.getShort(apduBuffer, i); - Util.setShort(apduBuffer, outOff, (short)((short)(((short)(vp | ((short) (w >>> rShift)))) >>> 5) & (short) 0x7ff)); + Util.setShort(apduBuffer, outOff, logicrShift((short) (vp | logicrShift(w, rShift)), (short) 5)); outOff += 2; rShift += 5; vp = (short) (w << (16 - rShift)); if (rShift >= 11) { - Util.setShort(apduBuffer, outOff, (short)((short) (vp >>> 5) & (short) 0x7ff)); + Util.setShort(apduBuffer, outOff, logicrShift(vp, (short) 5)); outOff += 2; rShift = (short) (rShift - 11); vp = (short) (w << (16 - rShift)); @@ -381,6 +381,20 @@ public class WalletApplet extends Applet { apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen); } + // This works on simulator AND on JavaCard. Since we do not do a lot of these operations, the performance hit is non-existent + private short logicrShift(short v, short amount) { + if (amount == 0) return v; // short circuit on 0 + short tmp = (short) (v & 0x7fff); + + if (tmp == v) { + return (short) (v >>> amount); + } + + tmp = (short) (tmp >>> amount); + + return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp); + } + private void sign(APDU apdu) { apdu.setIncomingAndReceive(); diff --git a/src/test/java/im/status/wallet/WalletAppletTest.java b/src/test/java/im/status/wallet/WalletAppletTest.java index af554fe..07aa011 100644 --- a/src/test/java/im/status/wallet/WalletAppletTest.java +++ b/src/test/java/im/status/wallet/WalletAppletTest.java @@ -30,6 +30,7 @@ import java.security.*; import java.util.Arrays; import java.util.Random; +import static org.apache.commons.codec.digest.DigestUtils.sha256; import static org.junit.jupiter.api.Assertions.*; @DisplayName("Test the Wallet Applet") @@ -560,18 +561,33 @@ public class WalletAppletTest { } private void assertMnemonic(int expectedLength, byte[] data) { - short[] shorts = new short[data.length/2]; + short[] shorts = new short[data.length / 2]; assertEquals(expectedLength, shorts.length); ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shorts); + boolean[] bits = new boolean[11 * shorts.length]; + int i = 0; + for (short mIdx : shorts) { assertTrue(mIdx >= 0 && mIdx < 2048); + for (int j = 0; j < 11; ++j) { + bits[i++] = (mIdx & (1 << (10 - j))) > 0; + } } - // TODO: the checksum should be validated. The problem is that the simulator should generate wrong values because of - // the bitwise operator extends the type to int, while JavaCard does not support int at all. If we make it work on - // the simulator then the code will not convert to CAP file at all. This means that the checksum can be tested only - // on a real card. + data = new byte[bits.length / 33 * 4]; + + for (i = 0; i < bits.length / 33 * 32; ++i) { + data[i / 8] |= (bits[i] ? 1 : 0) << (7 - (i % 8)); + } + + byte[] check = sha256(data); + + for (i = bits.length / 33 * 32; i < bits.length; ++i) { + if ((check[(i - bits.length / 33 * 32) / 8] & (1 << (7 - (i % 8))) ^ (bits[i] ? 1 : 0) << (7 - (i % 8))) != 0) { + fail("Checksum is invalid"); + } + } } private Sign.SignatureData signMessage(byte[] message) throws Exception {