diff --git a/.gitignore b/.gitignore
index c19e757777a6d2ac8e78234fa53adec9c144e008..fed024541dd11163b940ba3d5aa4880c09c33c93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,25 @@
 /docs/signing.html
 /olm-*.tgz
 /README.html
-/tracing/README.html
\ No newline at end of file
+/tracing/README.html
+
+# Xcode
+build/
+DerivedData/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+*.moved-aside
+*.xcuserstate
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+Pods/
+*.xcworkspace
\ No newline at end of file
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a35eedf0ff8d3fad36c8a246ec1a00221e249625..47b1547e96526c12153e2583a2a9c8552c1e1db9 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,10 @@
+Changes in `2.0.1 <http://matrix.org/git/olm/commit/?h=2.0.1>`_
+===============================================================
+
+This release includes the following changes since 2.0.0
+
+* Add OLMKit, the Objective-C wrapper.
+
 Changes in `2.0.0 <http://matrix.org/git/olm/commit/?h=2.0.0>`_
 ===============================================================
 
diff --git a/Makefile b/Makefile
index 77aa485f85789ac7f3addcafa2b53ccc651c0422..d511ddbea9935c593a1c4f90b737085da23f082e 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 
 MAJOR := 2
 MINOR := 0
-PATCH := 0
+PATCH := 1
 VERSION := $(MAJOR).$(MINOR).$(PATCH)
 PREFIX ?= /usr/local
 BUILD_DIR := build
diff --git a/OLMKit.podspec b/OLMKit.podspec
new file mode 100644
index 0000000000000000000000000000000000000000..6403fdc6d2a34d396d5d8af8609f00a726a912e3
--- /dev/null
+++ b/OLMKit.podspec
@@ -0,0 +1,61 @@
+Pod::Spec.new do |s|
+
+  # The libolm version
+  MAJOR = 2
+  MINOR = 0
+  PATCH = 1
+
+  s.name         = "OLMKit"
+  s.version      = "#{MAJOR}.#{MINOR}.#{PATCH}"
+  s.summary      = "An Objective-C wrapper of olm (http://matrix.org/git/olm)"
+
+  s.description  = <<-DESC
+				   olm is an implementation of the Double Ratchet cryptographic ratchet in C++
+                   DESC
+
+  s.homepage     = "http://matrix.org/git/olm"
+
+  s.license      = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
+
+  s.authors            = { "Chris Ballinger" => "chrisballinger@gmail.com", 
+                           "matrix.org" => "support@matrix.org" }
+
+  s.platform     = :ios, "5.0"
+
+  # Expose the Objective-C wrapper API of libolm
+  s.public_header_files = "xcode/OLMKit/*.h"
+
+  s.source       = { 
+    :git => "https://matrix.org/git/olm.git", 
+    :tag => s.version.to_s 
+  }
+    
+  s.source_files = "xcode/OLMKit/*.{h,m}", "include/**/*.{h,hh}", "src/*.{c,cpp}", "lib/crypto-algorithms/sha256.c",  "lib/crypto-algorithms/aes.c", "lib/curve25519-donna/curve25519-donna.c"
+  
+  # Those files (including .c) are included by ed25519.c. We do not want to compile them twice
+  s.preserve_paths = "lib/ed25519/**/*.{h,c}"
+  
+  s.library = "c++"
+  
+  
+  # Use the same compiler options for C and C++ as olm/Makefile
+  
+  s.compiler_flags = "-g -O3 -DOLMLIB_VERSION_MAJOR=#{MAJOR} -DOLMLIB_VERSION_MINOR=#{MINOR} -DOLMLIB_VERSION_PATCH=#{PATCH}"
+
+  # For headers search paths, manage first the normal installation. Then, use paths used
+  # when the pod is local
+  s.xcconfig = { 
+    'USER_HEADER_SEARCH_PATHS' =>"${PODS_ROOT}/OLMKit/include ${PODS_ROOT}/OLMKit/lib #{File.join(File.dirname(__FILE__), 'include')} #{File.join(File.dirname(__FILE__), 'lib')}"
+  }
+  
+  s.subspec 'olmc' do |olmc|
+    olmc.source_files   = "src/*.{c}", "lib/curve25519-donna.h", "lib/crypto-algorithms/sha256.{h,c}", "lib/crypto-algorithms/aes.{h,c}",  "lib/curve25519-donna/curve25519-donna.c"
+    olmc.compiler_flags = ' -std=c99 -fPIC'
+  end
+  
+  s.subspec 'olmcpp' do |olmcpp|
+    olmcpp.source_files   = "src/*.{cpp}"
+    olmcpp.compiler_flags = ' -std=c++11 -fPIC'
+  end
+  
+end
diff --git a/README.rst b/README.rst
index be1fb1a1270a86a830f30fc67dc79e00a28de9ba..0b2d915d3a0cddf76f105befb35893eb969f1ddc 100644
--- a/README.rst
+++ b/README.rst
@@ -33,6 +33,14 @@ To build the javascript bindings, install emscripten from http://kripken.github.
 
     make js
 
+To build the Xcode workspace for Objective-C bindings, run:
+
+.. code:: bash
+
+    cd xcode
+    pod install
+    open OLMKit.xcworkspace
+
 Release process
 ---------------
 
@@ -50,6 +58,14 @@ Release process
     git tag $VERSION -s
     git push --tags
 
+    # OLMKit CocoaPod release
+    # Make sure the version OLMKit.podspec is the same as the git tag
+    # (this must be checked before git tagging)
+    pod spec lint OLMKit.podspec --use-libraries --allow-warnings
+    pod trunk push OLMKit.podspec --use-libraries --allow-warnings
+    # Check the pod has been successully published with:
+    pod search OLMKit
+
 It's probably sensible to do the above on a release branch (``release-vx.y.z``
 by convention), and merge back to master once complete.
 
diff --git a/lib/crypto-algorithms/aes.c b/lib/crypto-algorithms/aes.c
index 948e36f4779260b0cabd81f78379d75c4022cc01..0c264da85d5440c5c3b4e530c82e5b7433a8220b 100644
--- a/lib/crypto-algorithms/aes.c
+++ b/lib/crypto-algorithms/aes.c
@@ -21,6 +21,7 @@
 #include "aes.h"
 
 #include <stdio.h>
+#include <string.h>
 
 /****************************** MACROS ******************************/
 // The least significant byte of the word is rotated to the end.
diff --git a/lib/crypto-algorithms/sha256.c b/lib/crypto-algorithms/sha256.c
index eb9c5c0733e7a6234998e1dff49300c4b58e7d71..3acc2743b1168ee04375f1205419e6fadd650609 100644
--- a/lib/crypto-algorithms/sha256.c
+++ b/lib/crypto-algorithms/sha256.c
@@ -15,6 +15,7 @@
 /*************************** HEADER FILES ***************************/
 #include <stdlib.h>
 #include <memory.h>
+#include <string.h>
 #include "sha256.h"
 
 /****************************** MACROS ******************************/
diff --git a/xcode/OLMKit.xcodeproj/project.pbxproj b/xcode/OLMKit.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000000000000000000000000000000000..a3b636fa53bd3e0c5368bfe30ed70f3937ddadd7
--- /dev/null
+++ b/xcode/OLMKit.xcodeproj/project.pbxproj
@@ -0,0 +1,528 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		3274F6021D9A633A005282E4 /* OLMKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3274F5F81D9A633A005282E4 /* OLMKit.framework */; };
+		3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3274F6061D9A633A005282E4 /* OLMKitTests.m */; };
+		3274F6131D9A698E005282E4 /* OLMKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3274F6121D9A698E005282E4 /* OLMKit.h */; };
+		32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A151301DABDD4300400192 /* OLMKitGroupTests.m */; };
+		7DBAD311AEA85CF6DB80DCFA /* libPods-OLMKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */; };
+		D667051A0BA47E17CCC4E5D7 /* libPods-OLMKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		3274F6031D9A633A005282E4 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 3274F5EF1D9A633A005282E4 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 3274F5F71D9A633A005282E4;
+			remoteInfo = OLMKit;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit.release.xcconfig"; sourceTree = "<group>"; };
+		3274F5F81D9A633A005282E4 /* OLMKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OLMKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		3274F5FC1D9A633A005282E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		3274F6011D9A633A005282E4 /* OLMKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OLMKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		3274F6061D9A633A005282E4 /* OLMKitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OLMKitTests.m; sourceTree = "<group>"; };
+		3274F6081D9A633A005282E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		3274F6121D9A698E005282E4 /* OLMKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLMKit.h; sourceTree = "<group>"; };
+		32A151301DABDD4300400192 /* OLMKitGroupTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLMKitGroupTests.m; sourceTree = "<group>"; };
+		7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OLMKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.debug.xcconfig"; sourceTree = "<group>"; };
+		D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.release.xcconfig"; sourceTree = "<group>"; };
+		E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit.debug.xcconfig"; sourceTree = "<group>"; };
+		F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OLMKit.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		3274F5F41D9A633A005282E4 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				D667051A0BA47E17CCC4E5D7 /* libPods-OLMKit.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		3274F5FE1D9A633A005282E4 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				3274F6021D9A633A005282E4 /* OLMKit.framework in Frameworks */,
+				7DBAD311AEA85CF6DB80DCFA /* libPods-OLMKitTests.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		1FA3F53DFAAAA773F07F5E56 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */,
+				1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */,
+				875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */,
+				D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */,
+			);
+			name = Pods;
+			sourceTree = "<group>";
+		};
+		3274F5EE1D9A633A005282E4 = {
+			isa = PBXGroup;
+			children = (
+				3274F5FA1D9A633A005282E4 /* OLMKit */,
+				3274F6051D9A633A005282E4 /* OLMKitTests */,
+				3274F5F91D9A633A005282E4 /* Products */,
+				1FA3F53DFAAAA773F07F5E56 /* Pods */,
+				A5D2E6F079A29F7CC2A8D9FE /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		3274F5F91D9A633A005282E4 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				3274F5F81D9A633A005282E4 /* OLMKit.framework */,
+				3274F6011D9A633A005282E4 /* OLMKitTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		3274F5FA1D9A633A005282E4 /* OLMKit */ = {
+			isa = PBXGroup;
+			children = (
+				3274F6121D9A698E005282E4 /* OLMKit.h */,
+				3274F5FC1D9A633A005282E4 /* Info.plist */,
+			);
+			path = OLMKit;
+			sourceTree = "<group>";
+		};
+		3274F6051D9A633A005282E4 /* OLMKitTests */ = {
+			isa = PBXGroup;
+			children = (
+				3274F6061D9A633A005282E4 /* OLMKitTests.m */,
+				32A151301DABDD4300400192 /* OLMKitGroupTests.m */,
+				3274F6081D9A633A005282E4 /* Info.plist */,
+			);
+			path = OLMKitTests;
+			sourceTree = "<group>";
+		};
+		A5D2E6F079A29F7CC2A8D9FE /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */,
+				7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		3274F5F51D9A633A005282E4 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				3274F6131D9A698E005282E4 /* OLMKit.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		3274F5F71D9A633A005282E4 /* OLMKit */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 3274F60C1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKit" */;
+			buildPhases = (
+				7FBCB292198F4156D9CA3B8D /* [CP] Check Pods Manifest.lock */,
+				3274F5F31D9A633A005282E4 /* Sources */,
+				3274F5F41D9A633A005282E4 /* Frameworks */,
+				3274F5F51D9A633A005282E4 /* Headers */,
+				3274F5F61D9A633A005282E4 /* Resources */,
+				30F93582035CD30D211A6C76 /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = OLMKit;
+			productName = OLMKit;
+			productReference = 3274F5F81D9A633A005282E4 /* OLMKit.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+		3274F6001D9A633A005282E4 /* OLMKitTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 3274F60F1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKitTests" */;
+			buildPhases = (
+				47E69E5BE6A019858DC41D4F /* [CP] Check Pods Manifest.lock */,
+				3274F5FD1D9A633A005282E4 /* Sources */,
+				3274F5FE1D9A633A005282E4 /* Frameworks */,
+				3274F5FF1D9A633A005282E4 /* Resources */,
+				0A185F0CAE96B33A4CD91B6A /* [CP] Embed Pods Frameworks */,
+				793D0533290528B7C0E17CAD /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				3274F6041D9A633A005282E4 /* PBXTargetDependency */,
+			);
+			name = OLMKitTests;
+			productName = OLMKitTests;
+			productReference = 3274F6011D9A633A005282E4 /* OLMKitTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		3274F5EF1D9A633A005282E4 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0810;
+				ORGANIZATIONNAME = matrix.org;
+				TargetAttributes = {
+					3274F5F71D9A633A005282E4 = {
+						CreatedOnToolsVersion = 8.0;
+						ProvisioningStyle = Automatic;
+					};
+					3274F6001D9A633A005282E4 = {
+						CreatedOnToolsVersion = 8.0;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = 3274F5F21D9A633A005282E4 /* Build configuration list for PBXProject "OLMKit" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 3274F5EE1D9A633A005282E4;
+			productRefGroup = 3274F5F91D9A633A005282E4 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				3274F5F71D9A633A005282E4 /* OLMKit */,
+				3274F6001D9A633A005282E4 /* OLMKitTests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		3274F5F61D9A633A005282E4 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		3274F5FF1D9A633A005282E4 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		0A185F0CAE96B33A4CD91B6A /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		30F93582035CD30D211A6C76 /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Copy Pods Resources";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		47E69E5BE6A019858DC41D4F /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n";
+			showEnvVarsInLog = 0;
+		};
+		793D0533290528B7C0E17CAD /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Copy Pods Resources";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		7FBCB292198F4156D9CA3B8D /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		3274F5F31D9A633A005282E4 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		3274F5FD1D9A633A005282E4 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */,
+				32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		3274F6041D9A633A005282E4 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 3274F5F71D9A633A005282E4 /* OLMKit */;
+			targetProxy = 3274F6031D9A633A005282E4 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+		3274F60A1D9A633B005282E4 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		3274F60B1D9A633B005282E4 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_SUSPICIOUS_MOVES = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+		3274F60D1D9A633B005282E4 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				INFOPLIST_FILE = OLMKit/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKit;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+			};
+			name = Debug;
+		};
+		3274F60E1D9A633B005282E4 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "";
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				INFOPLIST_FILE = OLMKit/Info.plist;
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKit;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+			};
+			name = Release;
+		};
+		3274F6101D9A633B005282E4 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */;
+			buildSettings = {
+				INFOPLIST_FILE = OLMKitTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		3274F6111D9A633B005282E4 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */;
+			buildSettings = {
+				INFOPLIST_FILE = OLMKitTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKitTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		3274F5F21D9A633A005282E4 /* Build configuration list for PBXProject "OLMKit" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				3274F60A1D9A633B005282E4 /* Debug */,
+				3274F60B1D9A633B005282E4 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		3274F60C1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKit" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				3274F60D1D9A633B005282E4 /* Debug */,
+				3274F60E1D9A633B005282E4 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		3274F60F1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKitTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				3274F6101D9A633B005282E4 /* Debug */,
+				3274F6111D9A633B005282E4 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 3274F5EF1D9A633A005282E4 /* Project object */;
+}
diff --git a/xcode/OLMKit/Info.plist b/xcode/OLMKit/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..d3de8eefb69556c5bf61c107232e095548dc387c
--- /dev/null
+++ b/xcode/OLMKit/Info.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>
diff --git a/xcode/OLMKit/OLMAccount.h b/xcode/OLMKit/OLMAccount.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8d65cddd41a3ffab8f5361f61c806f7cf127d7e
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount.h
@@ -0,0 +1,51 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "OLMSerializable.h"
+
+@class OLMSession;
+
+@interface OLMAccount : NSObject <OLMSerializable, NSSecureCoding>
+
+/** Creates new account */
+- (instancetype) initNewAccount;
+
+/** public identity keys. base64 encoded in "curve25519" and "ed25519" keys */
+- (NSDictionary*) identityKeys;
+
+/** signs message with ed25519 key for account */
+- (NSString*) signMessage:(NSData*)messageData;
+
+/** Public parts of the unpublished one time keys for the account */
+- (NSDictionary*) oneTimeKeys;
+
+- (BOOL) removeOneTimeKeysForSession:(OLMSession*)session;
+
+/** Marks the current set of one time keys as being published. */
+- (void) markOneTimeKeysAsPublished;
+
+/** The largest number of one time keys this account can store. */
+- (NSUInteger) maxOneTimeKeys;
+
+/** Generates a number of new one time keys. If the total number of keys stored
+ * by this account exceeds -maxOneTimeKeys then the old keys are
+ * discarded. */
+- (void) generateOneTimeKeys:(NSUInteger)numberOfKeys;
+
+@end
diff --git a/xcode/OLMKit/OLMAccount.m b/xcode/OLMKit/OLMAccount.m
new file mode 100644
index 0000000000000000000000000000000000000000..af1e30849d0ac2328a4ba1c3ec06dd8c7cf55233
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount.m
@@ -0,0 +1,265 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMAccount.h"
+#import "OLMAccount_Private.h"
+#import "OLMSession.h"
+#import "OLMSession_Private.h"
+#import "OLMUtility.h"
+
+@import Security;
+
+@implementation OLMAccount
+
+- (void) dealloc {
+    olm_clear_account(_account);
+    free(_account);
+}
+
+- (BOOL) initializeAccountMemory {
+    size_t accountSize = olm_account_size();
+    _account = malloc(accountSize);
+    NSParameterAssert(_account != nil);
+    if (!_account) {
+        return NO;
+    }
+    _account = olm_account(_account);
+    NSParameterAssert(_account != nil);
+    if (!_account) {
+        return NO;
+    }
+    return YES;
+}
+
+- (instancetype) init {
+    self = [super init];
+    if (!self) {
+        return nil;
+    }
+    BOOL success = [self initializeAccountMemory];
+    if (!success) {
+        return nil;
+    }
+    return self;
+}
+
+- (instancetype) initNewAccount {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    size_t randomLength = olm_create_account_random_length(_account);
+    NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+    size_t accountResult = olm_create_account(_account, random.mutableBytes, random.length);
+    [random resetBytesInRange:NSMakeRange(0, random.length)];
+    if (accountResult == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"error creating account: %s", error);
+        return nil;
+    }
+    return self;
+}
+
+- (NSUInteger) maxOneTimeKeys {
+    return olm_account_max_number_of_one_time_keys(_account);
+}
+
+
+/** public identity keys */
+- (NSDictionary*) identityKeys {
+    size_t identityKeysLength = olm_account_identity_keys_length(_account);
+    uint8_t *identityKeysBytes = malloc(identityKeysLength);
+    if (!identityKeysBytes) {
+        return nil;
+    }
+    size_t result = olm_account_identity_keys(_account, identityKeysBytes, identityKeysLength);
+    if (result == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"error getting id keys: %s", error);
+        free(identityKeysBytes);
+        return nil;
+    }
+    NSData *idKeyData = [NSData dataWithBytesNoCopy:identityKeysBytes length:identityKeysLength freeWhenDone:YES];
+    NSError *error = nil;
+    NSDictionary *keysDictionary = [NSJSONSerialization JSONObjectWithData:idKeyData options:0 error:&error];
+    if (error) {
+        NSLog(@"Could not decode JSON: %@", error.localizedDescription);
+    }
+    return keysDictionary;
+}
+
+- (NSString *)signMessage:(NSData *)messageData {
+    size_t signatureLength = olm_account_signature_length(_account);
+    uint8_t *signatureBytes = malloc(signatureLength);
+    if (!signatureBytes) {
+        return nil;
+    }
+
+    size_t result = olm_account_sign(_account, messageData.bytes, messageData.length, signatureBytes, signatureLength);
+    if (result == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"error signing message: %s", error);
+        free(signatureBytes);
+        return nil;
+    }
+
+    NSData *signatureData = [NSData dataWithBytesNoCopy:signatureBytes length:signatureLength freeWhenDone:YES];
+    return [[NSString alloc] initWithData:signatureData encoding:NSUTF8StringEncoding];
+}
+
+- (NSDictionary*) oneTimeKeys {
+    size_t otkLength = olm_account_one_time_keys_length(_account);
+    uint8_t *otkBytes = malloc(otkLength);
+    if (!otkBytes) {
+        return nil;
+    }
+    size_t result = olm_account_one_time_keys(_account, otkBytes, otkLength);
+    if (result == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"error getting id keys: %s", error);
+        free(otkBytes);
+    }
+    NSData *otk = [NSData dataWithBytesNoCopy:otkBytes length:otkLength freeWhenDone:YES];
+    NSError *error = nil;
+    NSDictionary *keysDictionary = [NSJSONSerialization JSONObjectWithData:otk options:0 error:&error];
+    if (error) {
+        NSLog(@"Could not decode JSON: %@", error.localizedDescription);
+    }
+    return keysDictionary;
+}
+
+
+- (void) generateOneTimeKeys:(NSUInteger)numberOfKeys {
+    size_t randomLength = olm_account_generate_one_time_keys_random_length(_account, numberOfKeys);
+    NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+    size_t result = olm_account_generate_one_time_keys(_account, numberOfKeys, random.mutableBytes, random.length);
+    [random resetBytesInRange:NSMakeRange(0, random.length)];
+    if (result == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"error generating keys: %s", error);
+    }
+}
+
+- (BOOL) removeOneTimeKeysForSession:(OLMSession *)session {
+    NSParameterAssert(session != nil);
+    if (!session) {
+        return NO;
+    }
+    size_t result = olm_remove_one_time_keys(self.account, session.session);
+    if (result == olm_error()) {
+        const char *error = olm_account_last_error(_account);
+        NSLog(@"olm_remove_one_time_keys error: %s", error);
+        return NO;
+    }
+    return YES;
+}
+
+- (void)markOneTimeKeysAsPublished
+{
+    olm_account_mark_keys_as_published(self.account);
+}
+
+#pragma mark OLMSerializable
+
+/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
+- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    NSParameterAssert(key.length > 0);
+    NSParameterAssert(serializedData.length > 0);
+    if (key.length == 0 || serializedData.length == 0) {
+        if (error) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}];
+        }
+        return nil;
+    }
+    NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+    size_t result = olm_unpickle_account(_account, key.bytes, key.length, pickle.mutableBytes, pickle.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_account_last_error(_account);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    return self;
+}
+
+/** Serializes and encrypts object data, outputs base64 blob */
+- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error {
+    NSParameterAssert(key.length > 0);
+    size_t length = olm_pickle_account_length(_account);
+    NSMutableData *pickled = [NSMutableData dataWithLength:length];
+    size_t result = olm_pickle_account(_account, key.bytes, key.length, pickled.mutableBytes, pickled.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_account_last_error(_account);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding];
+    return pickleString;
+}
+
+#pragma mark NSSecureCoding
+
++ (BOOL) supportsSecureCoding {
+    return YES;
+}
+
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"];
+    
+    NSError *error = nil;
+    
+    if ([version isEqualToString:@"1"]) {
+        NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"];
+        NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"];
+
+        self = [self initWithSerializedData:pickle key:key error:&error];
+    }
+    
+    NSParameterAssert(error == nil);
+    NSParameterAssert(self != nil);
+    if (!self) {
+        return nil;
+    }
+    
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    NSData *key = [OLMUtility randomBytesOfLength:32];
+    NSError *error = nil;
+    NSString *pickle = [self serializeDataWithKey:key error:&error];
+    NSParameterAssert(pickle.length > 0 && error == nil);
+    
+    [encoder encodeObject:pickle forKey:@"pickle"];
+    [encoder encodeObject:key forKey:@"key"];
+    [encoder encodeObject:@"1" forKey:@"version"];
+}
+
+
+@end
diff --git a/xcode/OLMKit/OLMAccount_Private.h b/xcode/OLMKit/OLMAccount_Private.h
new file mode 100644
index 0000000000000000000000000000000000000000..313ab71105968905d78c21a908df21d3a33d674e
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount_Private.h
@@ -0,0 +1,25 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include "olm/olm.h"
+
+@interface OLMAccount()
+
+@property (nonatomic) OlmAccount *account;
+
+@end
diff --git a/xcode/OLMKit/OLMInboundGroupSession.h b/xcode/OLMKit/OLMInboundGroupSession.h
new file mode 100644
index 0000000000000000000000000000000000000000..ede68e3d6cd3f517735c970b653f0ff289117c18
--- /dev/null
+++ b/xcode/OLMKit/OLMInboundGroupSession.h
@@ -0,0 +1,30 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "OLMSerializable.h"
+
+@interface OLMInboundGroupSession : NSObject <OLMSerializable, NSSecureCoding>
+
+- (instancetype) initInboundGroupSessionWithSessionKey:(NSString*)sessionKey error:(NSError**)error;
+
+- (NSString*)sessionIdentifier;
+
+/** base64 ciphertext -> UTF-8 plaintext */
+- (NSString*)decryptMessage:(NSString*)message messageIndex:(NSUInteger*)messageIndex error:(NSError**)error;
+
+@end
diff --git a/xcode/OLMKit/OLMInboundGroupSession.m b/xcode/OLMKit/OLMInboundGroupSession.m
new file mode 100644
index 0000000000000000000000000000000000000000..6ef51c3ed4d5a685979111c522170bc2178dba02
--- /dev/null
+++ b/xcode/OLMKit/OLMInboundGroupSession.m
@@ -0,0 +1,244 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMInboundGroupSession.h"
+
+#import "OLMUtility.h"
+#include "olm/olm.h"
+
+@interface OLMInboundGroupSession ()
+{
+    OlmInboundGroupSession *session;
+}
+@end
+
+
+@implementation OLMInboundGroupSession
+
+- (void)dealloc {
+    olm_clear_inbound_group_session(session);
+    free(session);
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self)
+    {
+        session = malloc(olm_inbound_group_session_size());
+        if (session) {
+            session = olm_inbound_group_session(session);
+        }
+
+        if (!session) {
+            return nil;
+        }
+    }
+    return self;
+}
+
+- (instancetype)initInboundGroupSessionWithSessionKey:(NSString *)sessionKey error:(NSError**)error {
+    self = [self init];
+    if (self) {
+        NSData *sessionKeyData = [sessionKey dataUsingEncoding:NSUTF8StringEncoding];
+        size_t result = olm_init_inbound_group_session(session, sessionKeyData.bytes, sessionKeyData.length);
+        if (result == olm_error())   {
+            const char *olm_error = olm_inbound_group_session_last_error(session);
+
+            NSString *errorString = [NSString stringWithUTF8String:olm_error];
+            NSLog(@"olm_init_inbound_group_session error: %@", errorString);
+
+            if (error && olm_error && errorString) {
+                *error = [NSError errorWithDomain:OLMErrorDomain
+                                             code:0
+                                         userInfo:@{
+                                                    NSLocalizedDescriptionKey: errorString,
+                                                    NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_init_inbound_group_session error: %@", errorString]
+                                                    }];
+            }
+
+            return nil;
+        }
+    }
+    return self;
+}
+
+- (NSString *)sessionIdentifier {
+    size_t length = olm_inbound_group_session_id_length(session);
+    NSMutableData *idData = [NSMutableData dataWithLength:length];
+    if (!idData) {
+        return nil;
+    }
+    size_t result = olm_inbound_group_session_id(session, idData.mutableBytes, idData.length);
+    if (result == olm_error()) {
+        const char *error = olm_inbound_group_session_last_error(session);
+        NSLog(@"olm_inbound_group_session_id error: %s", error);
+        return nil;
+    }
+    NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding];
+    return idString;
+}
+
+- (NSString *)decryptMessage:(NSString *)message messageIndex:(NSUInteger*)messageIndex error:(NSError**)error
+{
+    NSParameterAssert(message != nil);
+    NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding];
+    if (!messageData) {
+        return nil;
+    }
+    NSMutableData *mutMessage = messageData.mutableCopy;
+    size_t maxPlaintextLength = olm_group_decrypt_max_plaintext_length(session, mutMessage.mutableBytes, mutMessage.length);
+    if (maxPlaintextLength == olm_error()) {
+        const char *olm_error = olm_inbound_group_session_last_error(session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_group_decrypt_max_plaintext_length error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_decrypt_max_plaintext_length error: %@", errorString]
+                                                }];
+        }
+        
+        return nil;
+    }
+    // message buffer is destroyed by olm_group_decrypt_max_plaintext_length
+    mutMessage = messageData.mutableCopy;
+    NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength];
+
+    uint32_t message_index;
+    size_t plaintextLength = olm_group_decrypt(session, mutMessage.mutableBytes, mutMessage.length, plaintextData.mutableBytes, plaintextData.length, &message_index);
+    if (plaintextLength == olm_error()) {
+        const char *olm_error = olm_inbound_group_session_last_error(session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_group_decrypt error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_decrypt error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    plaintextData.length = plaintextLength;
+    NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding];
+    [plaintextData resetBytesInRange:NSMakeRange(0, plaintextData.length)];
+
+    if (messageIndex)
+    {
+        *messageIndex = message_index;
+    }
+
+    return plaintext;
+}
+
+
+#pragma mark OLMSerializable
+
+/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
+- (instancetype) initWithSerializedData:(NSString *)serializedData key:(NSData *)key error:(NSError *__autoreleasing *)error {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    NSParameterAssert(key.length > 0);
+    NSParameterAssert(serializedData.length > 0);
+    if (key.length == 0 || serializedData.length == 0) {
+        if (error) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}];
+        }
+        return nil;
+    }
+    NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+    size_t result = olm_unpickle_inbound_group_session(session, key.bytes, key.length, pickle.mutableBytes, pickle.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_inbound_group_session_last_error(session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    return self;
+}
+
+/** Serializes and encrypts object data, outputs base64 blob */
+- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error {
+    NSParameterAssert(key.length > 0);
+    size_t length = olm_pickle_inbound_group_session_length(session);
+    NSMutableData *pickled = [NSMutableData dataWithLength:length];
+    size_t result = olm_pickle_inbound_group_session(session, key.bytes, key.length, pickled.mutableBytes, pickled.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_inbound_group_session_last_error(session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding];
+    return pickleString;
+}
+
+#pragma mark NSSecureCoding
+
++ (BOOL) supportsSecureCoding {
+    return YES;
+}
+
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"];
+
+    NSError *error = nil;
+
+    if ([version isEqualToString:@"1"]) {
+        NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"];
+        NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"];
+
+        self = [self initWithSerializedData:pickle key:key error:&error];
+    }
+
+    NSParameterAssert(error == nil);
+    NSParameterAssert(self != nil);
+    if (!self) {
+        return nil;
+    }
+
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    NSData *key = [OLMUtility randomBytesOfLength:32];
+    NSError *error = nil;
+    NSString *pickle = [self serializeDataWithKey:key error:&error];
+    NSParameterAssert(pickle.length > 0 && error == nil);
+
+    [encoder encodeObject:pickle forKey:@"pickle"];
+    [encoder encodeObject:key forKey:@"key"];
+    [encoder encodeObject:@"1" forKey:@"version"];
+}
+
+@end
diff --git a/xcode/OLMKit/OLMKit.h b/xcode/OLMKit/OLMKit.h
new file mode 100644
index 0000000000000000000000000000000000000000..34db111b495efed0269b48b770dd84fbfb27d689
--- /dev/null
+++ b/xcode/OLMKit/OLMKit.h
@@ -0,0 +1,31 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+//! Project version string for OLMKit, the same as libolm.
+NSString *OLMKitVersionString();
+
+// In this header, you should import all the public headers of your framework using statements like #import <OLMKit/PublicHeader.h>
+
+#import <OLMKit/OLMAccount.h>
+#import <OLMKit/OLMSession.h>
+#import <OLMKit/OLMMessage.h>
+#import <OLMKit/OLMUtility.h>
+#import <OLMKit/OLMInboundGroupSession.h>
+#import <OLMKit/OLMOutboundGroupSession.h>
diff --git a/xcode/OLMKit/OLMKit.m b/xcode/OLMKit/OLMKit.m
new file mode 100644
index 0000000000000000000000000000000000000000..e7bfd253611e2357b6ada819d92f2659e86384fd
--- /dev/null
+++ b/xcode/OLMKit/OLMKit.m
@@ -0,0 +1,29 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMKit.h"
+
+#include "olm/olm.h"
+
+NSString *OLMKitVersionString()
+{
+    uint8_t major, minor, patch;
+
+    olm_get_library_version(&major, &minor, &patch);
+
+    return [NSString stringWithFormat:@"%tu.%tu.%tu", major, minor, patch];
+}
diff --git a/xcode/OLMKit/OLMMessage.h b/xcode/OLMKit/OLMMessage.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6e8c8fc862ba58985bab0ab97e524649801a02f
--- /dev/null
+++ b/xcode/OLMKit/OLMMessage.h
@@ -0,0 +1,38 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+/*
+ from olm.hh
+ static const size_t OLM_MESSAGE_TYPE_PRE_KEY = 0;
+ static const size_t OLM_MESSAGE_TYPE_MESSAGE = 1;
+ */
+typedef NS_ENUM(NSInteger, OLMMessageType) {
+    OLMMessageTypePreKey = 0,
+    OLMMessageTypeMessage = 1
+};
+
+@interface OLMMessage : NSObject
+
+@property (nonatomic, copy, readonly, nonnull) NSString *ciphertext;
+@property (readonly) OLMMessageType type;
+
+- (nullable instancetype) initWithCiphertext:(nonnull NSString*)ciphertext type:(OLMMessageType)type;
+
+@end
diff --git a/xcode/OLMKit/OLMMessage.m b/xcode/OLMKit/OLMMessage.m
new file mode 100644
index 0000000000000000000000000000000000000000..949f83486d9b11149e15f4540df7f6a4af8fe1f4
--- /dev/null
+++ b/xcode/OLMKit/OLMMessage.m
@@ -0,0 +1,34 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMMessage.h"
+
+@implementation OLMMessage
+
+- (nullable instancetype) initWithCiphertext:(nonnull NSString*)ciphertext type:(OLMMessageType)type {
+    NSParameterAssert(ciphertext != nil);
+    self = [super init];
+    if (!self) {
+        return nil;
+    }
+    _ciphertext = [ciphertext copy];
+    _type = type;
+    return self;
+}
+
+@end
diff --git a/xcode/OLMKit/OLMOutboundGroupSession.h b/xcode/OLMKit/OLMOutboundGroupSession.h
new file mode 100644
index 0000000000000000000000000000000000000000..c979b61f92e72f57875e5ae83d324d219545f155
--- /dev/null
+++ b/xcode/OLMKit/OLMOutboundGroupSession.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "OLMSerializable.h"
+
+@interface OLMOutboundGroupSession : NSObject <OLMSerializable, NSSecureCoding>
+
+- (instancetype) initOutboundGroupSession;
+
+- (NSString*)sessionIdentifier;
+- (NSUInteger)messageIndex;
+- (NSString*)sessionKey;
+
+/** UTF-8 plaintext -> base64 ciphertext */
+- (NSString*)encryptMessage:(NSString*)message error:(NSError**)error;
+
+@end
diff --git a/xcode/OLMKit/OLMOutboundGroupSession.m b/xcode/OLMKit/OLMOutboundGroupSession.m
new file mode 100644
index 0000000000000000000000000000000000000000..a3421fd73bf8d3586e0ebd93e2eae371679d4036
--- /dev/null
+++ b/xcode/OLMKit/OLMOutboundGroupSession.m
@@ -0,0 +1,220 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMOutboundGroupSession.h"
+
+#import "OLMUtility.h"
+#include "olm/olm.h"
+
+@interface OLMOutboundGroupSession ()
+{
+    OlmOutboundGroupSession *session;
+}
+@end
+
+@implementation OLMOutboundGroupSession
+
+- (void)dealloc {
+    olm_clear_outbound_group_session(session);
+    free(session);
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self)
+    {
+        session = malloc(olm_outbound_group_session_size());
+        if (session) {
+            session = olm_outbound_group_session(session);
+        }
+
+        if (!session) {
+            return nil;
+        }
+    }
+    return self;
+}
+
+- (instancetype)initOutboundGroupSession {
+    self = [self init];
+    if (self) {
+        NSMutableData *random = [OLMUtility randomBytesOfLength:olm_init_outbound_group_session_random_length(session)];
+
+        size_t result = olm_init_outbound_group_session(session, random.mutableBytes, random.length);
+        [random resetBytesInRange:NSMakeRange(0, random.length)];
+        if (result == olm_error())   {
+            const char *error = olm_outbound_group_session_last_error(session);
+            NSLog(@"olm_init_outbound_group_session error: %s", error);
+            return nil;
+        }
+    }
+    return self;
+}
+
+- (NSString *)sessionIdentifier {
+    size_t length = olm_outbound_group_session_id_length(session);
+    NSMutableData *idData = [NSMutableData dataWithLength:length];
+    if (!idData) {
+        return nil;
+    }
+    size_t result = olm_outbound_group_session_id(session, idData.mutableBytes, idData.length);
+    if (result == olm_error()) {
+        const char *error = olm_outbound_group_session_last_error(session);
+        NSLog(@"olm_outbound_group_session_id error: %s", error);
+        return nil;
+    }
+    NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding];
+    return idString;
+}
+
+- (NSUInteger)messageIndex {
+    return olm_outbound_group_session_message_index(session);
+}
+
+- (NSString *)sessionKey {
+    size_t length = olm_outbound_group_session_key_length(session);
+    NSMutableData *sessionKeyData = [NSMutableData dataWithLength:length];
+    if (!sessionKeyData) {
+        return nil;
+    }
+    size_t result = olm_outbound_group_session_key(session, sessionKeyData.mutableBytes, sessionKeyData.length);
+    if (result == olm_error()) {
+        const char *error = olm_outbound_group_session_last_error(session);
+        NSLog(@"olm_outbound_group_session_key error: %s", error);
+        return nil;
+    }
+    NSString *sessionKey = [[NSString alloc] initWithData:sessionKeyData encoding:NSUTF8StringEncoding];
+    [sessionKeyData resetBytesInRange:NSMakeRange(0, sessionKeyData.length)];
+    return sessionKey;
+}
+
+- (NSString *)encryptMessage:(NSString *)message error:(NSError**)error {
+    NSData *plaintextData = [message dataUsingEncoding:NSUTF8StringEncoding];
+    size_t ciphertextLength = olm_group_encrypt_message_length(session, plaintextData.length);
+    NSMutableData *ciphertext = [NSMutableData dataWithLength:ciphertextLength];
+    if (!ciphertext) {
+        return nil;
+    }
+    size_t result = olm_group_encrypt(session, plaintextData.bytes, plaintextData.length, ciphertext.mutableBytes, ciphertext.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_outbound_group_session_last_error(session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_group_encrypt error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_encrypt error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    return [[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding];
+}
+
+#pragma mark OLMSerializable
+
+/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
+- (instancetype) initWithSerializedData:(NSString *)serializedData key:(NSData *)key error:(NSError *__autoreleasing *)error {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    NSParameterAssert(key.length > 0);
+    NSParameterAssert(serializedData.length > 0);
+    if (key.length == 0 || serializedData.length == 0) {
+        if (error) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}];
+        }
+        return nil;
+    }
+    NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+    size_t result = olm_unpickle_outbound_group_session(session, key.bytes, key.length, pickle.mutableBytes, pickle.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_outbound_group_session_last_error(session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    return self;
+}
+
+/** Serializes and encrypts object data, outputs base64 blob */
+- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error {
+    NSParameterAssert(key.length > 0);
+    size_t length = olm_pickle_outbound_group_session_length(session);
+    NSMutableData *pickled = [NSMutableData dataWithLength:length];
+    size_t result = olm_pickle_outbound_group_session(session, key.bytes, key.length, pickled.mutableBytes, pickled.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_outbound_group_session_last_error(session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding];
+    return pickleString;
+}
+
+#pragma mark NSSecureCoding
+
++ (BOOL) supportsSecureCoding {
+    return YES;
+}
+
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"];
+
+    NSError *error = nil;
+
+    if ([version isEqualToString:@"1"]) {
+        NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"];
+        NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"];
+
+        self = [self initWithSerializedData:pickle key:key error:&error];
+    }
+
+    NSParameterAssert(error == nil);
+    NSParameterAssert(self != nil);
+    if (!self) {
+        return nil;
+    }
+
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    NSData *key = [OLMUtility randomBytesOfLength:32];
+    NSError *error = nil;
+    NSString *pickle = [self serializeDataWithKey:key error:&error];
+    NSParameterAssert(pickle.length > 0 && error == nil);
+
+    [encoder encodeObject:pickle forKey:@"pickle"];
+    [encoder encodeObject:key forKey:@"key"];
+    [encoder encodeObject:@"1" forKey:@"version"];
+}
+
+@end
diff --git a/xcode/OLMKit/OLMSerializable.h b/xcode/OLMKit/OLMSerializable.h
new file mode 100644
index 0000000000000000000000000000000000000000..e929903d2d988baa7e51ba55fcde00a11c9050e5
--- /dev/null
+++ b/xcode/OLMKit/OLMSerializable.h
@@ -0,0 +1,29 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@protocol OLMSerializable <NSObject>
+
+/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
+- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error;
+
+/** Serializes and encrypts object data, outputs base64 blob */
+- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error;
+
+@end
diff --git a/xcode/OLMKit/OLMSession.h b/xcode/OLMKit/OLMSession.h
new file mode 100644
index 0000000000000000000000000000000000000000..0446f984df2f56679452b5d4b537edc7121b4c42
--- /dev/null
+++ b/xcode/OLMKit/OLMSession.h
@@ -0,0 +1,44 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "OLMSerializable.h"
+#import "OLMAccount.h"
+#import "OLMMessage.h"
+
+@interface OLMSession : NSObject <OLMSerializable, NSSecureCoding>
+
+- (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey error:(NSError**)error;
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error;
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error;
+
+- (NSString*) sessionIdentifier;
+
+- (BOOL) matchesInboundSession:(NSString*)oneTimeKeyMessage;
+
+- (BOOL) matchesInboundSessionFrom:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage;
+
+/** UTF-8 plaintext -> base64 ciphertext */
+- (OLMMessage*) encryptMessage:(NSString*)message error:(NSError**)error;
+
+/** base64 ciphertext -> UTF-8 plaintext */
+- (NSString*) decryptMessage:(OLMMessage*)message error:(NSError**)error;
+
+@end
diff --git a/xcode/OLMKit/OLMSession.m b/xcode/OLMKit/OLMSession.m
new file mode 100644
index 0000000000000000000000000000000000000000..8c291130cda9ebff6185dc33fdb883d49f20093c
--- /dev/null
+++ b/xcode/OLMKit/OLMSession.m
@@ -0,0 +1,381 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMSession.h"
+#import "OLMUtility.h"
+#import "OLMAccount_Private.h"
+#import "OLMSession_Private.h"
+#include "olm/olm.h"
+
+@implementation OLMSession
+
+- (void) dealloc {
+    olm_clear_session(_session);
+    free(_session);
+}
+
+- (BOOL) initializeSessionMemory {
+    size_t size = olm_session_size();
+    _session = malloc(size);
+    NSParameterAssert(_session != nil);
+    if (!_session) {
+        return NO;
+    }
+    _session = olm_session(_session);
+    NSParameterAssert(_session != nil);
+    if (!_session) {
+        return NO;
+    }
+    return YES;
+}
+
+- (instancetype) init {
+    self = [super init];
+    if (!self) {
+        return nil;
+    }
+    BOOL success = [self initializeSessionMemory];
+    if (!success) {
+        return nil;
+    }
+    return self;
+}
+
+- (instancetype) initWithAccount:(OLMAccount*)account {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    NSParameterAssert(account != nil &&  account.account != NULL);
+    if (account == nil || account.account == NULL) {
+        return nil;
+    }
+    _account = account;
+    return self;
+}
+
+- (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey  error:(NSError**)error {
+    self = [self initWithAccount:account];
+    if (!self) {
+        return nil;
+    }
+    NSMutableData *random = [OLMUtility randomBytesOfLength:olm_create_outbound_session_random_length(_session)];
+    NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding];
+    NSData *otKey = [theirOneTimeKey dataUsingEncoding:NSUTF8StringEncoding];
+    size_t result = olm_create_outbound_session(_session, account.account, idKey.bytes, idKey.length, otKey.bytes, otKey.length, random.mutableBytes, random.length);
+    [random resetBytesInRange:NSMakeRange(0, random.length)];
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_create_outbound_session error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_outbound_session error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    return self;
+}
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error {
+    self = [self initWithAccount:account];
+    if (!self) {
+        return nil;
+    }
+    NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]];
+    size_t result = olm_create_inbound_session(_session, account.account, otk.mutableBytes, oneTimeKeyMessage.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_create_inbound_session error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_inbound_session error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    return self;
+}
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error {
+    self = [self initWithAccount:account];
+    if (!self) {
+        return nil;
+    }
+    NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding];
+    NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]];
+    size_t result = olm_create_inbound_session_from(_session, account.account, idKey.bytes, idKey.length, otk.mutableBytes, otk.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_create_inbound_session_from error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_inbound_session_from error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    return self;
+}
+
+- (NSString*) sessionIdentifier {
+    size_t length = olm_session_id_length(_session);
+    NSMutableData *idData = [NSMutableData dataWithLength:length];
+    if (!idData) {
+        return nil;
+    }
+    size_t result = olm_session_id(_session, idData.mutableBytes, idData.length);
+    if (result == olm_error()) {
+        const char *error = olm_session_last_error(_session);
+        NSLog(@"olm_session_id error: %s", error);
+        return nil;
+    }
+    NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding];
+    return idString;
+}
+
+- (BOOL)matchesInboundSession:(NSString *)oneTimeKeyMessage {
+    NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]];
+
+    size_t result = olm_matches_inbound_session(_session, otk.mutableBytes, otk.length);
+    if (result == 1) {
+        return YES;
+    }
+    else {
+        if (result == olm_error()) {
+            const char *error = olm_session_last_error(_session);
+            NSLog(@"olm_matches_inbound_session error: %s", error);
+        }
+        return NO;
+    }
+}
+
+- (BOOL)matchesInboundSessionFrom:(NSString *)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage {
+    NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding];
+    NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]];
+
+    size_t result = olm_matches_inbound_session_from(_session,
+                                                     idKey.bytes, idKey.length,
+                                                     otk.mutableBytes, otk.length);
+    if (result == 1) {
+        return YES;
+    }
+    else {
+        if (result == olm_error()) {
+            const char *error = olm_session_last_error(_session);
+            NSLog(@"olm_matches_inbound_session error: %s", error);
+        }
+        return NO;
+    }
+}
+
+- (OLMMessage*) encryptMessage:(NSString*)message error:(NSError**)error {
+    size_t messageType = olm_encrypt_message_type(_session);
+    size_t randomLength = olm_encrypt_random_length(_session);
+    NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+    NSData *plaintextData = [message dataUsingEncoding:NSUTF8StringEncoding];
+    size_t ciphertextLength = olm_encrypt_message_length(_session, plaintextData.length);
+    NSMutableData *ciphertext = [NSMutableData dataWithLength:ciphertextLength];
+    if (!ciphertext) {
+        return nil;
+    }
+    size_t result = olm_encrypt(_session, plaintextData.bytes, plaintextData.length, random.mutableBytes, random.length, ciphertext.mutableBytes, ciphertext.length);
+    [random resetBytesInRange:NSMakeRange(0, random.length)];
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_encrypt error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_encrypt error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    NSString *ciphertextString = [[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding];
+    OLMMessage *encryptedMessage = [[OLMMessage alloc] initWithCiphertext:ciphertextString type:messageType];
+    return encryptedMessage;
+}
+
+- (NSString*) decryptMessage:(OLMMessage*)message error:(NSError**)error {
+    NSParameterAssert(message != nil);
+    NSData *messageData = [message.ciphertext dataUsingEncoding:NSUTF8StringEncoding];
+    if (!messageData) {
+        return nil;
+    }
+    NSMutableData *mutMessage = messageData.mutableCopy;
+    size_t maxPlaintextLength = olm_decrypt_max_plaintext_length(_session, message.type, mutMessage.mutableBytes, mutMessage.length);
+    if (maxPlaintextLength == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_decrypt_max_plaintext_length error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_decrypt_max_plaintext_length error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    // message buffer is destroyed by olm_decrypt_max_plaintext_length
+    mutMessage = messageData.mutableCopy;
+    NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength];
+    size_t plaintextLength = olm_decrypt(_session, message.type, mutMessage.mutableBytes, mutMessage.length, plaintextData.mutableBytes, plaintextData.length);
+    if (plaintextLength == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        NSLog(@"olm_decrypt error: %@", errorString);
+
+        if (error && olm_error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain
+                                         code:0
+                                     userInfo:@{
+                                                NSLocalizedDescriptionKey: errorString,
+                                                NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_decrypt error: %@", errorString]
+                                                }];
+        }
+
+        return nil;
+    }
+    plaintextData.length = plaintextLength;
+    NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding];
+    [plaintextData resetBytesInRange:NSMakeRange(0, plaintextData.length)];
+    return plaintext;
+}
+
+#pragma mark OLMSerializable
+
+/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
+- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error {
+    self = [self init];
+    if (!self) {
+        return nil;
+    }
+    NSParameterAssert(key.length > 0);
+    NSParameterAssert(serializedData.length > 0);
+    if (key.length == 0 || serializedData.length == 0) {
+        if (error) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}];
+        }
+        return nil;
+    }
+    NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+    size_t result = olm_unpickle_session(_session, key.bytes, key.length, pickle.mutableBytes, pickle.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    return self;
+}
+
+/** Serializes and encrypts object data, outputs base64 blob */
+- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error {
+    NSParameterAssert(key.length > 0);
+    size_t length = olm_pickle_session_length(_session);
+    NSMutableData *pickled = [NSMutableData dataWithLength:length];
+    size_t result = olm_pickle_session(_session, key.bytes, key.length, pickled.mutableBytes, pickled.length);
+    if (result == olm_error()) {
+        const char *olm_error = olm_session_last_error(_session);
+        NSString *errorString = [NSString stringWithUTF8String:olm_error];
+        if (error && errorString) {
+            *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}];
+        }
+        return nil;
+    }
+    NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding];
+    return pickleString;
+}
+
+#pragma mark NSSecureCoding
+
++ (BOOL) supportsSecureCoding {
+    return YES;
+}
+
+#pragma mark NSCoding
+
+- (id)initWithCoder:(NSCoder *)decoder {
+    NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"];
+    
+    NSError *error = nil;
+    
+    if ([version isEqualToString:@"1"]) {
+        NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"];
+        NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"];
+        
+        self = [self initWithSerializedData:pickle key:key error:&error];
+    }
+    
+    NSParameterAssert(error == nil);
+    NSParameterAssert(self != nil);
+    if (!self) {
+        return nil;
+    }
+    
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)encoder {
+    NSData *key = [OLMUtility randomBytesOfLength:32];
+    NSError *error = nil;
+    NSString *pickle = [self serializeDataWithKey:key error:&error];
+    NSParameterAssert(pickle.length > 0 && error == nil);
+    
+    [encoder encodeObject:pickle forKey:@"pickle"];
+    [encoder encodeObject:key forKey:@"key"];
+    [encoder encodeObject:@"1" forKey:@"version"];
+}
+
+@end
diff --git a/xcode/OLMKit/OLMSession_Private.h b/xcode/OLMKit/OLMSession_Private.h
new file mode 100644
index 0000000000000000000000000000000000000000..28ba5e1c0a06e91c955e754e1f1b78e8e59083cb
--- /dev/null
+++ b/xcode/OLMKit/OLMSession_Private.h
@@ -0,0 +1,26 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include "olm/olm.h"
+
+@interface OLMSession()
+
+@property (nonatomic) OlmSession *session;
+@property (nonatomic, strong) OLMAccount *account;
+
+@end
diff --git a/xcode/OLMKit/OLMUtility.h b/xcode/OLMKit/OLMUtility.h
new file mode 100644
index 0000000000000000000000000000000000000000..22e972449f9396b9446d19ac8da4961bb55467be
--- /dev/null
+++ b/xcode/OLMKit/OLMUtility.h
@@ -0,0 +1,49 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+FOUNDATION_EXPORT NSString *const OLMErrorDomain;
+
+@interface OLMUtility : NSObject
+
+/**
+ Calculate the SHA-256 hash of the input and encodes it as base64.
+ 
+ @param message the message to hash.
+ @return the base64-encoded hash value.
+ */
+- (NSString*)sha256:(NSData*)message;
+
+/**
+ Verify an ed25519 signature.
+
+ @param signature the base64-encoded signature to be checked.
+ @param key the ed25519 key.
+ @param message the message which was signed.
+ @param the result error if there is a problem with the verification.
+ If the key was too small then the message will be "OLM.INVALID_BASE64".
+ If the signature was invalid then the message will be "OLM.BAD_MESSAGE_MAC".
+
+ @return YES if valid.
+ */
+- (BOOL)verifyEd25519Signature:(NSString*)signature key:(NSString*)key message:(NSData*)message error:(NSError**)error;
+
++ (NSMutableData*) randomBytesOfLength:(NSUInteger)length;
+
+@end
diff --git a/xcode/OLMKit/OLMUtility.m b/xcode/OLMKit/OLMUtility.m
new file mode 100644
index 0000000000000000000000000000000000000000..936785a90f5488b889d9edd8cf64b1f6f601ad9f
--- /dev/null
+++ b/xcode/OLMKit/OLMUtility.m
@@ -0,0 +1,121 @@
+/*
+ Copyright 2016 Chris Ballinger
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import "OLMUtility.h"
+
+#include "olm/olm.h"
+
+NSString *const OLMErrorDomain = @"org.matrix.olm";
+
+@interface OLMUtility()
+
+@property (nonatomic) OlmUtility *utility;
+
+@end
+
+@implementation OLMUtility
+
+- (void) dealloc {
+    olm_clear_utility(_utility);
+    free(_utility);
+}
+
+- (BOOL) initializeUtilityMemory {
+    size_t utilitySize = olm_utility_size();
+    _utility = malloc(utilitySize);
+    NSParameterAssert(_utility != nil);
+    if (!_utility) {
+        return NO;
+    }
+    _utility = olm_utility(_utility);
+    NSParameterAssert(_utility != nil);
+    if (!_utility) {
+        return NO;
+    }
+    return YES;
+}
+
+- (instancetype) init {
+    self = [super init];
+    if (!self) {
+        return nil;
+    }
+    BOOL success = [self initializeUtilityMemory];
+    if (!success) {
+        return nil;
+    }
+    return self;
+}
+
+- (NSString *)sha256:(NSData *)message {
+    size_t length = olm_sha256_length(_utility);
+
+    NSMutableData *shaData = [NSMutableData dataWithLength:length];
+    if (!shaData) {
+        return nil;
+    }
+
+    size_t result = olm_sha256(_utility, message.bytes, message.length, shaData.mutableBytes, shaData.length);
+    if (result == olm_error()) {
+        const char *error = olm_utility_last_error(_utility);
+        NSLog(@"olm_sha256 error: %s", error);
+        return nil;
+    }
+    
+    NSString *sha = [[NSString alloc] initWithData:shaData encoding:NSUTF8StringEncoding];
+    return sha;
+}
+
+- (BOOL)verifyEd25519Signature:(NSString*)signature key:(NSString*)key message:(NSData*)message error:(NSError**)error {
+
+    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
+    NSData *signatureData = [signature dataUsingEncoding:NSUTF8StringEncoding];
+
+    size_t result = olm_ed25519_verify(_utility,
+                                       keyData.bytes, keyData.length,
+                                       message.bytes, message.length,
+                                       (void*)signatureData.bytes, signatureData.length
+                                       );
+
+    if (result == olm_error()) {
+        if (error) {
+            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: [NSString stringWithUTF8String:olm_utility_last_error(_utility)]};
+
+            // @TODO
+            *error = [[NSError alloc] initWithDomain:@"OLMKitErrorDomain" code:0 userInfo:userInfo];
+        }
+        return NO;
+    }
+    else {
+        return YES;
+    }
+}
+
++ (NSMutableData*) randomBytesOfLength:(NSUInteger)length {
+    NSMutableData *randomData = [NSMutableData dataWithLength:length];
+    if (!randomData) {
+        return nil;
+    }
+    int result = SecRandomCopyBytes(kSecRandomDefault, randomData.length, randomData.mutableBytes);
+    if (result != 0) {
+        return nil;
+    }
+    return randomData;
+}
+
+@end
diff --git a/xcode/OLMKitTests/Info.plist b/xcode/OLMKitTests/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..6c6c23c43adc88621ce3abfbd1585c8792bd165c
--- /dev/null
+++ b/xcode/OLMKitTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/xcode/OLMKitTests/OLMKitGroupTests.m b/xcode/OLMKitTests/OLMKitGroupTests.m
new file mode 100644
index 0000000000000000000000000000000000000000..ea8229581e1c1cf67b20b6b9c342307f4f9e33c7
--- /dev/null
+++ b/xcode/OLMKitTests/OLMKitGroupTests.m
@@ -0,0 +1,94 @@
+/*
+ Copyright 2016 OpenMarket Ltd
+ Copyright 2016 Vector Creations Ltd
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import <XCTest/XCTest.h>
+
+#import <OLMKit/OLMKit.h>
+
+#include "olm/olm.h"
+
+@interface OLMKitGroupTests : XCTestCase
+
+@end
+
+@implementation OLMKitGroupTests
+
+- (void)setUp {
+    [super setUp];
+    // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown {
+    // Put teardown code here. This method is called after the invocation of each test method in the class.
+    [super tearDown];
+}
+
+- (void)testAliceAndBob {
+    NSError *error;
+
+    OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession];
+    XCTAssertGreaterThan(aliceSession.sessionIdentifier.length, 0);
+    XCTAssertGreaterThan(aliceSession.sessionKey.length, 0);
+    XCTAssertEqual(aliceSession.messageIndex, 0);
+
+    // Store the session key before starting encrypting
+    NSString *sessionKey = aliceSession.sessionKey;
+
+    NSString *message = @"Hello!";
+    NSString *aliceToBobMsg = [aliceSession encryptMessage:message error:&error];
+
+    XCTAssertEqual(aliceSession.messageIndex, 1);
+    XCTAssertGreaterThanOrEqual(aliceToBobMsg.length, 0);
+    XCTAssertNil(error);
+
+    OLMInboundGroupSession *bobSession = [[OLMInboundGroupSession alloc] initInboundGroupSessionWithSessionKey:sessionKey error:&error];
+    XCTAssertEqualObjects(aliceSession.sessionIdentifier, bobSession.sessionIdentifier);
+    XCTAssertNil(error);
+
+    NSUInteger messageIndex;
+
+    NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg messageIndex:&messageIndex error:&error];
+    XCTAssertEqualObjects(message, plaintext);
+
+    XCTAssertEqual(messageIndex, 0);
+    XCTAssertNil(error);
+}
+
+- (void)testOutboundGroupSessionSerialization {
+
+    OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession];
+
+    NSData *aliceData = [NSKeyedArchiver archivedDataWithRootObject:aliceSession];
+    OLMOutboundGroupSession *aliceSession2 = [NSKeyedUnarchiver unarchiveObjectWithData:aliceData];
+
+    XCTAssertEqualObjects(aliceSession2.sessionKey, aliceSession.sessionKey);
+    XCTAssertEqualObjects(aliceSession2.sessionIdentifier, aliceSession.sessionIdentifier);
+}
+
+- (void)testInboundGroupSessionSerialization {
+
+    OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession];
+
+    OLMInboundGroupSession *bobSession = [[OLMInboundGroupSession alloc] initInboundGroupSessionWithSessionKey:aliceSession.sessionKey error:nil];
+
+    NSData *bobData = [NSKeyedArchiver archivedDataWithRootObject:bobSession];
+    OLMInboundGroupSession *bobSession2 = [NSKeyedUnarchiver unarchiveObjectWithData:bobData];
+
+    XCTAssertEqualObjects(bobSession2.sessionIdentifier, aliceSession.sessionIdentifier);
+}
+
+@end
diff --git a/xcode/OLMKitTests/OLMKitTests.m b/xcode/OLMKitTests/OLMKitTests.m
new file mode 100644
index 0000000000000000000000000000000000000000..ee024209bf4a0822bf4b4370ab1020499c62ed77
--- /dev/null
+++ b/xcode/OLMKitTests/OLMKitTests.m
@@ -0,0 +1,206 @@
+/*
+Copyright 2016 Chris Ballinger
+Copyright 2016 OpenMarket Ltd
+Copyright 2016 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#import <XCTest/XCTest.h>
+#import <OLMKit/OLMKit.h>
+
+@interface OLMKitTests : XCTestCase
+
+@end
+
+@implementation OLMKitTests
+
+- (void)setUp {
+    [super setUp];
+    // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown {
+    // Put teardown code here. This method is called after the invocation of each test method in the class.
+    [super tearDown];
+}
+
+- (void)testAliceAndBob {
+    NSError *error;
+
+    OLMAccount *alice = [[OLMAccount alloc] initNewAccount];
+    OLMAccount *bob = [[OLMAccount alloc] initNewAccount];
+    [bob generateOneTimeKeys:5];
+    NSDictionary *bobIdKeys = bob.identityKeys;
+    NSString *bobIdKey = bobIdKeys[@"curve25519"];
+    NSDictionary *bobOneTimeKeys = bob.oneTimeKeys;
+    NSParameterAssert(bobIdKey != nil);
+    NSParameterAssert(bobOneTimeKeys != nil);
+    __block NSString *bobOneTimeKey = nil;
+    NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"];
+    [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
+        bobOneTimeKey = obj;
+    }];
+    XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]);
+    
+    OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil];
+    NSString *message = @"Hello!";
+    OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:&error];
+    XCTAssertNil(error);
+    
+    OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil];
+    NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:&error];
+    XCTAssertEqualObjects(message, plaintext);
+    XCTAssertNil(error);
+
+    XCTAssert([bobSession matchesInboundSession:aliceToBobMsg.ciphertext]);
+    XCTAssertFalse([aliceSession matchesInboundSession:@"ARandomOtkMessage"]);
+
+    NSString *aliceIdKey = alice.identityKeys[@"curve25519"];
+    XCTAssert([bobSession matchesInboundSessionFrom:aliceIdKey oneTimeKeyMessage:aliceToBobMsg.ciphertext]);
+    XCTAssertFalse([bobSession matchesInboundSessionFrom:@"ARandomIdKey" oneTimeKeyMessage:aliceToBobMsg.ciphertext]);
+    XCTAssertFalse([bobSession matchesInboundSessionFrom:aliceIdKey oneTimeKeyMessage:@"ARandomOtkMessage"]);
+
+    BOOL success = [bob removeOneTimeKeysForSession:bobSession];
+    XCTAssertTrue(success);
+}
+
+- (void) testBackAndForth {
+    OLMAccount *alice = [[OLMAccount alloc] initNewAccount];
+    OLMAccount *bob = [[OLMAccount alloc] initNewAccount];
+    [bob generateOneTimeKeys:1];
+    NSDictionary *bobIdKeys = bob.identityKeys;
+    NSString *bobIdKey = bobIdKeys[@"curve25519"];
+    NSDictionary *bobOneTimeKeys = bob.oneTimeKeys;
+    NSParameterAssert(bobIdKey != nil);
+    NSParameterAssert(bobOneTimeKeys != nil);
+    __block NSString *bobOneTimeKey = nil;
+    NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"];
+    [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
+        bobOneTimeKey = obj;
+    }];
+    XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]);
+    
+    OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil];
+    NSString *message = @"Hello I'm Alice!";
+    OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:nil];
+    
+    OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil];
+    NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:nil];
+    XCTAssertEqualObjects(message, plaintext);
+    BOOL success = [bob removeOneTimeKeysForSession:bobSession];
+    XCTAssertTrue(success);
+    
+    NSString *msg1 = @"Hello I'm Bob!";
+    NSString *msg2 = @"Isn't life grand?";
+    NSString *msg3 = @"Let's go to the opera.";
+    
+    OLMMessage *eMsg1 = [bobSession encryptMessage:msg1 error:nil];
+    OLMMessage *eMsg2 = [bobSession encryptMessage:msg2 error:nil];
+    OLMMessage *eMsg3 = [bobSession encryptMessage:msg3 error:nil];
+    
+    NSString *dMsg1 = [aliceSession decryptMessage:eMsg1 error:nil];
+    NSString *dMsg2 = [aliceSession decryptMessage:eMsg2 error:nil];
+    NSString *dMsg3 = [aliceSession decryptMessage:eMsg3 error:nil];
+    XCTAssertEqualObjects(msg1, dMsg1);
+    XCTAssertEqualObjects(msg2, dMsg2);
+    XCTAssertEqualObjects(msg3, dMsg3);
+}
+
+- (void) testAccountSerialization {
+    OLMAccount *bob = [[OLMAccount alloc] initNewAccount];
+    [bob generateOneTimeKeys:5];
+    NSDictionary *bobIdKeys = bob.identityKeys;
+    NSDictionary *bobOneTimeKeys = bob.oneTimeKeys;
+    
+    NSData *bobData = [NSKeyedArchiver archivedDataWithRootObject:bob];
+    
+    OLMAccount *bob2 = [NSKeyedUnarchiver unarchiveObjectWithData:bobData];
+    NSDictionary *bobIdKeys2 = bob2.identityKeys;
+    NSDictionary *bobOneTimeKeys2 = bob2.oneTimeKeys;
+    
+    XCTAssertEqualObjects(bobIdKeys, bobIdKeys2);
+    XCTAssertEqualObjects(bobOneTimeKeys, bobOneTimeKeys2);
+}
+
+- (void) testSessionSerialization {
+    NSError *error;
+
+    OLMAccount *alice = [[OLMAccount alloc] initNewAccount];
+    OLMAccount *bob = [[OLMAccount alloc] initNewAccount];
+    [bob generateOneTimeKeys:1];
+    NSDictionary *bobIdKeys = bob.identityKeys;
+    NSString *bobIdKey = bobIdKeys[@"curve25519"];
+    NSDictionary *bobOneTimeKeys = bob.oneTimeKeys;
+    NSParameterAssert(bobIdKey != nil);
+    NSParameterAssert(bobOneTimeKeys != nil);
+    __block NSString *bobOneTimeKey = nil;
+    NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"];
+    [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
+        bobOneTimeKey = obj;
+    }];
+    XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]);
+    
+    OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil];
+    NSString *message = @"Hello I'm Alice!";
+    OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:&error];
+    XCTAssertNil(error);
+
+    
+    OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil];
+    NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:nil];
+    XCTAssertEqualObjects(message, plaintext);
+    BOOL success = [bob removeOneTimeKeysForSession:bobSession];
+    XCTAssertTrue(success);
+    
+    NSString *msg1 = @"Hello I'm Bob!";
+    NSString *msg2 = @"Isn't life grand?";
+    NSString *msg3 = @"Let's go to the opera.";
+    
+    OLMMessage *eMsg1 = [bobSession encryptMessage:msg1 error:nil];
+    OLMMessage *eMsg2 = [bobSession encryptMessage:msg2 error:nil];
+    OLMMessage *eMsg3 = [bobSession encryptMessage:msg3 error:nil];
+    
+    NSData *aliceData = [NSKeyedArchiver archivedDataWithRootObject:aliceSession];
+    OLMSession *alice2 = [NSKeyedUnarchiver unarchiveObjectWithData:aliceData];
+    
+    NSString *dMsg1 = [alice2 decryptMessage:eMsg1 error:nil];
+    NSString *dMsg2 = [alice2 decryptMessage:eMsg2 error:nil];
+    NSString *dMsg3 = [alice2 decryptMessage:eMsg3 error:nil];
+    XCTAssertEqualObjects(msg1, dMsg1);
+    XCTAssertEqualObjects(msg2, dMsg2);
+    XCTAssertEqualObjects(msg3, dMsg3);
+}
+
+- (void)testEd25519Signing {
+
+    OLMUtility *olmUtility = [[OLMUtility alloc] init];
+    OLMAccount *alice = [[OLMAccount alloc] initNewAccount];
+
+    NSDictionary *aJSON = @{
+                            @"key1": @"value1",
+                            @"key2": @"value2"
+                            };
+    NSData *message = [NSKeyedArchiver archivedDataWithRootObject:aJSON];
+    NSString *signature = [alice signMessage:message];
+
+
+    NSString *aliceEd25519Key = alice.identityKeys[@"ed25519"];
+
+    NSError *error;
+    BOOL result = [olmUtility verifyEd25519Signature:signature key:aliceEd25519Key message:message error:&error];
+    XCTAssert(result);
+    XCTAssertNil(error);
+}
+
+@end
diff --git a/xcode/Podfile b/xcode/Podfile
new file mode 100644
index 0000000000000000000000000000000000000000..4c60dd32f760948a7e85bbe3e80946b1de20d899
--- /dev/null
+++ b/xcode/Podfile
@@ -0,0 +1,7 @@
+target "OLMKit" do
+pod 'OLMKit', :path => '../OLMKit.podspec'
+end
+
+target "OLMKitTests" do
+pod 'OLMKit', :path => '../OLMKit.podspec'
+end
\ No newline at end of file
diff --git a/xcode/Podfile.lock b/xcode/Podfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..ecafd79aa27a8af83fa3771648eaef29c68e390a
--- /dev/null
+++ b/xcode/Podfile.lock
@@ -0,0 +1,20 @@
+PODS:
+  - OLMKit (2.0.1):
+    - OLMKit/olmc (= 2.0.1)
+    - OLMKit/olmcpp (= 2.0.1)
+  - OLMKit/olmc (2.0.1)
+  - OLMKit/olmcpp (2.0.1)
+
+DEPENDENCIES:
+  - OLMKit (from `../OLMKit.podspec`)
+
+EXTERNAL SOURCES:
+  OLMKit:
+    :path: ../OLMKit.podspec
+
+SPEC CHECKSUMS:
+  OLMKit: 12a35a69f92c7facdd50b559128d1b4a17857ba7
+
+PODFILE CHECKSUM: 4e261dae61d833ec5585ced2473023b98909fd35
+
+COCOAPODS: 1.1.1
diff --git a/xcode/README.rst b/xcode/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d56fa85d39d9d475e85b91a38a748e5d6d9f9fb1
--- /dev/null
+++ b/xcode/README.rst
@@ -0,0 +1,26 @@
+OLMKit
+======
+
+OLMKit exposes an Objective-C wrapper to libolm.
+
+The original work by Chris Ballinger can be found at https://github.com/chrisballinger/OLMKit.
+
+Installation
+------------
+You can embed OLMKit to your application project with CocoaPods. The pod for
+the latest OLMKit release is::
+
+    pod 'OLMKit'
+
+Development
+-----------
+Run `pod install` and open `OLMKit.xcworkspace`.
+
+The project contains only tests files. The libolm and the Objective-C wrapper source files are loaded via the OLMKit CocoaPods pod.
+
+To add a new source file, add it to the file system and run `pod update` to make CocoaPods insert it into OLMKit.xcworkspace. 
+
+Release
+-------
+See ../README.rst for the release of the CocoaPod.
+