Package com.bitsofproof.supernode.wallet

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

/*
* 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.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

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.SecretKeySpec;

import org.bouncycastle.crypto.generators.SCrypt;

import com.bitsofproof.supernode.common.ByteUtils;
import com.bitsofproof.supernode.common.ExtendedKey;
import com.bitsofproof.supernode.common.Hash;
import com.bitsofproof.supernode.common.ValidationException;

/**
* BIP of serialized or encrypted HD key root discussion: https://bitcointalk.org/index.php?topic=258678.0
*/
public class EncryptedHDRoot
{
  public static enum ScryptDifficulty
  {
    LOW, MEDIUM, HIGH
  }

  private static final byte[] clear16 = { 0x0b, 0x2d, 0x7b };
  private static final byte[] clear32 = { 0x14, (byte) 0x82, 0x17 };
  private static final byte[] clear64 = { 0x01, 0x30, (byte) 0xb7 };
  private static final byte[] encrypted16 = { 0x14, (byte) 0xd6, 0x0d };
  private static final byte[] encrypted16l = encrypted16;
  private static final byte[] encrypted16m = { 0x14, (byte) 0xd6, 0x0e };
  private static final byte[] encrypted16h = { 0x14, (byte) 0xd6, 0x0f };
  private static final byte[] encrypted32 = { 0x26, 0x3a, (byte) 0xa2 };
  private static final byte[] encrypted32l = encrypted32;
  private static final byte[] encrypted32m = { 0x26, 0x3a, (byte) 0xa3 };
  private static final byte[] encrypted32h = { 0x26, 0x3a, (byte) 0xa4 };
  private static final byte[] encrypted64 = { 0x02, 0x38, 0x04 };
  private static final byte[] encrypted64l = encrypted64;
  private static final byte[] encrypted64m = { 0x02, 0x38, 0x05 };
  private static final byte[] encrypted64h = { 0x02, 0x38, 0x06 };

  public static Date decodeBirthDate (String ws) throws ValidationException
  {
    byte[] raw = ByteUtils.fromBase58WithChecksum (ws);
    int weeks = raw[3] + raw[4] << 8;
    Calendar c = new GregorianCalendar (2013, Calendar.JANUARY, 1);
    c.add (Calendar.DAY_OF_YEAR, weeks * 7);
    return c.getTime ();
  }

  public static ExtendedKey decode (String ws) throws ValidationException
  {
    byte[] raw = ByteUtils.fromBase58WithChecksum (ws);
    byte[] magic = Arrays.copyOf (raw, 3);
    byte[] seed;
    if ( Arrays.equals (magic, clear16) )
    {
      seed = Arrays.copyOfRange (raw, 9, 16 + 9);
    }
    else if ( Arrays.equals (magic, clear32) )
    {
      seed = Arrays.copyOfRange (raw, 9, 32 + 9);
    }
    else if ( Arrays.equals (magic, clear64) )
    {
      seed = Arrays.copyOfRange (raw, 9, 64 + 9);
    }
    else
    {
      throw new ValidationException ("Not an encoded HD root");
    }
    ExtendedKey key = ExtendedKey.create (seed);
    if ( !Arrays.equals (Arrays.copyOf (Hash.hash (key.getMaster ().getPrivate ()), 4), Arrays.copyOfRange (raw, 5, 9)) )
    {
      throw new ValidationException ("HD root checksum error");
    }
    return key;
  }

  public static ExtendedKey decrypt (String ws, String passphrase) throws ValidationException
  {
    byte[] raw = ByteUtils.fromBase58WithChecksum (ws);
    byte[] magic = Arrays.copyOf (raw, 3);
    byte[] encryptedSeed;
    int N = 1 << 14;
    int r = 16;
    int p = 16;
    if ( Arrays.equals (magic, encrypted16l) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 16 + 9);
      r = p = 8;
    }
    else if ( Arrays.equals (magic, encrypted16m) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 16 + 9);
      N = 1 << 16;
    }
    else if ( Arrays.equals (magic, encrypted16h) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 16 + 9);
      N = 1 << 18;
    }
    else if ( Arrays.equals (magic, encrypted32l) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 32 + 9);
      r = p = 8;
    }
    else if ( Arrays.equals (magic, encrypted32m) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 32 + 9);
      N = 1 << 16;
    }
    else if ( Arrays.equals (magic, encrypted32h) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 32 + 9);
      N = 1 << 18;
    }
    else if ( Arrays.equals (magic, encrypted64l) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 64 + 9);
      r = p = 8;
    }
    else if ( Arrays.equals (magic, encrypted64m) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 64 + 9);
      N = 1 << 16;
    }
    else if ( Arrays.equals (magic, encrypted64h) )
    {
      encryptedSeed = Arrays.copyOfRange (raw, 9, 64 + 9);
      N = 1 << 18;
    }
    else
    {
      throw new ValidationException ("Not an encoded HD root");
    }
    byte salt[] = Arrays.copyOf (raw, 9);

    Mac mac;
    try
    {
      mac = Mac.getInstance ("HmacSHA512", "BC");
      SecretKey seedkey = new SecretKeySpec (salt, "HmacSHA512");
      mac.init (seedkey);
      byte[] preH = mac.doFinal (passphrase.getBytes ("UTF-8"));
      byte[] strongH = SCrypt.generate (preH, preH, N, r, p, 64);
      seedkey = new SecretKeySpec (passphrase.getBytes ("UTF-8"), "HmacSHA512");
      mac.init (seedkey);
      byte[] postH = mac.doFinal (salt);
      byte[] H = SCrypt.generate (postH, strongH, 1 << 10, 1, 1, encryptedSeed.length + 32);
      byte[] X = Arrays.copyOf (H, encryptedSeed.length);
      SecretKeySpec keyspec = new SecretKeySpec (Arrays.copyOfRange (H, encryptedSeed.length, encryptedSeed.length + 32), "AES");
      Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
      cipher.init (Cipher.DECRYPT_MODE, keyspec);
      byte[] seed = cipher.doFinal (encryptedSeed);
      for ( int i = 0; i < encryptedSeed.length; ++i )
      {
        seed[i] ^= X[i];
      }
      ExtendedKey key = ExtendedKey.create (seed);
      if ( !Arrays.equals (Arrays.copyOf (Hash.hash (key.getMaster ().getPrivate ()), 4), Arrays.copyOfRange (raw, 5, 9)) )
      {
        throw new ValidationException ("HD root checksum error");
      }
      return key;
    }
    catch ( NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | UnsupportedEncodingException | NoSuchPaddingException
        | IllegalBlockSizeException | BadPaddingException e )
    {
      throw new ValidationException (e);
    }
  }

  public static String encode (byte[] seed, Date birth) throws ValidationException
  {
    if ( seed == null || (seed.length != 16 && seed.length != 32 && seed.length != 64) )
    {
      throw new ValidationException ("Seed must be 16, 32 or 64 bytes");
    }
    int weeks =
        (int) ((birth.getTime () - new GregorianCalendar (2013, Calendar.JANUARY, 1).getTime ().getTime ()) / (7 * 24 * 60 * 60 * 1000L));

    ExtendedKey key = ExtendedKey.create (seed);
    byte raw[];
    if ( seed.length == 16 )
    {
      raw = new byte[25];
      System.arraycopy (clear16, 0, raw, 0, 3);
    }
    else if ( seed.length == 32 )
    {
      raw = new byte[41];
      System.arraycopy (clear32, 0, raw, 0, 3);
    }
    else
    {
      raw = new byte[73];
      System.arraycopy (clear64, 0, raw, 0, 3);
    }
    raw[3] = (byte) (weeks & 0xff);
    raw[4] = (byte) ((weeks >>> 8) & 0xff);
    System.arraycopy (Hash.hash (key.getMaster ().getPrivate ()), 0, raw, 5, 4);
    System.arraycopy (seed, 0, raw, 9, seed.length);
    return ByteUtils.toBase58WithChecksum (raw);
  }

  public static String encrypt (byte[] seed, Date birth, String passphrase, ScryptDifficulty scryptDifficulty) throws ValidationException
  {
    if ( seed == null || (seed.length != 16 && seed.length != 32 && seed.length != 64) )
    {
      throw new ValidationException ("Seed must be 16, 32 or 64 bytes");
    }
    int weeks =
        (int) ((birth.getTime () - new GregorianCalendar (2013, Calendar.JANUARY, 1).getTime ().getTime ()) / (7 * 24 * 60 * 60 * 1000L));

    ExtendedKey key = ExtendedKey.create (seed);
    byte raw[];
    byte[] salt = new byte[9];
    if ( seed.length == 16 )
    {
      raw = new byte[25];
      System.arraycopy (encrypted16, 0, salt, 0, 3);
    }
    else if ( seed.length == 32 )
    {
      raw = new byte[41];
      System.arraycopy (encrypted32, 0, salt, 0, 3);
    }
    else
    {
      raw = new byte[73];
      System.arraycopy (encrypted64, 0, salt, 0, 3);
    }
    salt[2] += scryptDifficulty.ordinal ();
    salt[3] = (byte) (weeks & 0xff);
    salt[4] = (byte) ((weeks >>> 8) & 0xff);
    System.arraycopy (Hash.hash (key.getMaster ().getPrivate ()), 0, salt, 5, 4);
    System.arraycopy (salt, 0, raw, 0, 9);
    int N = (1 << 14) << (scryptDifficulty.ordinal () * 2);
    int r = scryptDifficulty == ScryptDifficulty.LOW ? 8 : 16;
    int p = scryptDifficulty == ScryptDifficulty.LOW ? 8 : 16;
    Mac mac;
    try
    {
      mac = Mac.getInstance ("HmacSHA512", "BC");
      SecretKey seedkey = new SecretKeySpec (salt, "HmacSHA512");
      mac.init (seedkey);
      byte[] preH = mac.doFinal (passphrase.getBytes ("UTF-8"));
      byte[] strongH = SCrypt.generate (preH, preH, N, r, p, 64);
      seedkey = new SecretKeySpec (passphrase.getBytes ("UTF-8"), "HmacSHA512");
      mac.init (seedkey);
      byte[] postH = mac.doFinal (salt);
      byte[] H = SCrypt.generate (postH, strongH, 1 << 10, 1, 1, seed.length + 32);
      byte[] X = Arrays.copyOf (H, seed.length);
      for ( int i = 0; i < seed.length; ++i )
      {
        X[i] ^= seed[i];
      }
      SecretKeySpec keyspec = new SecretKeySpec (Arrays.copyOfRange (H, seed.length, seed.length + 32), "AES");
      Cipher cipher = Cipher.getInstance ("AES/ECB/NoPadding", "BC");
      cipher.init (Cipher.ENCRYPT_MODE, keyspec);
      System.arraycopy (cipher.doFinal (X), 0, raw, 9, seed.length);
    }
    catch ( NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | UnsupportedEncodingException | NoSuchPaddingException
        | IllegalBlockSizeException | BadPaddingException e )
    {
      throw new ValidationException (e);
    }
    return ByteUtils.toBase58WithChecksum (raw);
  }
}
TOP

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

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.