├── .gitignore ├── Documentation ├── Classes.html ├── Classes │ ├── ReceiverChain.html │ ├── SenderKeyRecord.html │ ├── SenderKeyState.html │ ├── SessionRecord.html │ ├── SessionState.html │ └── SignalError.html ├── Enums.html ├── Enums │ ├── CipherTextType.html │ ├── HKDFVersion.html │ ├── SignalEncryptionScheme.html │ └── SignalErrorType.html ├── Functions.html ├── Global Variables.html ├── Protocols.html ├── Protocols │ ├── GroupKeyStore.html │ ├── IdentityKeyStore.html │ ├── IdentityKeyStoreDelegate.html │ ├── KeyStore.html │ ├── PreKeyStore.html │ ├── PreKeyStoreDelegate.html │ ├── ScannableFingerprint.html │ ├── SenderKeyStore.html │ ├── SenderKeyStoreDelegate.html │ ├── SessionStore.html │ ├── SessionStoreDelegate.html │ ├── SignalCryptoProvider.html │ ├── SignalProtocolStoreContext.html │ ├── SignedPreKeyStore.html │ └── SignedPreKeyStoreDelegate.html ├── Structs.html ├── Structs │ ├── CipherTextMessage.html │ ├── DeviceConsistencyCommitmentV0.html │ ├── DeviceConsistencyMessage.html │ ├── DeviceConsistencySignature.html │ ├── DisplayableFingerprint.html │ ├── Fingerprint.html │ ├── Fingerprint │ │ └── Version.html │ ├── GroupCipher.html │ ├── HKDF.html │ ├── KeyPair.html │ ├── PendingPreKey.html │ ├── PreKeySignalMessage.html │ ├── PrivateKey.html │ ├── PublicKey.html │ ├── RatchetChainKey.html │ ├── RatchetMessageKeys.html │ ├── RatchetRootKey.html │ ├── ScannableFingerprint.html │ ├── ScannableFingerprintV0.html │ ├── ScannableFingerprintV1.html │ ├── SenderChain.html │ ├── SenderChainKey.html │ ├── SenderKeyDistributionMessage.html │ ├── SenderKeyMessage.html │ ├── SenderMessageKey.html │ ├── SessionBuilder.html │ ├── SessionCipher.html │ ├── SessionPreKey.html │ ├── SessionPreKeyBundle.html │ ├── SessionPreKeyPublic.html │ ├── SessionPublicPreKey.html │ ├── SessionPublicSignedPreKey.html │ ├── SessionSignedPreKey.html │ ├── SessionSignedPreKeyPublic.html │ ├── SignalAddress.html │ ├── SignalCommonCrypto.html │ ├── SignalCrypto.html │ ├── SignalMessage.html │ ├── SignalSenderKeyName.html │ ├── Signal_DeviceConsistencyCodeMessage.html │ ├── Signal_Fingerprint.html │ ├── Signal_KeyPair.html │ ├── Signal_PreKey.html │ ├── Signal_PreKey │ │ ├── PublicPart.html │ │ └── _StorageClass.html │ ├── Signal_PreKeySignalMessage.html │ ├── Signal_Record.html │ ├── Signal_Record │ │ └── _StorageClass.html │ ├── Signal_SenderKeyDistributionMessage.html │ ├── Signal_SenderKeyMessage.html │ ├── Signal_SenderKeyRecord.html │ ├── Signal_SenderKeyState.html │ ├── Signal_SenderKeyState │ │ ├── SenderChainKey.html │ │ ├── SenderMessageKey.html │ │ ├── SenderSigningKey.html │ │ └── _StorageClass.html │ ├── Signal_Session.html │ ├── Signal_Session │ │ ├── Chain.html │ │ ├── Chain │ │ │ ├── ChainKey.html │ │ │ ├── MessageKey.html │ │ │ └── _StorageClass.html │ │ ├── PendingPreKey.html │ │ └── _StorageClass.html │ ├── Signal_SignalMessage.html │ ├── Signal_SignedPreKey.html │ ├── Signal_SignedPreKey │ │ ├── PublicPart.html │ │ └── _StorageClass.html │ ├── SymmetricParameters.html │ ├── Textsecure_CombinedFingerprints.html │ ├── Textsecure_CombinedFingerprints │ │ └── _StorageClass.html │ ├── Textsecure_DeviceConsistencyCodeMessage.html │ ├── Textsecure_IdentityKeyPairStructure.html │ ├── Textsecure_KeyExchangeMessage.html │ ├── Textsecure_LogicalFingerprint.html │ ├── Textsecure_PreKeyRecordStructure.html │ ├── Textsecure_PreKeySignalMessage.html │ ├── Textsecure_RecordStructure.html │ ├── Textsecure_RecordStructure │ │ └── _StorageClass.html │ ├── Textsecure_SenderKeyDistributionMessage.html │ ├── Textsecure_SenderKeyMessage.html │ ├── Textsecure_SenderKeyRecordStructure.html │ ├── Textsecure_SenderKeyStateStructure.html │ ├── Textsecure_SenderKeyStateStructure │ │ ├── SenderChainKey.html │ │ ├── SenderMessageKey.html │ │ ├── SenderSigningKey.html │ │ └── _StorageClass.html │ ├── Textsecure_SessionStructure.html │ ├── Textsecure_SessionStructure │ │ ├── Chain.html │ │ ├── Chain │ │ │ ├── ChainKey.html │ │ │ ├── MessageKey.html │ │ │ └── _StorageClass.html │ │ ├── PendingKeyExchange.html │ │ ├── PendingPreKey.html │ │ └── _StorageClass.html │ ├── Textsecure_SignalMessage.html │ ├── Textsecure_SignedPreKeyRecordStructure.html │ └── _GeneratedWithProtocGenSwiftVersion.html ├── Typealiases.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── LibSignalProtocolSwift.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── ReceiverChain.html │ │ │ │ ├── SenderKeyRecord.html │ │ │ │ ├── SenderKeyState.html │ │ │ │ ├── SessionRecord.html │ │ │ │ ├── SessionState.html │ │ │ │ └── SignalError.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── CipherTextType.html │ │ │ │ ├── HKDFVersion.html │ │ │ │ ├── SignalEncryptionScheme.html │ │ │ │ └── SignalErrorType.html │ │ │ ├── Functions.html │ │ │ ├── Global Variables.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── IdentityKeyStore.html │ │ │ │ ├── IdentityKeyStoreDelegate.html │ │ │ │ ├── KeyStore.html │ │ │ │ ├── PreKeyStore.html │ │ │ │ ├── PreKeyStoreDelegate.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── SenderKeyStore.html │ │ │ │ ├── SenderKeyStoreDelegate.html │ │ │ │ ├── SessionStore.html │ │ │ │ ├── SessionStoreDelegate.html │ │ │ │ ├── SignalCryptoProvider.html │ │ │ │ ├── SignalProtocolStoreContext.html │ │ │ │ ├── SignedPreKeyStore.html │ │ │ │ └── SignedPreKeyStoreDelegate.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── CipherTextMessage.html │ │ │ │ ├── DeviceConsistencyCommitmentV0.html │ │ │ │ ├── DeviceConsistencyMessage.html │ │ │ │ ├── DeviceConsistencySignature.html │ │ │ │ ├── DisplayableFingerprint.html │ │ │ │ ├── Fingerprint.html │ │ │ │ ├── Fingerprint │ │ │ │ │ └── Version.html │ │ │ │ ├── GroupCipher.html │ │ │ │ ├── HKDF.html │ │ │ │ ├── KeyPair.html │ │ │ │ ├── PendingPreKey.html │ │ │ │ ├── PreKeySignalMessage.html │ │ │ │ ├── PrivateKey.html │ │ │ │ ├── PublicKey.html │ │ │ │ ├── RatchetChainKey.html │ │ │ │ ├── RatchetMessageKeys.html │ │ │ │ ├── RatchetRootKey.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── ScannableFingerprintV0.html │ │ │ │ ├── ScannableFingerprintV1.html │ │ │ │ ├── SenderChain.html │ │ │ │ ├── SenderChainKey.html │ │ │ │ ├── SenderKeyDistributionMessage.html │ │ │ │ ├── SenderKeyMessage.html │ │ │ │ ├── SenderMessageKey.html │ │ │ │ ├── SessionBuilder.html │ │ │ │ ├── SessionCipher.html │ │ │ │ ├── SessionPreKey.html │ │ │ │ ├── SessionPreKeyBundle.html │ │ │ │ ├── SessionPreKeyPublic.html │ │ │ │ ├── SessionPublicPreKey.html │ │ │ │ ├── SessionPublicSignedPreKey.html │ │ │ │ ├── SessionSignedPreKey.html │ │ │ │ ├── SessionSignedPreKeyPublic.html │ │ │ │ ├── SignalAddress.html │ │ │ │ ├── SignalCommonCrypto.html │ │ │ │ ├── SignalCrypto.html │ │ │ │ ├── SignalMessage.html │ │ │ │ ├── SignalSenderKeyName.html │ │ │ │ ├── Signal_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Signal_Fingerprint.html │ │ │ │ ├── Signal_KeyPair.html │ │ │ │ ├── Signal_PreKey.html │ │ │ │ ├── Signal_PreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_PreKeySignalMessage.html │ │ │ │ ├── Signal_Record.html │ │ │ │ ├── Signal_Record │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SenderKeyDistributionMessage.html │ │ │ │ ├── Signal_SenderKeyMessage.html │ │ │ │ ├── Signal_SenderKeyRecord.html │ │ │ │ ├── Signal_SenderKeyState.html │ │ │ │ ├── Signal_SenderKeyState │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_Session.html │ │ │ │ ├── Signal_Session │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SignalMessage.html │ │ │ │ ├── Signal_SignedPreKey.html │ │ │ │ ├── Signal_SignedPreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── SymmetricParameters.html │ │ │ │ ├── Textsecure_CombinedFingerprints.html │ │ │ │ ├── Textsecure_CombinedFingerprints │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Textsecure_IdentityKeyPairStructure.html │ │ │ │ ├── Textsecure_KeyExchangeMessage.html │ │ │ │ ├── Textsecure_LogicalFingerprint.html │ │ │ │ ├── Textsecure_PreKeyRecordStructure.html │ │ │ │ ├── Textsecure_PreKeySignalMessage.html │ │ │ │ ├── Textsecure_RecordStructure.html │ │ │ │ ├── Textsecure_RecordStructure │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SenderKeyDistributionMessage.html │ │ │ │ ├── Textsecure_SenderKeyMessage.html │ │ │ │ ├── Textsecure_SenderKeyRecordStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SessionStructure.html │ │ │ │ ├── Textsecure_SessionStructure │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingKeyExchange.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SignalMessage.html │ │ │ │ ├── Textsecure_SignedPreKeyRecordStructure.html │ │ │ │ └── _GeneratedWithProtocGenSwiftVersion.html │ │ │ ├── Typealiases.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ ├── LibSignalProtocolSwift.tgz │ ├── SignalProtocol.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── ReceiverChain.html │ │ │ │ ├── SenderKeyRecord.html │ │ │ │ ├── SenderKeyState.html │ │ │ │ ├── SessionRecord.html │ │ │ │ ├── SessionState.html │ │ │ │ └── SignalError.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── CipherTextType.html │ │ │ │ ├── HKDFVersion.html │ │ │ │ ├── SignalEncryptionScheme.html │ │ │ │ └── SignalErrorType.html │ │ │ ├── Functions.html │ │ │ ├── Global Variables.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── GroupKeyStore.html │ │ │ │ ├── IdentityKeyStore.html │ │ │ │ ├── IdentityKeyStoreDelegate.html │ │ │ │ ├── KeyStore.html │ │ │ │ ├── PreKeyStore.html │ │ │ │ ├── PreKeyStoreDelegate.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── SenderKeyStore.html │ │ │ │ ├── SenderKeyStoreDelegate.html │ │ │ │ ├── SessionStore.html │ │ │ │ ├── SessionStoreDelegate.html │ │ │ │ ├── SignalCryptoProvider.html │ │ │ │ ├── SignalProtocolStoreContext.html │ │ │ │ ├── SignedPreKeyStore.html │ │ │ │ └── SignedPreKeyStoreDelegate.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── CipherTextMessage.html │ │ │ │ ├── DeviceConsistencyCommitmentV0.html │ │ │ │ ├── DeviceConsistencyMessage.html │ │ │ │ ├── DeviceConsistencySignature.html │ │ │ │ ├── DisplayableFingerprint.html │ │ │ │ ├── Fingerprint.html │ │ │ │ ├── Fingerprint │ │ │ │ │ └── Version.html │ │ │ │ ├── GroupCipher.html │ │ │ │ ├── HKDF.html │ │ │ │ ├── KeyPair.html │ │ │ │ ├── PendingPreKey.html │ │ │ │ ├── PreKeySignalMessage.html │ │ │ │ ├── PrivateKey.html │ │ │ │ ├── PublicKey.html │ │ │ │ ├── RatchetChainKey.html │ │ │ │ ├── RatchetMessageKeys.html │ │ │ │ ├── RatchetRootKey.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── ScannableFingerprintV0.html │ │ │ │ ├── ScannableFingerprintV1.html │ │ │ │ ├── SenderChain.html │ │ │ │ ├── SenderChainKey.html │ │ │ │ ├── SenderKeyDistributionMessage.html │ │ │ │ ├── SenderKeyMessage.html │ │ │ │ ├── SenderMessageKey.html │ │ │ │ ├── SessionBuilder.html │ │ │ │ ├── SessionCipher.html │ │ │ │ ├── SessionPreKey.html │ │ │ │ ├── SessionPreKeyBundle.html │ │ │ │ ├── SessionPreKeyPublic.html │ │ │ │ ├── SessionPublicPreKey.html │ │ │ │ ├── SessionPublicSignedPreKey.html │ │ │ │ ├── SessionSignedPreKey.html │ │ │ │ ├── SessionSignedPreKeyPublic.html │ │ │ │ ├── SignalAddress.html │ │ │ │ ├── SignalCommonCrypto.html │ │ │ │ ├── SignalCrypto.html │ │ │ │ ├── SignalMessage.html │ │ │ │ ├── SignalSenderKeyName.html │ │ │ │ ├── Signal_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Signal_Fingerprint.html │ │ │ │ ├── Signal_KeyPair.html │ │ │ │ ├── Signal_PreKey.html │ │ │ │ ├── Signal_PreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_PreKeySignalMessage.html │ │ │ │ ├── Signal_Record.html │ │ │ │ ├── Signal_Record │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SenderKeyDistributionMessage.html │ │ │ │ ├── Signal_SenderKeyMessage.html │ │ │ │ ├── Signal_SenderKeyRecord.html │ │ │ │ ├── Signal_SenderKeyState.html │ │ │ │ ├── Signal_SenderKeyState │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_Session.html │ │ │ │ ├── Signal_Session │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SignalMessage.html │ │ │ │ ├── Signal_SignedPreKey.html │ │ │ │ ├── Signal_SignedPreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── SymmetricParameters.html │ │ │ │ ├── Textsecure_CombinedFingerprints.html │ │ │ │ ├── Textsecure_CombinedFingerprints │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Textsecure_IdentityKeyPairStructure.html │ │ │ │ ├── Textsecure_KeyExchangeMessage.html │ │ │ │ ├── Textsecure_LogicalFingerprint.html │ │ │ │ ├── Textsecure_PreKeyRecordStructure.html │ │ │ │ ├── Textsecure_PreKeySignalMessage.html │ │ │ │ ├── Textsecure_RecordStructure.html │ │ │ │ ├── Textsecure_RecordStructure │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SenderKeyDistributionMessage.html │ │ │ │ ├── Textsecure_SenderKeyMessage.html │ │ │ │ ├── Textsecure_SenderKeyRecordStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SessionStructure.html │ │ │ │ ├── Textsecure_SessionStructure │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingKeyExchange.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SignalMessage.html │ │ │ │ ├── Textsecure_SignedPreKeyRecordStructure.html │ │ │ │ └── _GeneratedWithProtocGenSwiftVersion.html │ │ │ ├── Typealiases.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ ├── SignalProtocol.tgz │ ├── SignalProtocolSwift.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── ReceiverChain.html │ │ │ │ ├── SenderKeyRecord.html │ │ │ │ ├── SenderKeyState.html │ │ │ │ ├── SessionRecord.html │ │ │ │ ├── SessionState.html │ │ │ │ └── SignalError.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── CipherTextType.html │ │ │ │ ├── HKDFVersion.html │ │ │ │ ├── SignalEncryptionScheme.html │ │ │ │ └── SignalErrorType.html │ │ │ ├── Functions.html │ │ │ ├── Global Variables.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── IdentityKeyStoreDelegate.html │ │ │ │ ├── PreKeyStoreDelegate.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── SenderKeyStoreDelegate.html │ │ │ │ ├── SessionStoreDelegate.html │ │ │ │ ├── SignalCryptoProvider.html │ │ │ │ ├── SignalProtocolStoreContext.html │ │ │ │ └── SignedPreKeyStoreDelegate.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── CipherTextMessage.html │ │ │ │ ├── DeviceConsistencyCommitmentV0.html │ │ │ │ ├── DeviceConsistencyMessage.html │ │ │ │ ├── DeviceConsistencySignature.html │ │ │ │ ├── DisplayableFingerprint.html │ │ │ │ ├── Fingerprint.html │ │ │ │ ├── Fingerprint │ │ │ │ │ └── Version.html │ │ │ │ ├── GroupCipher.html │ │ │ │ ├── HKDF.html │ │ │ │ ├── KeyPair.html │ │ │ │ ├── PendingPreKey.html │ │ │ │ ├── PreKeySignalMessage.html │ │ │ │ ├── PrivateKey.html │ │ │ │ ├── PublicKey.html │ │ │ │ ├── RatchetChainKey.html │ │ │ │ ├── RatchetMessageKeys.html │ │ │ │ ├── RatchetRootKey.html │ │ │ │ ├── ScannableFingerprint.html │ │ │ │ ├── ScannableFingerprintV0.html │ │ │ │ ├── ScannableFingerprintV1.html │ │ │ │ ├── SenderChain.html │ │ │ │ ├── SenderChainKey.html │ │ │ │ ├── SenderKeyDistributionMessage.html │ │ │ │ ├── SenderKeyMessage.html │ │ │ │ ├── SenderMessageKey.html │ │ │ │ ├── SessionBuilder.html │ │ │ │ ├── SessionCipher.html │ │ │ │ ├── SessionPreKey.html │ │ │ │ ├── SessionPreKeyBundle.html │ │ │ │ ├── SessionPreKeyPublic.html │ │ │ │ ├── SessionPublicPreKey.html │ │ │ │ ├── SessionPublicSignedPreKey.html │ │ │ │ ├── SessionSignedPreKey.html │ │ │ │ ├── SessionSignedPreKeyPublic.html │ │ │ │ ├── SignalAddress.html │ │ │ │ ├── SignalCommonCrypto.html │ │ │ │ ├── SignalCrypto.html │ │ │ │ ├── SignalMessage.html │ │ │ │ ├── SignalSenderKeyName.html │ │ │ │ ├── Signal_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Signal_Fingerprint.html │ │ │ │ ├── Signal_KeyPair.html │ │ │ │ ├── Signal_PreKey.html │ │ │ │ ├── Signal_PreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_PreKeySignalMessage.html │ │ │ │ ├── Signal_Record.html │ │ │ │ ├── Signal_Record │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SenderKeyDistributionMessage.html │ │ │ │ ├── Signal_SenderKeyMessage.html │ │ │ │ ├── Signal_SenderKeyRecord.html │ │ │ │ ├── Signal_SenderKeyState.html │ │ │ │ ├── Signal_SenderKeyState │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_Session.html │ │ │ │ ├── Signal_Session │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Signal_SignalMessage.html │ │ │ │ ├── Signal_SignedPreKey.html │ │ │ │ ├── Signal_SignedPreKey │ │ │ │ │ ├── PublicPart.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── SymmetricParameters.html │ │ │ │ ├── Textsecure_CombinedFingerprints.html │ │ │ │ ├── Textsecure_CombinedFingerprints │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_DeviceConsistencyCodeMessage.html │ │ │ │ ├── Textsecure_IdentityKeyPairStructure.html │ │ │ │ ├── Textsecure_KeyExchangeMessage.html │ │ │ │ ├── Textsecure_LogicalFingerprint.html │ │ │ │ ├── Textsecure_PreKeyRecordStructure.html │ │ │ │ ├── Textsecure_PreKeySignalMessage.html │ │ │ │ ├── Textsecure_RecordStructure.html │ │ │ │ ├── Textsecure_RecordStructure │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SenderKeyDistributionMessage.html │ │ │ │ ├── Textsecure_SenderKeyMessage.html │ │ │ │ ├── Textsecure_SenderKeyRecordStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure.html │ │ │ │ ├── Textsecure_SenderKeyStateStructure │ │ │ │ │ ├── SenderChainKey.html │ │ │ │ │ ├── SenderMessageKey.html │ │ │ │ │ ├── SenderSigningKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SessionStructure.html │ │ │ │ ├── Textsecure_SessionStructure │ │ │ │ │ ├── Chain.html │ │ │ │ │ ├── Chain │ │ │ │ │ │ ├── ChainKey.html │ │ │ │ │ │ ├── MessageKey.html │ │ │ │ │ │ └── _StorageClass.html │ │ │ │ │ ├── PendingKeyExchange.html │ │ │ │ │ ├── PendingPreKey.html │ │ │ │ │ └── _StorageClass.html │ │ │ │ ├── Textsecure_SignalMessage.html │ │ │ │ ├── Textsecure_SignedPreKeyRecordStructure.html │ │ │ │ └── _GeneratedWithProtocGenSwiftVersion.html │ │ │ ├── Typealiases.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ └── SignalProtocolSwift.tgz ├── img │ ├── carat.png │ ├── dash.png │ └── gh.png ├── index.html ├── js │ ├── jazzy.js │ └── jquery.min.js ├── search.json └── undocumented.json ├── LibSignalProtocolSwift.podspec ├── License ├── Podfile ├── Podfile.lock ├── README.md ├── SignalProtocol.xcodeproj └── project.pbxproj ├── SignalProtocol.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Sources ├── Additional │ └── SignalError.swift ├── DeviceConsistency │ ├── DeviceConsistencyCommitment.swift │ ├── DeviceConsistencyMessage.swift │ └── DeviceConsistencySignature.swift ├── Elliptic Keys │ ├── KeyPair.swift │ ├── PrivateKey.swift │ └── PublicKey.swift ├── Encryption │ ├── GroupCipher.swift │ ├── SenderKeyRecord.swift │ ├── SenderKeyState.swift │ ├── SessionCipher.swift │ ├── SignalCommonCrypto.swift │ ├── SignalCrypto.swift │ └── SignalCryptoProvider.swift ├── Fingerprint │ ├── DisplayableFingerprint.swift │ ├── Fingerprint.swift │ └── ScannableFingerprint.swift ├── Info │ └── Info.plist ├── LocalStorage │ ├── GroupKeyStore.swift │ ├── IdentityKeyStore.swift │ ├── KeyStore.swift │ ├── PreKeyStore.swift │ ├── SenderKeyStore.swift │ ├── SessionStore.swift │ └── SignedPrekeyStore.swift ├── MessageExchange │ ├── CipherTextMessage.swift │ ├── PreKeySignalMessage.swift │ ├── SenderKeyDistributionMessage.swift │ ├── SenderKeyMessage.swift │ └── SignalMessage.swift ├── ProtocolBuffers │ ├── Fingerprint.pb.swift │ ├── Fingerprint.proto │ ├── LocalStorage.pb.swift │ ├── LocalStorage.proto │ ├── Messages.pb.swift │ ├── Messages.proto │ ├── ProtocolBufferConvertible.swift │ ├── ProtocolBufferEquivalent.swift │ └── ProtocolBufferSerializable.swift ├── Ratchet │ ├── HKDF.swift │ ├── RatchetChainKey.swift │ ├── RatchetMessageKeys.swift │ ├── RatchetRootKey.swift │ ├── ReceiverChain.swift │ ├── SenderChain.swift │ ├── SenderChainKey.swift │ └── SenderMessageKey.swift ├── Session │ ├── PendingPreKey.swift │ ├── SessionBuilder.swift │ ├── SessionPreKey.swift │ ├── SessionPreKeyBundle.swift │ ├── SessionPreKeyPublic.swift │ ├── SessionRecord.swift │ ├── SessionSignedPreKey.swift │ ├── SessionSignedPreKeyPublic.swift │ └── SessionState.swift └── SignalProtocol.h └── Tests ├── CurveTests.swift ├── DeviceConsistencyTests.swift ├── FingerprintTests.swift ├── GroupCipherTests.swift ├── HKDFTests.swift ├── Info.plist ├── KeyHelperTests.swift ├── ProtocolTests.swift ├── RatchetTests.swift ├── SenderKeyRecordTests.swift ├── SessionBuilderTests.swift ├── SessionCipherTests.swift ├── SessionRecordTests.swift ├── SignalProtocolSwiftTests.swift ├── SignedKeyTests.swift ├── Test Implementation ├── SignalAddress.swift ├── SignalSenderKeyName.swift ├── TestFakeCryptoProvider.swift ├── TestIdentityStore.swift ├── TestPreKeyStore.swift ├── TestSenderKeyStore.swift ├── TestSessionStore.swift ├── TestSignedPreKeyStore.swift └── TestStore.swift └── TestHelperFunctions.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/git,macos,swift,xcode 3 | 4 | ### Git ### 5 | *.orig 6 | 7 | ### macOS ### 8 | *.DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Swift ### 35 | # Xcode 36 | # 37 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 38 | 39 | ## Build generated 40 | build/ 41 | DerivedData/ 42 | 43 | ## Various settings 44 | *.pbxuser 45 | !default.pbxuser 46 | *.mode1v3 47 | !default.mode1v3 48 | *.mode2v3 49 | !default.mode2v3 50 | *.perspectivev3 51 | !default.perspectivev3 52 | xcuserdata/ 53 | 54 | ## Other 55 | *.moved-aside 56 | *.xccheckout 57 | *.xcscmblueprint 58 | 59 | ## Obj-C/Swift specific 60 | *.hmap 61 | *.ipa 62 | *.dSYM.zip 63 | *.dSYM 64 | 65 | ## Playgrounds 66 | timeline.xctimeline 67 | playground.xcworkspace 68 | 69 | # Swift Package Manager 70 | # 71 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 72 | # Packages/ 73 | # Package.pins 74 | .build/ 75 | 76 | # CocoaPods - Refactored to standalone file 77 | 78 | # Carthage - Refactored to standalone file 79 | 80 | # fastlane 81 | # 82 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 83 | # screenshots whenever they are needed. 84 | # For more information about the recommended setup visit: 85 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 86 | 87 | fastlane/report.xml 88 | fastlane/Preview.html 89 | fastlane/screenshots 90 | fastlane/test_output 91 | 92 | ### Xcode ### 93 | # Xcode 94 | # 95 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 96 | 97 | ## Build generated 98 | 99 | ## Various settings 100 | 101 | ## Other 102 | 103 | ### Xcode Patch ### 104 | *.xcodeproj/* 105 | !*.xcodeproj/project.pbxproj 106 | !*.xcodeproj/xcshareddata/ 107 | !*.xcworkspace/contents.xcworkspacedata 108 | /*.gcno 109 | 110 | 111 | # End of https://www.gitignore.io/api/git,macos,swift,xcode 112 | 113 | Pods 114 | -------------------------------------------------------------------------------- /Documentation/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Documentation/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.libsignalprotocolswift 7 | CFBundleName 8 | LibSignalProtocolSwift 9 | DocSetPlatformFamily 10 | libsignalprotocolswift 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation100%100% -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/Documents/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/User/Development/Github/LibSignalProtocolSwift" 6 | } -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/LibSignalProtocolSwift.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Documentation/docsets/LibSignalProtocolSwift.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/LibSignalProtocolSwift.tgz -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.signalprotocol 7 | CFBundleName 8 | SignalProtocol 9 | DocSetPlatformFamily 10 | signalprotocol 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 99% 23 | 24 | 25 | 99% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/Documents/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/user/Development/LibSignalProtocolSwift/Sources/Additional/SignalError.swift", 5 | "line": 33, 6 | "symbol": "SignalErrorType.invalidIV", 7 | "symbol_kind": "source.lang.swift.decl.enumelement", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/user/Development/LibSignalProtocolSwift/Sources/LocalStorage/GroupKeyStore.swift", 12 | "line": 11, 13 | "symbol": "GroupKeyStore", 14 | "symbol_kind": "source.lang.swift.decl.protocol", 15 | "warning": "undocumented" 16 | } 17 | ], 18 | "source_directory": "/Users/user/Development/LibSignalProtocolSwift" 19 | } -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocol.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocol.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocol.tgz -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.signalprotocolswift 7 | CFBundleName 8 | SignalProtocolSwift 9 | DocSetPlatformFamily 10 | signalprotocolswift 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation70%70% -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocolSwift.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Documentation/docsets/SignalProtocolSwift.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/docsets/SignalProtocolSwift.tgz -------------------------------------------------------------------------------- /Documentation/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/img/carat.png -------------------------------------------------------------------------------- /Documentation/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/img/dash.png -------------------------------------------------------------------------------- /Documentation/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophhagen/LibSignalProtocolSwift/5f93c2c571831d5fe92f7f76a5d45832e32ba94e/Documentation/img/gh.png -------------------------------------------------------------------------------- /Documentation/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | -------------------------------------------------------------------------------- /Documentation/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/user/Development/LibSignalProtocolSwift" 6 | } -------------------------------------------------------------------------------- /LibSignalProtocolSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'LibSignalProtocolSwift' 3 | spec.summary = 'A Swift implementation of the Signal Protocol' 4 | spec.license = 'MIT' 5 | 6 | spec.version = '1.3' 7 | spec.source = { 8 | :git => 'https://github.com/christophhagen/LibSignalProtocolSwift.git', 9 | :tag => spec.version 10 | } 11 | spec.swift_version = '5.0' 12 | spec.module_name = 'SignalProtocol' 13 | 14 | spec.authors = { 'Christoph Hagen' => 'christoph@spacemasters.eu' } 15 | spec.homepage = 'https://github.com/christophhagen/LibSignalProtocolSwift' 16 | 17 | spec.ios.deployment_target = '9.0' 18 | spec.osx.deployment_target = '10.9' 19 | spec.tvos.deployment_target = '9.0' 20 | spec.watchos.deployment_target = '4.0' 21 | 22 | spec.source_files = 'Sources/**/*.{swift,h}' 23 | 24 | spec.dependency 'SwiftProtobuf' 25 | spec.dependency 'Curve25519' 26 | spec.dependency 'CommonCryptoModule' 27 | end 28 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | abstract_target 'LibSignalProtocolSwift' do 4 | 5 | # Pods for LibSignalProtocolSwift 6 | 7 | # Protocol Buffers in Swift 8 | pod 'SwiftProtobuf', '~> 1.5.0' 9 | 10 | # Elliptic Curve functions 11 | pod 'Curve25519', '~> 1.1' 12 | 13 | # Cryptographic functions powered by CommonCrypto 14 | pod 'CommonCryptoModule', '~> 1.0.2' 15 | 16 | 17 | # iOS 18 | target 'SignalProtocol iOS' do 19 | platform :ios, '9.0' 20 | 21 | target 'SignalProtocol Tests' do 22 | inherit! :search_paths 23 | # Pods for testing 24 | 25 | end 26 | end 27 | 28 | 29 | # macOS 30 | target 'SignalProtocol macOS' do 31 | platform :osx, '10.9' 32 | # Pods for LibSignalProtocolSwift macOS 33 | end 34 | 35 | 36 | # tvOS 37 | target 'SignalProtocol tvOS' do 38 | platform :tvos, '9.0' 39 | # Pods for LibSignalProtocolSwift tvOS 40 | end 41 | 42 | 43 | # watchOS 44 | target 'SignalProtocol watchOS' do 45 | platform :watchos, '4.0' 46 | # Pods for LibSignalProtocolSwift watchOS 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CommonCryptoModule (1.0.2) 3 | - Curve25519 (1.1) 4 | - SwiftProtobuf (1.5.0) 5 | 6 | DEPENDENCIES: 7 | - CommonCryptoModule (~> 1.0.2) 8 | - Curve25519 9 | - SwiftProtobuf 10 | 11 | SPEC REPOS: 12 | https://github.com/cocoapods/specs.git: 13 | - CommonCryptoModule 14 | - Curve25519 15 | - SwiftProtobuf 16 | 17 | SPEC CHECKSUMS: 18 | CommonCryptoModule: 63a68b57abed79655b3a01011419bf037a05b0a0 19 | Curve25519: 5f595e906bd962248665978e2992a1fe96e2d831 20 | SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 21 | 22 | PODFILE CHECKSUM: b5550621b144cb6064196a28676c938b681e2db2 23 | 24 | COCOAPODS: 1.5.2 25 | -------------------------------------------------------------------------------- /SignalProtocol.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SignalProtocol.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/DeviceConsistency/DeviceConsistencyCommitment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceConsistencyCommitment.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | Create commitments that are hashes of the identity keys of different devices. 12 | These can be used to ensure that all identities are consistent across multiple 13 | devices. 14 | */ 15 | struct DeviceConsistencyCommitmentV0 { 16 | 17 | /// The version of the consistency implementation 18 | private static let codeVersion: UInt16 = 0 19 | 20 | /// An identifier used when hashing the identity keys 21 | private static let version = "DeviceConsistencyCommitment_V0".data(using: .utf8)! 22 | 23 | /// The generation of the message 24 | var generation: UInt32 25 | 26 | /// The hash of the public keys 27 | var serialized: Data 28 | 29 | /** 30 | Create a new commitment. 31 | - parameter generation: The version of the message 32 | - identityKeyList: The list of the identity keys of the participating devices 33 | - throws: `SignalError` errors thrown by the `sha512(for:)` function of the `SignalCryptoProvider` 34 | */ 35 | init(generation: UInt32, identityKeyList: [PublicKey]) throws { 36 | let list = identityKeyList.sorted() 37 | var gen = generation 38 | let data = withUnsafePointer(to: &gen) { Data(bytes: $0, count: MemoryLayout.size) } 39 | 40 | var bytes = DeviceConsistencyCommitmentV0.version + data 41 | for item in list { 42 | bytes += item.data 43 | } 44 | self.serialized = try SignalCrypto.sha512(for: bytes) 45 | self.generation = generation 46 | } 47 | 48 | /** 49 | Generate a String which can be used to compare the consistency across multiple devices. 50 | The output is created by hashing the code version, hashed identity keys and device signatures, and then using parts of that hash as a String. 51 | - parameter signatureList: The list of device consistancy signatures received from other devices 52 | - throws: `SignalError` errors thrown by the `sha512(for:)` function of the `SignalCryptoProvider` or other errors 53 | - returns: The String created from the signatures, 6 characters long 54 | */ 55 | func generateCode(for signatureList: [DeviceConsistencySignature]) throws -> String { 56 | let list = signatureList.sorted() 57 | 58 | let byte0 = UInt8(DeviceConsistencyCommitmentV0.codeVersion & 0x00FF) 59 | let byte1 = UInt8((DeviceConsistencyCommitmentV0.codeVersion & 0xFF00) >> 8) 60 | var bytes = Data([byte0, byte1]) + self.serialized 61 | 62 | for item in list { 63 | bytes += item.vrfOutput 64 | } 65 | 66 | let hash = try SignalCrypto.sha512(for: bytes) 67 | guard hash.count >= 10 else { 68 | throw SignalError(.digestError, "SHA512 hash is only \(hash.count) bytes") 69 | } 70 | let data = hash.map { UInt64($0) } 71 | let a1 = (data[0] << 32) | (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4] 72 | let a2 = (data[5] << 32) | (data[6] << 24) | (data[7] << 16) | (data[8] << 8) | data[9] 73 | let b1 = Int(a1) % 100000 74 | let b2 = Int(a2) % 100000 75 | let longString = String(format: "%05d%05d", b1, b2) 76 | return String(longString.prefix(7)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/DeviceConsistency/DeviceConsistencyMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceConsistencyMessage.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | Device consistency messages can be sent between multiple devices to verify that the 12 | identity keys and are consistent across devices. 13 | */ 14 | struct DeviceConsistencyMessage { 15 | 16 | /// The consistency signature 17 | var signature: DeviceConsistencySignature 18 | 19 | /// The generation of the consistency message 20 | var generation: UInt32 21 | 22 | /** 23 | Create a new consistency message. 24 | - parameter commitment: The hashed identity keys 25 | - parameter identityKeyPair: The key pair of the sender 26 | - throws: `SignalError` errors 27 | */ 28 | init(commitment: DeviceConsistencyCommitmentV0, identitykeyPair: KeyPair) throws { 29 | 30 | let serialized = commitment.serialized 31 | 32 | /* Calculate VRF signature */ 33 | let signature = try identitykeyPair.privateKey.signVRF(message: serialized) 34 | 35 | /* Verify VRF signature */ 36 | let vrfOutput = try identitykeyPair.publicKey.verify(vrfSignature: signature, for: serialized) 37 | 38 | /* Create and assign the signature */ 39 | self.signature = DeviceConsistencySignature(signature: signature, vrfOutput: vrfOutput) 40 | self.generation = commitment.generation 41 | } 42 | } 43 | 44 | // MARK: Protocol Buffers 45 | 46 | extension DeviceConsistencyMessage { 47 | 48 | /** 49 | The message converted to a protocol buffer object. 50 | */ 51 | var object: Signal_DeviceConsistencyCodeMessage { 52 | return Signal_DeviceConsistencyCodeMessage.with { 53 | $0.generation = self.generation 54 | $0.signature = self.signature.signature 55 | } 56 | } 57 | 58 | /** 59 | Create a consistency message from a protocol buffer object. 60 | - parameter object: The protocol buffer object 61 | - parameter commitment: The commitment needed for verification 62 | - parameter identityKey: The identity key needed for verification 63 | - throws: `SignalError` errors 64 | */ 65 | init(from object: Signal_DeviceConsistencyCodeMessage, 66 | commitment: DeviceConsistencyCommitmentV0, 67 | identityKey: PublicKey) throws { 68 | guard object.hasSignature, object.hasGeneration else { 69 | throw SignalError(.invalidProtoBuf, "Missing data in ProtoBuf object") 70 | } 71 | 72 | /* Verify VRF signature */ 73 | let vrfOutput = try identityKey.verify(vrfSignature: object.signature, for: commitment.serialized) 74 | 75 | /* Assign the message fields */ 76 | self.generation = object.generation 77 | self.signature = DeviceConsistencySignature(signature: object.signature, vrfOutput: vrfOutput) 78 | } 79 | } 80 | 81 | extension DeviceConsistencyMessage { 82 | 83 | /** 84 | The message serialized through a protocol buffer. 85 | - throws: `SignalError` of type `invalidProtoBuf` 86 | - returns: The serialized record of the message 87 | */ 88 | func data() throws -> Data { 89 | do { 90 | return try object.serializedData() 91 | } catch { 92 | throw SignalError(.invalidProtoBuf, 93 | "Could not serialize DeviceConsistencyMessage: \(error.localizedDescription)") 94 | } 95 | } 96 | 97 | /** 98 | Create a consistency message from a serialized protocol buffer record. 99 | - parameter data: The serialized data 100 | - parameter commitment: The commitment needed for verification 101 | - parameter identityKey: The identity key needed for verification 102 | - throws: `SignalError` errors 103 | */ 104 | init(from data: Data, commitment: DeviceConsistencyCommitmentV0, identityKey: PublicKey) throws { 105 | let object: Signal_DeviceConsistencyCodeMessage 106 | do { 107 | object = try Signal_DeviceConsistencyCodeMessage(serializedData: data) 108 | } catch { 109 | throw SignalError(.invalidProtoBuf, 110 | "Could not deserialize DeviceConsistencyMessage: \(error.localizedDescription)") 111 | } 112 | try self.init(from: object, commitment: commitment, identityKey: identityKey) 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Sources/DeviceConsistency/DeviceConsistencySignature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceConsistencySignature.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A signature used for device consistency checks 12 | */ 13 | struct DeviceConsistencySignature { 14 | 15 | /// The signature data 16 | var signature: Data 17 | 18 | /// The output of the VRF verification 19 | var vrfOutput: Data 20 | 21 | /** 22 | Create a new signature 23 | - parameter signature: The signature data 24 | - parameter vrfOutput: The output of the VRF verification 25 | */ 26 | init(signature: Data, vrfOutput: Data) { 27 | self.signature = signature 28 | self.vrfOutput = vrfOutput 29 | } 30 | } 31 | 32 | extension DeviceConsistencySignature: Comparable { 33 | 34 | /** 35 | Compare two consistency signatures. 36 | - note: The signatures are compared solely by their vrf outputs 37 | - parameter lhs: The first signature 38 | - parameter rhs: The second signature 39 | - returns: `True`, if the first signature is 'smaller' than the second signature 40 | */ 41 | static func <(lhs: DeviceConsistencySignature, rhs: DeviceConsistencySignature) -> Bool { 42 | guard lhs.vrfOutput.count == rhs.vrfOutput.count else { 43 | return lhs.vrfOutput.count < rhs.vrfOutput.count 44 | } 45 | for i in 0.. Bool { 61 | return lhs.vrfOutput == rhs.vrfOutput 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Elliptic Keys/PrivateKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrivateKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 27.01.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | import Foundation 9 | import Curve25519 10 | 11 | /** 12 | The private part of an elliptic curve key pair. 13 | The key has a length of `KeyPair.keyLength` byte. 14 | */ 15 | public struct PrivateKey { 16 | 17 | /// The key material of length `KeyPair.keyLength` 18 | private let key: Data 19 | 20 | /** 21 | Create a private key from a curve point. 22 | - parameter point: The private key data 23 | - returns: The key 24 | - throws: `SignalError` of type `invalidProtoBuf` 25 | */ 26 | init(point: Data) throws { 27 | guard point.count == Curve25519.keyLength else { 28 | throw SignalError(.invalidProtoBuf, "Invalid key length: \(point.count)") 29 | } 30 | guard point[0] & 0b00000111 == 0 else { 31 | throw SignalError(.invalidProtoBuf, "Invalid private key (byte 0 == \(point[0])") 32 | } 33 | 34 | let lastByteIndex = Curve25519.keyLength - 1 35 | guard point[lastByteIndex] & 0b10000000 == 0 else { 36 | throw SignalError(.invalidProtoBuf, "Invalid private key (byte \(lastByteIndex) == \(point[lastByteIndex])") 37 | } 38 | guard point[lastByteIndex] & 0b01000000 != 0 else { 39 | throw SignalError(.invalidProtoBuf, "Invalid private key (byte \(lastByteIndex) == \(point[lastByteIndex])") 40 | } 41 | key = point 42 | } 43 | 44 | /** 45 | Create a private key. Only checks the length, nothing else. 46 | - note: Possible errors are: 47 | - `invalidLength`, if the data has the wrong length 48 | - parameter point: The private key data 49 | - returns: The key 50 | - throws: `SignalError` errors 51 | */ 52 | init(unverifiedPoint point: Data) throws { 53 | guard point.count == Curve25519.keyLength else { 54 | throw SignalError(.invalidLength, "Invalid key length: \(point.count)") 55 | } 56 | key = point 57 | } 58 | 59 | /** 60 | Create a new random private key. 61 | - throws: Any error from `SignalCrypto.random(bytes:)` 62 | */ 63 | public init() throws { 64 | var random = try SignalCrypto.random(bytes: Curve25519.keyLength) 65 | random[0] &= 248 // 0b11111000 66 | random[31] = (random[31] & 127) | 64 // & 0b01111111 | 0b01000000 67 | self.key = random 68 | } 69 | 70 | /** 71 | Calculate the signature for the given message. 72 | - parameter message: The message to sign 73 | - returns: The signature of the message, `KeyPair.signatureLength` bytes 74 | - throws: `SignalError` errors: 75 | `invalidSignature`, if the message could not be signed. 76 | `noRandomBytes`, if the crypto provider could not provide random bytes. 77 | */ 78 | public func sign(message: Data) throws -> Data { 79 | let random = try SignalCrypto.random(bytes: Curve25519.signatureLength) 80 | 81 | do { 82 | return try Curve25519.signature(for: message, privateKey: key, randomData: random) 83 | } catch { 84 | throw SignalError(.invalidSignature, "Could not sign message: \(error)") 85 | } 86 | } 87 | 88 | /** 89 | Calculates a unique Curve25519 signature for the private key 90 | - parameter message: The message to sign 91 | - returns: The 96-byte signature on success 92 | - throws: `SignalError` 93 | */ 94 | func signVRF(message: Data) throws -> Data { 95 | let random = try SignalCrypto.random(bytes: 32) 96 | 97 | do { 98 | return try Curve25519.vrfSignature(for: message, privateKey: key, randomData: random) 99 | } catch { 100 | throw SignalError(.invalidSignature, "VRF signature failed: \(error)") 101 | } 102 | } 103 | 104 | /** 105 | Calculate the shared agreement between the private key and the given public key. 106 | - note: The returned data has a length of `KeyPair.keyLength` byte. 107 | - parameter publicKey: The public key from the other party 108 | - returns: The agreement data, or `nil` on error 109 | */ 110 | public func calculateAgreement(publicKey: PublicKey) throws -> Data { 111 | return try publicKey.calculateAgreement(privateKey: self) 112 | } 113 | 114 | /// The serialized data of the private key 115 | var data: Data { 116 | return key 117 | } 118 | 119 | /** 120 | Create the corresponding key pair for the private key 121 | - throws `SignalError.curveError` if the public key could not be created 122 | */ 123 | func keyPair() throws -> KeyPair { 124 | return try KeyPair(privateKey: self) 125 | } 126 | 127 | /** 128 | Create the corresponding public key for the private key 129 | - throws `SignalError.curveError` if the public key could not be created 130 | */ 131 | func publicKey() throws -> PublicKey { 132 | return try PublicKey(privateKey: self) 133 | } 134 | } 135 | 136 | extension PrivateKey: Equatable { 137 | /** 138 | Compare two private keys for equality. 139 | - parameter lhs: The first key. 140 | - parameter rhs: The second key. 141 | - returns: `True`, if the keys are equal 142 | */ 143 | public static func ==(lhs: PrivateKey, rhs: PrivateKey) -> Bool { 144 | return lhs.key == rhs.key 145 | } 146 | } 147 | 148 | // MARK: Protocol Buffers 149 | 150 | extension PrivateKey: ProtocolBufferSerializable { 151 | 152 | /** 153 | Create a private key from a byte record. 154 | - parameter data: The byte record 155 | - returns: The private key 156 | - throws: `SignalError.invalidProtoBuf` 157 | */ 158 | public init(from data: Data) throws { 159 | try self.init(point: data) 160 | } 161 | 162 | /// Convert the key to serialized data 163 | public func protoData() -> Data { 164 | return key 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Sources/Elliptic Keys/PublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 27.01.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Curve25519 11 | 12 | /** 13 | The public part of an elliptic curve key pair. 14 | The key has a length of `KeyPair.keyLength` byte. 15 | */ 16 | public struct PublicKey { 17 | 18 | /// The base point for the Curve25519 elliptic curve 19 | private static let basePoint = Data([9] + [UInt8](repeating: 0, count: 31)) 20 | 21 | /// The key material of length `KeyPair.keyLength` 22 | private let key: Data 23 | 24 | /** 25 | Create a public key from a UInt8 array. Checks 26 | if length and type are okay. 27 | - parameter point: The input point as an array 28 | - throws: `SignalError` of type `invalidProtoBuf` 29 | */ 30 | init(point: Data) throws { 31 | guard point.count == Curve25519.keyLength else { 32 | throw SignalError(.invalidProtoBuf, "Invalid key length \(point.count)") 33 | } 34 | self.key = point 35 | } 36 | 37 | /** 38 | Generate a public key from a given private key. 39 | Fails if the key could not be generated. 40 | - parameter privateKey: The private key of the pair 41 | - throws: `SignalError.curveError` if the public key could not be created 42 | */ 43 | public init(privateKey: PrivateKey) throws { 44 | do { 45 | self.key = try Curve25519.publicKey(for: privateKey.data, basepoint: PublicKey.basePoint) 46 | } catch { 47 | throw SignalError(.curveError, "Could not create public key from private key: \(error)") 48 | } 49 | } 50 | 51 | /** 52 | Verify that the signature corresponds to the message. 53 | - parameter signature: The signature data 54 | - parameter message: The message for which the signature is checked 55 | - returns: True, if the signature is valid 56 | */ 57 | public func verify(signature: Data, for message: Data) -> Bool { 58 | return Curve25519.verify(signature: signature, for: message, publicKey: key) 59 | } 60 | 61 | /** 62 | Verify that the vrf signature corresponds to the message. 63 | - parameter signature: The vrf signature data 64 | - parameter message: The message for which the signature is checked 65 | - returns: The vrf output 66 | - throws: `SignalError.invalidSignature` if the signature is invalid 67 | */ 68 | func verify(vrfSignature: Data, for message: Data) throws -> Data { 69 | do { 70 | return try Curve25519.verify(vrfSignature: vrfSignature, for: message, publicKey: key) 71 | } catch { 72 | throw SignalError(.invalidSignature, "Invalid vrf signature: \(error)") 73 | } 74 | } 75 | 76 | /** 77 | Calculate the shared agreement between the given private key and the public key. 78 | - note: The returned data has a length of `KeyPair.keyLength` byte. 79 | - parameter privateKey: The private key from the other party 80 | - returns: The agreement data, or `nil` on error 81 | */ 82 | public func calculateAgreement(privateKey: PrivateKey) throws -> Data { 83 | do { 84 | return try Curve25519.calculateAgreement(privateKey: privateKey.data, publicKey: key) 85 | } catch { 86 | throw SignalError(.curveError, "Could not calculate curve25519 agreement: \(error)") 87 | } 88 | } 89 | } 90 | 91 | extension PublicKey: Comparable { 92 | 93 | /** 94 | Compare two public keys. 95 | - parameter lhs: The key of the left hand side 96 | - parameter rhs: The key of the right hand side 97 | - returns: The comparison result of first pair of bytes that is not equal, or `false` 98 | */ 99 | public static func <(lhs: PublicKey, rhs: PublicKey) -> Bool { 100 | for i in 0.. Bool { 115 | return lhs.key == rhs.key 116 | } 117 | 118 | /// The serialized data of the public key 119 | public var data: Data { 120 | return key 121 | } 122 | } 123 | 124 | // MARK: Protocol Buffers 125 | 126 | extension PublicKey: ProtocolBufferSerializable { 127 | 128 | /** 129 | Create a public key from a serialized record. 130 | - parameter data: The byte record of the object 131 | - returns: The object 132 | - throws: `SignalError.invalidProtoBuf` 133 | */ 134 | public init(from data: Data) throws { 135 | try self.init(point: data) 136 | } 137 | 138 | /** 139 | Return a byte representation of the public key 140 | - returns: The byte record 141 | */ 142 | public func protoData() -> Data { 143 | return data 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Sources/Encryption/SenderKeyRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderKeyRecord.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 01.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Stores the states for a session. 13 | */ 14 | final class SenderKeyRecord { 15 | 16 | /// The maximum number of different states that are saved 17 | private static let maxStates = 5 18 | 19 | /// The states that are saved by the record, sorted by most recent 20 | private var states = [SenderKeyState]() 21 | 22 | /// The active state is the most recent, if any states exist 23 | var state: SenderKeyState? { 24 | return states.first 25 | } 26 | 27 | /// Indicate if there are any states in this record 28 | var isEmpty: Bool { 29 | return states.count == 0 30 | } 31 | 32 | /** 33 | Create a fresh session record without any states. 34 | */ 35 | init() { 36 | // Nothing to do here 37 | } 38 | 39 | /** 40 | Get the state for an id. 41 | - parameter id: The key id of the requested state 42 | - returns: The state for the id, if present 43 | */ 44 | func state(for id: UInt32) -> SenderKeyState? { 45 | for item in states { 46 | if item.keyId == id { 47 | return state 48 | } 49 | } 50 | return nil 51 | } 52 | 53 | /** 54 | Set a new sender key state and delete all previous states. 55 | - parameter id: The state id 56 | - parameter iteration: The state iteration 57 | - parameter chainKey: The serialized chain key for the state 58 | - parameter signatureKeyPair: The key for the state. 59 | */ 60 | func setSenderKey( 61 | id: UInt32, 62 | iteration: UInt32, 63 | chainKey: Data, 64 | signatureKeyPair: KeyPair) { 65 | 66 | self.states = [] 67 | addState( 68 | id: id, 69 | iteration: iteration, 70 | chainKey: chainKey, 71 | signatureKeyPair: signatureKeyPair) 72 | } 73 | 74 | /** 75 | Add a new sender key state. 76 | - note: Deletes old states if the maximum number is reached. 77 | - parameter id: The state id 78 | - parameter iteration: The state iteration 79 | - parameter chainKey: The serialized chain key for the state 80 | - parameter signaturePublicKey: The public key for the state. 81 | - parameter signaturePrivateKey: The private key for the state. 82 | */ 83 | func addState( 84 | id: UInt32, 85 | iteration: UInt32, 86 | chainKey: Data, 87 | signaturePublicKey: PublicKey, 88 | signaturePrivateKey: PrivateKey?) { 89 | 90 | let chainKeyElement = SenderChainKey( 91 | iteration: iteration, 92 | chainKey: chainKey) 93 | 94 | let state = SenderKeyState( 95 | keyId: id, 96 | chainKey: chainKeyElement, 97 | signaturePublicKey: signaturePublicKey, 98 | signaturePrivateKey: signaturePrivateKey) 99 | 100 | states.insert(state, at: 0) 101 | 102 | if states.count > SenderKeyRecord.maxStates { 103 | states = Array(states[0.. Bool { 161 | return lhs.states == rhs.states 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Sources/Encryption/SignalCryptoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignalCryptoProvider.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 07.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Specifies the type of algorithm to use for encryption and decryption. 13 | */ 14 | public enum SignalEncryptionScheme { 15 | /// Encrypt/decrypt with AES in CBC mode with PKCS5 padding 16 | case AES_CBCwithPKCS5 17 | /// Encrypt/decrypt with AES in CTR mode with no padding 18 | case AES_CTRnoPadding 19 | } 20 | 21 | /** 22 | The `SignalCryptoProvider` protocol can be implemented to provide a custom 23 | implementation of the cryptographic functions. Set the crypto provider 24 | by setting the static `provider` variable of the SignalCrypto class 25 | */ 26 | public protocol SignalCryptoProvider { 27 | 28 | /** 29 | Create a number of secure random bytes. 30 | - parameter bytes: The number of random bytes to create 31 | - returns: The random bytes of length `bytes` 32 | - throws: Should only throw errors of type `SignalError.noRandomBytes` 33 | */ 34 | func random(bytes: Int) throws -> Data 35 | 36 | /** 37 | Authenticate a message with the HMAC based on SHA256. 38 | - parameter message: The message to authenticate 39 | - salt: The salt for the HMAC. 40 | - returns: The HMAC 41 | - throws: Should only throw errors of type `SignalError.hmacError` 42 | */ 43 | func hmacSHA256(for message: Data, with salt: Data) throws -> Data 44 | 45 | /** 46 | Return the SHA512 message digest. 47 | - parameter message: The message to calculate the digest for 48 | - returns: The SHA512 digest 49 | - throws: Should only throw errors of type `SignalError.digestError` 50 | */ 51 | func sha512(for message: Data) throws -> Data 52 | 53 | /** 54 | Encrypt a message with the given scheme. 55 | - parameter message: The data to encrypt 56 | - parameter cipher: The encryption type to use, see `SignalEncryptionScheme` 57 | - parameter key: The encryption key 58 | - parameter iv: The initialization vector 59 | - returns: The encrypted message 60 | - throws: Should only throw errors of type `SignalError.encryptionError` 61 | */ 62 | func encrypt(message: Data, with cipher: SignalEncryptionScheme, key: Data, iv: Data) throws -> Data 63 | 64 | /** 65 | Decrypt a message with the given scheme. 66 | - parameter message: The data to decrypt 67 | - parameter cipher: The encryption type to use, see `SignalEncryptionScheme` 68 | - parameter key: The encryption key 69 | - parameter iv: The initialization vector 70 | - returns: The decrypted message 71 | - throws: Should only throw errors of type `SignalError.decryptionError` 72 | */ 73 | func decrypt(message: Data, with cipher: SignalEncryptionScheme, key: Data, iv: Data) throws -> Data 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Fingerprint/DisplayableFingerprint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DisplayableFingerprint.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A Fingerprint to verify the keys specifically for displaying to the user 12 | */ 13 | public struct DisplayableFingerprint { 14 | 15 | /// Fingerprint String of the local device 16 | let local: String 17 | 18 | /// Fingerprint String of the remote device 19 | let remote: String 20 | 21 | /// Displaytext 22 | public let displayText: String 23 | 24 | /** 25 | Create a displayable fingerprint from local and remote fingerprint data. 26 | - parameter local: The local fingerprint string 27 | - parameter remote: The remote fingerprint string 28 | */ 29 | init(local: String, remote: String) { 30 | self.local = local 31 | self.remote = remote 32 | 33 | if local <= remote { 34 | self.displayText = local + remote 35 | } else { 36 | self.displayText = remote + local 37 | } 38 | } 39 | 40 | /** 41 | Create a displayable fingerprint from local and remote fingerprint data. 42 | - parameter localFingerprint: The local fingerprint data 43 | - parameter remoteFingerprint: The remote fingerprint data 44 | - throws: The `SignalError` with `invalidLength` if the fingerprint data is invalid 45 | */ 46 | public init(localFingerprint: Data, remoteFingerprint: Data) throws { 47 | guard localFingerprint.count >= Fingerprint.length else { 48 | throw SignalError(.invalidLength, "Invalid local fingerprint length \(localFingerprint.count)") 49 | } 50 | guard remoteFingerprint.count >= Fingerprint.length else { 51 | throw SignalError(.invalidLength, "Invalid remote fingerprint length \(remoteFingerprint.count)") 52 | } 53 | let localString = DisplayableFingerprint.createDisplayString(fingerprint: localFingerprint) 54 | let remoteString = DisplayableFingerprint.createDisplayString(fingerprint: remoteFingerprint) 55 | self.init(local: localString, remote: remoteString) 56 | } 57 | 58 | /** 59 | Create a display string from fingerprint data. 60 | - parameter fingerprint: The fingerprint data 61 | - returns: The display string 62 | */ 63 | private static func createDisplayString(fingerprint: Data) -> String { 64 | let data = fingerprint.map { UInt64($0) } 65 | var output = "" 66 | for i in stride(from: 0, to: 30, by: 5) { 67 | let chunk = (data[i] << 32) | (data[i+1] << 24) 68 | let chunk2 = (data[i+2] << 16) | (data[i+3] << 8) | data[i+4] 69 | let chunk3 = chunk | chunk2 70 | let val = Int(chunk3 % 100000) 71 | output += String(format: "%05d", val) 72 | } 73 | return output 74 | } 75 | } 76 | 77 | extension DisplayableFingerprint: Equatable { 78 | 79 | /** 80 | Compare two displayable fingerprints for equality. 81 | 82 | - parameter lhs: The first fingerprint 83 | - parameter rhs: The second fingerprint 84 | - returns: `true`, if the fingerprints are equal 85 | */ 86 | public static func ==(lhs: DisplayableFingerprint, rhs: DisplayableFingerprint) -> Bool { 87 | return lhs.displayText == rhs.displayText 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Fingerprint/ScannableFingerprint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScannableFingerprint.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 11.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A fingerprint optimised to be scanned through e.g. a QR Code 13 | */ 14 | public struct ScannableFingerprint { 15 | 16 | /// The length of a fingerprint 17 | private static let length = 32 18 | 19 | /// The version of the fingerprint 20 | private static let version: UInt32 = 1 21 | 22 | /// The fingerprint data of the local party 23 | public let localFingerprint: Data 24 | 25 | /// The fingerprint data of the remote party 26 | public let remoteFingerprint: Data 27 | 28 | /** 29 | Create a new ScannableFingerprint Version 1. 30 | - parameter localFingerprint: The fingerprint data of the local party 31 | - parameter remoteFingerprint: The fingerprint data of the remote party 32 | - throws: `SignalError` of type `invalidLength` 33 | */ 34 | init(localFingerprint: Data, remoteFingerprint: Data) throws { 35 | let length = ScannableFingerprint.length 36 | guard localFingerprint.count >= length, 37 | remoteFingerprint.count >= length else { 38 | throw SignalError(.invalidLength, "Invalid fingerprint lengths \(localFingerprint.count), \(remoteFingerprint.count)") 39 | } 40 | self.localFingerprint = localFingerprint[0.. Bool { 86 | return localFingerprint == other.remoteFingerprint && 87 | remoteFingerprint == other.localFingerprint 88 | } 89 | } 90 | 91 | // MARK: Protocol Equatable 92 | 93 | extension ScannableFingerprint: Equatable { 94 | 95 | /** 96 | Compare two Fingerprints for equality. 97 | - parameter lhs: The first fingerprint 98 | - parameter rhs: The second fingerprint 99 | - returns: `true` if the fingerprints match 100 | */ 101 | public static func ==(lhs: ScannableFingerprint, rhs: ScannableFingerprint) -> Bool { 102 | return lhs.localFingerprint == rhs.localFingerprint && 103 | lhs.remoteFingerprint == rhs.remoteFingerprint 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Info/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.3 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/LocalStorage/GroupKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupKeyStore.swift 3 | // SignalProtocol 4 | // 5 | // Created by Christoph on 25.05.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A group key store stores the encryption keys for group messaging 13 | */ 14 | public protocol GroupKeyStore: KeyStore { 15 | 16 | /// The type that distinguishes different groups and devices/users 17 | associatedtype GroupAddress: CustomStringConvertible 18 | 19 | /// The type of the sender key store 20 | associatedtype SenderKeyStoreType: SenderKeyStore where SenderKeyStoreType.Address == GroupAddress 21 | 22 | /// The Sender Key store that stores the records for the sender key module 23 | var senderKeyStore: SenderKeyStoreType { get } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/LocalStorage/IdentityKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IdentityKeyStoreDelegate.swift 3 | // TestC 4 | // 5 | // Created by User on 21.09.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Implement the `IdentityKeyStore` protocol to handle the identity keys of the 13 | Signal Protocol. The keys should be stored in a secure database and be treated as 14 | unspecified data blobs. 15 | */ 16 | public protocol IdentityKeyStore: class { 17 | 18 | /// The type that distinguishes different devices/users 19 | associatedtype Address: Hashable 20 | 21 | /** 22 | Return the identity key pair. This key should be generated once at 23 | install time by calling `SignalCrypto.generateIdentityKeyPair()`, 24 | or given to the constructor. 25 | - note: An appropriate error should be thrown, if no identity key exists 26 | - returns: The identity key pair data 27 | - throws: `SignalError` of type `storageError` 28 | */ 29 | func getIdentityKeyData() throws -> Data 30 | 31 | /** 32 | Return the identity for the given address, if there is any. 33 | - note: An appropriate error should be thrown if the identity could not be accessed 34 | - parameter address: The address of the remote client 35 | - returns: The identity for the address, or nil if no data exists 36 | - throws: `SignalError` of type `storageError` 37 | */ 38 | func identity(for address: Address) throws -> Data? 39 | 40 | /** 41 | Store a remote client's identity key as trusted. 42 | - note: An appropriate error should be thrown if the identity could not be saved 43 | - parameter identity: The identity key data (may be nil, if the key should be removed) 44 | - parameter address: The address of the remote client 45 | - throws: `SignalError` of type `storageError` 46 | */ 47 | func store(identity: Data?, for address: Address) throws 48 | } 49 | 50 | extension IdentityKeyStore { 51 | 52 | /** 53 | Return the identity key pair. This key should be generated once at 54 | install time by calling `KeyStore.generateIdentityKeyPair()`. 55 | - note: Possible errors: 56 | - `storageError` if the key data could not be accessed 57 | - `invalidProtBuf` if the data is corrupt 58 | - returns: The identity key pair 59 | - throws: `SignalError` errors 60 | */ 61 | func getIdentityKey() throws -> KeyPair { 62 | let identity = try getIdentityKeyData() 63 | return try KeyPair(from: identity) 64 | } 65 | 66 | /** 67 | Return the public identity key. This key should be generated once at 68 | install time by calling `KeyStore.generateIdentityKeyPair()`. 69 | - note: Possible errors: 70 | - `storageError` if the key data could not be accessed 71 | - `invalidProtBuf` if the data is corrupt 72 | - returns: The public identity key data 73 | - throws: `SignalError` errors 74 | */ 75 | func getPublicIdentityKey() throws -> Data { 76 | let identity = try getIdentityKeyData() 77 | let pair = try KeyPair(from: identity) 78 | return pair.publicKey.data 79 | } 80 | 81 | /** 82 | Return the public identity key data. 83 | - note: Possible errors: 84 | - `storageError` if the key data could not be accessed 85 | - `invalidProtBuf` if the data is corrupt 86 | - returns: The public identity key data 87 | - throws: `SignalError` errors 88 | */ 89 | public func getIdentityKeyPublicData() throws -> Data { 90 | let identity = try getIdentityKeyData() 91 | let key = try KeyPair(from: identity) 92 | return key.publicKey.data 93 | } 94 | 95 | /** 96 | Determine whether a remote client's identity is trusted. The convention is 97 | that the TextSecure protocol is 'trust on first use.' This means that an 98 | identity key is considered 'trusted' if there is no entry for the recipient in 99 | the local store, or if it matches the saved key for a recipient in the local store. 100 | Only if it mismatches an entry in the local store is it considered 'untrusted.' 101 | 102 | - parameter identity: The identity key to verify 103 | - parameter address: The address of the remote client 104 | - returns: `true` if trusted, `false` if not trusted 105 | - throws: `SignalError` errors 106 | */ 107 | func isTrusted(identity: Data, for address: Address) throws -> Bool { 108 | if let data = try self.identity(for: address) { 109 | return data == identity 110 | } 111 | return true 112 | } 113 | 114 | /** 115 | Store a remote client's identity key as trusted. The value of key_data may be null. 116 | In this case remove the key data from the identity store, but retain any metadata 117 | that may be kept alongside it. 118 | 119 | - note: An appropriate error should be thrown if the identity could not be saved 120 | - parameter identity: The identity key (may be nil, if the key should be removed) 121 | - parameter address: The address of the remote client 122 | - throws: `SignalError` of type `storageError` 123 | */ 124 | func store(identity: PublicKey?, for address: Address) throws { 125 | try store(identity: identity?.data, for: address) 126 | } 127 | 128 | /** 129 | Determine whether a remote client's identity is trusted. The convention is 130 | that the TextSecure protocol is 'trust on first use.' This means that an 131 | identity key is considered 'trusted' if there is no entry for the recipient in 132 | the local store, or if it matches the saved key for a recipient in the local store. 133 | Only if it mismatches an entry in the local store is it considered 'untrusted.' 134 | 135 | - parameter identity: The identity key to verify 136 | - parameter address: The address of the remote client 137 | - returns: `true` if trusted, `false` if not trusted 138 | */ 139 | func isTrusted(identity: PublicKey, for address: Address) throws -> Bool { 140 | return try isTrusted(identity: identity.data, for: address) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/LocalStorage/PreKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreKeyStoreDelegate.swift 3 | // TestC 4 | // 5 | // Created by User on 21.09.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Implement the `PreKeyStore` protocol to handle the pre key storage of the 13 | Signal Protocol. The keys should be stored in a secure database and be treated as 14 | unspecified data blobs. 15 | */ 16 | public protocol PreKeyStore: class { 17 | 18 | /** 19 | Provide a Pre Key for a given id. 20 | 21 | - parameter id: The pre key ID 22 | - returns: The pre key 23 | - throws: `SignalError` of type `invalidId`, if no key exists 24 | */ 25 | func preKey(for id: UInt32) throws -> Data 26 | 27 | /** 28 | Store a pre key for a given id. 29 | - parameter preKey: The key to store 30 | - parameter id: The pre key id 31 | - throws: `SignalError` of type `storageError`, if the key can't be saved 32 | */ 33 | func store(preKey: Data, for id: UInt32) throws 34 | 35 | /** 36 | Indicate if a pre key exists for an id. 37 | - parameter id: The pre key id 38 | - returns: `true` if a key exists 39 | */ 40 | func containsPreKey(for id: UInt32) -> Bool 41 | 42 | /** 43 | Remove a pre key. 44 | - parameter id: The pre key id. 45 | - returns: `true` if the key was removed 46 | - throws: `SignalError` of type `storageError`, if the key can't be removed 47 | */ 48 | func removePreKey(for id: UInt32) throws 49 | 50 | /// Return the id of the last stored pre key. 51 | var lastId: UInt32 { get set } 52 | 53 | } 54 | 55 | extension PreKeyStore { 56 | 57 | /** 58 | Provide a Pre Key for a given id. 59 | - note: Possible errors: 60 | - `invalidId`, if no key exists 61 | - `invalidProtoBuf`, if data is corrupt or missing 62 | - parameter id: The pre key id 63 | - returns: The pre key 64 | - throws: `SignalError` 65 | */ 66 | func preKey(for id: UInt32) throws -> SessionPreKey { 67 | let data = try preKey(for: id) 68 | return try SessionPreKey(from: data) 69 | } 70 | 71 | /** 72 | Store a pre key for a given id. 73 | - note: Possible errors: 74 | - `storageError`, if no key exists 75 | - `invalidProtoBuf`, if the key could not be serialized 76 | - parameter preKey: The key to store 77 | - throws: `SignalError` 78 | */ 79 | func store(preKey: SessionPreKey) throws { 80 | let data = try preKey.protoData() 81 | try store(preKey: data, for: preKey.publicKey.id) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/LocalStorage/SenderKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderKeyStoreDelegate.swift 3 | // TestC 4 | // 5 | // Created by User on 21.09.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Implement the `SenderKeyStore` protocol to handle the sender key storage of the 13 | Signal Protocol. The keys should be stored in a secure database and be treated as 14 | unspecified data blobs. 15 | */ 16 | public protocol SenderKeyStore: class { 17 | 18 | /// The type that distinguishes different devices/users 19 | associatedtype Address: Hashable 20 | 21 | /** 22 | Returns a copy of the sender key record corresponding to the address tuple. 23 | 24 | - parameter address: The group address of the remote client 25 | - returns: The Sender Key, if it exists, or nil 26 | */ 27 | func senderKey(for address: Address) -> Data? 28 | 29 | /** 30 | Stores the sender key record. 31 | - parameter senderKey: The key to store 32 | - parameter address: The group address of the remote client 33 | - throws: `SignalError` of type `storageError`, if the record could not be stored 34 | */ 35 | func store(senderKey: Data, for address: Address) throws 36 | 37 | } 38 | 39 | extension SenderKeyStore { 40 | /** 41 | Returns a copy of the sender key record corresponding to the address. 42 | - parameter address: The group address of the remote client 43 | - returns: The Sender Key, or nil if no key exists 44 | - throws: `SignalError` of type `invalidProtoBuf`, if the record is corrupt 45 | */ 46 | func senderKey(for address: Address) throws -> SenderKeyRecord? { 47 | guard let senderKey = senderKey(for: address) else { 48 | return nil 49 | } 50 | return try SenderKeyRecord(from: senderKey) 51 | } 52 | 53 | /** 54 | Stores a copy of the sender key record corresponding to the address. 55 | - parameter senderKey: The key to store 56 | - parameter address: The group address of the remote client 57 | - throws: `SignalErrorType.storageError`, `SignalErrorType.invalidProtoBuf` 58 | */ 59 | func store(senderKey: SenderKeyRecord, for address: Address) throws { 60 | let data = try senderKey.protoData() 61 | try store(senderKey: data, for: address) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/LocalStorage/SessionStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionStoreDelegate.swift 3 | // TestC 4 | // 5 | // Created by User on 21.09.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Implement the `SessionStore` protocol to handle the session records of the 13 | Signal Protocol. The records should be stored in a secure database and be treated as 14 | unspecified data blobs. 15 | */ 16 | public protocol SessionStore: class { 17 | 18 | /// The type that distinguishes different devices/users 19 | associatedtype Address: Hashable 20 | 21 | /** 22 | Load a session for a given address. 23 | - parameter address: The address of the remote client 24 | - returns: The session record, or nil if no record exists 25 | - throws: `SignalError` of type `storageError` 26 | */ 27 | func loadSession(for address: Address) throws -> Data? 28 | 29 | /** 30 | Store a session record for a remote client. 31 | - parameter session: The session record to store 32 | - parameter address: The address of the remote client 33 | - returns: `true` on success, `false` on error 34 | - throws: `SignalError` of type `storageError` 35 | */ 36 | func store(session: Data, for address: Address) throws 37 | 38 | /** 39 | Indicate if a record exists for the client address 40 | - parameter address: The address of the remote client 41 | - returns: `true` if a record exists 42 | */ 43 | func containsSession(for address: Address) -> Bool 44 | 45 | /** 46 | Delete a session for a remote client. 47 | - parameter address: The address of the remote client 48 | - returns: `true` if the session was deleted 49 | - throws: `SignalError` of type `storageError` 50 | */ 51 | func deleteSession(for address: Address) throws 52 | } 53 | 54 | extension SessionStore { 55 | 56 | /** 57 | Load a session for a given address. 58 | - parameter address: The address of the remote client 59 | - returns: The loaded session record, or a new one if no session exists for the address 60 | - throws: `SignalError` of type `storageError` for an invalid record 61 | */ 62 | func loadSession(for address: Address) throws -> SessionRecord { 63 | guard let record = try loadSession(for: address) else { 64 | return SessionRecord(state: nil) 65 | } 66 | return try SessionRecord(from: record) 67 | } 68 | 69 | /** 70 | Store a session record for a remote client. 71 | - note: Possible errors: 72 | - ` storageError`, if the session record could not be stored 73 | - `invalidProtoBuf`, if the session record could not be serialized 74 | - parameter session: The session record to store 75 | - parameter address: The address of the remote client 76 | - throws: `SignalError` errors 77 | */ 78 | func store(session: SessionRecord, for address: Address) throws { 79 | let data = try session.protoData() 80 | try store(session: data, for: address) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/LocalStorage/SignedPrekeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignedPrekeyStoreDelegate.swift 3 | // TestC 4 | // 5 | // Created by User on 21.09.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Implement the `SignedPreKeyStore` protocol to handle the signed pre key storage of the 13 | Signal Protocol. The keys should be stored in a secure database and be treated as 14 | unspecified data blobs. 15 | */ 16 | public protocol SignedPreKeyStore: class { 17 | 18 | /** 19 | Provide a Signed Pre Key for a given id. 20 | - parameter id: The Signed Pre Key Id 21 | - returns: The Signed Pre Key 22 | - throws: `SignalError` of type `invalidId` if no key exists for the id 23 | */ 24 | func signedPreKey(for id: UInt32) throws -> Data 25 | 26 | /** 27 | Store a Signed Pre Key for a given id. 28 | - parameter signedPreKey: The Signed Pre Key to store 29 | - parameter id: The Signed Pre Key id 30 | - throws: `SignalError` of type `storageError`, if the key could not be stored 31 | */ 32 | func store(signedPreKey: Data, for id: UInt32) throws 33 | 34 | /** 35 | Indicate if a Signed Pre Key exists for an id. 36 | - parameter id: The Signed Pre Key id 37 | - returns: `true` if a key exists 38 | - throws: `SignalError` of type `storageError`, if the key could not be accessed 39 | */ 40 | func containsSignedPreKey(for id: UInt32) throws -> Bool 41 | 42 | /** 43 | Remove a Signed Pre Key. 44 | - parameter id: The Signed Pre Key id. 45 | - throws: `SignalError`of type `invalidId` 46 | */ 47 | func removeSignedPreKey(for id: UInt32) throws 48 | 49 | /** 50 | Get all Ids for the SignedPreKeys in the store. 51 | - returns: An array of all ids for which a key is stored 52 | - throws: `SignalError` of type `storageError`, if the key could not be accessed 53 | */ 54 | func allIds() throws -> [UInt32] 55 | 56 | /// The id of the last SignedPreKey that was stored. 57 | var lastId: UInt32 { get set } 58 | } 59 | 60 | extension SignedPreKeyStore { 61 | 62 | /** 63 | Provide a Signed Pre Key for a given id. 64 | - note: Possible errors: 65 | - `invalidId`, if no pre key exists for the id 66 | - `invalidProtobuf`, if the key data is corrupt 67 | - parameter id: The Signed Pre Key ID 68 | - returns: The Signed Pre Key 69 | - throws: `SignalError` errors 70 | */ 71 | func signedPreKey(for id: UInt32) throws -> SessionSignedPreKey { 72 | let record = try signedPreKey(for: id) 73 | return try SessionSignedPreKey(from: record) 74 | } 75 | 76 | /** 77 | Store a Signed Pre Key for a given id. 78 | - note: Possible errors: 79 | - `invalidProtobuf`, if the key could not be serialized 80 | - `storageError`, if the key could not be stored 81 | - parameter signedPreKey: The Signed Pre Key to store 82 | - throws: `SignalError` errors 83 | */ 84 | func store(signedPreKey: SessionSignedPreKey) throws { 85 | let data = try signedPreKey.protoData() 86 | try store(signedPreKey: data, for: signedPreKey.publicKey.id) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/MessageExchange/CipherTextMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CipherTextMessage.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 26.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | The `CipherTextType` enum describes the different types of messages. 13 | */ 14 | public enum CipherTextType: UInt8, CustomStringConvertible { 15 | 16 | /// A 'normal' message in an established session 17 | case signal = 2 18 | /// A pre key message to establish a new session 19 | case preKey = 3 20 | /// A normal message in an established group session 21 | case senderKey = 4 22 | /// A distribution message to establish a new group session 23 | case senderKeyDistribution = 5 24 | 25 | /// A String representation of the type 26 | public var description: String { 27 | switch self { 28 | case .signal: return "SignalMessage" 29 | case .preKey: return "PreKeyMessage" 30 | case .senderKey: return "SenderKeyMessage" 31 | case .senderKeyDistribution: return "SenderKeyDistributionMessage" 32 | } 33 | } 34 | 35 | /// Encode the type into a string 36 | public var data: Data { 37 | return Data([self.rawValue]) 38 | } 39 | 40 | /** 41 | Extract the `CipherTextType` from data. 42 | - note: Fails, if there is no data or an invalid type 43 | - parameter data: The data containing the version in the first byte 44 | */ 45 | public init?(from data: Data) { 46 | guard data.count > 0 else { 47 | return nil 48 | } 49 | self.init(rawValue: data[0]) 50 | } 51 | } 52 | 53 | /** 54 | A `CipherTextMessage` encapsulates an encrypted message and the type- 55 | */ 56 | public struct CipherTextMessage { 57 | 58 | /// The type of the message 59 | public var type: CipherTextType 60 | 61 | /// The encrypted message 62 | public var data: Data 63 | 64 | /** 65 | Create a message from the components. 66 | - parameter type: The message type 67 | - parameter data: The encrypted message 68 | */ 69 | public init(type: CipherTextType, data: Data) { 70 | self.type = type 71 | self.data = data 72 | } 73 | } 74 | 75 | // MARK: Protocol Buffers 76 | 77 | extension CipherTextMessage: ProtocolBufferSerializable { 78 | 79 | public func protoData() -> Data { 80 | return type.data + data 81 | } 82 | 83 | /** 84 | Create a `CipherTextMessage` from a serialized record. 85 | - parameter data: The serialized data. 86 | - throws: `SignalError` of type `invalidProtoBuf`, if data is corrupt or missing 87 | */ 88 | public init(from data: Data) throws { 89 | guard data.count > 0 else { 90 | throw SignalError(.invalidProtoBuf, "No data to create CipherTextMessage") 91 | } 92 | guard let byte = CipherTextType(rawValue: data[0]) else { 93 | throw SignalError(.invalidProtoBuf, "Invalid type for CipherTextMessage") 94 | } 95 | self.type = byte 96 | self.data = data.advanced(by: 1) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/MessageExchange/PreKeySignalMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreKeySignalMessage.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 26.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A `PreKeySignalMessage` can be used to establish a new session. 13 | */ 14 | public struct PreKeySignalMessage { 15 | 16 | /// The pre key id of the one time key from the other party 17 | let preKeyId: UInt32? 18 | 19 | /// The id of the signed pre key used for the message 20 | let signedPreKeyId: UInt32 21 | 22 | /// The base key used for the message 23 | let baseKey: PublicKey 24 | 25 | /// The identity key of the sender 26 | let identityKey: PublicKey 27 | 28 | /// The message included in the pre key message 29 | let message: SignalMessage 30 | 31 | /** 32 | Create a new pre key message. 33 | - parameter preKeyId: The pre key id of the one time key from the other party 34 | - parameter signedPreKeyId: The id of the signed pre key used for the message 35 | - parameter baseKey: The base key used for the message 36 | - parameter identityKey: The identity key of the sender 37 | - parameter message: The message included in the pre key message 38 | */ 39 | init(preKeyId: UInt32?, 40 | signedPreKeyId: UInt32, 41 | baseKey: PublicKey, 42 | identityKey: PublicKey, 43 | message: SignalMessage) { 44 | 45 | self.preKeyId = preKeyId 46 | self.signedPreKeyId = signedPreKeyId 47 | self.baseKey = baseKey 48 | self.identityKey = identityKey 49 | self.message = message 50 | } 51 | 52 | /** 53 | Get the serialized message. 54 | - returns: The serialized message 55 | - throws: `SignalError` of type `invalidProtoBuf` 56 | */ 57 | func baseMessage() throws -> CipherTextMessage { 58 | return CipherTextMessage(type: .preKey, data: try self.protoData()) 59 | } 60 | } 61 | 62 | // MARK: Protocol buffers 63 | 64 | extension PreKeySignalMessage: ProtocolBufferConvertible { 65 | 66 | /** 67 | Convert the message to a ProtoBuf object for serialization. 68 | - returns: The object 69 | - throws: `SignalError` of type `invalidProtoBuf` 70 | */ 71 | func asProtoObject() throws -> Signal_PreKeySignalMessage { 72 | return try Signal_PreKeySignalMessage.with { 73 | if let id = self.preKeyId { 74 | $0.preKeyID = id 75 | } 76 | $0.signedPreKeyID = self.signedPreKeyId 77 | $0.baseKey = self.baseKey.data 78 | $0.identityKey = self.identityKey.data 79 | $0.message = try self.message.baseMessage().data 80 | } 81 | } 82 | 83 | /** 84 | Create a `PreKeySignalMessage` from a ProtoBuf object. 85 | - note: The following errors can be thrown: 86 | `invalidProtoBuf`, if the object has missing or corrupt values. 87 | - parameter object: The serialized data. 88 | - throws: `SignalError` errors 89 | */ 90 | init(from object: Signal_PreKeySignalMessage) throws { 91 | guard object.hasBaseKey, object.hasMessage, object.hasIdentityKey, 92 | object.hasSignedPreKeyID else { 93 | throw SignalError(.invalidProtoBuf, "Missing data in PreKeySignalMessage") 94 | } 95 | self.baseKey = try PublicKey(from: object.baseKey) 96 | self.identityKey = try PublicKey(from: object.identityKey) 97 | self.message = try SignalMessage(from: object.message) 98 | self.signedPreKeyId = object.signedPreKeyID 99 | self.preKeyId = object.hasPreKeyID ? object.preKeyID : nil 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/MessageExchange/SenderKeyDistributionMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderKeyDistributionMessage.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 01.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | `SenderKeyDistributionMessage`s are used to establish group sessions. 13 | */ 14 | public struct SenderKeyDistributionMessage { 15 | 16 | /// The id of the message 17 | var id: UInt32 18 | 19 | /// The current chain iteration of the message 20 | var iteration: UInt32 21 | 22 | /// The chain key used for the message 23 | var chainKey: Data 24 | 25 | /// The signature key used for signing the message 26 | var signatureKey: PublicKey 27 | 28 | /** 29 | Create a serialized message from the distribution message 30 | - returns: The serialized message 31 | - throws: `SignalError` of type `invalidProtoBuf` if the serialization fails 32 | */ 33 | public func baseMessage() throws -> CipherTextMessage { 34 | return CipherTextMessage(type: .senderKeyDistribution, data: try self.protoData()) 35 | } 36 | 37 | /** 38 | Create a distribution message. 39 | - parameter id: The id of the message 40 | - parameter iteration: The current chain iteration of the message 41 | - parameter chainKey: The chain key used for the message 42 | - parameter signatureKey: The signature key used for signing the message 43 | */ 44 | init(id: UInt32, iteration: UInt32, chainKey: Data, signatureKey: PublicKey) { 45 | self.id = id 46 | self.iteration = iteration 47 | self.chainKey = chainKey 48 | self.signatureKey = signatureKey 49 | } 50 | } 51 | 52 | // MARK: Protocol Equatable 53 | 54 | extension SenderKeyDistributionMessage: Equatable { 55 | 56 | /** 57 | Compare two distribution messages. 58 | - parameter lhs: The first message. 59 | - parameter rhs: The second message. 60 | - returns: `True` if the messages match. 61 | */ 62 | public static func ==(lhs: SenderKeyDistributionMessage, rhs: SenderKeyDistributionMessage) -> Bool { 63 | return lhs.id == rhs.id && 64 | lhs.iteration == rhs.iteration && 65 | lhs.chainKey == rhs.chainKey && 66 | lhs.signatureKey == rhs.signatureKey 67 | } 68 | } 69 | 70 | // MARK: Protocol buffers 71 | 72 | extension SenderKeyDistributionMessage: ProtocolBufferEquivalent { 73 | 74 | /// Convert the distribution message to a ProtoBuf object 75 | var protoObject: Signal_SenderKeyDistributionMessage { 76 | return Signal_SenderKeyDistributionMessage.with { 77 | $0.id = self.id 78 | $0.iteration = self.iteration 79 | $0.chainKey = self.chainKey 80 | $0.signingKey = self.signatureKey.data 81 | } 82 | } 83 | 84 | /** 85 | Create a distribution message from a ProtoBuf object. 86 | - parameter protoObject: The ProtoBuf object 87 | - throws: `SignalError` of type `invalidProtoBuf`, if data is missing or corrupt 88 | */ 89 | init(from protoObject: Signal_SenderKeyDistributionMessage) throws { 90 | guard protoObject.hasID, protoObject.hasIteration, protoObject.hasChainKey, protoObject.hasSigningKey else { 91 | throw SignalError(.invalidProtoBuf, "Missing data in SenderKeyDistributionMessage Protobuf object") 92 | } 93 | 94 | self.id = protoObject.id 95 | self.iteration = protoObject.iteration 96 | self.chainKey = protoObject.chainKey 97 | self.signatureKey = try PublicKey(from: protoObject.signingKey) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/MessageExchange/SenderKeyMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderKeyMessage.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 26.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Curve25519 11 | 12 | /** 13 | A sender key message is used to send an encrypted message in an existing group session. 14 | */ 15 | public struct SenderKeyMessage { 16 | 17 | /// The id of the key that was used 18 | let keyId: UInt32 19 | 20 | /// The iteration of the chain key 21 | let iteration: UInt32 22 | 23 | /// The encrypted ciphertext 24 | let cipherText: Data 25 | 26 | /// The signature of the message 27 | var signature: Data 28 | 29 | /** 30 | Return the message serialized 31 | */ 32 | func baseMessage() throws -> CipherTextMessage { 33 | return CipherTextMessage(type: .senderKey, data: try self.protoData()) 34 | } 35 | 36 | /** 37 | Create a `SenderKeyMessage` from the components. 38 | - note: The possible error types are: 39 | `invalidProtoBuf`, if the ProtoBuf object can't be serialized for the signature. 40 | `invalidLength`, if the message is more than 256 or 0 byte. 41 | `invalidSignature`, if the message could not be signed. 42 | `noRandomBytes`, if the crypto provider could not provide random bytes for the signature. 43 | - parameter keyId: The id of the key that was used 44 | - parameter iteration: The iteration of the chain key 45 | - parameter cipherText: The encrypted ciphertext 46 | - parameter signatureKey: The key used for the message signature 47 | - throws: `SignalError` errors 48 | */ 49 | init(keyId: UInt32, iteration: UInt32, cipherText: Data, signatureKey: PrivateKey) throws { 50 | self.keyId = keyId 51 | self.iteration = iteration 52 | self.cipherText = cipherText 53 | // Empty signature for serialization 54 | self.signature = Data() 55 | let data = try self.protoData() 56 | self.signature = try signatureKey.sign(message: data) 57 | } 58 | 59 | /** 60 | Verify that the signature matches the message. 61 | - note: The possible error types are: 62 | 63 | - parameter signatureKey: The key used to verify the message 64 | - returns: `True`, if the signature matches 65 | - throws: `SignalError` of type `invalidProtoBuf`, if the ProtoBuf object can't be serialized for the signature. 66 | */ 67 | func verify(signatureKey: PublicKey) throws -> Bool { 68 | guard signature.count == Curve25519.signatureLength else { 69 | return false 70 | } 71 | let record = try self.protoData() 72 | let length = record.count - Curve25519.signatureLength 73 | let message = record[0.. Data { 117 | do { 118 | return try protoObject.serializedData() + signature 119 | } catch { 120 | throw SignalError(.invalidProtoBuf, "Could not serialize SenderKeyMessage: \(error)") 121 | } 122 | } 123 | 124 | /** 125 | Create a sender key message from serialized data. 126 | - note: The types of errors thrown are: 127 | `invalidProtoBuf`, if data is missing or corrupt 128 | `invalidSignature`, if the signature length is incorrect 129 | - parameter data: The serialized data 130 | - throws: `SignalError` errors 131 | */ 132 | public init(from data: Data) throws { 133 | guard data.count > Curve25519.signatureLength else { 134 | throw SignalError(.invalidProtoBuf, "Too few bytes in data for SenderKeyMessage") 135 | } 136 | let length = data.count - Curve25519.signatureLength 137 | guard length > 1 else { 138 | throw SignalError(.invalidProtoBuf, "Too few bytes in data for SenderKeyMessage") 139 | } 140 | let content = data[0..(decoder: inout D) throws { 76 | while let fieldNumber = try decoder.nextFieldNumber() { 77 | switch fieldNumber { 78 | case 1: try decoder.decodeSingularUInt32Field(value: &self._version) 79 | case 2: try decoder.decodeSingularBytesField(value: &self._local) 80 | case 3: try decoder.decodeSingularBytesField(value: &self._remote) 81 | default: break 82 | } 83 | } 84 | } 85 | 86 | func traverse(visitor: inout V) throws { 87 | if let v = self._version { 88 | try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) 89 | } 90 | if let v = self._local { 91 | try visitor.visitSingularBytesField(value: v, fieldNumber: 2) 92 | } 93 | if let v = self._remote { 94 | try visitor.visitSingularBytesField(value: v, fieldNumber: 3) 95 | } 96 | try unknownFields.traverse(visitor: &visitor) 97 | } 98 | 99 | func _protobuf_generated_isEqualTo(other: Signal_Fingerprint) -> Bool { 100 | if self._version != other._version {return false} 101 | if self._local != other._local {return false} 102 | if self._remote != other._remote {return false} 103 | if unknownFields != other.unknownFields {return false} 104 | return true 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/Fingerprint.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package Signal; 4 | 5 | message Fingerprint { 6 | optional uint32 version = 1; 7 | optional bytes local = 2; 8 | optional bytes remote = 3; 9 | } 10 | -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/LocalStorage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package Signal; 4 | 5 | message Session { 6 | message Chain { 7 | optional bytes senderRatchetKey = 1; 8 | optional bytes senderRatchetKeyPrivate = 2; 9 | 10 | message ChainKey { 11 | optional uint32 index = 1; 12 | optional bytes key = 2; 13 | } 14 | 15 | optional ChainKey chainKey = 3; 16 | 17 | message MessageKey { 18 | optional uint32 index = 1; 19 | optional bytes cipherKey = 2; 20 | optional bytes macKey = 3; 21 | optional bytes iv = 4; 22 | } 23 | 24 | repeated MessageKey messageKeys = 4; 25 | } 26 | 27 | message PendingPreKey { 28 | optional uint32 preKeyId = 1; 29 | optional int32 signedPreKeyId = 2; 30 | optional bytes baseKey = 3; 31 | } 32 | 33 | optional bytes localIdentityPublic = 1; 34 | optional bytes remoteIdentityPublic = 2; 35 | 36 | optional bytes rootKey = 3; 37 | optional uint32 previousCounter = 4; 38 | 39 | optional Chain senderChain = 5; 40 | repeated Chain receiverChains = 6; 41 | 42 | optional PendingPreKey pendingPreKey = 7; 43 | optional bytes aliceBaseKey = 8; 44 | } 45 | 46 | message Record { 47 | optional Session currentSession = 1; 48 | repeated Session previousSessions = 2; 49 | } 50 | 51 | message PreKey { 52 | 53 | message PublicPart { 54 | optional uint32 id = 1; 55 | optional bytes key = 2; 56 | } 57 | 58 | optional PublicPart publicKey = 1; 59 | optional bytes privateKey = 2; 60 | } 61 | 62 | message SignedPreKey { 63 | message PublicPart { 64 | optional uint32 id = 1; 65 | optional bytes key = 2; 66 | optional bytes signature = 3; 67 | optional fixed64 timestamp = 4; 68 | } 69 | 70 | optional PublicPart publicKey = 1; 71 | optional bytes privateKey = 2; 72 | } 73 | 74 | message KeyPair { 75 | optional bytes publicKey = 1; 76 | optional bytes privateKey = 2; 77 | } 78 | 79 | message SenderKeyState { 80 | message SenderChainKey { 81 | optional uint32 iteration = 1; 82 | optional bytes seed = 2; 83 | } 84 | 85 | message SenderMessageKey { 86 | optional uint32 iteration = 1; 87 | optional bytes seed = 2; 88 | } 89 | 90 | message SenderSigningKey { 91 | optional bytes public = 1; 92 | optional bytes private = 2; 93 | } 94 | 95 | optional uint32 senderKeyId = 1; 96 | optional SenderChainKey senderChainKey = 2; 97 | optional SenderSigningKey senderSigningKey = 3; 98 | repeated SenderMessageKey senderMessageKeys = 4; 99 | } 100 | 101 | message SenderKeyRecord { 102 | repeated SenderKeyState senderKeyStates = 1; 103 | } -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/Messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package Signal; 4 | 5 | message SignalMessage { 6 | optional bytes ratchetKey = 1; 7 | optional uint32 counter = 2; 8 | optional uint32 previousCounter = 3; 9 | optional bytes ciphertext = 4; 10 | } 11 | 12 | message PreKeySignalMessage { 13 | optional uint32 preKeyId = 1; 14 | optional uint32 signedPreKeyId = 6; 15 | optional bytes baseKey = 2; 16 | optional bytes identityKey = 3; 17 | optional bytes message = 4; // SignalMessage 18 | } 19 | 20 | message SenderKeyMessage { 21 | optional uint32 id = 1; 22 | optional uint32 iteration = 2; 23 | optional bytes ciphertext = 3; 24 | } 25 | 26 | message SenderKeyDistributionMessage { 27 | optional uint32 id = 1; 28 | optional uint32 iteration = 2; 29 | optional bytes chainKey = 3; 30 | optional bytes signingKey = 4; 31 | } 32 | 33 | message DeviceConsistencyCodeMessage { 34 | optional uint32 generation = 1; 35 | optional bytes signature = 2; 36 | } -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/ProtocolBufferConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtocolBufferConvertible.swift 3 | // SignalProtocol iOS 4 | // 5 | // Created by User on 13.02.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftProtobuf 11 | 12 | /** 13 | All types that conform to `ProtocolBufferConvertible` can be converted to and from a specific 14 | protobuf class. 15 | */ 16 | protocol ProtocolBufferConvertible: ProtocolBufferSerializable { 17 | 18 | /// The class type that the type can be converted to 19 | associatedtype ProtocolBufferClass: SwiftProtobuf.Message 20 | 21 | /** 22 | Convert to a protobuf object. 23 | - throws: `SignalError` of type `invalidProtoBuf` 24 | - returns: The protobuf object 25 | */ 26 | func asProtoObject() throws -> ProtocolBufferClass 27 | 28 | /** 29 | Create an object from its protobuf equivalent 30 | - parameter protoObject: The protobuf object containing the data 31 | - throws: `SignalError` of type `invalidProtoBuf` 32 | */ 33 | init(from protoObject: ProtocolBufferClass) throws 34 | } 35 | 36 | extension ProtocolBufferConvertible { 37 | 38 | /** 39 | Convert the object to data. 40 | - throws: `SignalError` of type `invalidProtoBuf` 41 | - returns: The serialized object 42 | */ 43 | public func protoData() throws -> Data { 44 | do { 45 | return try asProtoObject().serializedData() 46 | } catch { 47 | throw SignalError(.invalidProtoBuf, "Serialization error: \(error)") 48 | } 49 | } 50 | 51 | /** 52 | Create an object from its protobuf data 53 | - parameter protoData: The serialized data 54 | - throws: `SignalError` of type `invalidProtoBuf` 55 | */ 56 | public init(from protoData: Data) throws { 57 | let protoObject: ProtocolBufferClass 58 | do { 59 | protoObject = try ProtocolBufferClass(serializedData: protoData) 60 | } catch { 61 | throw SignalError(.invalidProtoBuf, "Deserialization error: \(error)") 62 | } 63 | try self.init(from: protoObject) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/ProtocolBufferEquivalent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtocolBufferEquivalent.swift 3 | // SignalProtocol iOS 4 | // 5 | // Created by User on 13.02.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | All types that conform to `ProtocolBufferConvertible` can be converted to and from a specific 13 | protobuf class. 14 | */ 15 | protocol ProtocolBufferEquivalent: ProtocolBufferConvertible { 16 | 17 | /// The object converted to a protobuf object. 18 | var protoObject: ProtocolBufferClass { get } 19 | } 20 | 21 | /** 22 | Default implementation to provide the `object()` function for all types that conform to `ProtocolBufferEquivalent`. 23 | */ 24 | extension ProtocolBufferEquivalent { 25 | 26 | /** 27 | Convert the object to a protobuf object. 28 | - returns: The protobuf object 29 | */ 30 | func asProtoObject() -> ProtocolBufferClass { 31 | return protoObject 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/ProtocolBuffers/ProtocolBufferSerializable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtocolBufferSerializable.swift 3 | // SignalProtocol iOS 4 | // 5 | // Created by User on 13.02.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | All types that conform to `Serializable` can be converted to and from data. 13 | */ 14 | public protocol ProtocolBufferSerializable { 15 | 16 | /** 17 | Convert the object to data. 18 | - throws: `SignalError` of type `invalidProtoBuf` 19 | - returns: The serialized object 20 | */ 21 | func protoData() throws -> Data 22 | 23 | /** 24 | Create an object from its protobuf data 25 | - parameter protoData: The serialized data 26 | - throws: `SignalError` of type `invalidProtoBuf` 27 | */ 28 | init(from protoData: Data) throws 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Ratchet/HKDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKDF.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 08.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /** 11 | The Key derivation function used for the Ratchet. 12 | */ 13 | struct HKDF { 14 | 15 | /// The total number of bytes to derive when creating a new root and chain key 16 | private static let derivedRootSecretsSize = RatchetRootKey.secretSize + RatchetChainKey.secretSize 17 | 18 | /// The offset for the expand iterations 19 | private static let iterationStartOffset: UInt8 = 1 20 | 21 | /** 22 | Derive new secrets from the KDF. 23 | - note: The number of output bytes is not necessarily equal to the `outputLength` parameter. 24 | It is a multiple of the hash output size. 25 | - parameter material: The bytes used for the extract stage 26 | - parameter salt: The salt used for the extract stage 27 | - parameter info: The info used for the expand stage 28 | - parameter outputLength: The number of bytes to produce 29 | - returns: The derived bytes 30 | - throws: `SignalError` of type `hmacError`, if the HMAC authentication fails 31 | */ 32 | static func deriveSecrets(material: Data, salt: Data, info: Data, outputLength: Int) throws -> Data { 33 | // Extract step 34 | let prk = try SignalCrypto.hmacSHA256(for: material, with: salt) 35 | // Expand step 36 | return try expand(prk: prk, info: info, outputLength: outputLength) 37 | } 38 | 39 | /** 40 | Expand the bytes to create enough output bytes. 41 | - note: The number of output bytes is not necessarily equal to the `outputLength` parameter. 42 | It is a multiple of the hash output size. 43 | - parameter prk: The bytes to expand 44 | - parameter info: The info bytes to use within the expand step 45 | - parameter outputLength: The number of bytes to generate from the input 46 | - returns: The expanded bytes 47 | - throws: `SignalError` of type `hmacError` if the Crypto delegate fails to calculate the HMAC 48 | */ 49 | private static func expand(prk: Data, info: Data, outputLength: Int) throws -> Data { 50 | var fraction = Double(outputLength) / Double(RatchetChainKey.hashOutputSize) 51 | fraction.round(.up) 52 | let iterations = UInt8(fraction) 53 | 54 | var result = Data() 55 | var remainingLength = outputLength 56 | var stepBuffer = Data() 57 | 58 | for index in iterationStartOffset.. (rootKey: RatchetRootKey, chainKey: RatchetChainKey) { 77 | 78 | let derivedSecret = try deriveSecrets( 79 | material: material, 80 | salt: salt, 81 | info: info, 82 | outputLength: HKDF.derivedRootSecretsSize) 83 | 84 | let rootKeySecret = derivedSecret[0.. Data { 53 | return try SignalCrypto.hmacSHA256(for: seed, with: key) 54 | } 55 | 56 | /** 57 | Get a set of message keys for the Ratchet 58 | - returns: A set of Ratchet message keys 59 | - throws: `SignalError` errors on failure 60 | */ 61 | func messageKeys() throws -> RatchetMessageKeys { 62 | let inputKeyMaterial = try getBaseMaterial(seed: RatchetChainKey.messageKeySeed) 63 | 64 | let salt = Data(count: RatchetChainKey.hashOutputSize) 65 | let keyMaterialData = 66 | try HKDF.deriveSecrets( 67 | material: inputKeyMaterial, 68 | salt: salt, 69 | info: RatchetChainKey.keyMaterialSeed, 70 | outputLength: RatchetMessageKeys.derivedMessageSecretsSize) 71 | 72 | var temp = index 73 | let indexData = withUnsafePointer(to: &temp) { Data(bytes: $0, count: MemoryLayout.size) } 74 | return try RatchetMessageKeys(material: keyMaterialData + indexData) 75 | } 76 | 77 | /** 78 | Return the next chain key 79 | - returns: The next chain key from the KDF 80 | - throws: `SignalError.hmacError` if CryptoSwift doesn't work 81 | */ 82 | func next() throws -> RatchetChainKey { 83 | let nextKey = try getBaseMaterial(seed: RatchetChainKey.chainKeySeed) 84 | return RatchetChainKey(key: nextKey, index: index + 1) 85 | } 86 | } 87 | 88 | // MARK: Protocol Buffers 89 | 90 | extension RatchetChainKey: ProtocolBufferEquivalent { 91 | 92 | /// The chain key converted to a ProtoBuf object 93 | var protoObject: Signal_Session.Chain.ChainKey { 94 | return Signal_Session.Chain.ChainKey.with { 95 | $0.index = self.index 96 | $0.key = self.key 97 | } 98 | } 99 | 100 | /** 101 | Create a chain key from a ProtoBuf object. 102 | - parameter protoObject: The ProtoBuf object 103 | - throws: `SignalError` of type `invalidProtoBuf`, if data is missing or corrupt 104 | */ 105 | init(from protoObject: Signal_Session.Chain.ChainKey) throws { 106 | guard protoObject.hasIndex, protoObject.hasKey else { 107 | throw SignalError(.invalidProtoBuf, "Missing data in RatchetChainKey protobuf object") 108 | } 109 | self.index = protoObject.index 110 | self.key = protoObject.key 111 | } 112 | } 113 | 114 | // MARK: Protocol Equatable 115 | 116 | extension RatchetChainKey: Equatable { 117 | /** 118 | Compare two SignalMessages for equality. 119 | - parameter lhs: The first message 120 | - parameter rhs: The second message 121 | - returns: `True`, if the chain keys are equal 122 | */ 123 | static func ==(lhs: RatchetChainKey, rhs: RatchetChainKey) -> Bool { 124 | return lhs.key == rhs.key && lhs.index == rhs.index 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/Ratchet/RatchetMessageKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RatchetMessageKeys.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 12.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | The keys needed to encrypt/decrypt a message 13 | */ 14 | struct RatchetMessageKeys { 15 | 16 | /// The length of cipher keys in bytes 17 | static let cipherKeyLength = 32 18 | 19 | /// The length of the mac key in bytes 20 | static let macKeyLength = 32 21 | 22 | /// The length of the initialization vector in bytes 23 | static let ivLength = 16 24 | 25 | /// The length of cipher key, mac key, and iv 26 | static let derivedMessageSecretsSize = cipherKeyLength + macKeyLength + ivLength 27 | 28 | /// The cipher key to encrypt/decrypt a message 29 | var cipherKey: Data 30 | 31 | /// The mac key of a message 32 | var macKey: Data 33 | 34 | /// The initialization vector 35 | var iv: Data 36 | 37 | /// The counter of the message 38 | var counter: UInt32 39 | 40 | /** 41 | Create the message keys from the components 42 | - parameter cipher: The cipher key 43 | - parameter mac: The mac key 44 | - parameter iv: The initialization vector 45 | - counter: The counter of the message 46 | - throws: `SignalError` of type `invalidLength`, if the key lengths are invalid 47 | */ 48 | init(cipher: Data, mac: Data, iv: Data, counter: UInt32) throws { 49 | guard cipher.count == RatchetMessageKeys.cipherKeyLength else { 50 | throw SignalError(.invalidLength, "Invalid cipher key length \(cipher.count)") 51 | } 52 | guard mac.count == RatchetMessageKeys.macKeyLength else { 53 | throw SignalError(.invalidLength, "Invalid mac key length \(mac.count)") 54 | } 55 | guard iv.count == RatchetMessageKeys.ivLength else { 56 | throw SignalError(.invalidLength, "Invalid iv length \(iv.count)") 57 | } 58 | self.cipherKey = cipher 59 | self.macKey = mac 60 | self.iv = iv 61 | self.counter = counter 62 | } 63 | 64 | /** 65 | Create the message keys from generated bytes. 66 | - parameter bytes: The bytes to create the message keys from. 67 | - throws: `SignalError` of type `invalidLength`, if the number of bytes is invalid 68 | */ 69 | init(material: Data) throws { 70 | guard material.count == RatchetMessageKeys.derivedMessageSecretsSize + MemoryLayout.size else { 71 | throw SignalError(.invalidLength, "Missing bytes in RatchetMessageKeys data") 72 | } 73 | self.cipherKey = material[0.. Bool { 125 | return lhs.counter == rhs.counter && 126 | lhs.cipherKey == rhs.cipherKey && 127 | lhs.iv == rhs.iv && 128 | lhs.macKey == rhs.macKey 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/Ratchet/RatchetRootKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RatchetRootKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 12.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A root key is within a ratchet to derive new sender and receiver chain keys. 13 | */ 14 | struct RatchetRootKey { 15 | 16 | /// Bytes used as input for the KDF 17 | private static let keyInfo = "WhisperRatchet".data(using: .utf8)! 18 | 19 | /// The number of bytes for the root key 20 | static let secretSize = 32 21 | 22 | /// The current root key 23 | let key: Data 24 | 25 | /** 26 | Create a new root key from the components 27 | - parameter key: The current root key 28 | */ 29 | init(key: Data) { 30 | self.key = key 31 | } 32 | 33 | /** 34 | Create a new root key and chain key. 35 | - parameter theirRatchetKey: The ratchet key from the other party. 36 | - parameter ourRatchetKey: The local ratchet key 37 | - throws: `SignalError.hmacError`, if the HMAC authentication fails 38 | - returns: A tuple of the root key and chain key 39 | */ 40 | func createChain(theirRatchetKey: PublicKey, ourRatchetKey: PrivateKey) throws -> (rootKey: RatchetRootKey, chainKey: RatchetChainKey) { 41 | let sharedSecret = try theirRatchetKey.calculateAgreement(privateKey: ourRatchetKey) 42 | 43 | return try HKDF.chainAndRootKey(material: sharedSecret, salt: key, info: RatchetRootKey.keyInfo) 44 | } 45 | } 46 | 47 | // MARK: Protocol Buffers 48 | 49 | extension RatchetRootKey: ProtocolBufferSerializable { 50 | 51 | /** 52 | Return the serialized root key 53 | - returns: The serialized data 54 | */ 55 | func protoData() -> Data { 56 | return key 57 | } 58 | 59 | /** 60 | Deserialize a root key. 61 | - parameter data: The serialized key. 62 | */ 63 | init(from data: Data) { 64 | self.key = data 65 | } 66 | } 67 | 68 | // MARK: Protocol Comparable 69 | 70 | extension RatchetRootKey: Comparable { 71 | 72 | /** 73 | Compare two root keys. 74 | - parameter lhs: The first key 75 | - parameter rhs: The second key 76 | - returns: `True`, if the first key is 'smaller' than the second key 77 | */ 78 | static func <(lhs: RatchetRootKey, rhs: RatchetRootKey) -> Bool { 79 | guard lhs.key.count == rhs.key.count else { 80 | return lhs.key.count < rhs.key.count 81 | } 82 | for i in 0.. Bool { 101 | return lhs.key == rhs.key 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/Ratchet/ReceiverChain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReceiverChain.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 12.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A receiver chain is the part of the ratchet that creates the message keys for the received messages. 13 | */ 14 | final class ReceiverChain: ProtocolBufferEquivalent { 15 | 16 | /// The current ratchet key 17 | var ratchetKey: PublicKey 18 | 19 | /// The current chain key 20 | var chainKey: RatchetChainKey 21 | 22 | /// The stored message keys for out-of-order messages 23 | private var messageKeys = [RatchetMessageKeys]() 24 | 25 | /** 26 | Create a receiver chain from the components. 27 | - parameter ratchetKey: The current ratchet key 28 | - parameter chainKey: The current chain key 29 | */ 30 | init(ratchetKey: PublicKey, chainKey: RatchetChainKey) { 31 | self.ratchetKey = ratchetKey 32 | self.chainKey = chainKey 33 | } 34 | 35 | /** 36 | Add a message key to the stored message keys. 37 | - parameter messageKey: The keys to add 38 | */ 39 | func add(messageKey: RatchetMessageKeys) { 40 | // Replace new keys if the counter already exists 41 | for index in 0.. SenderKeyState.messageKeyMaximum { 50 | messageKeys.removeLast(messageKeys.count - SenderKeyState.messageKeyMaximum) 51 | } 52 | } 53 | 54 | /** 55 | Check if a message key already exists. 56 | - parameter messageKey: The keys to check 57 | - returns: `True`, if a key already exists for the counter 58 | */ 59 | func has(messageKey: RatchetMessageKeys) -> Bool { 60 | for item in messageKeys { 61 | if item.counter == messageKey.counter { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | /** 69 | Get a message key if it exists. 70 | - parameter iteration: The counter for which to get the keys 71 | - returns: The message keys, if they exist 72 | */ 73 | func messageKey(for iteration: UInt32) -> RatchetMessageKeys? { 74 | for item in messageKeys { 75 | if item.counter == iteration { 76 | return item 77 | } 78 | } 79 | return nil 80 | } 81 | 82 | /** 83 | Remove a message key and return it. 84 | - parameter iteration: The counter for which to remove the keys 85 | - returns: The message keys, if they exist 86 | */ 87 | func removeMessageKey(for iteration: UInt32) -> RatchetMessageKeys? { 88 | for index in 0.. Bool { 129 | return lhs.ratchetKey == rhs.ratchetKey && 130 | lhs.chainKey == rhs.chainKey && 131 | lhs.messageKeys == rhs.messageKeys 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Sources/Ratchet/SenderChain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderChain.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 09.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | The sender chain of a ratchet used to encrypt messages for sending. 13 | */ 14 | struct SenderChain { 15 | 16 | /// The key pair of the ratchet 17 | var ratchetKey: KeyPair 18 | 19 | /// The current chain key of the ratchet 20 | var chainKey: RatchetChainKey 21 | 22 | /** 23 | Create a sender chain from the components. 24 | - parameter ratchetKey: The key pair of the ratchet 25 | - parameter chainKey: The current chain key of the ratchet 26 | */ 27 | init(ratchetKey: KeyPair, chainKey: RatchetChainKey) { 28 | self.ratchetKey = ratchetKey 29 | self.chainKey = chainKey 30 | } 31 | } 32 | 33 | // MARK: Protocol buffers 34 | 35 | extension SenderChain: ProtocolBufferEquivalent { 36 | 37 | /// The sender chain converted to a protobuf object 38 | var protoObject: Signal_Session.Chain { 39 | return Signal_Session.Chain.with { 40 | $0.senderRatchetKey = ratchetKey.publicKey.data 41 | $0.senderRatchetKeyPrivate = ratchetKey.privateKey.data 42 | $0.chainKey = chainKey.protoObject 43 | } 44 | } 45 | 46 | /** 47 | Create a sender chain from a protobuf object. 48 | - parameter protoObject: The protobuf object 49 | - throws: `SignalError` of type `invalidProtoBuf`, if data is missing or corrupt 50 | */ 51 | init(from protoObject: Signal_Session.Chain) throws { 52 | guard protoObject.hasChainKey, protoObject.hasSenderRatchetKey, protoObject.hasSenderRatchetKeyPrivate else { 53 | throw SignalError(.invalidProtoBuf, "Missing data in ProtoBuf object") 54 | } 55 | self.chainKey = try RatchetChainKey(from: protoObject.chainKey) 56 | let publicKey = try PublicKey(from: protoObject.senderRatchetKey) 57 | let privateKey = try PrivateKey(from: protoObject.senderRatchetKeyPrivate) 58 | self.ratchetKey = KeyPair(publicKey: publicKey, privateKey: privateKey) 59 | } 60 | } 61 | 62 | // MARK: Protocol Equatable 63 | 64 | extension SenderChain: Equatable { 65 | /** 66 | Compare two sender chains for equality. 67 | - parameter lhs: The first chain 68 | - parameter rhs: The second chain 69 | - returns: `True`, if the chains are equal 70 | */ 71 | static func ==(lhs: SenderChain, rhs: SenderChain) -> Bool { 72 | return lhs.ratchetKey == rhs.ratchetKey && lhs.chainKey == rhs.chainKey 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Ratchet/SenderChainKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderChainKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 25.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A key in the sender chain. 13 | */ 14 | struct SenderChainKey { 15 | 16 | /// The seed value for the message key when deriving the next key 17 | private static let messageKeySeed = Data([0x01]) 18 | 19 | /// The seed value for the chain key when deriving the next key 20 | private static let chainKeySeed = Data([0x02]) 21 | 22 | /// The current iteration of the chain 23 | var iteration: UInt32 24 | 25 | /// The current chain key 26 | var chainKey: Data 27 | 28 | /** 29 | Create a new chain key from the components. 30 | - parameter iteration: The current iteration of the chain 31 | - parameter chainKey: The data of the current chain key 32 | */ 33 | init(iteration: UInt32, chainKey: Data) { 34 | self.iteration = iteration 35 | self.chainKey = chainKey 36 | } 37 | 38 | /** 39 | Advance the chain and return the generated message key. 40 | - returns: The message key 41 | - throws: `SignalError` of type `hmacError`, if the HMAC could not be calculated for the chain key, or the authentication fails 42 | */ 43 | mutating func messageKey() throws -> SenderMessageKey { 44 | let derivative = try SignalCrypto.hmacSHA256(for: SenderChainKey.messageKeySeed, with: chainKey) 45 | let messageKey = try SenderMessageKey(iteration: iteration, seed: derivative) 46 | chainKey = Data(derivative) 47 | iteration += 1 48 | return messageKey 49 | } 50 | } 51 | 52 | // MARK: Protocol Buffers 53 | 54 | extension SenderChainKey: ProtocolBufferEquivalent { 55 | 56 | /// Convert the sender chain key to a ProtoBuf object 57 | var protoObject: Signal_SenderKeyState.SenderChainKey { 58 | return Signal_SenderKeyState.SenderChainKey.with { 59 | $0.seed = self.chainKey 60 | $0.iteration = self.iteration 61 | } 62 | } 63 | 64 | /** 65 | Create a chain key from a ProtoBuf object. 66 | - parameter protoObject: The chain key ProtoBuf object. 67 | - throws: `SignalError` of type `invalidProtoBuf`, if data is missing or corrupt 68 | */ 69 | init(from protoObject: Signal_SenderKeyState.SenderChainKey) throws { 70 | guard protoObject.hasSeed, protoObject.hasIteration else { 71 | throw SignalError(.invalidProtoBuf, "Missing data in SenderChainKey Protobuf object") 72 | } 73 | self.chainKey = protoObject.seed 74 | self.iteration = protoObject.iteration 75 | } 76 | } 77 | 78 | // MARK: Protocol Equatable 79 | 80 | extension SenderChainKey: Equatable { 81 | /** 82 | Compare two sender chain keys for equality. 83 | - parameter lhs: The first key 84 | - parameter rhs: The second key 85 | - returns: `True`, if the keys are equal 86 | */ 87 | static func ==(lhs: SenderChainKey, rhs: SenderChainKey) -> Bool { 88 | guard lhs.iteration == rhs.iteration else { 89 | return false 90 | } 91 | return lhs.chainKey == rhs.chainKey 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/Ratchet/SenderMessageKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SenderMessageKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 25.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A message key in a chain to encrypt/decrypt messages 13 | */ 14 | struct SenderMessageKey { 15 | 16 | /// The info used when creating the keys from the seed 17 | private static let infoMaterial = "WhisperGroup".data(using: .utf8)! 18 | 19 | /// The length of the initialization vector 20 | private static let ivLength = 16 21 | 22 | /// The length of the key 23 | private static let cipherKeyLength = 32 24 | 25 | /// The combined length of iv and key 26 | private static let secretLength = ivLength + cipherKeyLength 27 | 28 | /// The iteration of the message key in the chain 29 | var iteration: UInt32 30 | 31 | /// The initialization vector 32 | var iv: Data 33 | 34 | /// The encryption/decryption key 35 | var cipherKey: Data 36 | 37 | /// The seed used to derive the key and iv 38 | private var seed: Data 39 | 40 | /** 41 | Create a message key from the components. 42 | - parameter iteration: The iteration of the message key in the chain 43 | - parameter seed: The seed used to derive the key and iv 44 | - throws: `SignalError` of type `hmacError`, if the HMAC authentication fails 45 | */ 46 | init(iteration: UInt32, seed: Data) throws { 47 | let salt = Data(count: RatchetChainKey.hashOutputSize) 48 | let derivative = try HKDF.deriveSecrets( 49 | material: seed, 50 | salt: salt, 51 | info: SenderMessageKey.infoMaterial, 52 | outputLength: SenderMessageKey.secretLength) 53 | 54 | self.iteration = iteration 55 | self.seed = seed 56 | self.iv = derivative[0.. Bool { 97 | return lhs.iteration == rhs.iteration && 98 | lhs.iv == rhs.iv && 99 | lhs.seed == rhs.seed && 100 | lhs.cipherKey == rhs.cipherKey 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/Session/PendingPreKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PendingPreKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 12.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A pre key sent out as a pre key message, until a message is received from the other party 13 | */ 14 | struct PendingPreKey { 15 | 16 | /// The id of the pre key, if one was used 17 | var preKeyId: UInt32? 18 | 19 | /// The id of the signed pre key 20 | var signedPreKeyId: UInt32 21 | 22 | /// The base key used for the outgoing pre key message 23 | var baseKey: PublicKey 24 | 25 | } 26 | 27 | // MARK: Protocol Buffers 28 | 29 | extension PendingPreKey: ProtocolBufferEquivalent { 30 | 31 | /// Create a ProtoBuf object for serialization. 32 | var protoObject: Signal_Session.PendingPreKey { 33 | return Signal_Session.PendingPreKey.with { 34 | if let item = preKeyId { 35 | $0.preKeyID = item 36 | } 37 | $0.signedPreKeyID = Int32(self.signedPreKeyId) 38 | $0.baseKey = self.baseKey.data 39 | } 40 | } 41 | 42 | /** 43 | Create a pending pre key from a ProtoBuf object. 44 | - parameter object: The ProtoBuf object. 45 | - throws: `SignalError` error of type `invalidProtoBuf`, if data is missing or corrupt 46 | */ 47 | init(from protoObject: Signal_Session.PendingPreKey) throws { 48 | guard protoObject.hasBaseKey, protoObject.hasSignedPreKeyID else { 49 | throw SignalError(.invalidProtoBuf, "Missing data in object") 50 | } 51 | if protoObject.hasPreKeyID { 52 | self.preKeyId = protoObject.preKeyID 53 | } 54 | if protoObject.signedPreKeyID < 0 { 55 | throw SignalError(.invalidProtoBuf, "Invalid SignedPreKey id \(protoObject.signedPreKeyID)") 56 | } 57 | self.signedPreKeyId = UInt32(protoObject.signedPreKeyID) 58 | self.baseKey = try PublicKey(from: protoObject.baseKey) 59 | } 60 | } 61 | 62 | // MARK: Protocol Equatable 63 | 64 | extension PendingPreKey: Equatable { 65 | 66 | /** 67 | Compare two pending pre keys for equality. 68 | - parameters lhs: The first pre key 69 | - parameters rhs: The second pre key 70 | - returns: `True`, if the pre keys match 71 | */ 72 | static func ==(lhs: PendingPreKey, rhs: PendingPreKey) -> Bool { 73 | return lhs.preKeyId == rhs.preKeyId && 74 | lhs.signedPreKeyId == rhs.signedPreKeyId && 75 | lhs.baseKey == rhs.baseKey 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Session/SessionPreKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionPreKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 25.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A pre key used to esatblish a session. A unique pre key is used for 13 | each new session. 14 | */ 15 | struct SessionPreKey { 16 | 17 | /// The upper bound (inclusive) of the pre key id 18 | static let mediumMaxValue: UInt32 = 0xFFFFFF 19 | 20 | /// The public data of the pre key (id and public key) 21 | let publicKey: SessionPreKeyPublic 22 | 23 | /// The private key of the pre key 24 | let privateKey: PrivateKey 25 | 26 | /** 27 | Create a pre key from the components 28 | - parameter id: The pre key id 29 | - parameter keyPair: The key pair of the pre key 30 | */ 31 | init(id: UInt32, keyPair: KeyPair) { 32 | self.publicKey = SessionPreKeyPublic(id: id, key: keyPair.publicKey) 33 | self.privateKey = keyPair.privateKey 34 | } 35 | 36 | /** 37 | Create a new pre key with the index. 38 | - note: Possible errors: 39 | - `curveError` if the public key could not be created. 40 | - `noRandomBytes`, if the crypto delegate could not provide random data 41 | - parameter index: The index to create the id 42 | - throws: `SignalError` errors 43 | */ 44 | init(index: UInt32) throws { 45 | let id = index 46 | let keyPair = try KeyPair() 47 | self.init(id: id, keyPair: keyPair) 48 | } 49 | 50 | /// The key pair of the signed pre key 51 | var keyPair: KeyPair { 52 | return KeyPair(publicKey: publicKey.key, privateKey: privateKey) 53 | } 54 | } 55 | 56 | // MARK: Protocol Buffers 57 | 58 | extension SessionPreKey: ProtocolBufferEquivalent { 59 | 60 | /// Convert the pre key to a ProtoBuf object 61 | var protoObject: Signal_PreKey { 62 | return Signal_PreKey.with { 63 | $0.publicKey = publicKey.protoObject 64 | $0.privateKey = privateKey.data 65 | } 66 | } 67 | 68 | /** 69 | Create a pre key from a ProtoBuf object. 70 | - parameter object: The ProtoBuf object. 71 | - throws: `SignalError` of type `invalidProtoBuf` if data is corrupt or missing 72 | */ 73 | init(from object: Signal_PreKey) throws { 74 | guard object.hasPublicKey, object.hasPrivateKey else { 75 | throw SignalError(.invalidProtoBuf, "Missing data in SessionPreKey object") 76 | } 77 | self.publicKey = try SessionPreKeyPublic(from: object.publicKey) 78 | self.privateKey = try PrivateKey(from: object.privateKey) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/Session/SessionPreKeyBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionPreKeyBundle.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 25.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Pre key bundles are used to establish new sessions. 13 | */ 14 | public struct SessionPreKeyBundle { 15 | 16 | /// The id of the pre key that was used 17 | var preKeyId: UInt32 18 | 19 | /// The pre key, if a pre key was used 20 | var preKeyPublic: PublicKey? 21 | 22 | /// The id of the signed pre key 23 | var signedPreKeyId: UInt32 24 | 25 | /// The signed pre key that was used 26 | var signedPreKeyPublic: PublicKey 27 | 28 | /// The signature of the signed pre key 29 | var signedPreKeySignature: Data 30 | 31 | /// The identity key of the remote party 32 | var identityKey: PublicKey 33 | 34 | /** 35 | Create a pre key bundle from its components. 36 | - parameter deviceId: The device id of the remote party 37 | - parameter preKeyId: The id of the pre key that was used 38 | - parameter preKeyPublic: The pre key, if a pre key was used 39 | - parameter signedPreKeyId: The id of the signed pre key 40 | - parameter signedPreKeyPublic: The signed pre key that was used 41 | - parameter signedPreKeySignature: The signature of the signed pre key 42 | - parameter identityKey: The identity key of the remote party 43 | */ 44 | init( 45 | preKeyId: UInt32, 46 | preKeyPublic: PublicKey?, 47 | signedPreKeyId: UInt32, 48 | signedPreKeyPublic: PublicKey, 49 | signedPreKeySignature: Data, 50 | identityKey: PublicKey) { 51 | 52 | self.preKeyId = preKeyId 53 | self.preKeyPublic = preKeyPublic 54 | self.signedPreKeyId = signedPreKeyId 55 | self.signedPreKeyPublic = signedPreKeyPublic 56 | self.signedPreKeySignature = signedPreKeySignature 57 | self.identityKey = identityKey 58 | } 59 | 60 | /** 61 | Create a pre key bundle from its components. 62 | - parameter deviceId: The device id of the remote party 63 | - parameter preKey: The pre key to use 64 | - parameter signedPreKey: The signed pre key 65 | - parameter identityKey: The identity key of the remote party 66 | */ 67 | init(preKey: SessionPreKeyPublic, 68 | signedPreKey: SessionSignedPreKeyPublic, 69 | identityKey: PublicKey) { 70 | 71 | self.preKeyId = preKey.id 72 | self.preKeyPublic = preKey.key 73 | self.signedPreKeyId = signedPreKey.id 74 | self.signedPreKeyPublic = signedPreKey.key 75 | self.signedPreKeySignature = signedPreKey.signature 76 | self.identityKey = identityKey 77 | 78 | } 79 | 80 | /** 81 | Create a pre key bundle from its components. 82 | - parameter preKey: The pre key data to use 83 | - parameter signedPreKey: The signed pre key data 84 | - parameter identityKey: The identity key data of the remote party 85 | */ 86 | public init(preKey: Data, 87 | signedPreKey: Data, 88 | identityKey: Data) throws { 89 | 90 | self.init(preKey: try SessionPreKeyPublic(from: preKey), 91 | signedPreKey: try SessionSignedPreKeyPublic(from: signedPreKey), 92 | identityKey: try PublicKey(from: identityKey)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Session/SessionPreKeyPublic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionPublicPreKey.swift 3 | // SignalProtocolSwift iOS 4 | // 5 | // Created by User on 27.01.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A pre key used to esatblish a session. A unique pre key is used for 13 | each new session. 14 | */ 15 | struct SessionPreKeyPublic { 16 | 17 | /// The id of the pre key 18 | let id: UInt32 19 | 20 | /// The key pair of the pre key 21 | let key: PublicKey 22 | 23 | /** 24 | Create a public pre key from the components 25 | - parameter id: The pre key id 26 | - parameter keyPair: The public key of the pre key 27 | */ 28 | init(id: UInt32, key: PublicKey) { 29 | self.id = id 30 | self.key = key 31 | } 32 | } 33 | 34 | // MARK: Protocol Buffers 35 | 36 | extension SessionPreKeyPublic: ProtocolBufferEquivalent { 37 | 38 | /// Convert the public pre key to a ProtoBuf object 39 | var protoObject: Signal_PreKey.PublicPart { 40 | return Signal_PreKey.PublicPart.with { 41 | $0.id = self.id 42 | $0.key = key.data 43 | } 44 | } 45 | 46 | /** 47 | Create a public pre key from a ProtoBuf object. 48 | - parameter protoObject: The ProtoBuf object. 49 | - throws: `SignalError` of type `invalidProtoBuf` if data is corrupt or missing 50 | */ 51 | init(from protoObject: Signal_PreKey.PublicPart) throws { 52 | guard protoObject.hasID, protoObject.hasKey else { 53 | throw SignalError(.invalidProtoBuf, "Missing data in SessionPublicPreKey object") 54 | } 55 | self.id = protoObject.id 56 | self.key = try PublicKey(from: protoObject.key) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Session/SessionRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionRecord.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 01.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | The record of a session (and previous sessions) with another party. 13 | */ 14 | final class SessionRecord: ProtocolBufferEquivalent { 15 | 16 | /// The maximum number of archived states 17 | private static let archivedStatesMax = 40 18 | 19 | /// The current session 20 | private(set) var state: SessionState 21 | 22 | /// A list of previous sessions, sorted by most recent 23 | private(set) var previousStates: [SessionState] 24 | 25 | /// Indicates if the session was just created 26 | private(set) var isFresh: Bool 27 | 28 | /** 29 | Create a new session record for a session. 30 | - note: If the `state` parameter is nil, then a 'fresh' session record is created. 31 | - parameter state: The session state. 32 | */ 33 | init(state: SessionState?) { 34 | if state == nil { 35 | self.state = SessionState() 36 | self.isFresh = true 37 | } else { 38 | self.state = state! 39 | self.isFresh = false 40 | } 41 | self.previousStates = [SessionState]() 42 | } 43 | 44 | /** 45 | Check if the session record contains a specific state. 46 | - parameter baseKey: The key used for the session. 47 | - returns: `true`, if a session exists for the given key 48 | */ 49 | func hasSessionState(baseKey: PublicKey) -> Bool { 50 | if state.aliceBaseKey == baseKey { 51 | return true 52 | } 53 | return previousStates.contains { 54 | $0.aliceBaseKey == baseKey 55 | } 56 | } 57 | 58 | /** 59 | Create a new state and archive the old one. 60 | */ 61 | func archiveCurrentState() { 62 | let newState = SessionState() 63 | promoteState(state: newState) 64 | } 65 | 66 | /** 67 | Make a state the currently active state. 68 | - note: Will remove the oldest states if the maximum number of states is reached. 69 | - parameter state: The new current state. 70 | */ 71 | func promoteState(state: SessionState) { 72 | // Remove state if it already exists 73 | if let baseKey = state.aliceBaseKey { 74 | removeState(for: baseKey) 75 | } 76 | // Move the previously current state to the list of previous states 77 | previousStates.insert(self.state, at: 0) 78 | 79 | // Make the promoted state the current state 80 | self.state = state 81 | 82 | // Remove any previous nodes beyond the maximum length 83 | if previousStates.count > SessionRecord.archivedStatesMax { 84 | previousStates = Array(previousStates[0.. Bool { 131 | return lhs.state == rhs.state && 132 | lhs.isFresh == rhs.isFresh && 133 | lhs.previousStates == rhs.previousStates 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/Session/SessionSignedPreKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionSignedPreKey.swift 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 25.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A signed pre key is used as part of a session bundle to establish a new session. 13 | The public part of the key pair is signed with the identity key of the creator 14 | to provide authentication. 15 | */ 16 | struct SessionSignedPreKey { 17 | 18 | /// The public data of the signed pre key 19 | let publicKey: SessionSignedPreKeyPublic 20 | 21 | /// The private key of the signed pre key 22 | let privateKey: PrivateKey 23 | 24 | /** 25 | Create a signed pre key from its components. 26 | - parameter id: The id of the signed pre key 27 | - parameter keyPair: The key pair of the signed pre key 28 | - parameter timestamp: The time when the key was created 29 | - parameter signature: The signature of the public key of the key pair 30 | */ 31 | init(id: UInt32, timestamp: UInt64, keyPair: KeyPair, signature: Data) { 32 | self.publicKey = SessionSignedPreKeyPublic(id: id, timestamp: timestamp, key: keyPair.publicKey, signature: signature) 33 | self.privateKey = keyPair.privateKey 34 | } 35 | 36 | /** 37 | Create a signed pre key. 38 | - note: The following errors can be thrown: 39 | - `noRandomBytes`, if the crypto provider can't provide random bytes. 40 | - `curveError`, if no public key could be created from the random private key. 41 | - `invalidLength`, if the public key is more than 256 or 0 byte. 42 | - `invalidSignature`, if the message could not be signed. 43 | - parameter id: The id of the signed pre key 44 | - parameter keyPair: The key pair of the signed pre key 45 | - parameter timestamp: The time when the key was created 46 | - parameter signature: The signature of the public key of the key pair 47 | */ 48 | init(id: UInt32, signatureKey: PrivateKey, timestamp: UInt64) throws { 49 | let keyPair = try KeyPair() 50 | let signature = try signatureKey.sign(message: keyPair.publicKey.data) 51 | self.publicKey = SessionSignedPreKeyPublic(id: id, timestamp: timestamp, key: keyPair.publicKey, signature: signature) 52 | guard publicKey.verify(with: try signatureKey.publicKey()) else { 53 | throw SignalError(.invalidSignature) 54 | } 55 | self.privateKey = keyPair.privateKey 56 | } 57 | 58 | /// The key pair of the signed pre key 59 | var keyPair: KeyPair { 60 | return KeyPair(publicKey: publicKey.key, privateKey: privateKey) 61 | } 62 | } 63 | 64 | // MARK: Protocol Buffers 65 | 66 | extension SessionSignedPreKey: ProtocolBufferEquivalent { 67 | 68 | /// Convert the signed pre key to a ProtoBuf object 69 | var protoObject: Signal_SignedPreKey { 70 | return Signal_SignedPreKey.with { 71 | $0.publicKey = self.publicKey.protoObject 72 | $0.privateKey = self.privateKey.data 73 | } 74 | } 75 | 76 | /** 77 | Create a signed pre key from a ProtoBuf object. 78 | - parameter object: The ProtoBuf object. 79 | - throws: `SignalError` of type `invalidProtoBuf` if data is corrupt or missing 80 | */ 81 | init(from protoObject: Signal_SignedPreKey) throws { 82 | guard protoObject.hasPublicKey, protoObject.hasPrivateKey else { 83 | throw SignalError(.invalidProtoBuf, "Missing data in SessionSignedPreKey object") 84 | } 85 | self.publicKey = try SessionSignedPreKeyPublic(from: protoObject.publicKey) 86 | self.privateKey = try PrivateKey(from: protoObject.privateKey) 87 | } 88 | } 89 | 90 | // MARK: Protocol Equatable 91 | 92 | extension SessionSignedPreKey: Equatable { 93 | 94 | /** 95 | Compare two signed pre keys for equality. 96 | - parameters lhs: The first signed pre key 97 | - parameters rhs: The second signed pre key 98 | - returns: `True`, if the signed pre keys match 99 | */ 100 | static func ==(lhs: SessionSignedPreKey, rhs: SessionSignedPreKey) -> Bool { 101 | return lhs.privateKey == rhs.privateKey && 102 | lhs.publicKey == rhs.publicKey 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Session/SessionSignedPreKeyPublic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionPublicSignedPreKey.swift 3 | // SignalProtocolSwift iOS 4 | // 5 | // Created by User on 27.01.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | A public signed pre key is used as part of a session bundle to establish a new session. 14 | The public part of the key pair is signed with the identity key of the creator 15 | to provide authentication. 16 | */ 17 | struct SessionSignedPreKeyPublic { 18 | 19 | /// The id of the signed pre key 20 | public let id: UInt32 21 | 22 | /// The key pair of the signed pre key 23 | public let key: PublicKey 24 | 25 | /// The time when the key was created 26 | public let timestamp: UInt64 27 | 28 | /// The signature of the public key of the key pair 29 | public let signature: Data 30 | 31 | /** 32 | Create a public signed pre key from its components. 33 | - parameter id: The id of the signed pre key 34 | - parameter key: The public key of the signed pre key 35 | - parameter timestamp: The time when the key was created 36 | - parameter signature: The signature of the public key of the key pair 37 | */ 38 | init(id: UInt32, timestamp: UInt64, key: PublicKey, signature: Data) { 39 | self.id = id 40 | self.key = key 41 | self.timestamp = timestamp 42 | self.signature = signature 43 | } 44 | 45 | /** 46 | Verify that the signed key is valid. 47 | - parameter: The public key of the user who signed the key 48 | - returns: `true` if the signature is valid 49 | */ 50 | func verify(with publicKey: PublicKey) -> Bool { 51 | return publicKey.verify(signature: signature, for: key.data) 52 | } 53 | } 54 | 55 | // MARK: Protocol Buffers 56 | 57 | extension SessionSignedPreKeyPublic: ProtocolBufferEquivalent { 58 | 59 | /// Convert the public signed pre key to a ProtoBuf object 60 | var protoObject: Signal_SignedPreKey.PublicPart { 61 | return Signal_SignedPreKey.PublicPart.with { 62 | $0.id = self.id 63 | $0.key = self.key.data 64 | $0.timestamp = self.timestamp 65 | $0.signature = self.signature 66 | } 67 | } 68 | 69 | /** 70 | Create a signed pre key from a ProtoBuf object. 71 | - parameter protoObject: The ProtoBuf object. 72 | - throws: `SignalError` of type `invalidProtoBuf` if data is corrupt or missing 73 | */ 74 | init(from protoObject: Signal_SignedPreKey.PublicPart) throws { 75 | guard protoObject.hasID, protoObject.hasKey, 76 | protoObject.hasSignature, protoObject.hasTimestamp else { 77 | throw SignalError(.invalidProtoBuf, "Missing data in SessionSignedPreKey object") 78 | } 79 | self.id = protoObject.id 80 | self.key = try PublicKey(from: protoObject.key) 81 | self.timestamp = protoObject.timestamp 82 | self.signature = protoObject.signature 83 | } 84 | } 85 | 86 | // MARK: Protocol Equatable 87 | 88 | extension SessionSignedPreKeyPublic: Equatable { 89 | 90 | /** 91 | Compare two public signed pre keys for equality. 92 | - parameters lhs: The first public signed pre key 93 | - parameters rhs: The second public signed pre key 94 | - returns: `True`, if the public signed pre keys match 95 | */ 96 | static func ==(lhs: SessionSignedPreKeyPublic, rhs: SessionSignedPreKeyPublic) -> Bool { 97 | return lhs.id == rhs.id && 98 | lhs.key == rhs.key && 99 | lhs.signature == rhs.signature && 100 | lhs.timestamp == rhs.timestamp 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/SignalProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // SignalProtocolSwift.h 3 | // SignalProtocolSwift 4 | // 5 | // Created by User on 27.01.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | //! Project version number for SignalProtocolSwift. 12 | FOUNDATION_EXPORT double SignalProtocolSwiftVersionNumber; 13 | 14 | //! Project version string for SignalProtocolSwift. 15 | FOUNDATION_EXPORT const unsigned char SignalProtocolSwiftVersionString[]; 16 | -------------------------------------------------------------------------------- /Tests/CurveTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurveTests.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 24.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Curve25519 11 | @testable import SignalProtocol 12 | 13 | // The result from forming an agreement between alice and bob 14 | private let shared = Data([ 15 | 0x32, 0x5f, 0x23, 0x93, 0x28, 0x94, 0x1c, 0xed, 0x6e, 0x67, 0x3b, 16 | 0x86, 0xba, 0x41, 0x01, 0x74, 0x48, 0xe9, 0x9b, 0x64, 0x9a, 0x9c, 17 | 0x38, 0x06, 0xc1, 0xdd, 0x7c, 0xa4, 0xc4, 0x77, 0xe6, 0x29]) 18 | 19 | class CurveTests: XCTestCase { 20 | 21 | /** 22 | Test if signing and verification works 23 | */ 24 | func testSignature() { 25 | let testMessage = Data([UInt8]("WhisperTextMessage".utf8)) 26 | 27 | guard let keyPair = try? KeyPair() else { 28 | XCTFail("Could not create keys") 29 | return 30 | } 31 | 32 | guard let signature = try? keyPair.privateKey.sign(message: testMessage) else { 33 | XCTFail("Could not sign message") 34 | return 35 | } 36 | 37 | guard keyPair.publicKey.verify(signature: signature, for: testMessage) else { 38 | XCTFail("Could not verify message \(signature.count)") 39 | return 40 | } 41 | } 42 | 43 | /** 44 | Test if key agreements can be correctly calculated 45 | */ 46 | func testCurve25519Agreement() { 47 | 48 | guard let (alice, bob) = try? createBobAndAlice() else { 49 | XCTFail("Could not create keys for bob and alice") 50 | return 51 | } 52 | 53 | guard let sharedOne = try? alice.publicKey.calculateAgreement(privateKey: bob.privateKey), 54 | let sharedTwo = try? bob.publicKey.calculateAgreement(privateKey: alice.privateKey) else { 55 | XCTFail("Could not calculate agreements") 56 | return 57 | } 58 | 59 | guard sharedOne == shared, 60 | sharedTwo == shared else { 61 | XCTFail("Agreements not correct") 62 | return 63 | } 64 | } 65 | 66 | /** 67 | Test if public keys are correctly created from private keys 68 | */ 69 | func testGeneratePublic() { 70 | 71 | guard let (alice, _) = try? createBobAndAlice() else { 72 | XCTFail("Could not create keys for bob and alice") 73 | return 74 | } 75 | 76 | guard let alicePublicKey = try? PublicKey(privateKey: alice.privateKey) else { 77 | XCTFail("Alice public key creation failed") 78 | return 79 | } 80 | 81 | guard alice.publicKey == alicePublicKey else { 82 | XCTFail("Public key is not correct") 83 | return 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/HKDFTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKDFTests.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 24.10.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SignalProtocol 11 | 12 | class HKDFTests: XCTestCase { 13 | 14 | func testVectorV3() { 15 | let ikm = Data([ 16 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 17 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 18 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b]) 19 | 20 | let salt = Data([ 21 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 22 | 0x08, 0x09, 0x0a, 0x0b, 0x0c]) 23 | 24 | let info = Data([ 25 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 26 | 0xf8, 0xf9]) 27 | 28 | let okm = Data([ 29 | 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 30 | 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 31 | 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 32 | 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 33 | 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 34 | 0x58, 0x65]) 35 | 36 | testHKDF(ikm: ikm, salt: salt, info: info, okm: okm) 37 | } 38 | 39 | func testVectorLongV3() { 40 | let ikm = Data([ 41 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 42 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 43 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 44 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 45 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 46 | 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 47 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 48 | 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 49 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 50 | 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f]) 51 | 52 | let salt = Data([ 53 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 54 | 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 55 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 56 | 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 57 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 58 | 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 59 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 60 | 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 61 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 62 | 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf]) 63 | 64 | let info = Data([ 65 | 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 66 | 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 67 | 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 68 | 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 69 | 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 70 | 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 71 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 72 | 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 73 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 74 | 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]) 75 | 76 | let okm = Data([ 77 | 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 78 | 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, 79 | 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 80 | 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 81 | 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 82 | 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 83 | 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 84 | 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 85 | 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 86 | 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 87 | 0x1d, 0x87]) 88 | 89 | testHKDF(ikm: ikm, salt: salt, info: info, okm: okm) 90 | } 91 | 92 | private func testHKDF(ikm: Data, salt: Data, info: Data, okm: Data) { 93 | 94 | 95 | do { 96 | let result = try HKDF.deriveSecrets( 97 | material: ikm, 98 | salt: salt, 99 | info: info, 100 | outputLength: okm.count) 101 | 102 | guard result.count == okm.count else { 103 | XCTFail("Derived secret length not correct") 104 | return 105 | } 106 | guard result == okm else { 107 | XCTFail("Derived secrets not equal") 108 | return 109 | } 110 | } catch { 111 | XCTFail("Derive secrets failed: \(error.localizedDescription)") 112 | return 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/SignalProtocolSwiftTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignalProtocolSwiftTests.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 13.11.17. 6 | // 7 | 8 | import XCTest 9 | @testable import SignalProtocol 10 | 11 | class SignalProtocolSwiftTests: XCTestCase { 12 | 13 | func testSHA512() { 14 | let input = Data([0,1,2,3,4,5,6,7,8,9,10,11,12]) 15 | let correctHash = Data([182, 227, 10, 64, 22, 2, 148, 134, 16 | 249, 32, 92, 93, 20, 19, 68, 248, 17 | 133, 179, 222, 36, 104, 237, 251, 11, 18 | 135, 5, 69, 241, 119, 92, 232, 37, 19 | 151, 194, 164, 4, 98, 243, 133, 201, 20 | 87, 121, 12, 32, 130, 45, 158, 146, 21 | 14, 241, 174, 35, 8, 120, 214, 178, 22 | 63, 34, 27, 1, 130, 135, 156, 204]) 23 | 24 | guard let hash = try? SignalCrypto.sha512(for: input) else { 25 | XCTFail("Could not calculate hash") 26 | return 27 | } 28 | guard hash == correctHash else { 29 | XCTFail("Hash not correct") 30 | return 31 | } 32 | } 33 | 34 | func testHMAC() { 35 | let key = Data([1,2,3,4,5]) 36 | let message = Data([2,3,4,5,6,7]) 37 | let correctHMAC = Data([108, 92, 22, 255, 237, 114, 145, 181, 38 | 183, 207, 58, 230, 250, 143, 45, 56, 39 | 112, 47, 95, 160, 56, 209, 128, 40, 40 | 15, 23, 185, 155, 173, 46, 81, 206]) 41 | guard let hmac = try? SignalCrypto.hmacSHA256(for: message, with: key) else { 42 | XCTFail("Could not create HMAC") 43 | return 44 | } 45 | guard hmac == correctHMAC else { 46 | XCTFail("HMAC not correct") 47 | return 48 | } 49 | 50 | } 51 | 52 | func testEncrypt() { 53 | let key = Data([166, 76, 22, 20, 4, 226, 125, 111, 54 | 149, 116, 198, 25, 65, 110, 128, 77, 55 | 192, 134, 194, 157, 53, 76, 46, 198, 56 | 186, 85, 233, 171, 147, 88, 27, 23]) 57 | 58 | let iv = Data([36, 142, 179, 171, 247, 31, 92, 64, 59 | 97, 195, 73, 47, 251, 1, 163, 182]) 60 | 61 | let message = Data([117, 112, 32, 116, 104, 101, 32, 112, 117, 110, 107, 115]) 62 | 63 | let encryptedCBC = Data([199, 122, 147, 81, 12, 156, 63, 30, 64 | 102, 182, 96, 94, 151, 146, 65, 65]) 65 | let encryptedCTR = Data([153, 140, 109, 238, 184, 184, 66 | 41, 27, 134, 251, 139, 57]) 67 | 68 | process(message: message, key: key, iv: iv, with: .AES_CBCwithPKCS5, ciphertext: encryptedCBC) 69 | process(message: message, key: key, iv: iv, with: .AES_CTRnoPadding, ciphertext: encryptedCTR) 70 | } 71 | 72 | private func process(message: Data, key: Data, iv: Data, with cipher: SignalEncryptionScheme, ciphertext: Data) { 73 | do { 74 | let cryptor = SignalCommonCrypto() 75 | let encrypted = try cryptor.encrypt( 76 | message: message, with: cipher, key: key, iv: iv) 77 | guard encrypted == ciphertext else { 78 | XCTFail("Invalid ciphertext") 79 | return 80 | } 81 | let decrypted = try cryptor.decrypt( 82 | message: encrypted, with: cipher, key: key, iv: iv) 83 | guard decrypted == message else { 84 | XCTFail("Invalid decrypted text") 85 | return 86 | } 87 | } catch { 88 | XCTFail("Could not encrypt/decrypt message: \(error)") 89 | return 90 | } 91 | } 92 | 93 | func testSignedKey() { 94 | 95 | guard let keys = try? KeyPair(), 96 | let signedKey = try? KeyPair() else { 97 | XCTFail("Could not create key pairs") 98 | return 99 | } 100 | let key = signedKey.publicKey 101 | let data = key.data 102 | guard let signature = try? keys.privateKey.sign(message: data) else { 103 | XCTFail("Could not sign message") 104 | return 105 | } 106 | guard keys.publicKey.verify(signature: signature, for: data) else { 107 | XCTFail("Could not verify signed key") 108 | return 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /Tests/SignedKeyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignedKeyTests.swift 3 | // SignalProtocol Tests 4 | // 5 | // Created by Christoph on 18.05.18. 6 | // Copyright © 2018 User. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SignalProtocol 11 | 12 | 13 | class SignedKeyTests: XCTestCase { 14 | 15 | func testSignedPreKey() { 16 | let key = try! KeyPair() 17 | let aliceStore = TestStore(with: try! key.protoData()) 18 | 19 | guard let signedKeyData = try? aliceStore.updateSignedPrekey() else { 20 | XCTFail("Could not create signed pre key data") 21 | return 22 | } 23 | 24 | guard let signedKey = try? SessionSignedPreKeyPublic(from: signedKeyData) else { 25 | XCTFail("Could not create signed pre key") 26 | return 27 | } 28 | 29 | guard signedKey.verify(with: key.publicKey) else { 30 | XCTFail("Invalid signed key") 31 | return 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Test Implementation/SignalAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignalAddress.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A `SignalAddress` identifies a single device of a Signal user, with a user 12 | `identifier` (such as a phone number), and the `deviceId` which specifies the device 13 | */ 14 | public struct SignalAddress { 15 | 16 | /// The unique identifier of a user (such as a phone number) 17 | public let identifier: String 18 | 19 | /// The identifier for the individual device of a user 20 | public let deviceId: UInt32 21 | 22 | /** 23 | Create a `SignalAddress`. 24 | - parameter identifier: The user identifier (such as phone number) 25 | - parameter deviceId: The id of the user's device 26 | */ 27 | public init(identifier: String, deviceId: UInt32) { 28 | self.identifier = identifier 29 | self.deviceId = deviceId 30 | } 31 | } 32 | 33 | extension SignalAddress: Equatable { 34 | 35 | /** 36 | Compare two SignalAddresses. Two `SignalAddress` objects are 37 | equal if both their identifier and deviceId are equal. 38 | - parameter lhs: The first address 39 | - parameter rhs: The second address 40 | - returns: `True` if the addresses are equal. 41 | */ 42 | public static func ==(lhs: SignalAddress, rhs: SignalAddress) -> Bool { 43 | return lhs.identifier == rhs.identifier && lhs.deviceId == rhs.deviceId 44 | } 45 | } 46 | 47 | extension SignalAddress: Hashable { } 48 | 49 | extension SignalAddress: CustomStringConvertible { 50 | 51 | /** 52 | A description of the SignalAddress. 53 | */ 54 | public var description: String { 55 | return "SignalAddress(\(identifier),\(deviceId))" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Test Implementation/SignalSenderKeyName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignalSenderKeyName.swift 3 | // SignalProtocolSwift-iOS 4 | // 5 | // Created by User on 18.11.17. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | * A representation of a (group + sender + device) tuple 12 | */ 13 | public struct SignalSenderKeyName { 14 | 15 | /// The group identifier (such as the name) 16 | let groupId: String 17 | 18 | /// The contact 19 | let sender: SignalAddress 20 | 21 | /** 22 | Create a new `SignalSenderKeyName` 23 | - parameter groupId: The group identifier (such as the name) 24 | - parameter sender: The contact 25 | */ 26 | public init(groupId: String, sender: SignalAddress) { 27 | self.groupId = groupId 28 | self.sender = sender 29 | } 30 | } 31 | 32 | extension SignalSenderKeyName: Equatable { 33 | 34 | /** 35 | Compare two `SignalSenderKeyName`. Two `SignalSenderKeyName` objects are 36 | equal if their identifier and sender are equal. 37 | - parameter lhs: The first address 38 | - parameter rhs: The second address 39 | - returns: `True` if the addresses are equal. 40 | */ 41 | public static func ==(lhs: SignalSenderKeyName, rhs: SignalSenderKeyName) -> Bool { 42 | return lhs.groupId == rhs.groupId && lhs.sender == rhs.sender 43 | } 44 | } 45 | 46 | extension SignalSenderKeyName: Hashable { } 47 | 48 | extension SignalSenderKeyName: CustomStringConvertible { 49 | 50 | /** 51 | A String representation of the sender key name. 52 | */ 53 | public var description: String { 54 | return "SignalSenderKeyName(group: \(groupId), id: \(sender.identifier), device: \(sender.deviceId))" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestFakeCryptoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestFakeCryptoProvider.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 12.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SignalProtocol 11 | 12 | /** 13 | Use CommonCrypto, but use predictable random numbers 14 | */ 15 | class TestFakeCryptoProvider: SignalCryptoProvider { 16 | 17 | /// Delegate everything except random numbers to CommonCrypto 18 | private let delegate = SignalCommonCrypto() 19 | 20 | /// Current "random" value 21 | private var testRandom: UInt8 = 0 22 | 23 | /** 24 | Create a number of secure random bytes. 25 | - parameter bytes: The number of random bytes to create 26 | - returns: The random bytes of length `bytes` 27 | */ 28 | func random(bytes: Int) -> Data { 29 | var output = Data(count: bytes) 30 | for i in 0.. Data { 44 | return delegate.hmacSHA256(for: message, with: salt) 45 | } 46 | 47 | /** 48 | Create a SHA512 digest for a given message 49 | - parameter message: The input message to create the digest for 50 | - returns: The digest 51 | - throws: `SignalError.digestError` 52 | */ 53 | func sha512(for message: Data) throws -> Data { 54 | return try delegate.sha512(for: message) 55 | } 56 | 57 | /** 58 | Encrypt a message with AES 59 | - parameter message: The input message to encrypt 60 | - parameter cipher: THe encryption scheme to use 61 | - parameter key: The key for encryption (`kCCKeySizeAES128` bytes) 62 | - parameter iv: The initialization vector 63 | - returns: The encrypted message 64 | - throws: `SignalError.encryptionError` 65 | */ 66 | func encrypt(message: Data, with cipher: SignalEncryptionScheme, key: Data, iv: Data) throws -> Data { 67 | return try delegate.encrypt(message: message, with: cipher, key: key, iv: iv) 68 | } 69 | 70 | /** 71 | Decrypt a message with AES 72 | - parameter message: The input message to decrypt 73 | - parameter cipher: THe encryption scheme to use 74 | - parameter key: The key for decryption (`kCCKeySizeAES128` bytes) 75 | - parameter iv: The initialization vector 76 | - returns: The decrypted message 77 | - throws: `SignalError.decryptionError` 78 | */ 79 | func decrypt(message: Data, with cipher: SignalEncryptionScheme, key: Data, iv: Data) throws -> Data { 80 | return try delegate.decrypt(message: message, with: cipher, key: key, iv: iv) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestIdentityStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestIdentityStore.swift 3 | // SignalProtocolSwift-iOSTests 4 | // 5 | // Created by User on 26.01.18. 6 | // 7 | 8 | import Foundation 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the `IdentityKeyStore` protocol to handle the identity keys of the Signal Protocol. 13 | */ 14 | class TestIdentityStore: IdentityKeyStore { 15 | 16 | required init(with keyPair: Data) { 17 | self.identityKey = keyPair 18 | } 19 | 20 | 21 | /// The type that distinguishes different devices/users 22 | typealias Address = SignalAddress 23 | 24 | /// The local identity key data 25 | private var identityKey: Data! 26 | 27 | /// Dictionary of the identities 28 | private var identities = [SignalAddress : Data]() 29 | 30 | /** 31 | Return the identity key pair. 32 | - returns: The identity key pair data 33 | */ 34 | func getIdentityKeyData() throws -> Data { 35 | if identityKey == nil { 36 | identityKey = try SignalCrypto.generateIdentityKeyPair() 37 | } 38 | return identityKey 39 | } 40 | 41 | /** 42 | Save the identity key pair. 43 | - parameter identityKeyData: The data to store 44 | - throws: `SignalError` of type `storageError`, if the data could not be saved 45 | */ 46 | func store(identityKeyData: Data) { 47 | identityKey = identityKeyData 48 | } 49 | 50 | /** 51 | Return the identity for the given address, if there is any. 52 | - parameter address: The address of the remote client 53 | - returns: The identity for the address, or nil if no data exists 54 | */ 55 | func identity(for address: SignalAddress) throws -> Data? { 56 | return identities[address] 57 | } 58 | 59 | /** 60 | Store a remote client's identity key as trusted. 61 | - parameter identity: The identity key data (may be nil, if the key should be removed) 62 | - parameter address: The address of the remote client 63 | */ 64 | func store(identity: Data?, for address: SignalAddress) throws { 65 | identities[address] = identity 66 | } 67 | 68 | init() { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestPreKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestPreKeyStore.swift 3 | // SignalProtocolSwift-iOSTests 4 | // 5 | // Created by User on 26.01.18. 6 | // 7 | 8 | import Foundation 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the `PreKeyStore` protocol to handle the pre key storage of the Signal Protocol. 13 | */ 14 | class TestPreKeyStore: PreKeyStore { 15 | 16 | /// Return the id of the last stored pre key. 17 | var lastId: UInt32 = 0 18 | 19 | /// Dictionary of the pre keys by id 20 | private var preKeys = [UInt32 : Data]() 21 | 22 | /** 23 | Provide a Pre Key for a given id. 24 | - parameter id: The pre key ID 25 | - returns: The pre key 26 | */ 27 | func preKey(for id: UInt32) throws -> Data { 28 | guard let key = preKeys[id] else { 29 | throw SignalError(.storageError, "No pre key for id \(id)") 30 | } 31 | return key 32 | } 33 | 34 | /** 35 | Store a pre key for a given id. 36 | - parameter preKey: The key to store 37 | - parameter id: The pre key id 38 | */ 39 | func store(preKey: Data, for id: UInt32) throws { 40 | preKeys[id] = preKey 41 | lastId = id 42 | } 43 | 44 | /** 45 | Indicate if a pre key exists for an id. 46 | - parameter id: The pre key id 47 | - returns: `true` if a key exists 48 | */ 49 | func containsPreKey(for id: UInt32) -> Bool { 50 | return preKeys[id] != nil 51 | } 52 | 53 | /** 54 | Remove a pre key. 55 | - parameter id: The pre key id. 56 | - returns: `true` if the key was removed 57 | */ 58 | func removePreKey(for id: UInt32) throws { 59 | preKeys[id] = nil 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestSenderKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSenderKeyStore.swift 3 | // SignalProtocolSwift-iOSTests 4 | // 5 | // Created by User on 26.01.18. 6 | // 7 | 8 | import Foundation 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the `SenderKeyStore` protocol to handle the sender key storage of the Signal Protocol. 13 | */ 14 | class TestSenderKeyStore: SenderKeyStore { 15 | 16 | /// The type that distinguishes different groups and devices/users 17 | typealias Address = SignalSenderKeyName 18 | 19 | private var senderKeys = [SignalSenderKeyName : Data]() 20 | 21 | /** 22 | Returns a copy of the sender key record corresponding to the address tuple. 23 | - parameter address: The group address of the remote client 24 | - returns: The Sender Key, if it exists, or nil 25 | */ 26 | func senderKey(for address: SignalSenderKeyName) -> Data? { 27 | return senderKeys[address] 28 | } 29 | 30 | /** 31 | Stores the sender key record. 32 | - parameter senderKey: The key to store 33 | - parameter address: The group address of the remote client 34 | */ 35 | func store(senderKey: Data, for address: SignalSenderKeyName) throws { 36 | senderKeys[address] = senderKey 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestSessionStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSessionStore.swift 3 | // SignalProtocolSwift-iOSTests 4 | // 5 | // Created by User on 26.01.18. 6 | // 7 | 8 | import Foundation 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the `SessionStore` protocol to handle the session records of the Signal Protocol. 13 | */ 14 | class TestSessionStore: SessionStore { 15 | 16 | /// The type that distinguishes different devices/users 17 | typealias Address = SignalAddress 18 | 19 | /// Dictionary of the sessions 20 | private var sessions = [Address : Data]() 21 | 22 | /** 23 | Load a session for a given address. 24 | - parameter address: The address of the remote client 25 | - returns: The session record, or nil if no record exists 26 | */ 27 | func loadSession(for address: Address) -> Data? { 28 | return sessions[address] 29 | } 30 | 31 | /** 32 | Store a session record for a remote client. 33 | - parameter session: The session record to store 34 | - parameter address: The address of the remote client 35 | - returns: `true` on success, `false` on error 36 | */ 37 | func store(session: Data, for address: Address) throws { 38 | sessions[address] = session 39 | } 40 | 41 | /** 42 | Indicate if a record exists for the client address 43 | - parameter address: The address of the remote client 44 | - returns: `true` if a record exists 45 | */ 46 | func containsSession(for address: Address) -> Bool { 47 | return sessions[address] != nil 48 | } 49 | 50 | /** 51 | Delete a session for a remote client. 52 | - parameter address: The address of the remote client 53 | - returns: `true` if the session was deleted 54 | */ 55 | func deleteSession(for address: Address) throws { 56 | sessions[address] = nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestSignedPreKeyStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSignedPreKeyStore.swift 3 | // SignalProtocolSwift-iOSTests 4 | // 5 | // Created by User on 26.01.18. 6 | // 7 | 8 | import Foundation 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the `SignedPreKeyStore` protocol to handle the signed pre key storage of the Signal Protocol. 13 | */ 14 | class TestSignedPreKeyStore: SignedPreKeyStore { 15 | 16 | /// Dictionary of the signed pre keys 17 | private var signedKeys = [UInt32 : Data]() 18 | 19 | /// The id of the last SignedPreKey that was stored. 20 | var lastId: UInt32 = 0 21 | 22 | /** 23 | Provide a Signed Pre Key for a given id. 24 | - parameter id: The Signed Pre Key Id 25 | - returns: The Signed Pre Key 26 | - throws: `SignalError` of type `invalidId` if no key exists for the id 27 | */ 28 | func signedPreKey(for id: UInt32) throws -> Data { 29 | guard let key = signedKeys[id] else { 30 | throw SignalError(.invalidId, "No signed pre key for id \(id)") 31 | } 32 | return key 33 | } 34 | 35 | /** 36 | Store a Signed Pre Key for a given id. 37 | - parameter signedPreKey: The Signed Pre Key to store 38 | - parameter id: The Signed Pre Key id 39 | - throws: `SignalError` of type `storageError`, if the key could not be stored 40 | */ 41 | func store(signedPreKey: Data, for id: UInt32) throws { 42 | signedKeys[id] = signedPreKey 43 | lastId = id 44 | } 45 | 46 | /** 47 | Indicate if a Signed Pre Key exists for an id. 48 | - parameter id: The Signed Pre Key id 49 | - returns: `true` if a key exists 50 | - throws: `SignalError` of type `storageError`, if the key could not be accessed 51 | */ 52 | func containsSignedPreKey(for id: UInt32) -> Bool { 53 | return signedKeys[id] != nil 54 | } 55 | 56 | /** 57 | Remove a Signed Pre Key. 58 | - parameter id: The Signed Pre Key id. 59 | - throws: `SignalError`of type `invalidId` 60 | */ 61 | func removeSignedPreKey(for id: UInt32) throws { 62 | signedKeys[id] = nil 63 | } 64 | 65 | /** 66 | Get all Ids for the SignedPreKeys in the store. 67 | - returns: An array of all ids for which a key is stored 68 | - throws: `SignalError` of type `storageError`, if the key could not be accessed 69 | */ 70 | func allIds() -> [UInt32] { 71 | return [UInt32](signedKeys.keys) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Tests/Test Implementation/TestStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestStore.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 05.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import SignalProtocol 10 | 11 | /** 12 | Implement the key store for testing purposes. 13 | */ 14 | class TestStore: GroupKeyStore { 15 | 16 | // MARK: Typealiases 17 | 18 | /// The identifier to distinguish between different devices/users 19 | typealias Address = SignalAddress 20 | 21 | /// The identifier to distinguish between different groups and devices/users 22 | typealias GroupAddress = SignalSenderKeyName 23 | 24 | /// The type implementing the identity key store 25 | typealias IdentityKeyStore = TestIdentityStore 26 | 27 | /// The type implementing the sender key store 28 | typealias SenderKeyStore = TestSenderKeyStore 29 | 30 | /// The type implementing the session store 31 | typealias SessionStore = TestSessionStore 32 | 33 | // MARK: Variables 34 | 35 | /// The store for the identity keys 36 | let identityKeyStore: TestIdentityStore 37 | 38 | /// The store for the pre keys 39 | let preKeyStore: PreKeyStore = TestPreKeyStore() 40 | 41 | /// The store for the signed pre keys 42 | let signedPreKeyStore: SignedPreKeyStore = TestSignedPreKeyStore() 43 | 44 | /// The store for the sender keys 45 | let senderKeyStore = TestSenderKeyStore() 46 | 47 | /// The store for the sessions 48 | let sessionStore = TestSessionStore() 49 | 50 | init(with keyPair: Data) { 51 | self.identityKeyStore = TestIdentityStore(with: keyPair) 52 | } 53 | 54 | init() { 55 | self.identityKeyStore = TestIdentityStore() 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Tests/TestHelperFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestHelperFunctions.swift 3 | // SignalProtocolSwiftTests 4 | // 5 | // Created by User on 08.11.17. 6 | // Copyright © 2017 User. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import SignalProtocol 11 | 12 | private let alicePublic = Data([ 13 | 0x1b, 0xb7, 0x59, 0x66, 0xf2, 0xe9, 0x3a, 0x36, 0x91, 0xdf, 0xff, 14 | 0x94, 0x2b, 0xb2, 0xa4, 0x66, 0xa1, 0xc0, 0x8b, 0x8d, 0x78, 0xca, 15 | 0x3f, 0x4d, 0x6d, 0xf8, 0xb8, 0xbf, 0xa2, 0xe4, 0xee, 0x28]) 16 | 17 | private let alicePrivate = Data([ 18 | 0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 19 | 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b, 0xf7, 0xae, 20 | 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59]) 21 | 22 | private let bobPublic = Data([ 23 | 0x65, 0x36, 0x14, 0x99, 0x3d, 0x2b, 0x15, 0xee, 0x9e, 0x5f, 0xd3, 24 | 0xd8, 0x6c, 0xe7, 0x19, 0xef, 0x4e, 0xc1, 0xda, 0xae, 0x18, 0x86, 25 | 0xa8, 0x7b, 0x3f, 0x5f, 0xa9, 0x56, 0x5a, 0x27, 0xa2, 0x2f]) 26 | 27 | private let bobPrivate = Data([ 28 | 0xb0, 0x3b, 0x34, 0xc3, 0x3a, 0x1c, 0x44, 0xf2, 0x25, 0xb6, 0x62, 29 | 0xd2, 0xbf, 0x48, 0x59, 0xb8, 0x13, 0x54, 0x11, 0xfa, 0x7b, 0x03, 30 | 0x86, 0xd4, 0x5f, 0xb7, 0x5d, 0xc5, 0xb9, 0x1b, 0x44, 0x66]) 31 | 32 | let aliceAddress = SignalAddress(identifier: "+14159999999", deviceId: 1) 33 | 34 | let bobAddress = SignalAddress(identifier: "+14158888888", deviceId: 1) 35 | 36 | /** 37 | Create the key pairs for bob and alice for testing. 38 | */ 39 | func createBobAndAlice() throws -> (alice: KeyPair, bob: KeyPair) { 40 | let alice = KeyPair(publicKey: try PublicKey(point: alicePublic), 41 | privateKey: try PrivateKey(point: alicePrivate)) 42 | let bob = KeyPair(publicKey: try PublicKey(point: bobPublic), 43 | privateKey: try PrivateKey(point: bobPrivate)) 44 | return (alice, bob) 45 | } 46 | 47 | /** 48 | Randomize an array of elements 49 | */ 50 | func shuffle(_ buffer: inout [T]) { 51 | guard buffer.count > 1 else { 52 | return 53 | } 54 | for i in 0.. String { 63 | return data.map { String(format: "0x%02hhx, ", $0) }.joined() 64 | } 65 | --------------------------------------------------------------------------------