private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException {
    ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair();
    int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);
    byte[] bobSignature =
        Curve.calculateSignature(
            bobStore.getIdentityKeyPair().getPrivateKey(),
            bobSignedPreKey.getPublicKey().serialize());

    PreKeyBundle bobPreKeyBundle =
        new PreKeyBundle(
            1,
            1,
            bobUnsignedPreKeyId,
            bobUnsignedPreKey.getPublicKey(),
            bobSignedPreKeyId,
            bobSignedPreKey.getPublicKey(),
            bobSignature,
            bobStore.getIdentityKeyPair().getPublicKey());

    bobStore.storeSignedPreKey(
        bobSignedPreKeyId,
        new SignedPreKeyRecord(
            bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature));
    bobStore.storePreKey(
        bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey));

    return bobPreKeyBundle;
  }
  public byte[] encryptBytes(byte[] body) {
    try {
      ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey();
      ECKeyPair ourKeyPair = Curve.generateKeyPair();
      byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
      MasterCipher masterCipher = getMasterCipherForSecret(secret);
      byte[] encryptedBodyBytes = masterCipher.encryptBytes(body);

      PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey());
      byte[] publicKeyBytes = ourPublicKey.serialize();

      return Util.combine(publicKeyBytes, encryptedBodyBytes);
    } catch (InvalidKeyException e) {
      throw new AssertionError(e);
    }
  }
Пример #3
0
  public static AsymmetricMasterSecret getAsymmetricMasterSecret(
      @NonNull Context context, @Nullable MasterSecret masterSecret) {
    try {
      byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
      byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);

      ECPublicKey djbPublicKey = null;
      ECPrivateKey djbPrivateKey = null;

      if (djbPublicBytes != null) {
        djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
      }

      if (masterSecret != null) {
        MasterCipher masterCipher = new MasterCipher(masterSecret);

        if (djbPrivateBytes != null) {
          djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
        }
      }

      return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
    } catch (InvalidKeyException | IOException ike) {
      throw new AssertionError(ike);
    }
  }
Пример #4
0
 public ECPrivateKey decryptKey(byte[] key)
     throws org.whispersystems.libaxolotl.InvalidKeyException {
   try {
     return Curve.decodePrivatePoint(decryptBytes(key));
   } catch (InvalidMessageException ime) {
     throw new org.whispersystems.libaxolotl.InvalidKeyException(ime);
   }
 }
Пример #5
0
  public static AsymmetricMasterSecret generateAsymmetricMasterSecret(
      Context context, MasterSecret masterSecret) {
    MasterCipher masterCipher = new MasterCipher(masterSecret);
    ECKeyPair keyPair = Curve.generateKeyPair();

    save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
    save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));

    return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
  }
Пример #6
0
  public static SignedPreKeyRecord generateSignedPreKey(
      Context context, MasterSecret masterSecret, IdentityKeyPair identityKeyPair) {
    try {
      SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
      int signedPreKeyId = getNextSignedPreKeyId(context);
      ECKeyPair keyPair = Curve.generateKeyPair();
      byte[] signature =
          Curve.calculateSignature(
              identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
      SignedPreKeyRecord record =
          new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);

      signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
      setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);

      return record;
    } catch (InvalidKeyException e) {
      throw new AssertionError(e);
    }
  }
  private void initializeSessionsV2(SessionState aliceSessionState, SessionState bobSessionState)
      throws InvalidKeyException {
    ECKeyPair aliceIdentityKeyPair = Curve.generateKeyPair();
    IdentityKeyPair aliceIdentityKey =
        new IdentityKeyPair(
            new IdentityKey(aliceIdentityKeyPair.getPublicKey()),
            aliceIdentityKeyPair.getPrivateKey());
    ECKeyPair aliceBaseKey = Curve.generateKeyPair();
    ECKeyPair aliceEphemeralKey = Curve.generateKeyPair();

    ECKeyPair bobIdentityKeyPair = Curve.generateKeyPair();
    IdentityKeyPair bobIdentityKey =
        new IdentityKeyPair(
            new IdentityKey(bobIdentityKeyPair.getPublicKey()), bobIdentityKeyPair.getPrivateKey());
    ECKeyPair bobBaseKey = Curve.generateKeyPair();
    ECKeyPair bobEphemeralKey = bobBaseKey;

    AliceAxolotlParameters aliceParameters =
        AliceAxolotlParameters.newBuilder()
            .setOurIdentityKey(aliceIdentityKey)
            .setOurBaseKey(aliceBaseKey)
            .setTheirIdentityKey(bobIdentityKey.getPublicKey())
            .setTheirSignedPreKey(bobEphemeralKey.getPublicKey())
            .setTheirRatchetKey(bobEphemeralKey.getPublicKey())
            .setTheirOneTimePreKey(Optional.<ECPublicKey>absent())
            .create();

    BobAxolotlParameters bobParameters =
        BobAxolotlParameters.newBuilder()
            .setOurIdentityKey(bobIdentityKey)
            .setOurOneTimePreKey(Optional.<ECKeyPair>absent())
            .setOurRatchetKey(bobEphemeralKey)
            .setOurSignedPreKey(bobBaseKey)
            .setTheirBaseKey(aliceBaseKey.getPublicKey())
            .setTheirIdentityKey(aliceIdentityKey.getPublicKey())
            .create();

    RatchetingSession.initializeSession(aliceSessionState, 2, aliceParameters);
    RatchetingSession.initializeSession(bobSessionState, 2, bobParameters);
  }
  public byte[] decryptBytes(byte[] combined) throws IOException, InvalidMessageException {
    try {
      byte[][] parts =
          Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE);
      PublicKey theirPublicKey = new PublicKey(parts[0], 0);

      ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey();
      byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
      MasterCipher masterCipher = getMasterCipherForSecret(secret);

      return masterCipher.decryptBytes(parts[1]);
    } catch (InvalidKeyException e) {
      throw new InvalidMessageException(e);
    }
  }
Пример #9
0
  public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
    PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
    List<PreKeyRecord> records = new LinkedList<>();
    int preKeyIdOffset = getNextPreKeyId(context);

    for (int i = 0; i < BATCH_SIZE; i++) {
      int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
      ECKeyPair keyPair = Curve.generateKeyPair();
      PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);

      preKeyStore.storePreKey(preKeyId, record);
      records.add(record);
    }

    setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
    return records;
  }
Пример #10
0
  public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) {
    PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);

    if (preKeyStore.containsPreKey(Medium.MAX_VALUE)) {
      try {
        return preKeyStore.loadPreKey(Medium.MAX_VALUE);
      } catch (InvalidKeyIdException e) {
        Log.w("PreKeyUtil", e);
        preKeyStore.removePreKey(Medium.MAX_VALUE);
      }
    }

    ECKeyPair keyPair = Curve.generateKeyPair();
    PreKeyRecord record = new PreKeyRecord(Medium.MAX_VALUE, keyPair);

    preKeyStore.storePreKey(Medium.MAX_VALUE, record);

    return record;
  }
public class SimultaneousInitiateTests extends TestCase {

  private static final AxolotlAddress BOB_ADDRESS = new AxolotlAddress("+14151231234", 1);
  private static final AxolotlAddress ALICE_ADDRESS = new AxolotlAddress("+14159998888", 1);

  private static final ECKeyPair aliceSignedPreKey = Curve.generateKeyPair();
  private static final ECKeyPair bobSignedPreKey = Curve.generateKeyPair();

  private static final int aliceSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);
  private static final int bobSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);

  public void testBasicSimultaneousInitiate()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    aliceSessionBuilder.process(bobPreKeyBundle);
    bobSessionBuilder.process(alicePreKeyBundle);

    CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

    assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
    assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    byte[] alicePlaintext =
        aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
    byte[] bobPlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

    assertTrue(new String(alicePlaintext).equals("sample message"));
    assertTrue(new String(bobPlaintext).equals("hey there"));

    assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
    assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  public void testLostSimultaneousInitiate()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    aliceSessionBuilder.process(bobPreKeyBundle);
    bobSessionBuilder.process(alicePreKeyBundle);

    CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

    assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
    assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    byte[] bobPlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

    assertTrue(new String(bobPlaintext).equals("hey there"));
    assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.PREKEY_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  public void testSimultaneousInitiateLostMessage()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    aliceSessionBuilder.process(bobPreKeyBundle);
    bobSessionBuilder.process(alicePreKeyBundle);

    CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

    assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
    assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    byte[] alicePlaintext =
        aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
    byte[] bobPlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

    assertTrue(new String(alicePlaintext).equals("sample message"));
    assertTrue(new String(bobPlaintext).equals("hey there"));

    assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
    assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    //    byte[] responsePlaintext = bobSessionCipher.decrypt(new
    // WhisperMessage(aliceResponse.serialize()));
    //
    //    assertTrue(new String(responsePlaintext).equals("second message"));
    //    assertTrue(isSessionIdEqual(aliceStore, bobStore));
    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  public void testSimultaneousInitiateRepeatedMessages()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    aliceSessionBuilder.process(bobPreKeyBundle);
    bobSessionBuilder.process(alicePreKeyBundle);

    CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

    assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
    assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    byte[] alicePlaintext =
        aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
    byte[] bobPlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

    assertTrue(new String(alicePlaintext).equals("sample message"));
    assertTrue(new String(bobPlaintext).equals("hey there"));

    assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
    assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    for (int i = 0; i < 50; i++) {
      CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes());
      CiphertextMessage messageForAliceRepeat =
          bobSessionCipher.encrypt("sample message".getBytes());

      assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE);
      assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));

      byte[] alicePlaintextRepeat =
          aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize()));
      byte[] bobPlaintextRepeat =
          bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize()));

      assertTrue(new String(alicePlaintextRepeat).equals("sample message"));
      assertTrue(new String(bobPlaintextRepeat).equals("hey there"));

      assertFalse(isSessionIdEqual(aliceStore, bobStore));
    }

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  public void testRepeatedSimultaneousInitiateRepeatedMessages()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    for (int i = 0; i < 15; i++) {
      PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
      PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

      aliceSessionBuilder.process(bobPreKeyBundle);
      bobSessionBuilder.process(alicePreKeyBundle);

      CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
      CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

      assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
      assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));

      byte[] alicePlaintext =
          aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
      byte[] bobPlaintext =
          bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

      assertTrue(new String(alicePlaintext).equals("sample message"));
      assertTrue(new String(bobPlaintext).equals("hey there"));

      assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
      assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));
    }

    for (int i = 0; i < 50; i++) {
      CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes());
      CiphertextMessage messageForAliceRepeat =
          bobSessionCipher.encrypt("sample message".getBytes());

      assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE);
      assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));

      byte[] alicePlaintextRepeat =
          aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize()));
      byte[] bobPlaintextRepeat =
          bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize()));

      assertTrue(new String(alicePlaintextRepeat).equals("sample message"));
      assertTrue(new String(bobPlaintextRepeat).equals("hey there"));

      assertFalse(isSessionIdEqual(aliceStore, bobStore));
    }

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    //    PreKeyBundle aliceLostPreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobLostPreKeyBundle = createBobPreKeyBundle(bobStore);

    aliceSessionBuilder.process(bobLostPreKeyBundle);
    //    bobSessionBuilder.process(aliceLostPreKeyBundle);

    CiphertextMessage lostMessageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    //    CiphertextMessage lostMessageForAlice = bobSessionCipher.encrypt("sample
    // message".getBytes());

    for (int i = 0; i < 15; i++) {
      PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
      PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

      aliceSessionBuilder.process(bobPreKeyBundle);
      bobSessionBuilder.process(alicePreKeyBundle);

      CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
      CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

      assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
      assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));

      byte[] alicePlaintext =
          aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
      byte[] bobPlaintext =
          bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

      assertTrue(new String(alicePlaintext).equals("sample message"));
      assertTrue(new String(bobPlaintext).equals("hey there"));

      assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
      assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));
    }

    for (int i = 0; i < 50; i++) {
      CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes());
      CiphertextMessage messageForAliceRepeat =
          bobSessionCipher.encrypt("sample message".getBytes());

      assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE);
      assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE);

      assertFalse(isSessionIdEqual(aliceStore, bobStore));

      byte[] alicePlaintextRepeat =
          aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize()));
      byte[] bobPlaintextRepeat =
          bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize()));

      assertTrue(new String(alicePlaintextRepeat).equals("sample message"));
      assertTrue(new String(bobPlaintextRepeat).equals("hey there"));

      assertFalse(isSessionIdEqual(aliceStore, bobStore));
    }

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    byte[] lostMessagePlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(lostMessageForBob.serialize()));
    assertTrue(new String(lostMessagePlaintext).equals("hey there"));

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage blastFromThePast = bobSessionCipher.encrypt("unexpected!".getBytes());
    byte[] blastFromThePastPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(blastFromThePast.serialize()));

    assertTrue(new String(blastFromThePastPlaintext).equals("unexpected!"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }

  private boolean isSessionIdEqual(AxolotlStore aliceStore, AxolotlStore bobStore) {
    return Arrays.equals(
        aliceStore.loadSession(BOB_ADDRESS).getSessionState().getAliceBaseKey(),
        bobStore.loadSession(ALICE_ADDRESS).getSessionState().getAliceBaseKey());
  }

  private PreKeyBundle createAlicePreKeyBundle(AxolotlStore aliceStore) throws InvalidKeyException {
    ECKeyPair aliceUnsignedPreKey = Curve.generateKeyPair();
    int aliceUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);
    byte[] aliceSignature =
        Curve.calculateSignature(
            aliceStore.getIdentityKeyPair().getPrivateKey(),
            aliceSignedPreKey.getPublicKey().serialize());

    PreKeyBundle alicePreKeyBundle =
        new PreKeyBundle(
            1,
            1,
            aliceUnsignedPreKeyId,
            aliceUnsignedPreKey.getPublicKey(),
            aliceSignedPreKeyId,
            aliceSignedPreKey.getPublicKey(),
            aliceSignature,
            aliceStore.getIdentityKeyPair().getPublicKey());

    aliceStore.storeSignedPreKey(
        aliceSignedPreKeyId,
        new SignedPreKeyRecord(
            aliceSignedPreKeyId, System.currentTimeMillis(), aliceSignedPreKey, aliceSignature));
    aliceStore.storePreKey(
        aliceUnsignedPreKeyId, new PreKeyRecord(aliceUnsignedPreKeyId, aliceUnsignedPreKey));

    return alicePreKeyBundle;
  }

  private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException {
    ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair();
    int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);
    byte[] bobSignature =
        Curve.calculateSignature(
            bobStore.getIdentityKeyPair().getPrivateKey(),
            bobSignedPreKey.getPublicKey().serialize());

    PreKeyBundle bobPreKeyBundle =
        new PreKeyBundle(
            1,
            1,
            bobUnsignedPreKeyId,
            bobUnsignedPreKey.getPublicKey(),
            bobSignedPreKeyId,
            bobSignedPreKey.getPublicKey(),
            bobSignature,
            bobStore.getIdentityKeyPair().getPublicKey());

    bobStore.storeSignedPreKey(
        bobSignedPreKeyId,
        new SignedPreKeyRecord(
            bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature));
    bobStore.storePreKey(
        bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey));

    return bobPreKeyBundle;
  }
}