├── .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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------