@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(); }
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"; }
/** @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 *********"); }