Package com.bitsofproof.supernode.common

Source Code of com.bitsofproof.supernode.common.ExtendedKey

/*
* 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.common;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;

/**
* Key Generator following BIP32 https://en.bitcoin.it/wiki/BIP_0032
*/
public class ExtendedKey
{
  private static final SecureRandom rnd = new SecureRandom ();
  private static final X9ECParameters curve = SECNamedCurves.getByName ("secp256k1");

  private final Key master;
  private final byte[] chainCode;
  private final int depth;
  private final int parent;
  private final int sequence;

  private static final byte[] BITCOIN_SEED = "Bitcoin seed".getBytes ();

  public static ExtendedKey createFromPassphrase (String passphrase, byte[] encrypted) throws ValidationException
  {
    try
    {
      byte[] key = SCrypt.generate (passphrase.getBytes ("UTF-8"), BITCOIN_SEED, 16384, 8, 8, 32);
      SecretKeySpec keyspec = new SecretKeySpec (key, "AES");
      if ( encrypted.length == 32 )
      {
        // asssume encrypted is seed
        Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
        cipher.init (Cipher.DECRYPT_MODE, keyspec);
        return create (cipher.doFinal (encrypted));
      }
      else
      {
        // assume encrypted serialization of a key
        Cipher cipher = Cipher.getInstance ("AES/CBC/PKCS5Padding", "BC");
        byte[] iv = Arrays.copyOfRange (encrypted, 0, 16);
        byte[] data = Arrays.copyOfRange (encrypted, 16, encrypted.length);
        cipher.init (Cipher.DECRYPT_MODE, keyspec, new IvParameterSpec (iv));
        return ExtendedKey.parse (new String (cipher.doFinal (data)));
      }
    }
    catch ( UnsupportedEncodingException e )
    {
      throw new ValidationException (e);
    }
    catch ( IllegalBlockSizeException e )
    {
      throw new ValidationException (e);
    }
    catch ( BadPaddingException e )
    {
      throw new ValidationException (e);
    }
    catch ( InvalidKeyException 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 ( InvalidAlgorithmParameterException e )
    {
      throw new ValidationException (e);
    }
  }

  public byte[] encrypt (String passphrase, boolean production) throws ValidationException
  {
    try
    {
      byte[] key = SCrypt.generate (passphrase.getBytes ("UTF-8"), BITCOIN_SEED, 16384, 8, 8, 32);
      SecretKeySpec keyspec = new SecretKeySpec (key, "AES");
      Cipher cipher = Cipher.getInstance ("AES/CBC/PKCS5Padding", "BC");
      cipher.init (Cipher.ENCRYPT_MODE, keyspec);
      byte[] iv = cipher.getIV ();
      byte[] c = cipher.doFinal (serialize (production).getBytes ());
      byte[] result = new byte[iv.length + c.length];
      System.arraycopy (iv, 0, result, 0, iv.length);
      System.arraycopy (c, 0, result, iv.length, c.length);
      return result;
    }
    catch ( UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException
        | IllegalBlockSizeException | BadPaddingException e )
    {
      throw new ValidationException (e);
    }
  }

  public static ExtendedKey create (byte[] seed) throws ValidationException
  {
    try
    {
      Mac mac = Mac.getInstance ("HmacSHA512", "BC");
      SecretKey seedkey = new SecretKeySpec (BITCOIN_SEED, "HmacSHA512");
      mac.init (seedkey);
      byte[] lr = mac.doFinal (seed);
      byte[] l = Arrays.copyOfRange (lr, 0, 32);
      byte[] r = Arrays.copyOfRange (lr, 32, 64);
      BigInteger m = new BigInteger (1, l);
      if ( m.compareTo (curve.getN ()) >= 0 )
      {
        throw new ValidationException ("This is rather unlikely, but it did just happen");
      }
      ECKeyPair keyPair = new ECKeyPair (l, true);
      return new ExtendedKey (keyPair, r, 0, 0, 0);
    }
    catch ( NoSuchAlgorithmException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchProviderException e )
    {
      throw new ValidationException (e);
    }
    catch ( InvalidKeyException e )
    {
      throw new ValidationException (e);
    }
  }

  public static ExtendedKey createNew ()
  {
    Key key = ECKeyPair.createNew (true);
    byte[] chainCode = new byte[32];
    rnd.nextBytes (chainCode);
    return new ExtendedKey (key, chainCode, 0, 0, 0);
  }

  public ExtendedKey (Key key, byte[] chainCode, int depth, int parent, int sequence)
  {
    this.master = key;
    this.chainCode = chainCode;
    this.parent = parent;
    this.depth = depth;
    this.sequence = sequence;
  }

  public Key getMaster ()
  {
    return master;
  }

  public byte[] getChainCode ()
  {
    return Arrays.clone (chainCode);
  }

  public int getDepth ()
  {
    return depth;
  }

  public int getParent ()
  {
    return parent;
  }

  public int getSequence ()
  {
    return sequence;
  }

  public int getFingerPrint ()
  {
    int fingerprint = 0;
    byte[] address = master.getAddress ().toByteArray ();
    for ( int i = 0; i < 4; ++i )
    {
      fingerprint <<= 8;
      fingerprint |= address[i] & 0xff;
    }
    return fingerprint;
  }

  public Key getKey (int sequence) throws ValidationException
  {
    return generateKey (sequence).getMaster ();
  }

  public ExtendedKey getChild (int sequence) throws ValidationException
  {
    ExtendedKey sub = generateKey (sequence);
    return new ExtendedKey (sub.getMaster (), sub.getChainCode (), sub.getDepth () + 1, getFingerPrint (), sequence);
  }

  public ExtendedKey getReadOnly ()
  {
    return new ExtendedKey (new ECPublicKey (master.getPublic (), true), chainCode, depth, parent, sequence);
  }

  public boolean isReadOnly ()
  {
    return master.getPrivate () == null;
  }

  private ExtendedKey generateKey (int sequence) throws ValidationException
  {
    try
    {
      if ( (sequence & 0x80000000) != 0 && master.getPrivate () == null )
      {
        throw new ValidationException ("need private key for private generation");
      }
      Mac mac = Mac.getInstance ("HmacSHA512", "BC");
      SecretKey key = new SecretKeySpec (chainCode, "HmacSHA512");
      mac.init (key);

      byte[] extended;
      byte[] pub = master.getPublic ();
      if ( (sequence & 0x80000000) == 0 )
      {
        extended = new byte[pub.length + 4];
        System.arraycopy (pub, 0, extended, 0, pub.length);
        extended[pub.length] = (byte) ((sequence >>> 24) & 0xff);
        extended[pub.length + 1] = (byte) ((sequence >>> 16) & 0xff);
        extended[pub.length + 2] = (byte) ((sequence >>> 8) & 0xff);
        extended[pub.length + 3] = (byte) (sequence & 0xff);
      }
      else
      {
        byte[] priv = master.getPrivate ();
        extended = new byte[priv.length + 5];
        System.arraycopy (priv, 0, extended, 1, priv.length);
        extended[priv.length + 1] = (byte) ((sequence >>> 24) & 0xff);
        extended[priv.length + 2] = (byte) ((sequence >>> 16) & 0xff);
        extended[priv.length + 3] = (byte) ((sequence >>> 8) & 0xff);
        extended[priv.length + 4] = (byte) (sequence & 0xff);
      }
      byte[] lr = mac.doFinal (extended);
      byte[] l = Arrays.copyOfRange (lr, 0, 32);
      byte[] r = Arrays.copyOfRange (lr, 32, 64);

      BigInteger m = new BigInteger (1, l);
      if ( m.compareTo (curve.getN ()) >= 0 )
      {
        throw new ValidationException ("This is rather unlikely, but it did just happen");
      }
      if ( master.getPrivate () != null )
      {
        BigInteger k = m.add (new BigInteger (1, master.getPrivate ())).mod (curve.getN ());
        if ( k.equals (BigInteger.ZERO) )
        {
          throw new ValidationException ("This is rather unlikely, but it did just happen");
        }
        return new ExtendedKey (new ECKeyPair (k, true), r, depth, parent, sequence);
      }
      else
      {
        ECPoint q = curve.getG ().multiply (m).add (curve.getCurve ().decodePoint (pub));
        if ( q.isInfinity () )
        {
          throw new ValidationException ("This is rather unlikely, but it did just happen");
        }
        pub = new ECPoint.Fp (curve.getCurve (), q.getX (), q.getY (), true).getEncoded ();
        return new ExtendedKey (new ECPublicKey (pub, true), r, depth, parent, sequence);
      }
    }
    catch ( NoSuchAlgorithmException e )
    {
      throw new ValidationException (e);
    }
    catch ( NoSuchProviderException e )
    {
      throw new ValidationException (e);
    }
    catch ( InvalidKeyException e )
    {
      throw new ValidationException (e);
    }
  }

  private static final byte[] xprv = new byte[] { 0x04, (byte) 0x88, (byte) 0xAD, (byte) 0xE4 };
  private static final byte[] xpub = new byte[] { 0x04, (byte) 0x88, (byte) 0xB2, (byte) 0x1E };
  private static final byte[] tprv = new byte[] { 0x04, (byte) 0x35, (byte) 0x83, (byte) 0x94 };
  private static final byte[] tpub = new byte[] { 0x04, (byte) 0x35, (byte) 0x87, (byte) 0xCF };

  public String serialize (boolean production)
  {
    ByteArrayOutputStream out = new ByteArrayOutputStream ();
    try
    {
      if ( master.getPrivate () != null )
      {
        if ( production )
        {
          out.write (xprv);
        }
        else
        {
          out.write (tprv);
        }
      }
      else
      {
        if ( production )
        {
          out.write (xpub);
        }
        else
        {
          out.write (tpub);
        }
      }
      out.write (depth & 0xff);
      out.write ((parent >>> 24) & 0xff);
      out.write ((parent >>> 16) & 0xff);
      out.write ((parent >>> 8) & 0xff);
      out.write (parent & 0xff);
      out.write ((sequence >>> 24) & 0xff);
      out.write ((sequence >>> 16) & 0xff);
      out.write ((sequence >>> 8) & 0xff);
      out.write (sequence & 0xff);
      out.write (chainCode);
      if ( master.getPrivate () != null )
      {
        out.write (0x00);
        out.write (master.getPrivate ());
      }
      else
      {
        out.write (master.getPublic ());
      }
    }
    catch ( IOException e )
    {
    }
    return ByteUtils.toBase58WithChecksum (out.toByteArray ());
  }

  public static ExtendedKey parse (String serialized) throws ValidationException
  {
    byte[] data = ByteUtils.fromBase58WithChecksum (serialized);
    if ( data.length != 78 )
    {
      throw new ValidationException ("invalid extended key");
    }
    byte[] type = Arrays.copyOf (data, 4);
    boolean hasPrivate;
    if ( Arrays.areEqual (type, xprv) || Arrays.areEqual (type, tprv) )
    {
      hasPrivate = true;
    }
    else if ( Arrays.areEqual (type, xpub) || Arrays.areEqual (type, tpub) )
    {
      hasPrivate = false;
    }
    else
    {
      throw new ValidationException ("invalid magic number for an extended key");
    }

    int depth = data[4] & 0xff;

    int parent = data[5] & 0xff;
    parent <<= 8;
    parent |= data[6] & 0xff;
    parent <<= 8;
    parent |= data[7] & 0xff;
    parent <<= 8;
    parent |= data[8] & 0xff;

    int sequence = data[9] & 0xff;
    sequence <<= 8;
    sequence |= data[10] & 0xff;
    sequence <<= 8;
    sequence |= data[11] & 0xff;
    sequence <<= 8;
    sequence |= data[12] & 0xff;

    byte[] chainCode = Arrays.copyOfRange (data, 13, 13 + 32);
    byte[] pubOrPriv = Arrays.copyOfRange (data, 13 + 32, data.length);
    Key key;
    if ( hasPrivate )
    {
      key = new ECKeyPair (new BigInteger (1, pubOrPriv), true);
    }
    else
    {
      key = new ECPublicKey (pubOrPriv, true);
    }
    return new ExtendedKey (key, chainCode, depth, parent, sequence);
  }
}
TOP

Related Classes of com.bitsofproof.supernode.common.ExtendedKey

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.