Break core ratchet out into libaxolotol.

1) Break the core cryptography functions out into libaxolotol.

2) The objective for this code is a Java library that isn't
   dependent on any Android functions.  However, while the
   code has been separated from any Android functionality,
   it is still an 'android library project' because of the
   JNI.
This commit is contained in:
Moxie Marlinspike
2014-04-21 08:40:19 -07:00
parent fe3d91c40c
commit d902c12941
115 changed files with 2246 additions and 1017 deletions

View File

@@ -0,0 +1,321 @@
package org.whispersystems.test;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.util.Pair;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
public class InMemorySessionState implements SessionState {
private Map<ECPublicKey, InMemoryChain> receiverChains = new HashMap<>();
private boolean needsRefresh;
private int sessionVersion;
private IdentityKey remoteIdentityKey;
private IdentityKey localIdentityKey;
private int previousCounter;
private RootKey rootKey;
private ECKeyPair senderEphemeral;
private ChainKey senderChainKey;
private int pendingPreKeyid;
private ECPublicKey pendingPreKey;
private int remoteRegistrationId;
private int localRegistrationId;
public InMemorySessionState() {}
public InMemorySessionState(SessionState sessionState) {
try {
this.needsRefresh = sessionState.getNeedsRefresh();
this.sessionVersion = sessionState.getSessionVersion();
this.remoteIdentityKey = new IdentityKey(sessionState.getRemoteIdentityKey().serialize(), 0);
this.localIdentityKey = new IdentityKey(sessionState.getLocalIdentityKey().serialize(), 0);
this.previousCounter = sessionState.getPreviousCounter();
this.rootKey = new RootKey(sessionState.getRootKey().getKeyBytes());
this.senderEphemeral = sessionState.getSenderEphemeralPair();
this.senderChainKey = new ChainKey(sessionState.getSenderChainKey().getKey(),
sessionState.getSenderChainKey().getIndex());
this.pendingPreKeyid = sessionState.getPendingPreKey().first();
this.pendingPreKey = sessionState.getPendingPreKey().second();
this.remoteRegistrationId = sessionState.getRemoteRegistrationId();
this.localRegistrationId = sessionState.getLocalRegistrationId();
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
@Override
public void setNeedsRefresh(boolean needsRefresh) {
this.needsRefresh = needsRefresh;
}
@Override
public boolean getNeedsRefresh() {
return needsRefresh;
}
@Override
public void setSessionVersion(int version) {
this.sessionVersion = version;
}
@Override
public int getSessionVersion() {
return sessionVersion;
}
@Override
public void setRemoteIdentityKey(IdentityKey identityKey) {
this.remoteIdentityKey = identityKey;
}
@Override
public void setLocalIdentityKey(IdentityKey identityKey) {
this.localIdentityKey = identityKey;
}
@Override
public IdentityKey getRemoteIdentityKey() {
return remoteIdentityKey;
}
@Override
public IdentityKey getLocalIdentityKey() {
return localIdentityKey;
}
@Override
public int getPreviousCounter() {
return previousCounter;
}
@Override
public void setPreviousCounter(int previousCounter) {
this.previousCounter = previousCounter;
}
@Override
public RootKey getRootKey() {
return rootKey;
}
@Override
public void setRootKey(RootKey rootKey) {
this.rootKey = rootKey;
}
@Override
public ECPublicKey getSenderEphemeral() {
return senderEphemeral.getPublicKey();
}
@Override
public ECKeyPair getSenderEphemeralPair() {
return senderEphemeral;
}
@Override
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
return receiverChains.containsKey(senderEphemeral);
}
@Override
public boolean hasSenderChain() {
return senderChainKey != null;
}
@Override
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
InMemoryChain chain = receiverChains.get(senderEphemeral);
return new ChainKey(chain.chainKey, chain.index);
}
@Override
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
InMemoryChain chain = new InMemoryChain();
chain.chainKey = chainKey.getKey();
chain.index = chainKey.getIndex();
receiverChains.put(senderEphemeral, chain);
}
@Override
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
this.senderEphemeral = senderEphemeralPair;
this.senderChainKey = chainKey;
}
@Override
public ChainKey getSenderChainKey() {
return senderChainKey;
}
@Override
public void setSenderChainKey(ChainKey nextChainKey) {
this.senderChainKey = nextChainKey;
}
@Override
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
InMemoryChain chain = receiverChains.get(senderEphemeral);
if (chain == null) return false;
for (InMemoryChain.InMemoryMessageKey messageKey : chain.messageKeys) {
if (messageKey.index == counter) {
return true;
}
}
return false;
}
@Override
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
InMemoryChain chain = receiverChains.get(senderEphemeral);
MessageKeys results = null;
if (chain == null) return null;
Iterator<InMemoryChain.InMemoryMessageKey> iterator = chain.messageKeys.iterator();
while (iterator.hasNext()) {
InMemoryChain.InMemoryMessageKey messageKey = iterator.next();
if (messageKey.index == counter) {
results = new MessageKeys(new SecretKeySpec(messageKey.cipherKey, "AES"),
new SecretKeySpec(messageKey.macKey, "HmacSHA256"),
messageKey.index);
iterator.remove();
break;
}
}
return results;
}
@Override
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
InMemoryChain chain = receiverChains.get(senderEphemeral);
InMemoryChain.InMemoryMessageKey key = new InMemoryChain.InMemoryMessageKey();
key.cipherKey = messageKeys.getCipherKey().getEncoded();
key.macKey = messageKeys.getMacKey().getEncoded();
key.index = messageKeys.getCounter();
chain.messageKeys.add(key);
}
@Override
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
InMemoryChain chain = receiverChains.get(senderEphemeral);
chain.chainKey = chainKey.getKey();
chain.index = chainKey.getIndex();
}
@Override
public void setPendingKeyExchange(int sequence, ECKeyPair ourBaseKey, ECKeyPair ourEphemeralKey, IdentityKeyPair ourIdentityKey) {
throw new AssertionError();
}
@Override
public int getPendingKeyExchangeSequence() {
throw new AssertionError();
}
@Override
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
throw new AssertionError();
}
@Override
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
throw new AssertionError();
}
@Override
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
throw new AssertionError();
}
@Override
public boolean hasPendingKeyExchange() {
throw new AssertionError();
}
@Override
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
this.pendingPreKeyid = preKeyId;
this.pendingPreKey = baseKey;
}
@Override
public boolean hasPendingPreKey() {
return this.pendingPreKey != null;
}
@Override
public Pair<Integer, ECPublicKey> getPendingPreKey() {
return new Pair<>(pendingPreKeyid, pendingPreKey);
}
@Override
public void clearPendingPreKey() {
this.pendingPreKey = null;
this.pendingPreKeyid = -1;
}
@Override
public void setRemoteRegistrationId(int registrationId) {
this.remoteRegistrationId = registrationId;
}
@Override
public int getRemoteRegistrationId() {
return remoteRegistrationId;
}
@Override
public void setLocalRegistrationId(int registrationId) {
this.localRegistrationId = registrationId;
}
@Override
public int getLocalRegistrationId() {
return localRegistrationId;
}
@Override
public byte[] serialize() {
throw new AssertionError();
}
private static class InMemoryChain {
byte[] chainKey;
int index;
List<InMemoryMessageKey> messageKeys = new LinkedList<>();
public static class InMemoryMessageKey {
public InMemoryMessageKey(){}
int index;
byte[] cipherKey;
byte[] macKey;
}
}
}

View File

@@ -0,0 +1,44 @@
package org.whispersystems.test;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.SessionStore;
import java.util.LinkedList;
import java.util.List;
public class InMemorySessionStore implements SessionStore {
private SessionState currentSessionState;
private List<SessionState> previousSessionStates;
private SessionState checkedOutSessionState;
private List<SessionState> checkedOutPreviousSessionStates;
public InMemorySessionStore(SessionState sessionState) {
this.currentSessionState = sessionState;
this.previousSessionStates = new LinkedList<>();
this.checkedOutPreviousSessionStates = new LinkedList<>();
}
@Override
public SessionState getSessionState() {
checkedOutSessionState = new InMemorySessionState(currentSessionState);
return checkedOutSessionState;
}
@Override
public List<SessionState> getPreviousSessionStates() {
checkedOutPreviousSessionStates = new LinkedList<>();
for (SessionState state : previousSessionStates) {
checkedOutPreviousSessionStates.add(new InMemorySessionState(state));
}
return checkedOutPreviousSessionStates;
}
@Override
public void save() {
this.currentSessionState = this.checkedOutSessionState;
this.previousSessionStates = this.checkedOutPreviousSessionStates;
}
}

View File

@@ -0,0 +1,76 @@
package org.whispersystems.test;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.SessionStore;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import java.util.Arrays;
public class SessionCipherTest extends AndroidTestCase {
public void testBasicSession()
throws InvalidKeyException, DuplicateMessageException,
LegacyMessageException, InvalidMessageException
{
SessionState aliceSessionState = new InMemorySessionState();
SessionState bobSessionState = new InMemorySessionState();
initializeSessions(aliceSessionState, bobSessionState);
SessionStore aliceSessionStore = new InMemorySessionStore(aliceSessionState);
SessionStore bobSessionStore = new InMemorySessionStore(bobSessionState);
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore);
SessionCipher bobCipher = new SessionCipher(bobSessionStore);
byte[] alicePlaintext = "This is a plaintext message.".getBytes();
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);
byte[] bobPlaintext = bobCipher.decrypt(message.serialize());
assertTrue(Arrays.equals(alicePlaintext, bobPlaintext));
byte[] bobReply = "This is a message from Bob.".getBytes();
CiphertextMessage reply = bobCipher.encrypt(bobReply);
byte[] receivedReply = aliceCipher.decrypt(reply.serialize());
assertTrue(Arrays.equals(bobReply, receivedReply));
}
private void initializeSessions(SessionState aliceSessionState, SessionState bobSessionState)
throws InvalidKeyException
{
ECKeyPair aliceIdentityKeyPair = Curve.generateKeyPair(false);
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(new IdentityKey(aliceIdentityKeyPair.getPublicKey()),
aliceIdentityKeyPair.getPrivateKey());
ECKeyPair aliceBaseKey = Curve.generateKeyPair(true);
ECKeyPair aliceEphemeralKey = Curve.generateKeyPair(true);
ECKeyPair bobIdentityKeyPair = Curve.generateKeyPair(false);
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(new IdentityKey(bobIdentityKeyPair.getPublicKey()),
bobIdentityKeyPair.getPrivateKey());
ECKeyPair bobBaseKey = Curve.generateKeyPair(true);
ECKeyPair bobEphemeralKey = bobBaseKey;
RatchetingSession.initializeSession(aliceSessionState, aliceBaseKey, bobBaseKey.getPublicKey(),
aliceEphemeralKey, bobEphemeralKey.getPublicKey(),
aliceIdentityKey, bobIdentityKey.getPublicKey());
RatchetingSession.initializeSession(bobSessionState, bobBaseKey, aliceBaseKey.getPublicKey(),
bobEphemeralKey, aliceEphemeralKey.getPublicKey(),
bobIdentityKey, aliceIdentityKey.getPublicKey());
}
}

View File

@@ -0,0 +1,82 @@
package org.whispersystems.test.ecc;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import java.util.Arrays;
public class Curve25519Test extends AndroidTestCase {
public void testAgreement() throws InvalidKeyException {
byte[] alicePublic = {(byte) 0x05, (byte) 0x1b, (byte) 0xb7, (byte) 0x59, (byte) 0x66,
(byte) 0xf2, (byte) 0xe9, (byte) 0x3a, (byte) 0x36, (byte) 0x91,
(byte) 0xdf, (byte) 0xff, (byte) 0x94, (byte) 0x2b, (byte) 0xb2,
(byte) 0xa4, (byte) 0x66, (byte) 0xa1, (byte) 0xc0, (byte) 0x8b,
(byte) 0x8d, (byte) 0x78, (byte) 0xca, (byte) 0x3f, (byte) 0x4d,
(byte) 0x6d, (byte) 0xf8, (byte) 0xb8, (byte) 0xbf, (byte) 0xa2,
(byte) 0xe4, (byte) 0xee, (byte) 0x28};
byte[] alicePrivate = {(byte) 0xc8, (byte) 0x06, (byte) 0x43, (byte) 0x9d, (byte) 0xc9,
(byte) 0xd2, (byte) 0xc4, (byte) 0x76, (byte) 0xff, (byte) 0xed,
(byte) 0x8f, (byte) 0x25, (byte) 0x80, (byte) 0xc0, (byte) 0x88,
(byte) 0x8d, (byte) 0x58, (byte) 0xab, (byte) 0x40, (byte) 0x6b,
(byte) 0xf7, (byte) 0xae, (byte) 0x36, (byte) 0x98, (byte) 0x87,
(byte) 0x90, (byte) 0x21, (byte) 0xb9, (byte) 0x6b, (byte) 0xb4,
(byte) 0xbf, (byte) 0x59};
byte[] bobPublic = {(byte) 0x05, (byte) 0x65, (byte) 0x36, (byte) 0x14, (byte) 0x99,
(byte) 0x3d, (byte) 0x2b, (byte) 0x15, (byte) 0xee, (byte) 0x9e,
(byte) 0x5f, (byte) 0xd3, (byte) 0xd8, (byte) 0x6c, (byte) 0xe7,
(byte) 0x19, (byte) 0xef, (byte) 0x4e, (byte) 0xc1, (byte) 0xda,
(byte) 0xae, (byte) 0x18, (byte) 0x86, (byte) 0xa8, (byte) 0x7b,
(byte) 0x3f, (byte) 0x5f, (byte) 0xa9, (byte) 0x56, (byte) 0x5a,
(byte) 0x27, (byte) 0xa2, (byte) 0x2f};
byte[] bobPrivate = {(byte) 0xb0, (byte) 0x3b, (byte) 0x34, (byte) 0xc3, (byte) 0x3a,
(byte) 0x1c, (byte) 0x44, (byte) 0xf2, (byte) 0x25, (byte) 0xb6,
(byte) 0x62, (byte) 0xd2, (byte) 0xbf, (byte) 0x48, (byte) 0x59,
(byte) 0xb8, (byte) 0x13, (byte) 0x54, (byte) 0x11, (byte) 0xfa,
(byte) 0x7b, (byte) 0x03, (byte) 0x86, (byte) 0xd4, (byte) 0x5f,
(byte) 0xb7, (byte) 0x5d, (byte) 0xc5, (byte) 0xb9, (byte) 0x1b,
(byte) 0x44, (byte) 0x66};
byte[] shared = {(byte) 0x32, (byte) 0x5f, (byte) 0x23, (byte) 0x93, (byte) 0x28,
(byte) 0x94, (byte) 0x1c, (byte) 0xed, (byte) 0x6e, (byte) 0x67,
(byte) 0x3b, (byte) 0x86, (byte) 0xba, (byte) 0x41, (byte) 0x01,
(byte) 0x74, (byte) 0x48, (byte) 0xe9, (byte) 0x9b, (byte) 0x64,
(byte) 0x9a, (byte) 0x9c, (byte) 0x38, (byte) 0x06, (byte) 0xc1,
(byte) 0xdd, (byte) 0x7c, (byte) 0xa4, (byte) 0xc4, (byte) 0x77,
(byte) 0xe6, (byte) 0x29};
ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0);
ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
ECPrivateKey bobPrivateKey = Curve.decodePrivatePoint(bobPrivate);
byte[] sharedOne = Curve.calculateAgreement(alicePublicKey, bobPrivateKey);
byte[] sharedTwo = Curve.calculateAgreement(bobPublicKey, alicePrivateKey);
assertTrue(Arrays.equals(sharedOne, shared));
assertTrue(Arrays.equals(sharedTwo, shared));
}
public void testRandomAgreements() throws InvalidKeyException {
for (int i=0;i<50;i++) {
ECKeyPair alice = Curve.generateKeyPair(false);
ECKeyPair bob = Curve.generateKeyPair(false);
byte[] sharedAlice = Curve.calculateAgreement(bob.getPublicKey(), alice.getPrivateKey());
byte[] sharedBob = Curve.calculateAgreement(alice.getPublicKey(), bob.getPrivateKey());
assertTrue(Arrays.equals(sharedAlice, sharedBob));
}
}
}

View File

@@ -0,0 +1,44 @@
package org.whispersystems.test.kdf;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.kdf.DerivedSecrets;
import org.whispersystems.libaxolotl.kdf.HKDF;
import java.util.Arrays;
public class HKDFTest extends AndroidTestCase {
public void testVector() {
byte[] ikm = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
0x0b, 0x0b};
byte[] salt = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x0c};
byte[] info = {(byte)0xf0, (byte)0xf1, (byte)0xf2, (byte)0xf3, (byte)0xf4,
(byte)0xf5, (byte)0xf6, (byte)0xf7, (byte)0xf8, (byte)0xf9};
byte[] expectedOutputOne = {(byte)0x6e, (byte)0xc2, (byte)0x55, (byte)0x6d, (byte)0x5d,
(byte)0x7b, (byte)0x1d, (byte)0x81, (byte)0xde, (byte)0xe4,
(byte)0x22, (byte)0x2a, (byte)0xd7, (byte)0x48, (byte)0x36,
(byte)0x95, (byte)0xdd, (byte)0xc9, (byte)0x8f, (byte)0x4f,
(byte)0x5f, (byte)0xab, (byte)0xc0, (byte)0xe0, (byte)0x20,
(byte)0x5d, (byte)0xc2, (byte)0xef, (byte)0x87, (byte)0x52,
(byte)0xd4, (byte)0x1e};
byte[] expectedOutputTwo = {(byte)0x04, (byte)0xe2, (byte)0xe2, (byte)0x11, (byte)0x01,
(byte)0xc6, (byte)0x8f, (byte)0xf0, (byte)0x93, (byte)0x94,
(byte)0xb8, (byte)0xad, (byte)0x0b, (byte)0xdc, (byte)0xb9,
(byte)0x60, (byte)0x9c, (byte)0xd4, (byte)0xee, (byte)0x82,
(byte)0xac, (byte)0x13, (byte)0x19, (byte)0x9b, (byte)0x4a,
(byte)0xa9, (byte)0xfd, (byte)0xa8, (byte)0x99, (byte)0xda,
(byte)0xeb, (byte)0xec};
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(ikm, salt, info);
assertTrue(Arrays.equals(derivedSecrets.getCipherKey().getEncoded(), expectedOutputOne));
assertTrue(Arrays.equals(derivedSecrets.getMacKey().getEncoded(), expectedOutputTwo));
}
}

View File

@@ -0,0 +1,58 @@
package org.whispersystems.test.ratchet;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class ChainKeyTest extends AndroidTestCase {
public void testChainKeyDerivation() throws NoSuchAlgorithmException {
byte[] seed = {(byte) 0x8a, (byte) 0xb7, (byte) 0x2d, (byte) 0x6f, (byte) 0x4c,
(byte) 0xc5, (byte) 0xac, (byte) 0x0d, (byte) 0x38, (byte) 0x7e,
(byte) 0xaf, (byte) 0x46, (byte) 0x33, (byte) 0x78, (byte) 0xdd,
(byte) 0xb2, (byte) 0x8e, (byte) 0xdd, (byte) 0x07, (byte) 0x38,
(byte) 0x5b, (byte) 0x1c, (byte) 0xb0, (byte) 0x12, (byte) 0x50,
(byte) 0xc7, (byte) 0x15, (byte) 0x98, (byte) 0x2e, (byte) 0x7a,
(byte) 0xd4, (byte) 0x8f};
byte[] messageKey = {(byte) 0x02, (byte) 0xa9, (byte) 0xaa, (byte) 0x6c, (byte) 0x7d,
(byte) 0xbd, (byte) 0x64, (byte) 0xf9, (byte) 0xd3, (byte) 0xaa,
(byte) 0x92, (byte) 0xf9, (byte) 0x2a, (byte) 0x27, (byte) 0x7b,
(byte) 0xf5, (byte) 0x46, (byte) 0x09, (byte) 0xda, (byte) 0xdf,
(byte) 0x0b, (byte) 0x00, (byte) 0x82, (byte) 0x8a, (byte) 0xcf,
(byte) 0xc6, (byte) 0x1e, (byte) 0x3c, (byte) 0x72, (byte) 0x4b,
(byte) 0x84, (byte) 0xa7};
byte[] macKey = {(byte) 0xbf, (byte) 0xbe, (byte) 0x5e, (byte) 0xfb, (byte) 0x60,
(byte) 0x30, (byte) 0x30, (byte) 0x52, (byte) 0x67, (byte) 0x42,
(byte) 0xe3, (byte) 0xee, (byte) 0x89, (byte) 0xc7, (byte) 0x02,
(byte) 0x4e, (byte) 0x88, (byte) 0x4e, (byte) 0x44, (byte) 0x0f,
(byte) 0x1f, (byte) 0xf3, (byte) 0x76, (byte) 0xbb, (byte) 0x23,
(byte) 0x17, (byte) 0xb2, (byte) 0xd6, (byte) 0x4d, (byte) 0xeb,
(byte) 0x7c, (byte) 0x83};
byte[] nextChainKey = {(byte) 0x28, (byte) 0xe8, (byte) 0xf8, (byte) 0xfe, (byte) 0xe5,
(byte) 0x4b, (byte) 0x80, (byte) 0x1e, (byte) 0xef, (byte) 0x7c,
(byte) 0x5c, (byte) 0xfb, (byte) 0x2f, (byte) 0x17, (byte) 0xf3,
(byte) 0x2c, (byte) 0x7b, (byte) 0x33, (byte) 0x44, (byte) 0x85,
(byte) 0xbb, (byte) 0xb7, (byte) 0x0f, (byte) 0xac, (byte) 0x6e,
(byte) 0xc1, (byte) 0x03, (byte) 0x42, (byte) 0xa2, (byte) 0x46,
(byte) 0xd1, (byte) 0x5d};
ChainKey chainKey = new ChainKey(seed, 0);
assertTrue(Arrays.equals(chainKey.getKey(), seed));
assertTrue(Arrays.equals(chainKey.getMessageKeys().getCipherKey().getEncoded(), messageKey));
assertTrue(Arrays.equals(chainKey.getMessageKeys().getMacKey().getEncoded(), macKey));
assertTrue(Arrays.equals(chainKey.getNextChainKey().getKey(), nextChainKey));
assertTrue(chainKey.getIndex() == 0);
assertTrue(chainKey.getMessageKeys().getCounter() == 0);
assertTrue(chainKey.getNextChainKey().getIndex() == 1);
assertTrue(chainKey.getNextChainKey().getMessageKeys().getCounter() == 1);
}
}

View File

@@ -0,0 +1,218 @@
package org.whispersystems.test.ratchet;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.test.InMemorySessionState;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import java.util.Arrays;
public class RatchetingSessionTest extends AndroidTestCase {
public void testRatchetingSessionAsBob() throws InvalidKeyException {
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97,
(byte) 0x76, (byte) 0xb8, (byte) 0x77, (byte) 0x02,
(byte) 0x05, (byte) 0x74, (byte) 0x5a, (byte) 0x3a,
(byte) 0x6e, (byte) 0x24, (byte) 0xf5, (byte) 0x79,
(byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05,
(byte) 0x92, (byte) 0x8e, (byte) 0xbb, (byte) 0xad,
(byte) 0xc9, (byte) 0xc0, (byte) 0x5a, (byte) 0xd4,
(byte) 0x58};
byte[] bobPrivate = {(byte) 0xa1, (byte) 0xca, (byte) 0xb4, (byte) 0x8f,
(byte) 0x7c, (byte) 0x89, (byte) 0x3f, (byte) 0xaf,
(byte) 0xa9, (byte) 0x88, (byte) 0x0a, (byte) 0x28,
(byte) 0xc3, (byte) 0xb4, (byte) 0x99, (byte) 0x9d,
(byte) 0x28, (byte) 0xd6, (byte) 0x32, (byte) 0x95,
(byte) 0x62, (byte) 0xd2, (byte) 0x7a, (byte) 0x4e,
(byte) 0xa4, (byte) 0xe2, (byte) 0x2e, (byte) 0x9f,
(byte) 0xf1, (byte) 0xbd, (byte) 0xd6, (byte) 0x5a};
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38,
(byte) 0x74, (byte) 0xf6, (byte) 0x96, (byte) 0x69,
(byte) 0x56, (byte) 0xc2, (byte) 0xdd, (byte) 0x47,
(byte) 0x3f, (byte) 0x8f, (byte) 0xa1, (byte) 0x5a,
(byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34,
(byte) 0x16, (byte) 0x92, (byte) 0x32, (byte) 0x4c,
(byte) 0xef, (byte) 0xb1, (byte) 0xc5, (byte) 0xe6,
(byte) 0x26};
byte[] bobIdentityPrivate = {(byte) 0x48, (byte) 0x75, (byte) 0xcc, (byte) 0x69,
(byte) 0xdd, (byte) 0xf8, (byte) 0xea, (byte) 0x07,
(byte) 0x19, (byte) 0xec, (byte) 0x94, (byte) 0x7d,
(byte) 0x61, (byte) 0x08, (byte) 0x11, (byte) 0x35,
(byte) 0x86, (byte) 0x8d, (byte) 0x5f, (byte) 0xd8,
(byte) 0x01, (byte) 0xf0, (byte) 0x2c, (byte) 0x02,
(byte) 0x25, (byte) 0xe5, (byte) 0x16, (byte) 0xdf,
(byte) 0x21, (byte) 0x56, (byte) 0x60, (byte) 0x5e};
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f,
(byte) 0xb1, (byte) 0xa9, (byte) 0x86, (byte) 0x2c,
(byte) 0x3a, (byte) 0xf6, (byte) 0xbe, (byte) 0xac,
(byte) 0xa8, (byte) 0x92, (byte) 0x02, (byte) 0x77,
(byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7,
(byte) 0xc9, (byte) 0x06, (byte) 0xae, (byte) 0xb3,
(byte) 0x5e, (byte) 0x03, (byte) 0xcf, (byte) 0x89,
(byte) 0x50};
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d,
(byte) 0x1f, (byte) 0x52, (byte) 0x02, (byte) 0x83,
(byte) 0xef, (byte) 0xcc, (byte) 0x55, (byte) 0xfc,
(byte) 0xa5, (byte) 0xe6, (byte) 0x70, (byte) 0x75,
(byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51,
(byte) 0xaf, (byte) 0x76, (byte) 0xdf, (byte) 0x18,
(byte) 0xc5, (byte) 0x1d, (byte) 0x29, (byte) 0xd3,
(byte) 0x4b};
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45,
(byte) 0x56, (byte) 0x60, (byte) 0xad, (byte) 0xa6,
(byte) 0x5b, (byte) 0x40, (byte) 0x10, (byte) 0x07,
(byte) 0xf6, (byte) 0x15, (byte) 0xe6, (byte) 0x54,
(byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6,
(byte) 0x87, (byte) 0x51, (byte) 0x49, (byte) 0xbc,
(byte) 0xee, (byte) 0xfc, (byte) 0xb4, (byte) 0x2b,
(byte) 0x4a};
byte[] senderChain = {(byte)0xd2, (byte)0x2f, (byte)0xd5, (byte)0x6d, (byte)0x3f,
(byte)0xec, (byte)0x81, (byte)0x9c, (byte)0xf4, (byte)0xc3,
(byte)0xd5, (byte)0x0c, (byte)0x56, (byte)0xed, (byte)0xfb,
(byte)0x1c, (byte)0x28, (byte)0x0a, (byte)0x1b, (byte)0x31,
(byte)0x96, (byte)0x45, (byte)0x37, (byte)0xf1, (byte)0xd1,
(byte)0x61, (byte)0xe1, (byte)0xc9, (byte)0x31, (byte)0x48,
(byte)0xe3, (byte)0x6b};
IdentityKey bobIdentityKeyPublic = new IdentityKey(bobIdentityPublic, 0);
ECPrivateKey bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate);
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate);
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
ECPrivateKey bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate);
ECKeyPair bobEphemeralKey = new ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey);
ECKeyPair bobBaseKey = bobEphemeralKey;
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
SessionState session = new InMemorySessionState();
RatchetingSession.initializeSession(session, bobBaseKey, aliceBasePublicKey,
bobEphemeralKey, aliceEphemeralPublicKey,
bobIdentityKey, aliceIdentityPublicKey);
assertTrue(session.getLocalIdentityKey().equals(bobIdentityKey.getPublicKey()));
assertTrue(session.getRemoteIdentityKey().equals(aliceIdentityPublicKey));
assertTrue(Arrays.equals(session.getSenderChainKey().getKey(), senderChain));
}
public void testRatchetingSessionAsAlice() throws InvalidKeyException {
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, (byte) 0x76,
(byte) 0xb8, (byte) 0x77, (byte) 0x02, (byte) 0x05, (byte) 0x74,
(byte) 0x5a, (byte) 0x3a, (byte) 0x6e, (byte) 0x24, (byte) 0xf5,
(byte) 0x79, (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, (byte) 0x92,
(byte) 0x8e, (byte) 0xbb, (byte) 0xad, (byte) 0xc9, (byte) 0xc0,
(byte) 0x5a, (byte) 0xd4, (byte) 0x58};
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, (byte) 0x74,
(byte) 0xf6, (byte) 0x96, (byte) 0x69, (byte) 0x56, (byte) 0xc2,
(byte) 0xdd, (byte) 0x47, (byte) 0x3f, (byte) 0x8f, (byte) 0xa1,
(byte) 0x5a, (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, (byte) 0x16,
(byte) 0x92, (byte) 0x32, (byte) 0x4c, (byte) 0xef, (byte) 0xb1,
(byte) 0xc5, (byte) 0xe6, (byte) 0x26};
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, (byte) 0xb1,
(byte) 0xa9, (byte) 0x86, (byte) 0x2c, (byte) 0x3a, (byte) 0xf6,
(byte) 0xbe, (byte) 0xac, (byte) 0xa8, (byte) 0x92, (byte) 0x02,
(byte) 0x77, (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, (byte) 0xc9,
(byte) 0x06, (byte) 0xae, (byte) 0xb3, (byte) 0x5e, (byte) 0x03,
(byte) 0xcf, (byte) 0x89, (byte) 0x50};
byte[] aliceBasePrivate = {(byte) 0x11, (byte) 0xae, (byte) 0x7c, (byte) 0x64, (byte) 0xd1,
(byte) 0xe6, (byte) 0x1c, (byte) 0xd5, (byte) 0x96, (byte) 0xb7,
(byte) 0x6a, (byte) 0x0d, (byte) 0xb5, (byte) 0x01, (byte) 0x26,
(byte) 0x73, (byte) 0x39, (byte) 0x1c, (byte) 0xae, (byte) 0x66,
(byte) 0xed, (byte) 0xbf, (byte) 0xcf, (byte) 0x07, (byte) 0x3b,
(byte) 0x4d, (byte) 0xa8, (byte) 0x05, (byte) 0x16, (byte) 0xa4,
(byte) 0x74, (byte) 0x49};
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, (byte) 0x1f,
(byte) 0x52, (byte) 0x02, (byte) 0x83, (byte) 0xef, (byte) 0xcc,
(byte) 0x55, (byte) 0xfc, (byte) 0xa5, (byte) 0xe6, (byte) 0x70,
(byte) 0x75, (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, (byte) 0xaf,
(byte) 0x76, (byte) 0xdf, (byte) 0x18, (byte) 0xc5, (byte) 0x1d,
(byte) 0x29, (byte) 0xd3, (byte) 0x4b};
byte[] aliceEphemeralPrivate = {(byte) 0xd1, (byte) 0xba, (byte) 0x38, (byte) 0xce, (byte) 0xa9,
(byte) 0x17, (byte) 0x43, (byte) 0xd3, (byte) 0x39, (byte) 0x39,
(byte) 0xc3, (byte) 0x3c, (byte) 0x84, (byte) 0x98, (byte) 0x65,
(byte) 0x09, (byte) 0x28, (byte) 0x01, (byte) 0x61, (byte) 0xb8,
(byte) 0xb6, (byte) 0x0f, (byte) 0xc7, (byte) 0x87, (byte) 0x0c,
(byte) 0x59, (byte) 0x9c, (byte) 0x1d, (byte) 0x46, (byte) 0x20,
(byte) 0x12, (byte) 0x48};
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, (byte) 0x56,
(byte) 0x60, (byte) 0xad, (byte) 0xa6, (byte) 0x5b, (byte) 0x40,
(byte) 0x10, (byte) 0x07, (byte) 0xf6, (byte) 0x15, (byte) 0xe6,
(byte) 0x54, (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, (byte) 0x87,
(byte) 0x51, (byte) 0x49, (byte) 0xbc, (byte) 0xee, (byte) 0xfc,
(byte) 0xb4, (byte) 0x2b, (byte) 0x4a};
byte[] aliceIdentityPrivate = {(byte) 0x90, (byte) 0x40, (byte) 0xf0, (byte) 0xd4, (byte) 0xe0,
(byte) 0x9c, (byte) 0xf3, (byte) 0x8f, (byte) 0x6d, (byte) 0xc7,
(byte) 0xc1, (byte) 0x37, (byte) 0x79, (byte) 0xc9, (byte) 0x08,
(byte) 0xc0, (byte) 0x15, (byte) 0xa1, (byte) 0xda, (byte) 0x4f,
(byte) 0xa7, (byte) 0x87, (byte) 0x37, (byte) 0xa0, (byte) 0x80,
(byte) 0xeb, (byte) 0x0a, (byte) 0x6f, (byte) 0x4f, (byte) 0x5f,
(byte) 0x8f, (byte) 0x58};
byte[] receiverChain = {(byte) 0xd2, (byte) 0x2f, (byte) 0xd5, (byte) 0x6d, (byte) 0x3f,
(byte) 0xec, (byte) 0x81, (byte) 0x9c, (byte) 0xf4, (byte) 0xc3,
(byte) 0xd5, (byte) 0x0c, (byte) 0x56, (byte) 0xed, (byte) 0xfb,
(byte) 0x1c, (byte) 0x28, (byte) 0x0a, (byte) 0x1b, (byte) 0x31,
(byte) 0x96, (byte) 0x45, (byte) 0x37, (byte) 0xf1, (byte) 0xd1,
(byte) 0x61, (byte) 0xe1, (byte) 0xc9, (byte) 0x31, (byte) 0x48,
(byte) 0xe3, (byte) 0x6b};
IdentityKey bobIdentityKey = new IdentityKey(bobIdentityPublic, 0);
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
ECPublicKey bobBasePublicKey = bobEphemeralPublicKey;
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
ECPrivateKey aliceBasePrivateKey = Curve.decodePrivatePoint(aliceBasePrivate);
ECKeyPair aliceBaseKey = new ECKeyPair(aliceBasePublicKey, aliceBasePrivateKey);
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
ECPrivateKey aliceEphemeralPrivateKey = Curve.decodePrivatePoint(aliceEphemeralPrivate);
ECKeyPair aliceEphemeralKey = new ECKeyPair(aliceEphemeralPublicKey, aliceEphemeralPrivateKey);
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
ECPrivateKey aliceIdentityPrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate);
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(aliceIdentityPublicKey, aliceIdentityPrivateKey);
SessionState session = new InMemorySessionState();
RatchetingSession.initializeSession(session, aliceBaseKey, bobBasePublicKey,
aliceEphemeralKey, bobEphemeralPublicKey,
aliceIdentityKey, bobIdentityKey);
assertTrue(session.getLocalIdentityKey().equals(aliceIdentityKey.getPublicKey()));
assertTrue(session.getRemoteIdentityKey().equals(bobIdentityKey));
assertTrue(Arrays.equals(session.getReceiverChainKey(bobEphemeralPublicKey).getKey(),
receiverChain));
}
}

View File

@@ -0,0 +1,83 @@
package org.whispersystems.test.ratchet;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.util.Pair;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class RootKeyTest extends AndroidTestCase {
public void testRootKeyDerivation() 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(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));
}
}