/*
*******************************************************************************
* Java Card Bitcoin Hardware Wallet
* (c) 2015 Ledger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************
*/
package com.ledger.wallet;
import javacard.framework.JCSystem;
import javacard.framework.Util;
public class Bip32Cache {
private static final short CACHE_SIZE = (short)10;
private static short lastIndex = (short)0;
private byte[] privateComponent;
private byte[] publicComponent;
private byte[] path;
private byte pathLength;
private boolean hasPrivate;
private boolean hasPublic;
private static Bip32Cache[] cache = null;
private static byte[] lastCacheIndex;
private static final byte INDEX_NOT_AVAILABLE = (byte)0xff;
public Bip32Cache() {
privateComponent = new byte[64];
publicComponent = new byte[65];
path = new byte[40];
pathLength = INDEX_NOT_AVAILABLE;
}
public static void init() {
cache = new Bip32Cache[CACHE_SIZE];
for (short i=0; i<CACHE_SIZE; i++) {
cache[i] = new Bip32Cache();
}
lastCacheIndex = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
}
public static void reset() {
for (short i=0; i<CACHE_SIZE; i++) {
cache[i].pathLength = INDEX_NOT_AVAILABLE;
}
}
private static Bip32Cache findFree() {
Bip32Cache result = null;
for (short i=0; i<CACHE_SIZE; i++) {
if (cache[i].pathLength == INDEX_NOT_AVAILABLE) {
result = cache[i];
break;
}
}
if (result == null) {
lastIndex++;
lastIndex %= CACHE_SIZE;
result = cache[lastIndex];
}
// Recycle
result.pathLength = INDEX_NOT_AVAILABLE;
result.hasPrivate = false;
result.hasPublic = false;
return result;
}
private static Bip32Cache findPath(byte[] path, short pathOffset, byte pathLength, boolean setLast) {
for (short i=0; i<CACHE_SIZE; i++) {
if ((cache[i].pathLength == pathLength) &&
(Util.arrayCompare(path, pathOffset, cache[i].path, (short)0, (short)(pathLength * 4)) == 0)) {
if (setLast) {
lastCacheIndex[0] = (byte)i;
}
return cache[i];
}
}
return null;
}
public static void storePrivate(byte[] path, short pathOffset, byte pathLength, byte[] privateComponent) {
Bip32Cache cache = findPath(path, pathOffset, pathLength, false);
if (!((cache != null) && cache.hasPrivate)) {
if (cache == null) {
cache = findFree();
cache.pathLength = pathLength;
Util.arrayCopy(path, pathOffset, cache.path, (short)0, (short)(pathLength * 4));
}
Crypto.initCipher(LedgerWalletApplet.chipKey, true);
Crypto.blobEncryptDecrypt.doFinal(privateComponent, (short)0, (short)64, cache.privateComponent, (short)0);
cache.hasPrivate = true;
}
}
public static void storePublic(byte[] path, short pathOffset, byte pathLength, byte[] publicComponent, short publicComponentOffset) {
Bip32Cache cache = findPath(path, pathOffset, pathLength, false);
if (!((cache != null) && cache.hasPublic)) {
if (cache == null) {
cache = findFree();
cache.pathLength = pathLength;
Util.arrayCopy(path, pathOffset, cache.path, (short)0, (short)(pathLength * 4));
}
Util.arrayCopy(publicComponent, publicComponentOffset, cache.publicComponent, (short)0, (short)65);
cache.hasPublic = true;
}
}
public static byte copyPrivateBest(byte[] path, short pathOffset, byte pathLength, byte[] target, short targetOffset) {
for (byte i=pathLength; i>0; i--) {
Bip32Cache cache = findPath(path, pathOffset, i, false);
if ((cache != null) && (cache.hasPrivate)) {
Crypto.initCipher(LedgerWalletApplet.chipKey, false);
Crypto.blobEncryptDecrypt.doFinal(cache.privateComponent, (short)0, (short)64, target, targetOffset);
return i;
}
}
return (byte)0;
}
public static boolean hasPublic(byte[] path, short pathOffset, byte pathLength) {
Bip32Cache cache = findPath(path, pathOffset, pathLength, false);
if ((cache == null) || (!cache.hasPublic)) {
return false;
}
return true;
}
public static boolean setPublicIndex(byte[] path, short pathOffset, byte pathLength) {
Bip32Cache cache = findPath(path, pathOffset, pathLength, true);
if ((cache == null) || (!cache.hasPublic)) {
lastCacheIndex[0] = INDEX_NOT_AVAILABLE;
return false;
}
return true;
}
public static boolean copyLastPublic(byte[] target, short targetOffset) {
Bip32Cache lastCache;
if (lastCacheIndex[0] == INDEX_NOT_AVAILABLE) {
return false;
}
lastCache = cache[lastCacheIndex[0]];
if (!lastCache.hasPublic) {
return false;
}
Util.arrayCopyNonAtomic(lastCache.publicComponent, (short)0, target, targetOffset, (short)65);
return true;
}
}