Example #1
0
 @Override
 public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;
   NetworkParameters other = (NetworkParameters) o;
   return getId().equals(other.getId());
 }
 public BitcoinCrypto(NetworkParameters networkParameters, DeterministicSeed seed)
     throws Exception, NoSuchAlgorithmException {
   this.params = NetworkParameters.fromID(networkParameters.getId());
   this.keyChainGroup = new KeyChainGroup(networkParameters, seed);
   Security.insertProviderAt(new BouncyCastleProvider(), 1);
   crashIfJCEMissing();
   // this.sr = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider());
   this.sr = SecureRandom.getInstance("SHA1PRNG");
   this.keyPG = KeyPairGenerator.getInstance("ECIES", new BouncyCastleProvider());
   // this.kit = initKit(seed);
   // this.wallet = kit.wallet();
 }
Example #3
0
  public static final class Files {
    private static final String FILENAME_NETWORK_SUFFIX =
        NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ? "" : "-testnet";

    /** Filename of the wallet. */
    public static final String WALLET_FILENAME_PROTOBUF =
        "wallet-protobuf" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the automatic key backup (old format, can only be read). */
    public static final String WALLET_KEY_BACKUP_BASE58 =
        "key-backup-base58" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the automatic wallet backup. */
    public static final String WALLET_KEY_BACKUP_PROTOBUF =
        "key-backup-protobuf" + FILENAME_NETWORK_SUFFIX;

    /** Path to external storage */
    public static final File EXTERNAL_STORAGE_DIR = Environment.getExternalStorageDirectory();

    /** Manual backups go here. */
    public static final File EXTERNAL_WALLET_BACKUP_DIR =
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

    /** Filename of the manual key backup (old format, can only be read). */
    public static final String EXTERNAL_WALLET_KEY_BACKUP =
        "bitcoin-wallet-keys" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the manual wallet backup. */
    public static final String EXTERNAL_WALLET_BACKUP =
        "bitcoin-wallet-backup" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the block store for storing the chain. */
    public static final String BLOCKCHAIN_FILENAME = "blockchain" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the block checkpoints file. */
    public static final String CHECKPOINTS_FILENAME =
        "checkpoints" + FILENAME_NETWORK_SUFFIX + ".txt";
  }
Example #4
0
/** @author Andreas Schildbach */
public final class Constants {
  public static final boolean TEST = R.class.getPackage().getName().contains("_test");

  /** Network this wallet is on (e.g. testnet or mainnet). */
  public static final NetworkParameters NETWORK_PARAMETERS =
      TEST ? TestNet3Params.get() : MainNetParams.get();

  public static final class Files {
    private static final String FILENAME_NETWORK_SUFFIX =
        NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ? "" : "-testnet";

    /** Filename of the wallet. */
    public static final String WALLET_FILENAME_PROTOBUF =
        "wallet-protobuf" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the automatic key backup (old format, can only be read). */
    public static final String WALLET_KEY_BACKUP_BASE58 =
        "key-backup-base58" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the automatic wallet backup. */
    public static final String WALLET_KEY_BACKUP_PROTOBUF =
        "key-backup-protobuf" + FILENAME_NETWORK_SUFFIX;

    /** Path to external storage */
    public static final File EXTERNAL_STORAGE_DIR = Environment.getExternalStorageDirectory();

    /** Manual backups go here. */
    public static final File EXTERNAL_WALLET_BACKUP_DIR =
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

    /** Filename of the manual key backup (old format, can only be read). */
    public static final String EXTERNAL_WALLET_KEY_BACKUP =
        "bitcoin-wallet-keys" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the manual wallet backup. */
    public static final String EXTERNAL_WALLET_BACKUP =
        "bitcoin-wallet-backup" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the block store for storing the chain. */
    public static final String BLOCKCHAIN_FILENAME = "blockchain" + FILENAME_NETWORK_SUFFIX;

    /** Filename of the block checkpoints file. */
    public static final String CHECKPOINTS_FILENAME =
        "checkpoints" + FILENAME_NETWORK_SUFFIX + ".txt";
  }

  /** Maximum size of backups. Files larger will be rejected. */
  public static final long BACKUP_MAX_CHARS = 10000000;

  private static final String BITEASY_API_URL_PROD = "https://api.biteasy.com/blockchain/v1/";
  private static final String BITEASY_API_URL_TEST = "https://api.biteasy.com/testnet/v1/";
  /** Base URL for blockchain API. */
  public static final String BITEASY_API_URL =
      NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET)
          ? BITEASY_API_URL_PROD
          : BITEASY_API_URL_TEST;

  /** URL to fetch version alerts from. */
  public static final String VERSION_URL = "https://wallet.schildbach.de/version";

  /** MIME type used for transmitting single transactions. */
  public static final String MIMETYPE_TRANSACTION = "application/x-btctx";

  /** MIME type used for transmitting wallet backups. */
  public static final String MIMETYPE_WALLET_BACKUP = "application/x-bitcoin-wallet-backup";

  /** Number of confirmations until a transaction is fully confirmed. */
  public static final int MAX_NUM_CONFIRMATIONS = 7;

  /** User-agent to use for network access. */
  public static final String USER_AGENT = "Bitcoin Wallet";

  /** Default currency to use if all default mechanisms fail. */
  public static final String DEFAULT_EXCHANGE_CURRENCY = "USD";

  /** Donation address for tip/donate action. */
  public static final String DONATION_ADDRESS = "18CK5k1gajRKKSC7yVSTXT9LUzbheh1XY4";

  /** Recipient e-mail address for reports. */
  public static final String REPORT_EMAIL = "*****@*****.**";

  /** Subject line for manually reported issues. */
  public static final String REPORT_SUBJECT_ISSUE = "Reported issue";

  /** Subject line for crash reports. */
  public static final String REPORT_SUBJECT_CRASH = "Crash report";

  public static final char CHAR_HAIR_SPACE = '\u200a';
  public static final char CHAR_THIN_SPACE = '\u2009';
  public static final char CHAR_ALMOST_EQUAL_TO = '\u2248';
  public static final char CHAR_CHECKMARK = '\u2713';
  public static final char CURRENCY_PLUS_SIGN = '\uff0b';
  public static final char CURRENCY_MINUS_SIGN = '\uff0d';
  public static final String PREFIX_ALMOST_EQUAL_TO =
      Character.toString(CHAR_ALMOST_EQUAL_TO) + CHAR_THIN_SPACE;
  public static final int ADDRESS_FORMAT_GROUP_SIZE = 4;
  public static final int ADDRESS_FORMAT_LINE_SIZE = 12;

  public static final MonetaryFormat LOCAL_FORMAT =
      new MonetaryFormat().noCode().minDecimals(2).optionalDecimals();

  public static final BaseEncoding HEX = BaseEncoding.base16().lowerCase();

  public static final String SOURCE_URL = "https://github.com/schildbach/bitcoin-wallet";
  public static final String BINARY_URL = "https://github.com/schildbach/bitcoin-wallet/releases";
  public static final String MARKET_APP_URL = "market://details?id=%s";
  public static final String WEBMARKET_APP_URL = "https://play.google.com/store/apps/details?id=%s";

  public static final int HTTP_TIMEOUT_MS = 15 * (int) DateUtils.SECOND_IN_MILLIS;
  public static final int PEER_TIMEOUT_MS = 15 * (int) DateUtils.SECOND_IN_MILLIS;

  public static final long LAST_USAGE_THRESHOLD_JUST_MS = DateUtils.HOUR_IN_MILLIS;
  public static final long LAST_USAGE_THRESHOLD_RECENTLY_MS = 2 * DateUtils.DAY_IN_MILLIS;

  public static final int SDK_JELLY_BEAN = 16;
  public static final int SDK_JELLY_BEAN_MR2 = 18;
  public static final int SDK_LOLLIPOP = 21;

  public static final int SDK_DEPRECATED_BELOW = Build.VERSION_CODES.ICE_CREAM_SANDWICH;

  public static final boolean BUG_OPENSSL_HEARTBLEED =
      Build.VERSION.SDK_INT == Constants.SDK_JELLY_BEAN
          && Build.VERSION.RELEASE.startsWith("4.1.1");

  public static final int MEMORY_CLASS_LOWEND = 48;
}
  /**
   * Loads wallet data from the given protocol buffer and inserts it into the given Wallet object.
   * This is primarily useful when you wish to pre-register extension objects. Note that if loading
   * fails the provided Wallet object may be in an indeterminate state and should be thrown away.
   *
   * <p>A wallet can be unreadable for various reasons, such as inability to open the file, corrupt
   * data, internally inconsistent data, a wallet extension marked as mandatory that cannot be
   * handled and so on. You should always handle {@link UnreadableWalletException} and communicate
   * failure to the user in an appropriate manner.
   *
   * @throws UnreadableWalletException thrown in various error conditions (see description).
   */
  public Wallet readWallet(
      NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto)
      throws UnreadableWalletException {
    if (walletProto.getVersion() > 1) throw new UnreadableWalletException.FutureVersion();
    if (!walletProto.getNetworkIdentifier().equals(params.getId()))
      throw new UnreadableWalletException.WrongNetwork();

    int sigsRequiredToSpend = walletProto.getSigsRequiredToSpend();

    // Read the scrypt parameters that specify how encryption and decryption is performed.
    KeyChainGroup chain;
    if (walletProto.hasEncryptionParameters()) {
      Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
      final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters);
      chain =
          KeyChainGroup.fromProtobufEncrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter);
    } else {
      chain =
          KeyChainGroup.fromProtobufUnencrypted(
              params, walletProto.getKeyList(), sigsRequiredToSpend);
    }
    Wallet wallet = factory.create(params, chain);

    List<Script> scripts = Lists.newArrayList();
    for (Protos.Script protoScript : walletProto.getWatchedScriptList()) {
      try {
        Script script =
            new Script(
                protoScript.getProgram().toByteArray(), protoScript.getCreationTimestamp() / 1000);
        scripts.add(script);
      } catch (ScriptException e) {
        throw new UnreadableWalletException("Unparseable script in wallet");
      }
    }

    wallet.addWatchedScripts(scripts);

    if (walletProto.hasDescription()) {
      wallet.setDescription(walletProto.getDescription());
    }

    // Read all transactions and insert into the txMap.
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      readTransaction(txProto, wallet.getParams());
    }

    // Update transaction outputs to point to inputs that spend them
    for (Protos.Transaction txProto : walletProto.getTransactionList()) {
      WalletTransaction wtx = connectTransactionOutputs(txProto);
      wallet.addWalletTransaction(wtx);
    }

    // Update the lastBlockSeenHash.
    if (!walletProto.hasLastSeenBlockHash()) {
      wallet.setLastBlockSeenHash(null);
    } else {
      wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash()));
    }
    if (!walletProto.hasLastSeenBlockHeight()) {
      wallet.setLastBlockSeenHeight(-1);
    } else {
      wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
    }
    // Will default to zero if not present.
    wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs());

    if (walletProto.hasKeyRotationTime()) {
      wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000));
    }

    loadExtensions(wallet, extensions != null ? extensions : new WalletExtension[0], walletProto);

    for (Protos.Tag tag : walletProto.getTagsList()) {
      wallet.setTag(tag.getTag(), tag.getData());
    }

    for (Protos.TransactionSigner signerProto : walletProto.getTransactionSignersList()) {
      try {
        Class signerClass = Class.forName(signerProto.getClassName());
        TransactionSigner signer = (TransactionSigner) signerClass.newInstance();
        signer.deserialize(signerProto.getData().toByteArray());
        wallet.addTransactionSigner(signer);
      } catch (Exception e) {
        throw new UnreadableWalletException(
            "Unable to deserialize TransactionSigner instance: " + signerProto.getClassName(), e);
      }
    }

    if (walletProto.hasVersion()) {
      wallet.setVersion(walletProto.getVersion());
    }

    // Make sure the object can be re-used to read another wallet without corruption.
    txMap.clear();

    return wallet;
  }
  private void doMigrateFrom_v1_0_262(Context context, NetworkParameters migrationParams)
      throws IOException {
    Log.d(TAG, "********* MIGRATION FROM v1.0.262 - " + migrationParams.getId() + "*********");

    final File rootDir = context.getFilesDir();
    final File storageDir;
    final File archiveDir = new File(rootDir, "archive_" + System.currentTimeMillis());
    final File walletFile;
    final File chainFile;

    final File newWalletFile;
    final File newChainFile;

    if (ClientUtils.isMainNet(migrationParams)) {
      String walletFilesPrefix = AppConfig.MainNetConfig.get().getWalletFilesPrefix();
      storageDir = new File(rootDir, "mainnet_wallet__uuid_object_storage");
      walletFile = new File(rootDir, "mainnet_wallet_.wallet");
      newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
      chainFile = new File(rootDir, "mainnet_wallet_.spvchain");
      newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
    } else if (ClientUtils.isTestNet(migrationParams)) {
      String walletFilesPrefix = AppConfig.TestNetConfig.get().getWalletFilesPrefix();
      storageDir = new File(rootDir, "testnet_wallet__uuid_object_storage");
      walletFile = new File(rootDir, "testnet_wallet_.wallet");
      newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
      chainFile = new File(rootDir, "testnet_wallet_.spvchain");
      newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
    } else {
      throw new RuntimeException(
          "Network params not supported (unknown): " + migrationParams.toString());
    }

    final File keyFile = new File(storageDir, "ECKeyWrapper.json");

    if (keyFile.exists() && walletFile.exists()) {

      // Keys: stored in ECKeyWrapper.json
      /* Key format (JSON):
        {
          "...uuid1...": {
              "isPublicOnly": true,
              "keyPayload": [...bytes (integers)...],
              "name": "remote_server_public_key",
              "uuid": "...uuid1..."
          },
          "...uuid2...": {
              "isPublicOnly": false,
              "keyPayload": [...bytes (integers)...],
              "name": "remote_client_public_key",
              "uuid": "...uuid2..."
          }
        }
      */

      Log.d(TAG, "Key file found: " + keyFile);
      String keyFileJson = FileUtils.readFileToString(keyFile);
      Type type = new TypeToken<Map<String, ECKeyWrapper>>() {}.getType();
      // Note: do not use gson from serializeutils (key is not stored in base64).
      Map<String, ECKeyWrapper> keys = new Gson().fromJson(keyFileJson, type);
      ECKey serverKey = null;
      ECKey clientKey = null;
      for (ECKeyWrapper key : keys.values()) {
        if (key.isPublicOnly && key.name.equals("remote_server_public_key")) {
          serverKey = ECKey.fromPublicOnly(key.keyPayload);
        } else if (!key.isPublicOnly && key.name.equals("remote_client_public_key")) {
          clientKey = ECKey.fromPrivate(key.keyPayload);
        } else {
          Log.d(TAG, "Unknown key name: " + key.name);
        }
      }

      if (clientKey != null && serverKey != null) {
        Log.d(TAG, "Found client and server keys - store in shared preferences.");
        try {

          /** ******** Actual Migration Code ********* */
          SharedPrefUtils.setClientKey(context, migrationParams, clientKey);
          SharedPrefUtils.setServerKey(context, migrationParams, serverKey, "n/a - migration");
          Log.d(
              TAG,
              "Migrated keys:"
                  + " clientPubKey="
                  + clientKey.getPublicKeyAsHex()
                  + ", serverPubKey="
                  + serverKey.getPublicKeyAsHex());

          // move wallet file
          Log.d(
              TAG,
              "Migrate wallet file: " + walletFile.toString() + " -> " + newWalletFile.toString());
          FileUtils.copyFile(walletFile, newWalletFile);
          Log.d(
              TAG,
              "Migrate chain file: " + chainFile.toString() + " -> " + newChainFile.toString());
          FileUtils.copyFile(chainFile, newChainFile);

          SharedPrefUtils.enableMultisig2of2ToCltvForwarder(context);

          // move everything to an archive file.
          Log.d(TAG, "Move old files to archive dir: " + archiveDir.toString());
          FileUtils.moveToDirectory(storageDir, archiveDir, true);
          FileUtils.moveToDirectory(walletFile, archiveDir, true);
          FileUtils.moveToDirectory(chainFile, archiveDir, true);

        } catch (Exception e) {
          Log.d(TAG, "Exception: ", e);
          // clear the changes made.
          SharedPrefUtils.setClientKey(context, migrationParams, null);
          SharedPrefUtils.setServerKey(context, migrationParams, null, "");
          if (newWalletFile.exists()) {
            newWalletFile.delete();
          }
        }
      }
    } else {
      Log.d(
          TAG,
          "Key file or wallet file not found - no migration required - "
              + String.format(
                  "keyFile: %s (exists: %s), walletFile: %s (exists: %s)",
                  keyFile.toString(),
                  keyFile.exists(),
                  walletFile.toString(),
                  walletFile.exists()));
    }

    Log.d(TAG, "********* MIGRATION FROM v1.0.262 FINISHED *********");
  }