com.bitsofproof.supernode.api.SerializedWallet.java Source code

Java tutorial

Introduction

Here is the source code for com.bitsofproof.supernode.api.SerializedWallet.java

Source

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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.crypto.generators.SCrypt;

import com.bitsofproof.supernode.api.BloomFilter.UpdateMode;

public class SerializedWallet implements Wallet {
    private String passphrase;
    private String fileName;
    private long timeStamp;

    private BCSAPI api;

    public void setApi(BCSAPI api) {
        this.api = api;
    }

    public long getTimeStamp() {
        return timeStamp;
    }

    @Override
    public AccountManager getAccountManager(String name) throws BCSAPIException {
        try {
            int nextSequence = 0;
            ExtendedKey extended = null;
            for (WalletKey key : getKeys()) {
                if (key.name.equals(name)) {
                    extended = ExtendedKey.parse(key.key);
                    nextSequence = key.nextSequence;
                    break;
                }
            }
            if (extended == null) {
                extended = ExtendedKey.createNew();
            }
            final DefaultAccountManager am = new DefaultAccountManager(name, extended, nextSequence);
            am.setApi(api);
            am.registerFilter();
            for (Transaction t : getTransactions()) {
                am.updateWithTransaction(t);
            }
            api.scanTransactions(am.getAddresses(), UpdateMode.all, getTimeStamp(), new TransactionListener() {
                @Override
                public void process(Transaction t) {
                    if (am.updateWithTransaction(t)) {
                        addTransaction(t);
                    }
                }
            });
            return am;
        } catch (ValidationException e) {
            throw new BCSAPIException(e);
        }
    }

    public static SerializedWallet read(String fileName, String passphrase) throws BCSAPIException {
        try {
            File f = new File(fileName);
            if (f.exists()) {
                long timeStamp = f.lastModified() / 1000;
                FileInputStream in = null;
                try {
                    in = new FileInputStream(f);
                    SerializedWallet wallet = readWallet(in, passphrase);
                    wallet.fileName = fileName;
                    wallet.passphrase = passphrase;
                    wallet.timeStamp = timeStamp;
                    return wallet;
                } finally {
                    in.close();
                }
            } else {
                SerializedWallet wallet = new SerializedWallet();
                wallet.timeStamp = System.currentTimeMillis() / 1000;
                wallet.fileName = fileName;
                wallet.passphrase = passphrase;
                return wallet;
            }
        } catch (FileNotFoundException e) {
            throw new BCSAPIException(e);
        } catch (IOException e) {
            throw new BCSAPIException(e);
        } catch (ValidationException e) {
            throw new BCSAPIException(e);
        }
    }

    @Override
    public void persist() throws BCSAPIException {
        try {
            if (fileName != null) {
                File f = new File(fileName);
                File tmp = File.createTempFile("tmp", ".wallet", f.getParentFile());
                tmp.setReadable(true, true);
                tmp.setWritable(true, true);
                FileOutputStream out = new FileOutputStream(tmp);
                writeWallet(out, passphrase);
                out.close();
                f.delete();
                tmp.renameTo(f);
                timeStamp = f.lastModified();
            }
        } catch (IOException e) {
            throw new BCSAPIException(e);
        } catch (ValidationException e) {
            throw new BCSAPIException(e);
        }
    }

    public static class WalletKey {
        String key;
        String name;
        long created;
        int nextSequence;
    }

    private List<WalletKey> keys = new ArrayList<WalletKey>();
    private final Set<String> hasTransaction = new HashSet<String>();
    private List<Transaction> transactions = new ArrayList<Transaction>();
    private Map<String, String> addresses = new HashMap<String, String>();

    private static final String MAGIC = "bopwallet";

    private static SecureRandom rnd = new SecureRandom();

    public void addKey(WalletKey key) {
        keys.add(key);
    }

    public void addTransaction(Transaction t) {
        if (!hasTransaction.contains(t.getHash())) {
            transactions.add(t);
            hasTransaction.add(t.getHash());
        }
    }

    public void addAddress(String name, String address) {
        addresses.put(name, address);
    }

    public List<WalletKey> getKeys() {
        return keys;
    }

    public List<Transaction> getTransactions() {
        return Collections.unmodifiableList(transactions);
    }

    public Map<String, String> getAddresses() {
        return addresses;
    }

    public void writeWallet(OutputStream output, String passphrase) throws ValidationException {
        try {
            output.write(MAGIC.getBytes("US-ASCII"));
            byte[] iv = new byte[16];
            rnd.nextBytes(iv);
            output.write(iv);
            byte[] derived = SCrypt.generate(passphrase.getBytes("UTF-8"), iv, 16384, 8, 8, 32);
            SecretKeySpec keyspec = new SecretKeySpec(derived, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
            cipher.init(Cipher.ENCRYPT_MODE, keyspec, new IvParameterSpec(iv));
            CipherOutputStream cip = new CipherOutputStream(output, cipher);
            GZIPOutputStream zip = new GZIPOutputStream(cip);
            BCSAPIMessage.Wallet.Builder builder = BCSAPIMessage.Wallet.newBuilder();
            builder.setBcsapiversion(1);
            for (WalletKey key : keys) {
                BCSAPIMessage.Wallet.Key.Builder kb = BCSAPIMessage.Wallet.Key.newBuilder();
                kb.setKey(key.key);
                kb.setNextSequence(key.nextSequence);
                if (key.name != null) {
                    kb.setName(key.name);
                }
                if (key.created != 0) {
                    kb.setCreated(key.created);
                }
                builder.addKeys(kb.build());
            }
            for (Transaction t : transactions) {
                builder.addTransactions(t.toProtobuf());
            }
            for (Map.Entry<String, String> e : addresses.entrySet()) {
                BCSAPIMessage.Wallet.PublicAddress.Builder pa = BCSAPIMessage.Wallet.PublicAddress.newBuilder();
                pa.setName(e.getKey());
                pa.setAddress(e.getValue());
                builder.addPublicAddresses(pa.build());
            }
            builder.build().writeTo(zip);
            zip.finish();
            zip.flush();
            cip.close();
        } catch (UnsupportedEncodingException e) {
        } catch (IOException 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 (InvalidAlgorithmParameterException e) {
            throw new ValidationException(e);
        }
    }

    public static SerializedWallet readWallet(InputStream input, String passphrase) throws ValidationException {
        SerializedWallet wallet = new SerializedWallet();

        try {
            byte[] derived;
            byte[] magic = new byte[MAGIC.getBytes("US-ASCII").length];
            input.read(magic);
            if (!Arrays.equals(magic, MAGIC.getBytes("US-ASCII"))) {
                throw new ValidationException("Not a wallet");
            }
            byte[] iv = new byte[16];
            input.read(iv);
            derived = SCrypt.generate(passphrase.getBytes("UTF-8"), iv, 16384, 8, 8, 32);
            SecretKeySpec keyspec = new SecretKeySpec(derived, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, keyspec, new IvParameterSpec(iv));

            CipherInputStream cis = new CipherInputStream(input, cipher);
            GZIPInputStream unzip = new GZIPInputStream(cis);

            BCSAPIMessage.Wallet walletMessage = BCSAPIMessage.Wallet.parseFrom(unzip);

            wallet.keys = new ArrayList<WalletKey>();
            for (BCSAPIMessage.Wallet.Key k : walletMessage.getKeysList()) {
                WalletKey key = new WalletKey();
                key.key = k.getKey();
                key.nextSequence = k.getNextSequence();
                if (k.hasName()) {
                    key.name = k.getName();
                }
                if (k.hasCreated()) {
                    key.created = k.getCreated();
                }
                wallet.keys.add(key);
            }
            wallet.transactions = new ArrayList<Transaction>();
            for (BCSAPIMessage.Transaction t : walletMessage.getTransactionsList()) {
                Transaction transaction = Transaction.fromProtobuf(t);
                transaction.computeHash();
                wallet.addTransaction(transaction);
            }
            wallet.addresses = new HashMap<String, String>();
            for (BCSAPIMessage.Wallet.PublicAddress p : walletMessage.getPublicAddressesList()) {
                wallet.addresses.put(p.getName(), p.getAddress());
            }
        } catch (UnsupportedEncodingException 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 (IOException e) {
            throw new ValidationException(e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new ValidationException(e);
        }
        return wallet;
    }
}