Java Code Examples for org.whispersystems.libsignal.util.Pair#first()

The following examples show how to use org.whispersystems.libsignal.util.Pair#first() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: RemoteAttestationUtil.java    From mollyim-android with GNU General Public License v3.0 6 votes vote down vote up
public static RemoteAttestation getAndVerifyRemoteAttestation(PushServiceSocket socket,
                                                              PushServiceSocket.ClientSet clientSet,
                                                              KeyStore iasKeyStore,
                                                              String enclaveName,
                                                              String mrenclave,
                                                              String authorization)
  throws IOException, Quote.InvalidQuoteFormatException, InvalidCiphertextException, UnauthenticatedQuoteException, SignatureException
{
  Curve25519                                    curve                   = Curve25519.getInstance(Curve25519.BEST);
  Curve25519KeyPair                             keyPair                 = curve.generateKeyPair();
  RemoteAttestationRequest                      attestationRequest      = new RemoteAttestationRequest(keyPair.getPublicKey());
  Pair<RemoteAttestationResponse, List<String>> attestationResponsePair = getRemoteAttestation(socket, clientSet, authorization, attestationRequest, enclaveName);
  RemoteAttestationResponse                     attestationResponse     = attestationResponsePair.first();
  List<String>                                  attestationCookies      = attestationResponsePair.second();

  RemoteAttestationKeys keys      = new RemoteAttestationKeys(keyPair, attestationResponse.getServerEphemeralPublic(), attestationResponse.getServerStaticPublic());
  Quote                 quote     = new Quote(attestationResponse.getQuote());
  byte[]                requestId = RemoteAttestationCipher.getRequestId(keys, attestationResponse);

  RemoteAttestationCipher.verifyServerQuote(quote, attestationResponse.getServerStaticPublic(), mrenclave);

  RemoteAttestationCipher.verifyIasSignature(iasKeyStore, attestationResponse.getCertificates(), attestationResponse.getSignatureBody(), attestationResponse.getSignature(), quote);

  return new RemoteAttestation(requestId, keys, attestationCookies);
}
 
Example 2
Source File: SessionState.java    From libsignal-protocol-java with GNU General Public License v3.0 6 votes vote down vote up
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
  Pair<Chain,Integer> chainAndIndex       = getReceiverChain(senderEphemeral);
  Chain               chain               = chainAndIndex.first();
  Chain.MessageKey    messageKeyStructure = Chain.MessageKey.newBuilder()
                                                            .setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
                                                            .setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
                                                            .setIndex(messageKeys.getCounter())
                                                            .setIv(ByteString.copyFrom(messageKeys.getIv().getIV()))
                                                            .build();

  Chain.Builder updatedChain = chain.toBuilder().addMessageKeys(messageKeyStructure);

  if (updatedChain.getMessageKeysCount() > MAX_MESSAGE_KEYS) {
    updatedChain.removeMessageKeys(0);
  }

  this.sessionStructure = this.sessionStructure.toBuilder()
                                               .setReceiverChains(chainAndIndex.second(),
                                                                  updatedChain.build())
                                               .build();
}
 
Example 3
Source File: SessionState.java    From libsignal-protocol-java with GNU General Public License v3.0 6 votes vote down vote up
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
  Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
  Chain               chain         = chainAndIndex.first();

  if (chain == null) {
    return false;
  }

  List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();

  for (Chain.MessageKey messageKey : messageKeyList) {
    if (messageKey.getIndex() == counter) {
      return true;
    }
  }

  return false;
}
 
Example 4
Source File: SignalAccount.java    From signal-cli with GNU General Public License v3.0 6 votes vote down vote up
public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException {
    IOUtils.createPrivateDirectories(dataPath);
    String fileName = getFileName(dataPath, username);
    if (!new File(fileName).exists()) {
        IOUtils.createPrivateFile(fileName);
    }

    final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
    SignalAccount account = new SignalAccount(pair.first(), pair.second());

    account.username = username;
    account.uuid = uuid;
    account.password = password;
    account.profileKey = profileKey;
    account.deviceId = deviceId;
    account.signalingKey = signalingKey;
    account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
    account.groupStore = new JsonGroupStore();
    account.contactStore = new JsonContactsStore();
    account.recipientStore = new RecipientStore();
    account.registered = true;
    account.isMultiDevice = true;

    return account;
}
 
Example 5
Source File: SignalAccount.java    From signal-cli with GNU General Public License v3.0 6 votes vote down vote up
public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey) throws IOException {
    IOUtils.createPrivateDirectories(dataPath);
    String fileName = getFileName(dataPath, username);
    if (!new File(fileName).exists()) {
        IOUtils.createPrivateFile(fileName);
    }

    final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
    SignalAccount account = new SignalAccount(pair.first(), pair.second());

    account.username = username;
    account.profileKey = profileKey;
    account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
    account.groupStore = new JsonGroupStore();
    account.contactStore = new JsonContactsStore();
    account.recipientStore = new RecipientStore();
    account.registered = false;

    return account;
}
 
Example 6
Source File: SignalServiceMessagePipe.java    From libsignal-service-java with GNU General Public License v3.0 6 votes vote down vote up
public AttachmentUploadAttributes getAttachmentUploadAttributes() throws IOException {
  try {
    WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
                                                                    .setId(new SecureRandom().nextLong())
                                                                    .setVerb("GET")
                                                                    .setPath("/v2/attachments/form/upload")
                                                                    .build();

    Pair<Integer, String> response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);

    if (response.first() < 200 || response.first() >= 300) {
      throw new IOException("Non-successful response: " + response.first());
    }

    return JsonUtil.fromJson(response.second(), AttachmentUploadAttributes.class);
  } catch (InterruptedException | ExecutionException | TimeoutException e) {
    throw new IOException(e);
  }
}
 
Example 7
Source File: SaveAttachmentTask.java    From mollyim-android with GNU General Public License v3.0 6 votes vote down vote up
@Override
protected void onPostExecute(final Pair<Integer, String> result) {
  super.onPostExecute(result);
  final Context context = contextReference.get();
  if (context == null) return;

  switch (result.first()) {
    case FAILURE:
      Toast.makeText(context,
                     context.getResources().getQuantityText(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card,
                                                            attachmentCount),
                     Toast.LENGTH_LONG).show();
      break;
    case SUCCESS:
      String message = !TextUtils.isEmpty(result.second())  ? context.getResources().getString(R.string.SaveAttachmentTask_saved_to, result.second())
                                                            : context.getResources().getString(R.string.SaveAttachmentTask_saved);
      Toast.makeText(context, message, Toast.LENGTH_LONG).show();
      break;
    case WRITE_ACCESS_FAILURE:
      Toast.makeText(context, R.string.ConversationFragment_unable_to_write_to_sd_card_exclamation,
          Toast.LENGTH_LONG).show();
      break;
  }
}
 
Example 8
Source File: MmsSmsDatabase.java    From mollyim-android with GNU General Public License v3.0 6 votes vote down vote up
/**
 * @return The user that added you to the group, otherwise null.
 */
public @Nullable RecipientId getGroupAddedBy(long threadId) {
  long lastQuitChecked = System.currentTimeMillis();
  Pair<RecipientId, Long> pair;

  do {
    pair = getGroupAddedBy(threadId, lastQuitChecked);
    if (pair.first() != null) {
      return pair.first();
    } else {
      lastQuitChecked = pair.second();
    }

  } while (pair.second() != -1);

  return null;
}
 
Example 9
Source File: ConversationRepository.java    From mollyim-android with GNU General Public License v3.0 6 votes vote down vote up
private @NonNull ConversationData getConversationDataInternal(long threadId, int jumpToPosition) {
  Pair<Long, Boolean> lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId);

  long    lastSeen         = lastSeenAndHasSent.first();
  boolean hasSent          = lastSeenAndHasSent.second();
  int     lastSeenPosition = 0;

  boolean isMessageRequestAccepted     = RecipientUtil.isMessageRequestAccepted(context, threadId);
  boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);

  if (lastSeen > 0) {
    lastSeenPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionForLastSeen(threadId, lastSeen);
  }

  if (lastSeenPosition <= 0) {
    lastSeen = 0;
  }

  return new ConversationData(threadId, lastSeen, lastSeenPosition, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages, jumpToPosition);
}
 
Example 10
Source File: SignalServiceMessagePipe.java    From libsignal-service-java with GNU General Public License v3.0 5 votes vote down vote up
public SignalServiceProfile getProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess) throws IOException {
  try {
    List<String> headers = new LinkedList<>();

    if (unidentifiedAccess.isPresent()) {
      headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
    }

    WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
                                                                    .setId(SecureRandom.getInstance("SHA1PRNG").nextLong())
                                                                    .setVerb("GET")
                                                                    .setPath(String.format("/v1/profile/%s", address.getIdentifier()))
                                                                    .addAllHeaders(headers)
                                                                    .build();

    Pair<Integer, String> response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);

    if (response.first() < 200 || response.first() >= 300) {
      throw new IOException("Non-successful response: " + response.first());
    }

    return JsonUtil.fromJson(response.second(), SignalServiceProfile.class);
  } catch (NoSuchAlgorithmException nsae) {
    throw new AssertionError(nsae);
  } catch (InterruptedException | ExecutionException | TimeoutException e) {
    throw new IOException(e);
  }
}
 
Example 11
Source File: SignalAccount.java    From signal-cli with GNU General Public License v3.0 5 votes vote down vote up
public static SignalAccount load(String dataPath, String username) throws IOException {
    final String fileName = getFileName(dataPath, username);
    final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
    try {
        SignalAccount account = new SignalAccount(pair.first(), pair.second());
        account.load();
        return account;
    } catch (Throwable e) {
        pair.second().close();
        pair.first().close();
        throw e;
    }
}
 
Example 12
Source File: SessionState.java    From libsignal-protocol-java with GNU General Public License v3.0 5 votes vote down vote up
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
  Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
  Chain               receiverChain         = receiverChainAndIndex.first();

  if (receiverChain == null) {
    return null;
  } else {
    return new ChainKey(HKDF.createFor(getSessionVersion()),
                        receiverChain.getChainKey().getKey().toByteArray(),
                        receiverChain.getChainKey().getIndex());
  }
}
 
Example 13
Source File: ContactRepository.java    From mollyim-android with GNU General Public License v3.0 5 votes vote down vote up
SearchCursorWrapper(Cursor cursor, @NonNull List<Pair<String, ValueMapper>> mappers) {
  super(cursor);

  this.wrapped     = cursor;
  this.mappers     = mappers;
  this.positions   = new HashMap<>();
  this.columnNames = new String[mappers.size()];

  for (int i = 0; i < mappers.size(); i++) {
    Pair<String, ValueMapper> pair = mappers.get(i);

    positions.put(pair.first(), i);
    columnNames[i] = pair.first();
  }
}
 
Example 14
Source File: SessionState.java    From libsignal-protocol-java with GNU General Public License v3.0 5 votes vote down vote up
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
  Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
  Chain               chain         = chainAndIndex.first();

  if (chain == null) {
    return null;
  }

  List<Chain.MessageKey>     messageKeyList     = new LinkedList<>(chain.getMessageKeysList());
  Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
  MessageKeys                result             = null;

  while (messageKeyIterator.hasNext()) {
    Chain.MessageKey messageKey = messageKeyIterator.next();

    if (messageKey.getIndex() == counter) {
      result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
                               new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
                               new IvParameterSpec(messageKey.getIv().toByteArray()),
                               messageKey.getIndex());

      messageKeyIterator.remove();
      break;
    }
  }

  Chain updatedChain = chain.toBuilder().clearMessageKeys()
                            .addAllMessageKeys(messageKeyList)
                            .build();

  this.sessionStructure = this.sessionStructure.toBuilder()
                                               .setReceiverChains(chainAndIndex.second(), updatedChain)
                                               .build();

  return result;
}
 
Example 15
Source File: SignalServiceMessageSender.java    From mollyim-android with GNU General Public License v3.0 5 votes vote down vote up
private SignalServiceAttachmentPointer uploadAttachmentV2(SignalServiceAttachmentStream attachment, byte[] attachmentKey, PushAttachmentData attachmentData) throws NonSuccessfulResponseCodeException, PushNetworkException {
  AttachmentV2UploadAttributes       v2UploadAttributes = null;
  Optional<SignalServiceMessagePipe> localPipe          = pipe.get();

  if (localPipe.isPresent()) {
    Log.d(TAG, "Using pipe to retrieve attachment upload attributes...");
    try {
      v2UploadAttributes = localPipe.get().getAttachmentV2UploadAttributes();
    } catch (IOException e) {
      Log.w(TAG, "Failed to retrieve attachment upload attributes using pipe. Falling back...");
    }
  }

  if (v2UploadAttributes == null) {
    Log.d(TAG, "Not using pipe to retrieve attachment upload attributes...");
    v2UploadAttributes = socket.getAttachmentV2UploadAttributes();
  }

  Pair<Long, byte[]> attachmentIdAndDigest = socket.uploadAttachment(attachmentData, v2UploadAttributes);

  return new SignalServiceAttachmentPointer(0,
                                            new SignalServiceAttachmentRemoteId(attachmentIdAndDigest.first()),
                                            attachment.getContentType(),
                                            attachmentKey,
                                            Optional.of(Util.toIntExact(attachment.getLength())),
                                            attachment.getPreview(),
                                            attachment.getWidth(), attachment.getHeight(),
                                            Optional.of(attachmentIdAndDigest.second()),
                                            attachment.getFileName(),
                                            attachment.getVoiceNote(),
                                            attachment.getCaption(),
                                            attachment.getBlurHash(),
                                            attachment.getUploadTimestamp());
}
 
Example 16
Source File: SessionState.java    From libsignal-protocol-java with GNU General Public License v3.0 5 votes vote down vote up
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
  Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
  Chain               chain         = chainAndIndex.first();

  Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
                                                   .setKey(ByteString.copyFrom(chainKey.getKey()))
                                                   .setIndex(chainKey.getIndex())
                                                   .build();

  Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();

  this.sessionStructure = this.sessionStructure.toBuilder()
                                               .setReceiverChains(chainAndIndex.second(), updatedChain)
                                               .build();
}
 
Example 17
Source File: SignalServiceMessageSender.java    From libsignal-service-java with GNU General Public License v3.0 4 votes vote down vote up
public SignalServiceAttachmentPointer uploadAttachment(SignalServiceAttachmentStream attachment) throws IOException {
  byte[]             attachmentKey    = Util.getSecretBytes(64);
  long               paddedLength     = PaddingInputStream.getPaddedSize(attachment.getLength());
  InputStream        dataStream       = new PaddingInputStream(attachment.getInputStream(), attachment.getLength());
  long               ciphertextLength = AttachmentCipherOutputStream.getCiphertextLength(paddedLength);
  PushAttachmentData attachmentData   = new PushAttachmentData(attachment.getContentType(),
                                                               dataStream,
                                                               ciphertextLength,
                                                               new AttachmentCipherOutputStreamFactory(attachmentKey),
                                                               attachment.getListener());

  AttachmentUploadAttributes uploadAttributes = null;

  if (pipe.get().isPresent()) {
    Log.d(TAG, "Using pipe to retrieve attachment upload attributes...");
    try {
      uploadAttributes = pipe.get().get().getAttachmentUploadAttributes();
    } catch (IOException e) {
      Log.w(TAG, "Failed to retrieve attachment upload attributes using pipe. Falling back...");
    }
  }

  if (uploadAttributes == null) {
    Log.d(TAG, "Not using pipe to retrieve attachment upload attributes...");
    uploadAttributes = socket.getAttachmentUploadAttributes();
  }

  Pair<Long, byte[]> attachmentIdAndDigest = socket.uploadAttachment(attachmentData, uploadAttributes);

  return new SignalServiceAttachmentPointer(attachmentIdAndDigest.first(),
                                            attachment.getContentType(),
                                            attachmentKey,
                                            Optional.of(Util.toIntExact(attachment.getLength())),
                                            attachment.getPreview(),
                                            attachment.getWidth(), attachment.getHeight(),
                                            Optional.of(attachmentIdAndDigest.second()),
                                            attachment.getFileName(),
                                            attachment.getVoiceNote(),
                                            attachment.getCaption(),
                                            attachment.getBlurHash());
}
 
Example 18
Source File: HelpViewModel.java    From mollyim-android with GNU General Public License v3.0 4 votes vote down vote up
private boolean transformValidationData(Pair<Boolean, Boolean> validationData) {
  return validationData.first() == Boolean.TRUE && validationData.second() == Boolean.TRUE;
}
 
Example 19
Source File: RootKeyTest.java    From libsignal-protocol-java with GNU General Public License v3.0 4 votes vote down vote up
public void testRootKeyDerivationV2() throws NoSuchAlgorithmException, InvalidKeyException {
  byte[] rootKeySeed  = {(byte) 0x7b, (byte) 0xa6, (byte) 0xde, (byte) 0xbc, (byte) 0x2b,
                         (byte) 0xc1, (byte) 0xbb, (byte) 0xf9, (byte) 0x1a, (byte) 0xbb,
                         (byte) 0xc1, (byte) 0x36, (byte) 0x74, (byte) 0x04, (byte) 0x17,
                         (byte) 0x6c, (byte) 0xa6, (byte) 0x23, (byte) 0x09, (byte) 0x5b,
                         (byte) 0x7e, (byte) 0xc6, (byte) 0x6b, (byte) 0x45, (byte) 0xf6,
                         (byte) 0x02, (byte) 0xd9, (byte) 0x35, (byte) 0x38, (byte) 0x94,
                         (byte) 0x2d, (byte) 0xcc};

  byte[] alicePublic  = {(byte) 0x05, (byte) 0xee, (byte) 0x4f, (byte) 0xa6, (byte) 0xcd,
                         (byte) 0xc0, (byte) 0x30, (byte) 0xdf, (byte) 0x49, (byte) 0xec,
                         (byte) 0xd0, (byte) 0xba, (byte) 0x6c, (byte) 0xfc, (byte) 0xff,
                         (byte) 0xb2, (byte) 0x33, (byte) 0xd3, (byte) 0x65, (byte) 0xa2,
                         (byte) 0x7f, (byte) 0xad, (byte) 0xbe, (byte) 0xff, (byte) 0x77,
                         (byte) 0xe9, (byte) 0x63, (byte) 0xfc, (byte) 0xb1, (byte) 0x62,
                         (byte) 0x22, (byte) 0xe1, (byte) 0x3a};

  byte[] alicePrivate = {(byte) 0x21, (byte) 0x68, (byte) 0x22, (byte) 0xec, (byte) 0x67,
                         (byte) 0xeb, (byte) 0x38, (byte) 0x04, (byte) 0x9e, (byte) 0xba,
                         (byte) 0xe7, (byte) 0xb9, (byte) 0x39, (byte) 0xba, (byte) 0xea,
                         (byte) 0xeb, (byte) 0xb1, (byte) 0x51, (byte) 0xbb, (byte) 0xb3,
                         (byte) 0x2d, (byte) 0xb8, (byte) 0x0f, (byte) 0xd3, (byte) 0x89,
                         (byte) 0x24, (byte) 0x5a, (byte) 0xc3, (byte) 0x7a, (byte) 0x94,
                         (byte) 0x8e, (byte) 0x50};

  byte[] bobPublic    = {(byte) 0x05, (byte) 0xab, (byte) 0xb8, (byte) 0xeb, (byte) 0x29,
                         (byte) 0xcc, (byte) 0x80, (byte) 0xb4, (byte) 0x71, (byte) 0x09,
                         (byte) 0xa2, (byte) 0x26, (byte) 0x5a, (byte) 0xbe, (byte) 0x97,
                         (byte) 0x98, (byte) 0x48, (byte) 0x54, (byte) 0x06, (byte) 0xe3,
                         (byte) 0x2d, (byte) 0xa2, (byte) 0x68, (byte) 0x93, (byte) 0x4a,
                         (byte) 0x95, (byte) 0x55, (byte) 0xe8, (byte) 0x47, (byte) 0x57,
                         (byte) 0x70, (byte) 0x8a, (byte) 0x30};

  byte[] nextRoot     = {(byte) 0xb1, (byte) 0x14, (byte) 0xf5, (byte) 0xde, (byte) 0x28,
                         (byte) 0x01, (byte) 0x19, (byte) 0x85, (byte) 0xe6, (byte) 0xeb,
                         (byte) 0xa2, (byte) 0x5d, (byte) 0x50, (byte) 0xe7, (byte) 0xec,
                         (byte) 0x41, (byte) 0xa9, (byte) 0xb0, (byte) 0x2f, (byte) 0x56,
                         (byte) 0x93, (byte) 0xc5, (byte) 0xc7, (byte) 0x88, (byte) 0xa6,
                         (byte) 0x3a, (byte) 0x06, (byte) 0xd2, (byte) 0x12, (byte) 0xa2,
                         (byte) 0xf7, (byte) 0x31};

  byte[] nextChain    = {(byte) 0x9d, (byte) 0x7d, (byte) 0x24, (byte) 0x69, (byte) 0xbc,
                         (byte) 0x9a, (byte) 0xe5, (byte) 0x3e, (byte) 0xe9, (byte) 0x80,
                         (byte) 0x5a, (byte) 0xa3, (byte) 0x26, (byte) 0x4d, (byte) 0x24,
                         (byte) 0x99, (byte) 0xa3, (byte) 0xac, (byte) 0xe8, (byte) 0x0f,
                         (byte) 0x4c, (byte) 0xca, (byte) 0xe2, (byte) 0xda, (byte) 0x13,
                         (byte) 0x43, (byte) 0x0c, (byte) 0x5c, (byte) 0x55, (byte) 0xb5,
                         (byte) 0xca, (byte) 0x5f};

  ECPublicKey  alicePublicKey  = Curve.decodePoint(alicePublic, 0);
  ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
  ECKeyPair    aliceKeyPair    = new ECKeyPair(alicePublicKey, alicePrivateKey);

  ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
  RootKey     rootKey      = new RootKey(HKDF.createFor(2), rootKeySeed);

  Pair<RootKey, ChainKey> rootKeyChainKeyPair = rootKey.createChain(bobPublicKey, aliceKeyPair);
  RootKey                 nextRootKey         = rootKeyChainKeyPair.first();
  ChainKey                nextChainKey        = rootKeyChainKeyPair.second();

  assertTrue(Arrays.equals(rootKey.getKeyBytes(), rootKeySeed));
  assertTrue(Arrays.equals(nextRootKey.getKeyBytes(), nextRoot));
  assertTrue(Arrays.equals(nextChainKey.getKey(), nextChain));
}