/** * Deletes an Address object from the application's SQLite database<br> * <br> * <b>NOTE:</b> This method uses the given Address's ID field to determine which record in the * database to delete * * @param a - The Address object to be deleted */ public void deleteAddress(Address a) { long id = a.getId(); // Query the database via the ContentProvider and delete the record with the matching ID int recordsDeleted = mContentResolver.delete( DatabaseContentProvider.CONTENT_URI_ADDRESSES, AddressesTable.COLUMN_ID + " = ? ", new String[] {String.valueOf(id)}); if (recordsDeleted > 0) { Log.i(TAG, "Address " + a.getAddress() + " deleted from database"); } else { Log.e( TAG, "Unable to find the address specified for deletion. The address specified was " + a.getAddress()); } }
/** * Returns an ArrayList containing all the Addresses stored in the application's database * * @return An ArrayList containing one Address object for each record in the Addresses table. */ public ArrayList<Address> getAllAddresses() { ArrayList<Address> addresses = new ArrayList<Address>(); // Specify which columns from the table we are interested in String[] projection = { AddressesTable.COLUMN_ID, AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID, AddressesTable.COLUMN_LABEL, AddressesTable.COLUMN_ADDRESS, AddressesTable.COLUMN_PRIVATE_SIGNING_KEY, AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY, AddressesTable.COLUMN_RIPE_HASH, AddressesTable.COLUMN_TAG }; // Query the database via the ContentProvider Cursor cursor = mContentResolver.query( DatabaseContentProvider.CONTENT_URI_ADDRESSES, projection, null, null, null); if (cursor.moveToFirst()) { do { long id = cursor.getLong(0); long correspondingPubkeyId = cursor.getLong(1); String label = cursor.getString(2); String address = cursor.getString(3); String privateSigningKey = cursor.getString(4); String privateEncryptionKey = cursor.getString(5); byte[] ripeHash = Base64.decode(cursor.getString(6), Base64.DEFAULT); byte[] tag = Base64.decode(cursor.getString(7), Base64.DEFAULT); Address a = new Address(); a.setId(id); a.setCorrespondingPubkeyId(correspondingPubkeyId); a.setLabel(label); a.setAddress(address); a.setPrivateSigningKey(privateSigningKey); a.setPrivateEncryptionKey(privateEncryptionKey); a.setRipeHash(ripeHash); a.setTag(tag); addresses.add(a); } while (cursor.moveToNext()); } cursor.close(); return addresses; }
/** * Takes an Address object and adds it to the app's SQLite database as a new record, returning the * ID of the newly created record. * * @param a - The Address object to be added * @return id - A long value representing the ID of the newly created record */ public long addAddress(Address a) { ContentValues values = new ContentValues(); values.put(AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID, a.getCorrespondingPubkeyId()); values.put(AddressesTable.COLUMN_LABEL, a.getLabel()); values.put(AddressesTable.COLUMN_ADDRESS, a.getAddress()); values.put(AddressesTable.COLUMN_PRIVATE_SIGNING_KEY, a.getPrivateSigningKey()); values.put(AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY, a.getPrivateEncryptionKey()); values.put( AddressesTable.COLUMN_RIPE_HASH, Base64.encodeToString(a.getRipeHash(), Base64.DEFAULT)); values.put(AddressesTable.COLUMN_TAG, Base64.encodeToString(a.getTag(), Base64.DEFAULT)); Uri insertionUri = mContentResolver.insert(DatabaseContentProvider.CONTENT_URI_ADDRESSES, values); Log.i(TAG, "Address with address " + a.getAddress() + " saved to database"); // Parse the ID of the newly created record from the insertion Uri String uriString = insertionUri.toString(); String idString = uriString.substring(uriString.indexOf("/") + 1); long id = Long.parseLong(idString); return id; }
/** * Updates the database record for a given Address object<br> * <br> * <b>NOTE:</b> This method uses the given Address's ID field to determine which record in the * database to update * * @param a - The Address object to be updated */ public void updateAddress(Address a) { ContentValues values = new ContentValues(); values.put(AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID, a.getCorrespondingPubkeyId()); values.put(AddressesTable.COLUMN_LABEL, a.getLabel()); values.put(AddressesTable.COLUMN_ADDRESS, a.getAddress()); values.put(AddressesTable.COLUMN_PRIVATE_SIGNING_KEY, a.getPrivateSigningKey()); values.put(AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY, a.getPrivateEncryptionKey()); values.put( AddressesTable.COLUMN_RIPE_HASH, Base64.encodeToString(a.getRipeHash(), Base64.DEFAULT)); values.put(AddressesTable.COLUMN_TAG, Base64.encodeToString(a.getTag(), Base64.DEFAULT)); long id = a.getId(); // Query the database via the ContentProvider and update the record with the matching ID mContentResolver.update( DatabaseContentProvider.CONTENT_URI_ADDRESSES, values, AddressesTable.COLUMN_ID + " = ? ", new String[] {String.valueOf(id)}); Log.i(TAG, "Address ID " + id + " updated"); }
/** * Constructs an UnencryptedMsg object from a given Message object. Used when sending a message. * <br> * <br> * <b>NOTE!</b> Calling this method results in proof of work calculations being done for the * acknowledgement data of the message. This can take a long time and lots of CPU power!<br> * <br> * <b>NOTE!</b> Calling this method can result in requests to a Bitseal server to retrieve pubkey * data. These requests may take some time to complete! * * @param message - The Message object to convert into an UnencryptedMsg object * @param toPubkey - A Pubkey object containing the public keys of the address the message is * being sent to * @param doPOW - A boolean indicating whether or not POW should be done for msgs generated during * this process * @param timeToLive - The 'time to live' value (in seconds) to be used in processing this message * @return An UnencryptedMsg object based on the supplied Message object. */ private UnencryptedMsg constructUnencryptedMsg( Message message, Pubkey toPubkey, boolean doPOW, long timeToLive) { String messageSubject = message.getSubject(); String messageBody = message.getBody(); // First let us check that the to address and from address Strings taken from the Message object // are in fact valid Bitmessage addresses String toAddressString = message.getToAddress(); String fromAddressString = message.getFromAddress(); AddressProcessor addProc = new AddressProcessor(); if (addProc.validateAddress(toAddressString) != true) { throw new RuntimeException( "During the execution of constructUnencryptedMsg(), it was found that the 'to' address in the supplied Message was not a valid Bitmessage address"); } if (addProc.validateAddress(fromAddressString) != true) { throw new RuntimeException( "During the execution of constructUnencryptedMsg(), it was found that the 'from' address in the supplied Message was not a valid Bitmessage address"); } // Now that we have validated the to address and the from address, let us retrieve or create // their corresponding Address and Pubkey objects. Address fromAddress = null; AddressProvider addProv = AddressProvider.get(App.getContext()); ArrayList<Address> retrievedAddresses = addProv.searchAddresses(AddressesTable.COLUMN_ADDRESS, fromAddressString); if (retrievedAddresses.size() != 1) { Log.e( TAG, "There should be exactly 1 record found in this search. Instead " + retrievedAddresses.size() + " records were found"); } else { fromAddress = retrievedAddresses.get(0); } // Now we need to get the behaviour bitfield from the pubkey which corresponds to the from // address, so let us retrieve that pubkey. PubkeyProvider pubProv = PubkeyProvider.get(App.getContext()); ArrayList<Pubkey> retrievedPubkeys = pubProv.searchPubkeys( PubkeysTable.COLUMN_CORRESPONDING_ADDRESS_ID, String.valueOf(fromAddress.getId())); Pubkey fromPubkey = null; if (retrievedPubkeys.size() == 1) { fromPubkey = retrievedPubkeys.get(0); } else if (retrievedPubkeys.size() > 1) // If there are duplicate pubkeys for this address { Log.e( TAG, "There should be exactly 1 record found in this search. Instead " + retrievedPubkeys.size() + " records were found"); // Delete all but the most recent of the duplicate pubkeys long firstPubkeyTime = retrievedPubkeys.get(0).getExpirationTime(); Pubkey pubkeyToKeep = retrievedPubkeys.get(0); for (Pubkey p : retrievedPubkeys) { if (p.getExpirationTime() > firstPubkeyTime) { pubkeyToKeep = p; } } for (Pubkey p : retrievedPubkeys) { if (p.equals(pubkeyToKeep) == false) { pubProv.deletePubkey(p); } } // Use the most recent of the duplicate pubkeys fromPubkey = pubkeyToKeep; } if (fromPubkey == null) { Log.e( TAG, "Could not find the Pubkey which corresponds to the from address, even though it should be one of our own. Something is wrong!"); Log.d(TAG, "Regenerating the Pubkey for the from address"); fromPubkey = new PubkeyGenerator() .generateAndSaveNewPubkey( fromAddress); // If we can't find the pubkey we need then let us generate it again } // Now extract the public signing and public encryption keys from the "from" pubkey // If the public signing and encryption keys taken from the Pubkey object have an "\x04" byte at // their beginning, we need to remove it now. byte[] publicSigningKey = fromPubkey.getPublicSigningKey(); byte[] publicEncryptionKey = fromPubkey.getPublicEncryptionKey(); if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) { publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length); } if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) { publicEncryptionKey = ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length); } // Generate the ack data (32 random bytes) byte[] ackData = new byte[32]; new SecureRandom().nextBytes(ackData); // Generate the full ack Message that will be included in this unencrypted msg. // NOTE: Calling generateFullAckMessage() results in Proof of Work calculations being done for // the // acknowledgement Message. This can take a long time and lots of CPU power! byte[] fullAckMessage = generateFullAckMessage(message, ackData, fromPubkey.getStreamNumber(), doPOW, timeToLive); Log.d(TAG, "Full ack Message: " + ByteFormatter.byteArrayToHexString(fullAckMessage)); // Create the single "message" text String which contains both the subject and the body of the // message // See https://bitmessage.org/wiki/Protocol_specification#Message_Encodings String messsageText = "Subject:" + messageSubject + "\n" + "Body:" + messageBody; // Now create the UnencryptedMsg object and populate its fields. UnencryptedMsg unencMsg = new UnencryptedMsg(); unencMsg.setBelongsToMe(true); unencMsg.setExpirationTime(TimeUtils.getFuzzedExpirationTime(timeToLive)); unencMsg.setObjectType(OBJECT_TYPE_MSG); unencMsg.setObjectVersion(OBJECT_VERSION_MSG); unencMsg.setStreamNumber(toPubkey.getStreamNumber()); unencMsg.setSenderAddressVersion(fromPubkey.getObjectVersion()); unencMsg.setSenderStreamNumber(fromPubkey.getStreamNumber()); unencMsg.setBehaviourBitfield(fromPubkey.getBehaviourBitfield()); unencMsg.setPublicSigningKey(publicSigningKey); unencMsg.setPublicEncryptionKey(publicEncryptionKey); unencMsg.setNonceTrialsPerByte(fromPubkey.getNonceTrialsPerByte()); unencMsg.setExtraBytes(fromPubkey.getExtraBytes()); unencMsg.setDestinationRipe(new KeyConverter().calculateRipeHashFromPubkey(toPubkey)); unencMsg.setEncoding(MESSAGE_ENCODING_TYPE); unencMsg.setMessageLength( messsageText.getBytes() .length); // We have to use the byte length rather than the string length - some // characters take more bytes than others unencMsg.setMessage( messsageText .getBytes()); // PyBitmessage also uses UTF-8 as its character set, so this ought to be // adequate unencMsg.setAckLength(fullAckMessage.length); unencMsg.setAckMsg(fullAckMessage); // Save the acknowledgment data to the database so that when we receive the acknowledgment for // this message we will recognise it Payload ackPayload = new Payload(); ackPayload.setBelongsToMe(true); // i.e. This is an acknowledgment created by me ackPayload.setPOWDone(true); ackPayload.setAck(true); // This payload is an acknowledgment ackPayload.setType( Payload.OBJECT_TYPE_MSG); // Currently we treat all acks from other people as msgs. Strictly // though they can be objects of any type, so this may change ackPayload.setPayload(ackData); PayloadProvider payProv = PayloadProvider.get(App.getContext()); long ackPayloadId = payProv.addPayload(ackPayload); // Set the "ackPayloadId" field of the original Message object so that we know which Message // this ack data is for message.setAckPayloadId(ackPayloadId); MessageProvider msgProv = MessageProvider.get(App.getContext()); msgProv.updateMessage(message); // Now create the signature for this message SigProcessor sigProc = new SigProcessor(); byte[] signaturePayload = sigProc.createUnencryptedMsgSignaturePayload(unencMsg); byte[] signature = sigProc.signWithWIFKey(signaturePayload, fromAddress.getPrivateSigningKey()); unencMsg.setSignature(signature); unencMsg.setSignatureLength(signature.length); return unencMsg; }
/** * Finds all Addresses in the application's database that match the given field * * @param columnName - A String specifying the name of the column in the database that should be * used to find matching records. See the AddressesTable class to find the relevant column * name. * @param searchString - A String specifying the value to search for. There are 4 use cases for * this:<br> * 1) The value to search for is a String (e.g. A label from the UI). In this case the value * can be passed in directly.<br> * 2) The value to search for is an int or long. In this case you should use String.valueOf(x) * and pass in the resulting String.<br> * 3) The value to search for is a boolean. In this case you should pass in the String "0" for * false or the String "1" for true. <br> * 4) The value to search for is a byte[]. In this case you should encode the byte[] into a * Base64 encoded String using the class android.util.Base64 and pass in the resulting String. * <br> * <br> * <b>NOTE:</b> The above String conversion is very clumsy, but seems to be necessary. See * https://stackoverflow.com/questions/20911760/android-how-to-query-sqlitedatabase-with-non-string-selection-args * @return An ArrayList containing Address objects populated with the data from the database * search */ public ArrayList<Address> searchAddresses(String columnName, String searchString) { ArrayList<Address> matchingRecords = new ArrayList<Address>(); // Specify which columns from the table we are interested in String[] projection = { AddressesTable.COLUMN_ID, AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID, AddressesTable.COLUMN_LABEL, AddressesTable.COLUMN_ADDRESS, AddressesTable.COLUMN_PRIVATE_SIGNING_KEY, AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY, AddressesTable.COLUMN_RIPE_HASH, AddressesTable.COLUMN_TAG }; // Query the database via the ContentProvider Cursor cursor = mContentResolver.query( DatabaseContentProvider.CONTENT_URI_ADDRESSES, projection, AddressesTable.TABLE_ADDRESSES + "." + columnName + " = ? ", new String[] {searchString}, null); if (cursor.moveToFirst()) { do { long id = cursor.getLong(0); long correspondingPubkeyId = cursor.getLong(1); String label = cursor.getString(2); String address = cursor.getString(3); String privateSigningKey = cursor.getString(4); String privateEncryptionKey = cursor.getString(5); byte[] ripeHash = Base64.decode(cursor.getString(6), Base64.DEFAULT); byte[] tag = Base64.decode(cursor.getString(7), Base64.DEFAULT); Address a = new Address(); a.setId(id); a.setCorrespondingPubkeyId(correspondingPubkeyId); a.setLabel(label); a.setAddress(address); a.setPrivateSigningKey(privateSigningKey); a.setPrivateEncryptionKey(privateEncryptionKey); a.setRipeHash(ripeHash); a.setTag(tag); matchingRecords.add(a); } while (cursor.moveToNext()); } else { Log.i( TAG, "Unable to find any Addresses with the value " + searchString + " in the " + columnName + " column"); cursor.close(); return matchingRecords; } cursor.close(); return matchingRecords; }