Package com.bitsofproof.supernode.wallet

Source Code of com.bitsofproof.supernode.wallet.KeyFormatter

/*
* Copyright 2013 bits of proof zrt.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.bitsofproof.supernode.wallet;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.crypto.generators.SCrypt;

import com.bitsofproof.supernode.api.Address;
import com.bitsofproof.supernode.api.Network;
import com.bitsofproof.supernode.common.ByteUtils;
import com.bitsofproof.supernode.common.ECKeyPair;
import com.bitsofproof.supernode.common.Hash;
import com.bitsofproof.supernode.common.Key;
import com.bitsofproof.supernode.common.ValidationException;

/**
* Key serializer following BIP38 https://en.bitcoin.it/wiki/BIP_0038 and WIF https://en.bitcoin.it/wiki/Wallet_import_format
*/
public class KeyFormatter
{
  private final Network network;
  private final String passphrase;

  public KeyFormatter (String passphrase, Network network)
  {
    this.passphrase = passphrase;
    this.network = network;
  }

  public boolean hasPassPhrase ()
  {
    return passphrase != null;
  }

  public Network getNetwork ()
  {
    return network;
  }

  public String serializeKey (Key key) throws ValidationException
  {
    if ( passphrase == null )
    {
      return ECKeyPair.serializeWIF (key);
    }
    return serializeBIP38 (key);
  }

  public ECKeyPair parseSerializedKey (String serialized) throws ValidationException
  {
    byte[] store = ByteUtils.fromBase58 (serialized);
    return parseBytesKey (store);
  }

  private ECKeyPair parseBytesKey (byte[] store) throws ValidationException
  {
    if ( (store[0] & 0xff) == 0x80 )
    {
      return ECKeyPair.parseBytesWIF (store);
    }
    else if ( (store[0] & 0xff) == 0x01 )
    {
      if ( passphrase == null )
      {
        throw new ValidationException ("Need passphrase");
      }
      return parseBIP38 (store);
    }

    throw new ValidationException ("invalid key");
  }

  private String serializeBIP38 (Key key) throws ValidationException
  {
    return ByteUtils.toBase58 (bytesBIP38 (key));
  }

  public String createBIP38Request (int lot, int sequence) throws ValidationException
  {
    byte[] result = new byte[49];

    SecureRandom random = new SecureRandom ();
    byte[] ownersalt = null;
    byte[] ownentropy = new byte[8];
    if ( lot != 0 )
    {
      ownersalt = new byte[4];
      random.nextBytes (ownersalt);
      byte[] ls = BigInteger.valueOf (lot << 12 + sequence).toByteArray ();
      System.arraycopy (ownersalt, 0, ownentropy, 0, 4);
      System.arraycopy (ls, Math.max (0, ls.length - 4), ownentropy, 4 + Math.max (0, 4 - ls.length), Math.min (4, ls.length));
    }
    else
    {
      ownersalt = new byte[8];
      random.nextBytes (ownersalt);
      ownentropy = ownersalt;
    }
    try
    {
      byte[] prefactor = SCrypt.generate (passphrase.getBytes ("UTF-8"), ownersalt, 16384, 8, 8, 32);
      byte[] passfactor = prefactor;
      if ( lot != 0 )
      {
        byte[] tmp = new byte[32 + 8];
        System.arraycopy (prefactor, 0, tmp, 0, 32);
        System.arraycopy (ownentropy, 0, tmp, 32, 8);
        passfactor = Hash.hash (tmp);
      }
      ECKeyPair kp = new ECKeyPair (passfactor, true);
      byte[] passpoint = kp.getPublic ();
      result[0] = (byte) 0x2C;
      result[1] = (byte) 0xE9;
      result[2] = (byte) 0xB3;
      result[3] = (byte) 0xE1;
      result[4] = (byte) 0xFF;
      result[5] = (byte) 0x39;
      result[6] = (byte) 0xE2;
      if ( lot != 0 )
      {
        result[7] = (byte) 0x53;
      }
      else
      {
        result[7] = (byte) 0x51;
      }
      System.arraycopy (ownentropy, 0, result, 8, 8);
      System.arraycopy (passpoint, 0, result, 16, 33);

    }
    catch ( UnsupportedEncodingException e )
    {
    }
    return ByteUtils.toBase58WithChecksum (result);
  }

  private ECKeyPair parseBIP38 (byte[] store) throws ValidationException
  {
    if ( store.length != 43 )
    {
      throw new ValidationException ("invalid key length for BIP38");
    }
    boolean ec = false;
    boolean compressed = false;
    boolean hasLot = false;
    if ( (store[1] & 0xff) == 0x42 )
    {
      if ( (store[2] & 0xff) == 0xc0 )
      {
        // non-EC-multiplied keys without compression (prefix 6PR)
      }
      else if ( (store[2] & 0xff) == 0xe0 )
      {
        // non-EC-multiplied keys with compression (prefix 6PY)
        compressed = true;
      }
      else
      {
        throw new ValidationException ("invalid key");
      }
    }
    else if ( (store[1] & 0xff) == 0x43 )
    {
      // EC-multiplied keys without compression (prefix 6Pf)
      // EC-multiplied keys with compression (prefix 6Pn)
      ec = true;
      compressed = (store[2] & 0x20) != 0;
      hasLot = (store[2] & 0x04) != 0;
      if ( (store[2] & 0x24) != store[2] )
      {
        throw new ValidationException ("invalid key");
      }
    }
    else
    {
      throw new ValidationException ("invalid key");
    }

    byte[] checksum = new byte[4];
    System.arraycopy (store, store.length - 4, checksum, 0, 4);
    byte[] ekey = new byte[store.length - 4];
    System.arraycopy (store, 0, ekey, 0, store.length - 4);
    byte[] hash = Hash.hash (ekey);
    for ( int i = 0; i < 4; ++i )
    {
      if ( hash[i] != checksum[i] )
      {
        throw new ValidationException ("checksum mismatch");
      }
    }

    if ( ec == false )
    {
      return parseBIP38NoEC (store, compressed);
    }
    else
    {
      return parseBIP38EC (store, compressed, hasLot);
    }
  }

  private ECKeyPair parseBIP38NoEC (byte[] store, boolean compressed) throws ValidationException
  {
    byte[] addressHash = new byte[4];
    System.arraycopy (store, 3, addressHash, 0, 4);
    try
    {
      byte[] derived = SCrypt.generate (passphrase.getBytes ("UTF-8"), addressHash, 16384, 8, 8, 64);
      byte[] key = new byte[32];
      System.arraycopy (derived, 32, key, 0, 32);
      SecretKeySpec keyspec = new SecretKeySpec (key, "AES");
      Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
      cipher.init (Cipher.DECRYPT_MODE, keyspec);
      byte[] decrypted = cipher.doFinal (store, 7, 32);
      for ( int i = 0; i < 32; ++i )
      {
        decrypted[i] ^= derived[i];
      }
      ECKeyPair kp = new ECKeyPair (decrypted, compressed);

      byte[] acs = Hash.hash (new Address (network, kp.getAddress ()).toString ().getBytes ("US-ASCII"));
      byte[] check = new byte[4];
      System.arraycopy (acs, 0, check, 0, 4);
      if ( !Arrays.equals (check, addressHash) )
      {
        throw new ValidationException ("failed to decrpyt");
      }
      return kp;
    }
    catch ( UnsupportedEncodingException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchAlgorithmException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchPaddingException e )
    {
      throw new ValidationException (e);
    }
    catch ( InvalidKeyException e )
    {
      throw new ValidationException (e);
    }
    catch ( IllegalBlockSizeException e )
    {
      throw new ValidationException (e);
    }
    catch ( BadPaddingException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchProviderException e )
    {
      throw new ValidationException (e);
    }
  }

  private ECKeyPair parseBIP38EC (byte[] store, boolean compressed, boolean hasLot) throws ValidationException
  {
    byte[] addressHash = new byte[4];
    System.arraycopy (store, 3, addressHash, 0, 4);

    byte[] ownentropy = new byte[8];
    System.arraycopy (store, 7, ownentropy, 0, 8);

    byte[] ownersalt = ownentropy;
    if ( hasLot )
    {
      ownersalt = new byte[4];
      System.arraycopy (ownentropy, 0, ownersalt, 0, 4);
    }
    try
    {
      byte[] passfactor = SCrypt.generate (passphrase.getBytes ("UTF-8"), ownersalt, 16384, 8, 8, 32);
      if ( hasLot )
      {
        byte[] tmp = new byte[40];
        System.arraycopy (passfactor, 0, tmp, 0, 32);
        System.arraycopy (ownentropy, 0, tmp, 32, 8);
        passfactor = Hash.hash (tmp);
      }
      ECKeyPair kp = new ECKeyPair (passfactor, true);

      byte[] salt = new byte[12];
      System.arraycopy (store, 3, salt, 0, 12);
      byte[] derived = SCrypt.generate (kp.getPublic (), salt, 1024, 1, 1, 64);
      byte[] aeskey = new byte[32];
      System.arraycopy (derived, 32, aeskey, 0, 32);

      SecretKeySpec keyspec = new SecretKeySpec (aeskey, "AES");
      Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
      cipher.init (Cipher.DECRYPT_MODE, keyspec);

      byte[] encrypted = new byte[16];
      System.arraycopy (store, 23, encrypted, 0, 16);
      byte[] decrypted2 = cipher.doFinal (encrypted);
      for ( int i = 0; i < 16; ++i )
      {
        decrypted2[i] ^= derived[i + 16];
      }

      System.arraycopy (store, 15, encrypted, 0, 8);
      System.arraycopy (decrypted2, 0, encrypted, 8, 8);
      byte[] decrypted1 = cipher.doFinal (encrypted);
      for ( int i = 0; i < 16; ++i )
      {
        decrypted1[i] ^= derived[i];
      }

      byte[] seed = new byte[24];
      System.arraycopy (decrypted1, 0, seed, 0, 16);
      System.arraycopy (decrypted2, 8, seed, 16, 8);
      BigInteger priv =
          new BigInteger (1, passfactor).multiply (new BigInteger (1, Hash.hash (seed))).remainder (SECNamedCurves.getByName ("secp256k1").getN ());

      kp = new ECKeyPair (priv, compressed);
      byte[] acs = Hash.hash (new Address (network, kp.getAddress ()).toString ().getBytes ("US-ASCII"));
      byte[] check = new byte[4];
      System.arraycopy (acs, 0, check, 0, 4);
      if ( !Arrays.equals (check, addressHash) )
      {
        throw new ValidationException ("failed to decrpyt");
      }
      return kp;
    }
    catch ( UnsupportedEncodingException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchAlgorithmException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchProviderException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchPaddingException e )
    {
      throw new ValidationException (e);
    }
    catch ( InvalidKeyException e )
    {
      throw new ValidationException (e);
    }
    catch ( IllegalBlockSizeException e )
    {
      throw new ValidationException (e);
    }
    catch ( BadPaddingException e )
    {
      throw new ValidationException (e);
    }
  }

  private byte[] bytesBIP38 (Key key) throws ValidationException
  {
    if ( passphrase == null )
    {
      throw new ValidationException ("Must have passphrase to encrypt keys");
    }
    byte[] store = new byte[43];
    store[0] = 0x01;
    store[1] = 0x42;
    store[2] = key.isCompressed () ? (byte) 0xe0 : (byte) 0xc0;
    byte[] addressHash = new byte[4];
    byte[] aesKey = new byte[32];
    byte[] xor = new byte[32];
    try
    {
      byte[] ac = Hash.hash (new Address (network, key.getAddress ()).toString ().getBytes ("US-ASCII"));
      System.arraycopy (ac, 0, addressHash, 0, 4);
      System.arraycopy (ac, 0, store, 3, 4);
      byte[] derived = SCrypt.generate (passphrase.getBytes ("UTF-8"), addressHash, 16384, 8, 8, 64);
      System.arraycopy (derived, 32, aesKey, 0, 32);
      System.arraycopy (derived, 0, xor, 0, 32);
    }
    catch ( UnsupportedEncodingException e )
    {
    }
    SecretKeySpec keyspec = new SecretKeySpec (aesKey, "AES");
    try
    {
      byte[] priv = key.getPrivate ();
      for ( int i = 0; i < 32; ++i )
      {
        priv[i] ^= xor[i];
      }
      Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
      cipher.init (Cipher.ENCRYPT_MODE, keyspec);
      byte[] encrypted = cipher.doFinal (priv);
      System.arraycopy (encrypted, 0, store, 7, encrypted.length);
      byte[] cs = Hash.hash (store, 0, 39);
      System.arraycopy (cs, 0, store, 39, 4);
    }
    catch ( NoSuchAlgorithmException e )
    {
    }
    catch ( NoSuchProviderException e )
    {
    }
    catch ( NoSuchPaddingException e )
    {
    }
    catch ( InvalidKeyException e )
    {
    }
    catch ( IllegalBlockSizeException e )
    {
    }
    catch ( BadPaddingException e )
    {
    }
    return store;
  }
}
TOP

Related Classes of com.bitsofproof.supernode.wallet.KeyFormatter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.