/*
*******************************************************************************    
*   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.Util;
import javacard.security.Signature;

public class Bip32 {
	
	protected static final short OFFSET_DERIVATION_INDEX = (short)64;
	
	private static final byte BITCOIN_SEED[] = {
		'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'
	};		
	
	private static final short OFFSET_TMP = (short)100;	
	private static final short OFFSET_BLOCK = (short)127;
	    
    // seed : scratch, offset 0 -> result in masterDerived 
    // keyHmac was duplicated because
	// depending on the implementation, if a native transient HMAC is used, the key size might be fixed
	// on the first call
	// if that's the case, power cycle / deselect between initial seed derivation and all other key derivations
	// would solve it
	public static void deriveSeed(byte seedLength) {
		if (Crypto.signatureHmac != null) {
			Crypto.keyHmac2.setKey(BITCOIN_SEED, (short)0, (short)BITCOIN_SEED.length);
			if ((LedgerWalletApplet.proprietaryAPI != null) && (LedgerWalletApplet.proprietaryAPI.hasHmacSHA512())) {
				LedgerWalletApplet.proprietaryAPI.hmacSHA512(Crypto.keyHmac2, LedgerWalletApplet.scratch256, (short)0, seedLength, LedgerWalletApplet.masterDerived, (short)0);
			}
			else {
				Crypto.signatureHmac.init(Crypto.keyHmac2, Signature.MODE_SIGN);
				Crypto.signatureHmac.sign(LedgerWalletApplet.scratch256, (short)0, seedLength, LedgerWalletApplet.masterDerived, (short)0);
			}
		}
		else {
			HmacSha512.hmac(BITCOIN_SEED, (short)0, (short)BITCOIN_SEED.length, LedgerWalletApplet.scratch256, (short)0, seedLength, LedgerWalletApplet.masterDerived, (short)0, LedgerWalletApplet.scratch256, (short)64);
		}
	}
	
	// scratch255 : 0-64 : key + chain / 64-67 : derivation index / 100-165 : tmp
	// apduBuffer : block (128, starting at 127)
	// result : scratch255 0-64
	public static boolean derive(byte[] apduBuffer) {
		boolean isZero = true;
		byte i;
		if ((LedgerWalletApplet.scratch256[OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0) {
			if (LedgerWalletApplet.proprietaryAPI != null) {
				LedgerWalletApplet.proprietaryAPI.getUncompressedPublicPoint(LedgerWalletApplet.scratch256, (short)0, LedgerWalletApplet.scratch256, OFFSET_TMP);				
			}
			else {				
				if (!Bip32Cache.copyLastPublic(LedgerWalletApplet.scratch256, OFFSET_TMP)) {
					return false;
				}
			}
			AddressUtils.compressPublicKey(LedgerWalletApplet.scratch256, OFFSET_TMP);
		}
		else {
			LedgerWalletApplet.scratch256[OFFSET_TMP] = 0;
			Util.arrayCopyNonAtomic(LedgerWalletApplet.scratch256, (short)0, LedgerWalletApplet.scratch256, (short)(OFFSET_TMP + 1), (short)32);
		}
		Util.arrayCopyNonAtomic(LedgerWalletApplet.scratch256, OFFSET_DERIVATION_INDEX, LedgerWalletApplet.scratch256, (short)(OFFSET_TMP + 33), (short)4);
		if (Crypto.signatureHmac != null) {
			Crypto.keyHmac.setKey(LedgerWalletApplet.scratch256, (short)32, (short)32);
			if ((LedgerWalletApplet.proprietaryAPI != null) && (LedgerWalletApplet.proprietaryAPI.hasHmacSHA512())) {
				LedgerWalletApplet.proprietaryAPI.hmacSHA512(Crypto.keyHmac, LedgerWalletApplet.scratch256, OFFSET_TMP, (short)37, LedgerWalletApplet.scratch256, OFFSET_TMP);
			}
			else {
				Crypto.signatureHmac.init(Crypto.keyHmac, Signature.MODE_SIGN);
				Crypto.signatureHmac.sign(LedgerWalletApplet.scratch256, OFFSET_TMP, (short)37, LedgerWalletApplet.scratch256, OFFSET_TMP);
			}
		}
		else {
			HmacSha512.hmac(LedgerWalletApplet.scratch256, (short)32, (short)32, LedgerWalletApplet.scratch256, OFFSET_TMP, (short)37, LedgerWalletApplet.scratch256, OFFSET_TMP, apduBuffer, OFFSET_BLOCK);
		}
		if (MathMod256.ucmp(LedgerWalletApplet.scratch256, OFFSET_TMP, Secp256k1.SECP256K1_R, (short)0) >= 0) {
			return false;
		}
		MathMod256.addm(LedgerWalletApplet.scratch256, (short)0, LedgerWalletApplet.scratch256, OFFSET_TMP, LedgerWalletApplet.scratch256, (short)0, Secp256k1.SECP256K1_R, (short)0);
		for (i=0; i<(byte)32; i++) {
			if (LedgerWalletApplet.scratch256[i] != 0) {
				isZero = false;
				break;
			}
		}
		if (isZero) {
			return false;
		}
		Util.arrayCopyNonAtomic(LedgerWalletApplet.scratch256, (short)(OFFSET_TMP + 32), LedgerWalletApplet.scratch256, (short)32, (short)32);		
		return true;
	}

}