public PreKeyWhisperMessage(UInt32 messageVersion, UInt32 registrationId, Maybe<UInt32> preKeyId,
		                            UInt32 signedPreKeyId, ECPublicKey baseKey, IdentityKey identityKey,
		                            WhisperMessage message)
        {
            MessageVersion = messageVersion;
            RegistrationId = registrationId;
            PreKeyId = preKeyId;
            SignedPreKeyId = signedPreKeyId;
            BaseKey = baseKey;
            IdentityKey = identityKey;
            Message = message;

            var preKeyMessage = new WhisperProtos.PreKeyWhisperMessage {
                signedPreKeyId = SignedPreKeyId,
                baseKey = BaseKey.Serialize(),
                identityKey = IdentityKey.Serialize(),
                message = Message.Serialize(),
                registrationId = registrationId
            };

            preKeyId.Do(pKid => preKeyMessage.preKeyId = pKid);

            byte[] versionBytes = { ByteUtil.IntsToByteHighAndLow(MessageVersion, CURRENT_VERSION) };

            byte[] messageBytes;
            using(var stream = new MemoryStream())
            {
                Serializer.Serialize(stream, preKeyMessage);
                messageBytes = stream.ToArray();
            }

            _serialized = ByteUtil.Combine(versionBytes, messageBytes);
        }
        public PreKeyWhisperMessage(byte[] serialized)
        {
            try
            {
                MessageVersion = (UInt32)ByteUtil.HighBitsToUInt(serialized[0]);

                if(MessageVersion > CiphertextMessage.CURRENT_VERSION)
                {
                    throw new InvalidVersionException("Unknown version: " + MessageVersion);
                }

                WhisperProtos.PreKeyWhisperMessage preKeyWhisperMessage;
                using(var stream = new MemoryStream(serialized))
                {
                    preKeyWhisperMessage = Serializer.Deserialize<WhisperProtos.PreKeyWhisperMessage>(stream);
                }

                if((MessageVersion == 2 && !preKeyWhisperMessage.preKeyId.HasValue) ||
                   (MessageVersion == 3 && !preKeyWhisperMessage.signedPreKeyId.HasValue) ||
                   preKeyWhisperMessage.baseKey == null ||
                   preKeyWhisperMessage.identityKey == null ||
                   preKeyWhisperMessage.message == null)
                {
                    throw new InvalidMessageException("Incomplete message.");
                }

                _serialized = serialized;
                RegistrationId = preKeyWhisperMessage.registrationId.Value;
                PreKeyId = preKeyWhisperMessage.preKeyId.Value.ToMaybe();
                SignedPreKeyId = preKeyWhisperMessage.signedPreKeyId.Value; //() ? preKeyWhisperMessage.getSignedPreKeyId() : -1;
                BaseKey = Curve.DecodePoint(preKeyWhisperMessage.baseKey, 0);
                IdentityKey = new IdentityKey(Curve.DecodePoint(preKeyWhisperMessage.identityKey, 0));
                Message = new WhisperMessage(preKeyWhisperMessage.message);
            }
            catch(InvalidKeyException e)
            {
                throw new InvalidMessageException(e);
            }
            catch(LegacyMessageException e)
            {
                throw new InvalidMessageException(e);
            }
        }
        public byte[] Decrypt(WhisperMessage ciphertext, IDecryptionCallback callback)
        {
            lock(SESSION_LOCK)
            {

                if(!_sessionStore.ContainsSession(_remoteAddress))
                {
                    throw new NoSessionException("No session for: " + _remoteAddress);
                }

                SessionRecord sessionRecord = _sessionStore.LoadSession(_remoteAddress);
                byte[] plaintext = Decrypt(sessionRecord, ciphertext);

                callback.HandlePlaintext(plaintext);

                _sessionStore.StoreSession(_remoteAddress, sessionRecord);

                return plaintext;
            }
        }
 public byte[] Decrypt(WhisperMessage ciphertext)
 {
     return Decrypt(ciphertext, new NullDecryptionCallback());
 }
        private byte[] Decrypt(SessionState sessionState, WhisperMessage ciphertextMessage)
        {
            if(!sessionState.HasSenderChain())
            {
                throw new InvalidMessageException("Uninitialized session!");
            }

            if(ciphertextMessage.MessageVersion != sessionState.GetSessionVersion())
            {
                throw new InvalidMessageException(string.Format("Message version {0}, but session version {1}",
                    ciphertextMessage.MessageVersion,
                    sessionState.GetSessionVersion()));
            }

            UInt32 messageVersion = ciphertextMessage.MessageVersion;
            ECPublicKey theirEphemeral = ciphertextMessage.SenderRatchetKey;
            UInt32 counter = ciphertextMessage.Counter;
            ChainKey chainKey = GetOrCreateChainKey(sessionState, theirEphemeral);
            MessageKeys messageKeys = GetOrCreateMessageKeys(sessionState, theirEphemeral,
                                          chainKey, counter);

            ciphertextMessage.VerifyMac(messageVersion,
                sessionState.GetRemoteIdentityKey(),
                sessionState.GetLocalIdentityKey(),
                messageKeys.MacKey);

            byte[] plaintext = GetPlaintext(messageVersion, messageKeys, ciphertextMessage.Body);

            sessionState.ClearUnacknowledgedPreKeyMessage();

            return plaintext;
        }
        private byte[] Decrypt(SessionRecord sessionRecord, WhisperMessage ciphertext)
        {
            lock(SESSION_LOCK)
            {
                var previousStates = new List<SessionState>(sessionRecord.PreviousStates);
                var exceptions = new List<Exception>();

                try
                {
                    var sessionState = new SessionState(sessionRecord.SessionState);
                    byte[] plaintext = Decrypt(sessionState, ciphertext);

                    sessionRecord.SetState(sessionState);
                    return plaintext;
                }
                catch(InvalidMessageException e)
                {
                    exceptions.Add(e);
                }

                // TODO: Check~
                foreach(var state in previousStates)
                {
                    try
                    {
                        var promotedState = new SessionState(state);
                        byte[] plainText = Decrypt(promotedState, ciphertext);
                        sessionRecord.PreviousStates.Remove(state);
                        return plainText;
                    }
                    catch(InvalidMessageException e)
                    {
                        exceptions.Add(e);
                    }

                }

                throw new InvalidMessageException("No valid sessions.", exceptions);
            }
        }
        /// <summary>
        /// Encrypt a message.
        /// </summary>
        /// <param name="paddedMessage">The plaintext message bytes, optionally padded to a constant multiple.</param>
        public CiphertextMessage Encrypt(byte[] paddedMessage)
        {
            lock(SESSION_LOCK)
            {
                SessionRecord sessionRecord = _sessionStore.LoadSession(_remoteAddress);
                SessionState sessionState = sessionRecord.SessionState;
                ChainKey chainKey = sessionState.GetSenderChainKey();
                MessageKeys messageKeys = chainKey.GetMessageKeys();
                ECPublicKey senderEphemeral = sessionState.SenderRatchetKey;
                UInt32 previousCounter = sessionState.PreviousCounter;
                UInt32 sessionVersion = sessionState.GetSessionVersion();

                byte[] ciphertextBody = GetCiphertext(sessionVersion, messageKeys, paddedMessage);
                CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.MacKey,
                                                          senderEphemeral, chainKey.Index,
                                                          previousCounter, ciphertextBody,
                                                          sessionState.GetLocalIdentityKey(),
                                                          sessionState.GetRemoteIdentityKey());

                if(sessionState.HasUnacknowledgedPreKeyMessage())
                {
                    SessionState.UnacknowledgedPreKeyMessageItems items = sessionState.GetUnacknowledgedPreKeyMessageItems();
                    UInt32 localRegistrationId = sessionState.LocalRegistrationId;

                    ciphertextMessage = new PreKeyWhisperMessage(sessionVersion, localRegistrationId, items.PreKeyId,
                        items.SignedPreKeyId, items.BaseKey,
                        sessionState.GetLocalIdentityKey(),
                        (WhisperMessage)ciphertextMessage);
                }

                sessionState.SetSenderChainKey (chainKey.GetNextChainKey ());
                _sessionStore.StoreSession(_remoteAddress, sessionRecord);
                return ciphertextMessage;
            }
        }