]> git.saurik.com Git - apple/security.git/blobdiff - sec/SOSCircle/SecureObjectSync/SOSAccount.c
Security-55471.tar.gz
[apple/security.git] / sec / SOSCircle / SecureObjectSync / SOSAccount.c
diff --git a/sec/SOSCircle/SecureObjectSync/SOSAccount.c b/sec/SOSCircle/SecureObjectSync/SOSAccount.c
new file mode 100644 (file)
index 0000000..8ac8431
--- /dev/null
@@ -0,0 +1,3193 @@
+/*
+ * Created by Michael Brouwer on 6/22/12.
+ * Copyright 2012 Apple Inc. All Rights Reserved.
+ */
+
+/*
+ * SOSAccount.c -  Implementation of the secure object syncing account.
+ * An account contains a SOSCircle for each protection domain synced.
+ */
+
+#include <SecureObjectSync/SOSInternal.h>
+#include <SecureObjectSync/SOSAccount.h>
+#include <SecureObjectSync/SOSCircle.h>
+#include <SecureObjectSync/SOSCloudCircle.h>
+#include <SecureObjectSync/SOSEngine.h>
+#include <SecureObjectSync/SOSPeer.h>
+#include <SecureObjectSync/SOSFullPeerInfo.h>
+#include <SecureObjectSync/SOSPeerInfo.h>
+#include <SecureObjectSync/SOSPeerInfoInternal.h>
+#include <SecureObjectSync/SOSUserKeygen.h>
+#include <Security/SecKeyPriv.h>
+#include <Security/SecItemPriv.h>
+#include <CoreFoundation/CFArray.h>
+#include <dispatch/dispatch.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <AssertMacros.h>
+#include <utilities/SecCFWrappers.h>
+
+#include <utilities/der_plist.h>
+#include <utilities/der_plist_internal.h>
+#include <utilities/iOSforOSX.h>
+
+#include <utilities/SecAKSWrappers.h>
+
+#include <corecrypto/ccder.h>
+
+#include <securityd/SOSCloudCircleServer.h>
+#include <securityd/SecDbItem.h> // For SecError
+
+#include <utilities/debugging.h>
+#include <utilities/iCloudKeychainTrace.h>
+
+#include <notify.h>
+
+static CFStringRef kicloud_identity_name = CFSTR("Cloud Identity");
+
+//
+// Forward statics.
+//
+
+static bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef newCircle, bool writeUpdate, bool initialSync, CFErrorRef *error);
+
+//
+// DER Encoding utilities
+//
+
+//
+// Encodes data or a zero length data
+//
+static size_t der_sizeof_data_or_null(CFDataRef data, CFErrorRef* error)
+{
+       if (data) {
+               return der_sizeof_data(data, error);
+       } else {
+               return der_sizeof_null(kCFNull, error);
+       }
+}
+
+static uint8_t* der_encode_data_or_null(CFDataRef data, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    if (data) {
+               return der_encode_data(data, error, der, der_end);
+       } else {
+               return der_encode_null(kCFNull, error, der, der_end);
+       }
+}
+
+
+static const uint8_t* der_decode_data_or_null(CFAllocatorRef allocator, CFDataRef* data,
+                                              CFErrorRef* error,
+                                              const uint8_t* der, const uint8_t* der_end)
+{
+    CFTypeRef value = NULL;
+       der = der_decode_plist(allocator, 0, &value, error, der, der_end);
+       if (value && CFGetTypeID(value) != CFDataGetTypeID()) {
+               CFReleaseNull(value);
+       }
+       if (data) {
+               *data = value;
+       }
+       return der;
+}
+
+
+//
+// Mark: public_bytes encode/decode
+//
+
+static size_t der_sizeof_public_bytes(SecKeyRef publicKey, CFErrorRef* error)
+{
+    CFDataRef publicData = NULL;
+
+    if (publicKey)
+        SecKeyCopyPublicBytes(publicKey, &publicData);
+
+    size_t size = der_sizeof_data_or_null(publicData, error);
+
+    CFReleaseNull(publicData);
+
+    return size;
+}
+
+static uint8_t* der_encode_public_bytes(SecKeyRef publicKey, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    CFDataRef publicData = NULL;
+
+    if (publicKey)
+        SecKeyCopyPublicBytes(publicKey, &publicData);
+
+    uint8_t *result = der_encode_data_or_null(publicData, error, der, der_end);
+
+    CFReleaseNull(publicData);
+
+    return result;
+}
+
+static const uint8_t* der_decode_public_bytes(CFAllocatorRef allocator, CFIndex algorithmID, SecKeyRef* publicKey, CFErrorRef* error, const uint8_t* der, const uint8_t* der_end)
+{
+    CFDataRef dataFound = NULL;
+    der = der_decode_data_or_null(allocator, &dataFound, error, der, der_end);
+
+    if (der && dataFound && publicKey) {
+        *publicKey = SecKeyCreateFromPublicData(allocator, algorithmID, dataFound);
+    }
+    CFReleaseNull(dataFound);
+
+    return der;
+}
+
+
+//
+// Cloud Paramters encode/decode
+//
+
+static size_t der_sizeof_cloud_parameters(SecKeyRef publicKey, CFDataRef paramters, CFErrorRef* error)
+{
+    size_t public_key_size = der_sizeof_public_bytes(publicKey, error);
+    size_t parameters_size = der_sizeof_data_or_null(paramters, error);
+
+    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, public_key_size + parameters_size);
+}
+
+static uint8_t* der_encode_cloud_parameters(SecKeyRef publicKey, CFDataRef paramters, CFErrorRef* error,
+                                            const uint8_t* der, uint8_t* der_end)
+{
+    uint8_t* original_der_end = der_end;
+
+    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, original_der_end, der,
+                                       der_encode_public_bytes(publicKey, error, der,
+                                                               der_encode_data_or_null(paramters, error, der, der_end)));
+}
+
+static const uint8_t* der_decode_cloud_parameters(CFAllocatorRef allocator,
+                                                  CFIndex algorithmID, SecKeyRef* publicKey,
+                                                  CFDataRef *parameters,
+                                                  CFErrorRef* error,
+                                                  const uint8_t* der, const uint8_t* der_end)
+{
+    const uint8_t *sequence_end;
+    der = ccder_decode_sequence_tl(&sequence_end, der, der_end);
+    der = der_decode_public_bytes(allocator, algorithmID, publicKey, error, der, sequence_end);
+    der = der_decode_data_or_null(allocator, parameters, error, der, sequence_end);
+
+    return der;
+}
+
+
+//
+// bool encoding/decoding
+//
+
+
+static const uint8_t* ccder_decode_bool(bool* boolean, const uint8_t* der, const uint8_t *der_end)
+{
+    if (NULL == der)
+        return NULL;
+
+    size_t payload_size = 0;
+    const uint8_t *payload = ccder_decode_tl(CCDER_BOOLEAN, &payload_size, der, der_end);
+
+    if (NULL == payload || (der_end - payload) < 1 || payload_size != 1) {
+        return NULL;
+    }
+
+    if (boolean)
+        *boolean = (*payload != 0);
+
+    return payload + payload_size;
+}
+
+
+static size_t ccder_sizeof_bool(bool value __unused, CFErrorRef *error)
+{
+    return ccder_sizeof(CCDER_BOOLEAN, 1);
+}
+
+
+static uint8_t* ccder_encode_bool(bool value, const uint8_t *der, uint8_t *der_end)
+{
+    uint8_t value_byte = value;
+
+    return ccder_encode_tl(CCDER_BOOLEAN, 1, der,
+           ccder_encode_body(1, &value_byte, der, der_end));
+}
+
+struct __OpaqueSOSAccount {
+    CFRuntimeBase           _base;
+
+    dispatch_queue_t        queue;
+
+    CFDictionaryRef         gestalt;
+
+    CFMutableDictionaryRef  circle_identities;
+    CFMutableDictionaryRef  circles;
+    CFMutableDictionaryRef  retired_peers;
+
+    bool      user_public_trusted;
+    CFDataRef user_key_parameters;
+    SecKeyRef user_public;
+    SecKeyRef previous_public;
+    enum DepartureReason    departure_code;
+
+    // Non-persistent data
+
+    SOSDataSourceFactoryRef factory;
+    SecKeyRef _user_private;
+    dispatch_source_t user_private_timer;
+    int               lock_notification_token;
+
+    // Live Notification
+    CFMutableArrayRef       change_blocks;
+
+    SOSAccountKeyInterestBlock        update_interest_block;
+    SOSAccountDataUpdateBlock         update_block;
+    SOSAccountMessageProcessedBlock   processed_message_block;
+
+    CFMutableDictionaryRef  deferred_updates;
+
+    CFMutableDictionaryRef  pending_changes;
+};
+
+CFGiblisWithCompareFor(SOSAccount);
+
+static inline bool SOSAccountHasLeft(SOSAccountRef account) {
+    switch(account->departure_code) {
+        case kSOSWithdrewMembership: /* Fallthrough */
+        case kSOSMembershipRevoked: /* Fallthrough */
+        case kSOSLeftUntrustedCircle:
+            return true;
+        case kSOSNeverAppliedToCircle: /* Fallthrough */
+        case kSOSNeverLeftCircle: /* Fallthrough */
+        default:
+            return false;
+    }
+}
+
+// Private static functions.
+
+static bool SOSUpdateKeyInterest(SOSAccountRef account, bool getNewKeysOnly, CFErrorRef *error);
+
+static bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
+{
+    bool result = false;
+    if (a)
+    {
+        require(a->factory, xit);
+        CFArrayRef circle_names = a->factory->copy_names(a->factory);
+        require(circle_names, xit);
+        CFArrayForEach(circle_names, ^(const void*name) {
+            if (isString(name))
+                SOSAccountEnsureCircle(a, (CFStringRef)name, NULL);
+        });
+
+        CFReleaseNull(circle_names);
+        result = true;
+    }
+xit:
+    return result;
+}
+
+static SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
+                                           CFDictionaryRef gestalt,
+                                           SOSDataSourceFactoryRef factory,
+                                           SOSAccountKeyInterestBlock interest_block,
+                                           SOSAccountDataUpdateBlock update_block) {
+    SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
+
+    a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
+
+    a->gestalt = gestalt;
+    CFRetain(a->gestalt);
+
+    a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
+    
+    a->factory = factory; // We adopt the factory. kthanksbai.
+    
+    a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
+    
+    a->update_interest_block = Block_copy(interest_block);
+    a->update_block = Block_copy(update_block);
+
+    a->pending_changes = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->departure_code = kSOSNeverAppliedToCircle;
+
+    return a;
+}
+
+
+static SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircleNamedIfPresent(SOSAccountRef account, CFStringRef name, CFErrorRef *error) {
+    if (CFDictionaryGetValue(account->circles, name) == NULL) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name);
+        return NULL;
+    }
+    
+    return (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name);
+}
+
+
+static void SOSAccountForEachKnownCircle(SOSAccountRef account,
+                                         void (^handle_incompatible)(CFStringRef name),
+                                         void (^handle_no_peer)(SOSCircleRef circle),
+                                         void (^handle_peer)(SOSCircleRef circle, SOSFullPeerInfoRef full_peer)) {
+    CFDictionaryForEach(account->circles, ^(const void *key, const void *value) {
+        if (isNull(value)) {
+            if (handle_incompatible)
+                handle_incompatible((CFStringRef)key);
+        } else {
+            SOSCircleRef circle = (SOSCircleRef) value;
+            CFRetainSafe(circle);
+            SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), NULL);
+            if (!fpi) {
+                if (handle_no_peer)
+                    handle_no_peer(circle);
+            } else {
+                CFRetainSafe(fpi);
+                if (handle_peer)
+                    handle_peer(circle, fpi);
+                CFReleaseSafe(fpi);
+            }
+            CFReleaseSafe(circle);
+        }
+    });
+}
+
+
+bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
+{
+    if (CFEqual(new_gestalt, account->gestalt))
+        return false;
+    
+    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+        if (SOSFullPeerInfoUpdateGestalt(full_peer, new_gestalt, NULL)) {
+            SOSAccountModifyCircle(account, SOSCircleGetName(circle),
+                                   NULL, ^(SOSCircleRef circle_to_change) {
+                                       (void) SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer));
+                                   });
+        };
+    });
+
+    CFReleaseNull(account->gestalt);
+    account->gestalt = new_gestalt;
+    CFRetain(account->gestalt);
+    
+    return true;
+}
+
+SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
+                               CFDictionaryRef gestalt,
+                               SOSDataSourceFactoryRef factory,
+                               SOSAccountKeyInterestBlock interest_block,
+                               SOSAccountDataUpdateBlock update_block) {
+    SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory, interest_block, update_block);
+
+    SOSAccountEnsureFactoryCircles(a);
+
+    return a;
+}
+
+static void SOSAccountDestroy(CFTypeRef aObj) {
+    SOSAccountRef a = (SOSAccountRef) aObj;
+
+    if (a->factory)
+        a->factory->release(a->factory);
+
+    CFReleaseNull(a->gestalt);
+    CFReleaseNull(a->circle_identities);
+    CFReleaseNull(a->circles);
+    CFReleaseNull(a->retired_peers);
+
+    a->user_public_trusted = false;
+    CFReleaseNull(a->user_public);
+    CFReleaseNull(a->user_key_parameters);
+
+    SOSAccountPurgePrivateCredential(a);
+    CFReleaseNull(a->previous_public);
+
+    CFReleaseNull(a->change_blocks);
+    Block_release(a->update_interest_block);
+    Block_release(a->update_block);
+    CFReleaseNull(a->processed_message_block);
+    CFReleaseNull(a->pending_changes);
+    CFReleaseNull(a->deferred_updates);
+    a->departure_code = kSOSNeverAppliedToCircle;
+
+    dispatch_release(a->queue);
+}
+
+static void SOSAccountSetToNew(SOSAccountRef a) {
+    CFAllocatorRef allocator = CFGetAllocator(a);
+    CFReleaseNull(a->circle_identities);
+    CFReleaseNull(a->circles);
+    CFReleaseNull(a->retired_peers);
+
+    CFReleaseNull(a->user_key_parameters);
+    CFReleaseNull(a->user_public);
+    CFReleaseNull(a->previous_public);
+    CFReleaseNull(a->_user_private);
+    
+    CFReleaseNull(a->pending_changes);
+    CFReleaseNull(a->deferred_updates);
+    
+    a->user_public_trusted = false;
+    a->departure_code = kSOSNeverAppliedToCircle;
+    a->user_private_timer = 0;
+    a->lock_notification_token = 0;
+    
+    // keeping gestalt;
+    // keeping factory;
+    // Live Notification
+    // change_blocks;
+    // update_interest_block;
+    // update_block;
+    
+    a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
+    a->pending_changes = CFDictionaryCreateMutableForCFTypes(allocator);
+
+    SOSAccountEnsureFactoryCircles(a);
+}
+
+
+static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) {
+    SOSAccountRef a = (SOSAccountRef) aObj;
+    
+    return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
+}
+
+static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
+{
+    SOSAccountRef laccount = (SOSAccountRef) lhs;
+    SOSAccountRef raccount = (SOSAccountRef) rhs;
+
+    return CFEqual(laccount->gestalt, raccount->gestalt)
+        && CFEqual(laccount->circles, raccount->circles)
+        && CFEqual(laccount->circle_identities, raccount->circle_identities);
+    //  ??? retired_peers
+}
+
+#if OLD_CODERS_SUPPORTED
+
+//
+// MARK: Persistent Encode decode
+//
+SOSAccountRef SOSAccountCreateFromDER_V1(CFAllocatorRef allocator,
+                                      SOSDataSourceFactoryRef factory,
+                                      SOSAccountKeyInterestBlock interest_block,
+                                      SOSAccountDataUpdateBlock update_block,
+                                      CFErrorRef* error,
+                                      const uint8_t** der_p, const uint8_t *der_end)
+{
+    SOSAccountRef account = NULL;
+
+    const uint8_t *sequence_end;
+    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
+
+    {
+        CFDictionaryRef decoded_gestalt = NULL;
+        *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error,
+                                       *der_p, der_end);
+
+        if (*der_p == 0)
+            return NULL;
+
+        account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block);
+        CFReleaseNull(decoded_gestalt);
+    }
+
+    CFArrayRef array = NULL;
+    *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end);
+
+    *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end);
+    *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end);
+    *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end);
+    *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end);
+    if (*der_p != sequence_end)
+        *der_p = NULL;
+
+    __block bool success = true;
+
+    require_quiet(array && *der_p, fail);
+    
+    CFArrayForEach(array, ^(const void *value) {
+        if (success) {
+            if (isString(value)) {
+                CFDictionaryAddValue(account->circles, value, kCFNull);
+            } else {
+                CFDataRef circleData = NULL;
+                CFDataRef fullPeerInfoData = NULL;
+
+                if (isData(value)) {
+                    circleData = (CFDataRef) value;
+                } else if (isArray(value)) {
+                    CFArrayRef pair = (CFArrayRef) value;
+                    
+                    CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0);
+                    CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1);
+                    
+                    if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) {
+                        circleData = (CFDataRef) circleObject;
+                        fullPeerInfoData = (CFDataRef) fullPeerInfoObject;
+                    }
+                }
+                
+                if (circleData) {
+                    SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error);
+                    require_action_quiet(circle, fail, success = false);
+                    
+                    CFStringRef circleName = SOSCircleGetName(circle);
+                    CFDictionaryAddValue(account->circles, circleName, circle);
+
+                    if (fullPeerInfoData) {
+                        SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error);
+                        require_action_quiet(full_peer, fail, success = false);
+                        
+                        CFDictionaryAddValue(account->circle_identities, circleName, full_peer);
+                        CFReleaseNull(full_peer);
+                    }
+                fail:
+                    CFReleaseNull(circle);
+                }
+            }
+        }
+    });
+    CFReleaseNull(array);
+    
+    require_quiet(success, fail);
+    require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail,
+                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error));
+
+    return account;
+    
+fail:
+    // Create a default error if we don't have one:
+    SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Account DER"), NULL, error);
+    CFReleaseNull(account);
+    return NULL;
+}
+
+SOSAccountRef SOSAccountCreateFromDER_V2(CFAllocatorRef allocator,
+                                         SOSDataSourceFactoryRef factory,
+                                         SOSAccountKeyInterestBlock interest_block,
+                                         SOSAccountDataUpdateBlock update_block,
+                                         CFErrorRef* error,
+                                         const uint8_t** der_p, const uint8_t *der_end)
+{
+    SOSAccountRef account = NULL;
+    const uint8_t *dersave = *der_p;
+    const uint8_t *derend = der_end;
+
+    const uint8_t *sequence_end;
+    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
+
+    {
+        CFDictionaryRef decoded_gestalt = NULL;
+        *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error,
+                                       *der_p, der_end);
+
+        if (*der_p == 0)
+            return NULL;
+
+        account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block);
+        CFReleaseNull(decoded_gestalt);
+    }
+
+    CFArrayRef array = NULL;
+    *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end);
+
+    uint64_t tmp_departure_code = kSOSNeverAppliedToCircle;
+    *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end);
+    *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end);
+    *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end);
+    *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end);
+    *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end);
+    if (*der_p != sequence_end)
+        *der_p = NULL;
+    account->departure_code = (enum DepartureReason) tmp_departure_code;
+
+    __block bool success = true;
+
+    require_quiet(array && *der_p, fail);
+
+    CFArrayForEach(array, ^(const void *value) {
+        if (success) {
+            if (isString(value)) {
+                CFDictionaryAddValue(account->circles, value, kCFNull);
+            } else {
+                CFDataRef circleData = NULL;
+                CFDataRef fullPeerInfoData = NULL;
+
+                if (isData(value)) {
+                    circleData = (CFDataRef) value;
+                } else if (isArray(value)) {
+                    CFArrayRef pair = (CFArrayRef) value;
+
+                    CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0);
+                    CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1);
+
+                    if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) {
+                        circleData = (CFDataRef) circleObject;
+                        fullPeerInfoData = (CFDataRef) fullPeerInfoObject;
+                    }
+                }
+
+                if (circleData) {
+                    SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error);
+                    require_action_quiet(circle, fail, success = false);
+
+                    CFStringRef circleName = SOSCircleGetName(circle);
+                    CFDictionaryAddValue(account->circles, circleName, circle);
+
+                    if (fullPeerInfoData) {
+                        SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error);
+                        require_action_quiet(full_peer, fail, success = false);
+
+                        CFDictionaryAddValue(account->circle_identities, circleName, full_peer);
+                    }
+                fail:
+                    CFReleaseNull(circle);
+                }
+            }
+        }
+    });
+    CFReleaseNull(array);
+
+    require_quiet(success, fail);
+    require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail,
+                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error));
+
+    return account;
+
+fail:
+    // Create a default error if we don't have one:
+    account->factory = NULL; // give the factory back.
+    CFReleaseNull(account);
+    // Try the der inflater from the previous release.
+    account = SOSAccountCreateFromDER_V1(allocator, factory, interest_block, update_block, error, &dersave, derend);
+    if(account) account->departure_code = kSOSNeverAppliedToCircle;
+    return account;
+}
+
+#endif /* OLD_CODERS_SUPPORTED */
+
+#define CURRENT_ACCOUNT_PERSISTENT_VERSION 6
+
+SOSAccountRef SOSAccountCreateFromDER(CFAllocatorRef allocator,
+                                         SOSDataSourceFactoryRef factory,
+                                         SOSAccountKeyInterestBlock interest_block,
+                                         SOSAccountDataUpdateBlock update_block,
+                                         CFErrorRef* error,
+                                         const uint8_t** der_p, const uint8_t *der_end)
+{
+    SOSAccountRef account = NULL;
+#if UPGRADE_FROM_PREVIOUS_VERSION
+    const uint8_t *dersave = *der_p;
+    const uint8_t *derend = der_end;
+#endif
+    uint64_t version = 0;
+    
+    const uint8_t *sequence_end;
+    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
+    *der_p = ccder_decode_uint64(&version, *der_p, sequence_end);
+    if(!(*der_p) || version < CURRENT_ACCOUNT_PERSISTENT_VERSION) {
+#if UPGRADE_FROM_PREVIOUS_VERSION
+        return SOSAccountCreateFromDER_V3(allocator, factory, interest_block, update_block, error, &dersave, derend);
+#else
+        return NULL;
+#endif
+    }
+    
+    {
+        CFDictionaryRef decoded_gestalt = NULL;
+        *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error,
+                                       *der_p, der_end);
+        
+        if (*der_p == 0)
+            return NULL;
+        
+        account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block);
+        CFReleaseNull(decoded_gestalt);
+    }
+    
+    CFArrayRef array = NULL;
+    *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end);
+    
+    uint64_t tmp_departure_code = kSOSNeverAppliedToCircle;
+    *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end);
+    *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end);
+    *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end);
+    *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->previous_public, error, *der_p, sequence_end);
+    *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end);
+    *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end);
+    if (*der_p != sequence_end)
+        *der_p = NULL;
+    account->departure_code = (enum DepartureReason) tmp_departure_code;
+    
+    __block bool success = true;
+    
+    require_quiet(array && *der_p, fail);
+    
+    CFArrayForEach(array, ^(const void *value) {
+        if (success) {
+            if (isString(value)) {
+                CFDictionaryAddValue(account->circles, value, kCFNull);
+            } else {
+                CFDataRef circleData = NULL;
+                CFDataRef fullPeerInfoData = NULL;
+                
+                if (isData(value)) {
+                    circleData = (CFDataRef) value;
+                } else if (isArray(value)) {
+                    CFArrayRef pair = (CFArrayRef) value;
+                    
+                    CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0);
+                    CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1);
+                    
+                    if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) {
+                        circleData = (CFDataRef) circleObject;
+                        fullPeerInfoData = (CFDataRef) fullPeerInfoObject;
+                    }
+                }
+                
+                if (circleData) {
+                    SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error);
+                    require_action_quiet(circle, fail, success = false);
+                    
+                    CFStringRef circleName = SOSCircleGetName(circle);
+                    CFDictionaryAddValue(account->circles, circleName, circle);
+                    
+                    if (fullPeerInfoData) {
+                        SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error);
+                        require_action_quiet(full_peer, fail, success = false);
+                        
+                        CFDictionaryAddValue(account->circle_identities, circleName, full_peer);
+                    }
+                fail:
+                    CFReleaseNull(circle);
+                }
+            }
+        }
+    });
+    CFReleaseNull(array);
+    
+    require_quiet(success, fail);
+    require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail,
+                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error));
+    
+    return account;
+    
+fail:
+    account->factory = NULL; // give the factory back.
+    CFReleaseNull(account);
+    return NULL;
+}
+
+
+SOSAccountRef SOSAccountCreateFromDER_V3(CFAllocatorRef allocator,
+                                         SOSDataSourceFactoryRef factory,
+                                         SOSAccountKeyInterestBlock interest_block,
+                                         SOSAccountDataUpdateBlock update_block,
+                                         CFErrorRef* error,
+                                         const uint8_t** der_p, const uint8_t *der_end)
+{
+    SOSAccountRef account = NULL;
+    uint64_t version = 0;
+    
+    const uint8_t *sequence_end;
+    *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
+    *der_p = ccder_decode_uint64(&version, *der_p, sequence_end);
+    if(!(*der_p) || version != 3) {
+        // In this case we want to silently fail so that an account gets newly created.
+        return NULL;
+    }
+
+    {
+        CFDictionaryRef decoded_gestalt = NULL;
+        *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error,
+                                       *der_p, der_end);
+        
+        if (*der_p == 0)
+            return NULL;
+        
+        account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block);
+        CFReleaseNull(decoded_gestalt);
+    }
+    
+    CFArrayRef array = NULL;
+    *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end);
+    
+    uint64_t tmp_departure_code = kSOSNeverAppliedToCircle;
+    *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end);
+    *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end);
+    *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end);
+    *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end);
+    *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end);
+    if (*der_p != sequence_end)
+        *der_p = NULL;
+    account->departure_code = (enum DepartureReason) tmp_departure_code;
+    
+    __block bool success = true;
+    
+    require_quiet(array && *der_p, fail);
+    
+    CFArrayForEach(array, ^(const void *value) {
+        if (success) {
+            if (isString(value)) {
+                CFDictionaryAddValue(account->circles, value, kCFNull);
+            } else {
+                CFDataRef circleData = NULL;
+                CFDataRef fullPeerInfoData = NULL;
+                
+                if (isData(value)) {
+                    circleData = (CFDataRef) value;
+                } else if (isArray(value)) {
+                    CFArrayRef pair = (CFArrayRef) value;
+                    
+                    CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0);
+                    CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1);
+                    
+                    if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) {
+                        circleData = (CFDataRef) circleObject;
+                        fullPeerInfoData = (CFDataRef) fullPeerInfoObject;
+                    }
+                }
+                
+                if (circleData) {
+                    SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error);
+                    require_action_quiet(circle, fail, success = false);
+                    
+                    CFStringRef circleName = SOSCircleGetName(circle);
+                    CFDictionaryAddValue(account->circles, circleName, circle);
+                    
+                    if (fullPeerInfoData) {
+                        SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error);
+                        require_action_quiet(full_peer, fail, success = false);
+                        
+                        CFDictionaryAddValue(account->circle_identities, circleName, full_peer);
+                    }
+                fail:
+                    CFReleaseNull(circle);
+                }
+            }
+        }
+    });
+    CFReleaseNull(array);
+    
+    require_quiet(success, fail);
+    require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail,
+                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error));
+    
+    return account;
+    
+fail:
+    // Create a default error if we don't have one:
+    account->factory = NULL; // give the factory back.
+    CFReleaseNull(account);
+    // Don't try the der inflater from the previous release.
+    // account = SOSAccountCreateFromDER_V2(allocator, factory, interest_block, update_block, error, &dersave, derend);
+    if(account) account->departure_code = kSOSNeverAppliedToCircle;
+    return account;
+}
+
+SOSAccountRef SOSAccountCreateFromData(CFAllocatorRef allocator, CFDataRef circleData,
+                                       SOSDataSourceFactoryRef factory,
+                                       SOSAccountKeyInterestBlock interest_block,
+                                       SOSAccountDataUpdateBlock update_block,
+                                       CFErrorRef* error)
+{
+    size_t size = CFDataGetLength(circleData);
+    const uint8_t *der = CFDataGetBytePtr(circleData);
+    SOSAccountRef account = SOSAccountCreateFromDER(allocator, factory, interest_block, update_block,
+                                                    error,
+                                                    &der, der + size);
+    return account;
+}
+
+static CFMutableArrayRef SOSAccountCopyCircleArrayToEncode(SOSAccountRef account)
+{
+    CFMutableArrayRef arrayToEncode = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+    CFDictionaryForEach(account->circles, ^(const void *key, const void *value) {
+        if (isNull(value)) {
+            CFArrayAppendValue(arrayToEncode, key); // Encode the name of the circle that's out of date.
+        } else {
+            SOSCircleRef circle = (SOSCircleRef) value;
+            CFDataRef encodedCircle = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, NULL);
+            CFTypeRef arrayEntry = encodedCircle;
+            CFRetainSafe(arrayEntry);
+            
+            SOSFullPeerInfoRef full_peer = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, key);
+        
+            if (full_peer) {
+                CFDataRef encodedPeer = SOSFullPeerInfoCopyEncodedData(full_peer, kCFAllocatorDefault, NULL);
+                CFTypeRef originalArrayEntry = arrayEntry;
+                arrayEntry = CFArrayCreateForCFTypes(kCFAllocatorDefault, encodedCircle, encodedPeer, NULL);
+                
+                CFReleaseSafe(originalArrayEntry);
+                CFReleaseNull(encodedPeer);
+            }
+            
+            CFArrayAppendValue(arrayToEncode, arrayEntry);
+
+            CFReleaseSafe(arrayEntry);
+            CFReleaseNull(encodedCircle);
+        }
+               
+    });
+
+    return arrayToEncode;
+}
+
+size_t SOSAccountGetDEREncodedSize(SOSAccountRef account, CFErrorRef *error)
+{
+    size_t sequence_size = 0;
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+    uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION;
+    
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(version)),                                    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)),                  fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)),                          fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)),                    fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)),          fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)),            fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->previous_public, error)),        fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)),    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)),            fail);
+    
+    CFReleaseNull(arrayToEncode);
+    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size);
+    
+fail:
+    CFReleaseNull(arrayToEncode);
+    SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
+    return 0;
+}
+
+uint8_t* SOSAccountEncodeToDER(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+    uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION;
+    der_end =  ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+        ccder_encode_uint64(version, der,
+        der_encode_dictionary(account->gestalt, error, der,
+        der_encode_array(arrayToEncode, error, der,
+        ccder_encode_uint64(account->departure_code, der,
+        ccder_encode_bool(account->user_public_trusted, der,
+        der_encode_public_bytes(account->user_public, error, der,
+        der_encode_public_bytes(account->previous_public, error, der,
+        der_encode_data_or_null(account->user_key_parameters, error, der,
+        der_encode_dictionary(account->retired_peers, error, der, der_end))))))))));
+
+    CFReleaseNull(arrayToEncode);
+    
+    return der_end;
+}
+
+
+
+size_t SOSAccountGetDEREncodedSize_V3(SOSAccountRef account, CFErrorRef *error)
+{
+    size_t sequence_size = 0;
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+    uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION;
+
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(version)),                                    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)),                  fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)),                          fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)),                    fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)),          fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)),            fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)),    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)),            fail);
+    
+    CFReleaseNull(arrayToEncode);
+    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size);
+    
+fail:
+    CFReleaseNull(arrayToEncode);
+    SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
+    return 0;
+}
+
+uint8_t* SOSAccountEncodeToDER_V3(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+    uint64_t version = 3;
+    der_end =  ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+        ccder_encode_uint64(version, der,
+        der_encode_dictionary(account->gestalt, error, der,
+        der_encode_array(arrayToEncode, error, der,
+        ccder_encode_uint64(account->departure_code, der,
+        ccder_encode_bool(account->user_public_trusted, der,
+        der_encode_public_bytes(account->user_public, error, der,
+        der_encode_data_or_null(account->user_key_parameters, error, der,
+        der_encode_dictionary(account->retired_peers, error, der, der_end)))))))));
+    
+    CFReleaseNull(arrayToEncode);
+    
+    return der_end;
+}
+
+#if OLD_CODERS_SUPPORTED
+
+/* Original V2 encoders */
+
+size_t SOSAccountGetDEREncodedSize_V2(SOSAccountRef account, CFErrorRef *error)
+{
+    size_t sequence_size = 0;
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)),                  fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)),                          fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)), fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)),          fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)),            fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)),    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)),            fail);
+
+    CFReleaseNull(arrayToEncode);
+    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size);
+
+fail:
+    CFReleaseNull(arrayToEncode);
+    SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
+    return 0;
+}
+
+uint8_t* SOSAccountEncodeToDER_V2(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+
+    der_end =  ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+               der_encode_dictionary(account->gestalt, error, der,
+               der_encode_array(arrayToEncode, error, der,
+               ccder_encode_uint64(account->departure_code, der,
+               ccder_encode_bool(account->user_public_trusted, der,
+               der_encode_public_bytes(account->user_public, error, der,
+               der_encode_data_or_null(account->user_key_parameters, error, der,
+               der_encode_dictionary(account->retired_peers, error, der, der_end))))))));
+
+    CFReleaseNull(arrayToEncode);
+
+    return der_end;
+}
+
+
+/* Original V1 encoders */
+
+
+size_t SOSAccountGetDEREncodedSize_V1(SOSAccountRef account, CFErrorRef *error)
+{
+    size_t sequence_size = 0;
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)),                  fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)),                          fail);
+    require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)),          fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)),            fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)),    fail);
+    require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)),            fail);
+
+    CFReleaseNull(arrayToEncode);
+    return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size);
+
+fail:
+    CFReleaseNull(arrayToEncode);
+    SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
+    return 0;
+}
+
+uint8_t* SOSAccountEncodeToDER_V1(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end)
+{
+    CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account);
+
+    der_end =  ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+        der_encode_dictionary(account->gestalt, error, der,
+        der_encode_array(arrayToEncode, error, der,
+        ccder_encode_bool(account->user_public_trusted, der,
+        der_encode_public_bytes(account->user_public, error, der,
+        der_encode_data_or_null(account->user_key_parameters, error, der,
+               der_encode_dictionary(account->retired_peers, error, der, der_end)))))));
+
+    CFReleaseNull(arrayToEncode);
+
+    return der_end;
+}
+#endif /* OLD_CODERS_SUPPORTED */
+
+/************************/
+
+CFDataRef SOSAccountCopyEncodedData(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef *error)
+{
+    size_t size = SOSAccountGetDEREncodedSize(account, error);
+    if (size == 0)
+        return NULL;
+    uint8_t buffer[size];
+    uint8_t* start = SOSAccountEncodeToDER(account, error, buffer, buffer + sizeof(buffer));
+    CFDataRef result = CFDataCreate(kCFAllocatorDefault, start, size);
+    return result;
+}
+
+dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
+    return account->queue;
+}
+
+//
+// MARK: User Credential management
+//
+
+void SOSAccountPurgePrivateCredential(SOSAccountRef account)
+{
+    CFReleaseNull(account->_user_private);
+    if (account->user_private_timer) {
+        dispatch_source_cancel(account->user_private_timer);
+        dispatch_release(account->user_private_timer);
+        account->user_private_timer = NULL;
+        xpc_transaction_end();
+    }
+    if (account->lock_notification_token) {
+        notify_cancel(account->lock_notification_token);
+        account->lock_notification_token = 0;
+    }
+}
+
+static void SOSAccountSetPrivateCredential(SOSAccountRef account, SecKeyRef private) {
+    if (!private)
+        return SOSAccountPurgePrivateCredential(account);
+
+    CFRetain(private);
+    CFReleaseSafe(account->_user_private);
+    account->_user_private = private;
+
+    bool resume_timer = false;
+    if (!account->user_private_timer) {
+        xpc_transaction_begin();
+        resume_timer = true;
+        account->user_private_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, account->queue);
+        dispatch_source_set_event_handler(account->user_private_timer, ^{
+            SOSAccountPurgePrivateCredential(account);
+        });
+
+        notify_register_dispatch(kUserKeybagStateChangeNotification, &account->lock_notification_token, account->queue, ^(int token) {
+            bool locked = false;
+            CFErrorRef lockCheckError = NULL;
+
+            if (!SecAKSGetIsLocked(&locked, &lockCheckError)) {
+                secerror("Checking for locked after change failed: %@", lockCheckError);
+            }
+
+            if (locked) {
+                SOSAccountPurgePrivateCredential(account);
+            }
+        });
+    }
+
+    // (Re)set the timer's fire time to now + 120 seconds with a 5 second fuzz factor.
+    dispatch_time_t purgeTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * 60 * NSEC_PER_SEC));
+    dispatch_source_set_timer(account->user_private_timer, purgeTime, DISPATCH_TIME_FOREVER, (int64_t)(5 * NSEC_PER_SEC));
+    if (resume_timer)
+        dispatch_resume(account->user_private_timer);
+}
+
+SecKeyRef SOSAccountGetPrivateCredential(SOSAccountRef account, CFErrorRef* error)
+{
+    if (account->_user_private == NULL) {
+        SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("Private Key not available - failed to prompt user recently"), NULL, error);
+    }
+    return account->_user_private;
+}
+
+static bool SOSAccountHasPublicKey(SOSAccountRef account, CFErrorRef* error)
+{
+    if (account->user_public == NULL || account->user_public_trusted == false) {
+        SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key not available - failed to register before call"), NULL, error);
+        return false;
+    }
+
+    return true;
+}
+
+static bool SOSAccountIsMyPeerActiveInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error);
+
+static void SOSAccountGenerationSignatureUpdate(SOSAccountRef account, SecKeyRef privKey) {
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
+        if(SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(fpi), NULL) &&
+           !SOSCircleVerify(circle, account->user_public, NULL)) {
+            SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
+                SOSFullPeerInfoRef cloud_fpi = SOSCircleGetiCloudFullPeerInfoRef(circle);
+                require_quiet(cloud_fpi != NULL, gen_sign);
+                require_quiet(SOSFullPeerInfoUpgradeSignatures(cloud_fpi, privKey, NULL), gen_sign);
+                if(!SOSCircleUpdatePeerInfo(circle, SOSFullPeerInfoGetPeerInfo(cloud_fpi))) {
+                }
+            gen_sign: // finally generation sign this.
+                SOSCircleGenerationSign(circle, privKey, fpi, NULL);
+                account->departure_code = kSOSNeverLeftCircle;
+            });
+        }
+    });
+}
+
+/* this one is meant to be local - not published over KVS. */
+static void SOSAccountPeerSignatureUpdate(SOSAccountRef account, SecKeyRef privKey) {
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
+        SOSFullPeerInfoUpgradeSignatures(fpi, privKey, NULL);
+    });
+}
+
+static void SOSAccountSetPreviousPublic(SOSAccountRef account) {
+    CFReleaseNull(account->previous_public);
+    account->previous_public = account->user_public;
+    CFRetain(account->previous_public);
+}
+
+static void SOSAccountSetTrustedUserPublicKey(SOSAccountRef account, bool public_was_trusted, SecKeyRef privKey)
+{
+    if (!privKey) return;
+    SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey);
+
+    if (account->user_public && account->user_public_trusted && CFEqual(publicKey, account->user_public)) return;
+
+    if(public_was_trusted && account->user_public) {
+        CFReleaseNull(account->previous_public);
+        account->previous_public = account->user_public;
+        CFRetain(account->previous_public);
+    }
+    
+    CFReleaseNull(account->user_public);
+    account->user_public = publicKey;
+    account->user_public_trusted = true;
+    
+    if(!account->previous_public) {
+        account->previous_public = account->user_public;
+        CFRetain(account->previous_public);
+    }
+
+       secnotice("trust", "trusting new public key: %@", account->user_public);
+}
+
+static void SOSAccountProcessDeferredUpdates(SOSAccountRef account) {
+    CFErrorRef error = NULL;
+    if (account->deferred_updates && !SOSAccountHandleUpdates(account, account->deferred_updates, &error))
+        secerror("Failed to handle updates when setting public key (%@)", error);
+
+    CFReleaseNull(account->deferred_updates);
+}
+
+
+bool SOSAccountTryUserCredentials(SOSAccountRef account, CFStringRef user_account __unused, CFDataRef user_password, CFErrorRef *error)
+{
+    bool success = false;
+
+    if (!SOSAccountHasPublicKey(account, error))
+        return false;
+    
+    if (account->user_key_parameters) {
+        SecKeyRef new_key = SOSUserKeygen(user_password, account->user_key_parameters, error);
+        if (new_key) {
+            SecKeyRef new_public_key = SecKeyCreatePublicFromPrivate(new_key);
+
+            if (CFEqualSafe(new_public_key, account->user_public)) {
+                SOSAccountSetPrivateCredential(account, new_key);
+                success = true;
+            } else {
+                SOSCreateError(kSOSErrorWrongPassword, CFSTR("Password passed in incorrect: ▇█████▇▇██"), NULL, error);
+            }
+            CFReleaseSafe(new_public_key);
+            CFReleaseSafe(new_key);
+        }
+    } else {
+        SOSCreateError(kSOSErrorProcessingFailure, CFSTR("Have public key but no parameters??"), NULL, error);
+    }
+
+    return success;
+}
+
+static bool SOSAccountPublishCloudParameters(SOSAccountRef account, CFErrorRef* error)
+{
+    bool success = false;
+    CFMutableDataRef cloudParameters = CFDataCreateMutableWithScratch(kCFAllocatorDefault,
+                                                                      der_sizeof_cloud_parameters(account->user_public,
+                                                                                                  account->user_key_parameters,
+                                                                                                  error));
+    if (der_encode_cloud_parameters(account->user_public, account->user_key_parameters, error,
+                                    CFDataGetMutableBytePtr(cloudParameters),
+                                    CFDataGetMutablePastEndPtr(cloudParameters)) != NULL) {
+
+        CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                                               kSOSKVSKeyParametersKey, cloudParameters,
+                                                               NULL);
+
+        CFErrorRef changeError = NULL;
+        if (account->update_block(changes, &changeError)) {
+            success = true;
+        } else {
+            SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL,
+                                     CFSTR("update parameters key failed [%@]"), changes);
+        }
+        CFReleaseSafe(changes);
+        CFReleaseSafe(changeError);
+    } else {
+        SOSCreateError(kSOSErrorEncodeFailure, CFSTR("Encoding parameters failed"), NULL, error);
+    }
+
+    CFReleaseNull(cloudParameters);
+
+    return success;
+}
+
+bool SOSAccountAssertUserCredentials(SOSAccountRef account, CFStringRef user_account __unused, CFDataRef user_password, CFErrorRef *error)
+{
+    bool public_was_trusted = account->user_public_trusted;
+    account->user_public_trusted = false;
+    SecKeyRef user_private = NULL;
+
+    if (account->user_public && account->user_key_parameters) {
+        // We have an untrusted public key – see if our generation makes the same key:
+        // if so we trust it and we have the private key.
+        // if not we still don't trust it.
+        require_quiet(user_private = SOSUserKeygen(user_password, account->user_key_parameters, error), exit);
+        SecKeyRef public_candidate = SecKeyCreatePublicFromPrivate(user_private);
+        if (!CFEqualSafe(account->user_public, public_candidate)) {
+            secnotice("trust", "Public keys don't match:  calculated: %@, expected: %@",
+                      account->user_public, public_candidate);
+            debugDumpUserParameters(CFSTR("params"), account->user_key_parameters);
+            CFReleaseNull(user_private);
+        } else {
+            SOSAccountPeerSignatureUpdate(account, user_private);
+            SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private);
+        }
+        CFReleaseSafe(public_candidate);
+    }
+
+    if (!account->user_public_trusted) {
+        // We may or may not have parameters here.
+        // In any case we tried using them and they didn't match
+        // So forget all that and start again, assume we're the first to push anything useful.
+        
+        CFReleaseNull(account->user_key_parameters);
+        account->user_key_parameters = SOSUserKeyCreateGenerateParameters(error);
+        require_quiet(user_private = SOSUserKeygen(user_password, account->user_key_parameters, error), exit);
+
+        SOSAccountPeerSignatureUpdate(account, user_private);
+        SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private);
+
+        CFErrorRef publishError = NULL;
+        if (!SOSAccountPublishCloudParameters(account, &publishError))
+            secerror("Failed to publish new cloud parameters: %@", publishError);
+        CFReleaseSafe(publishError);
+    }
+
+    SOSAccountProcessDeferredUpdates(account);
+    SOSAccountGenerationSignatureUpdate(account, user_private);
+    SOSAccountSetPrivateCredential(account, user_private);
+exit:
+    CFReleaseSafe(user_private);
+
+    return account->user_public_trusted;
+}
+
+//
+// MARK: Circle management
+//
+
+int SOSAccountCountCircles(SOSAccountRef a) {
+    assert(a);
+    assert(a->circle_identities);
+    assert(a->circles);
+    return (int)CFDictionaryGetCount(a->circles);
+}
+
+static SecKeyRef GeneratePermanentFullECKey_internal(int keySize, CFStringRef name, CFTypeRef accessibility, CFBooleanRef sync,  CFErrorRef* error)
+{    
+    SecKeyRef full_key = NULL;
+    
+    CFNumberRef key_size_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize);
+
+    CFDictionaryRef priv_key_attrs = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                                                  kSecAttrIsPermanent,    kCFBooleanTrue,
+                                                                  NULL);
+    
+    CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+        kSecAttrKeyType,        kSecAttrKeyTypeEC,
+        kSecAttrKeySizeInBits,  key_size_num,
+        kSecPrivateKeyAttrs,    priv_key_attrs,
+        kSecAttrAccessible,     accessibility,
+        kSecAttrAccessGroup,    kSOSInternalAccessGroup,
+        kSecAttrLabel,          name,
+        kSecAttrSynchronizable, sync,
+        kSecUseTombstones,      kCFBooleanTrue,
+        NULL);
+
+    CFReleaseNull(priv_key_attrs);
+
+    CFReleaseNull(key_size_num);
+    OSStatus status = SecKeyGeneratePair(keygen_parameters, NULL, &full_key);
+    CFReleaseNull(keygen_parameters);
+    
+    if (status)
+        secerror("status: %ld", (long)status);
+    if (status != errSecSuccess && error != NULL && *error == NULL) {
+        *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
+    }
+    
+    return full_key;
+}
+
+static SecKeyRef GeneratePermanentFullECKey(int keySize, CFStringRef name, CFErrorRef* error) {
+    return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kCFBooleanFalse, error);
+}
+
+static SecKeyRef GeneratePermanentFullECKeyForCloudIdentity(int keySize, CFStringRef name, CFErrorRef* error) {
+    return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlocked, kCFBooleanTrue, error);
+}
+
+
+SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircleNamed(SOSAccountRef account, CFStringRef name, CFErrorRef *error) {
+    if (CFDictionaryGetValue(account->circles, name) == NULL) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name);
+        return NULL;
+    }
+    SOSFullPeerInfoRef circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name);
+    
+
+    if (circle_full_peer_info == NULL) {
+        CFStringRef keyName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("ID for %@-%@"), SOSPeerGestaltGetName(account->gestalt), name);
+        SecKeyRef full_key = GeneratePermanentFullECKey(256, keyName, error);
+        CFReleaseNull(keyName);
+
+        if (full_key) {
+            circle_full_peer_info = SOSFullPeerInfoCreate(kCFAllocatorDefault, account->gestalt, full_key, error);
+    
+            CFReleaseNull(full_key);
+
+            if (!circle_full_peer_info) {
+                secerror("Can't make FullPeerInfo for %@-%@ (%@) - is AKS ok?", SOSPeerGestaltGetName(account->gestalt), name, error ? (void*)*error : (void*)CFSTR("-"));
+                return circle_full_peer_info;
+            }
+            
+            CFDictionarySetValue(account->circle_identities, name, circle_full_peer_info);
+            CFReleaseNull(circle_full_peer_info);
+            circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name);
+        }
+        else
+            secerror("No full_key: %@:", error ? *error : NULL);
+    }
+    
+    return circle_full_peer_info;
+}
+
+static bool SOSAccountDestroyCirclePeerInfoNamed(SOSAccountRef account, CFStringRef name, CFErrorRef* error) {
+    if (CFDictionaryGetValue(account->circles, name) == NULL) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name);
+        return false;
+    }
+    
+    SOSFullPeerInfoRef circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name);
+    
+    if (circle_full_peer_info) {
+        SOSPeerPurgeAllFor(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(circle_full_peer_info)));
+        
+        SOSFullPeerInfoPurgePersistentKey(circle_full_peer_info, NULL);
+    }
+    
+    CFDictionaryRemoveValue(account->circle_identities, name);
+    
+    return true;
+}
+
+static bool SOSAccountDestroyCirclePeerInfo(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {    
+    return SOSAccountDestroyCirclePeerInfoNamed(account, SOSCircleGetName(circle), error);
+}
+
+SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
+    return SOSAccountGetMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
+}
+
+SOSPeerInfoRef SOSAccountGetMyPeerInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
+    SOSFullPeerInfoRef fpi =  SOSAccountGetMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
+    
+    return fpi ? SOSFullPeerInfoGetPeerInfo(fpi) : NULL;
+}
+
+SOSPeerInfoRef SOSAccountGetMyPeerInCircleNamed(SOSAccountRef account, CFStringRef name, CFErrorRef *error)
+{
+    SOSFullPeerInfoRef fpi =  SOSAccountGetMyFullPeerInCircleNamed(account, name, error);
+    
+    return fpi ? SOSFullPeerInfoGetPeerInfo(fpi) : NULL;
+}
+
+CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
+{
+    CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
+
+    CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
+        SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
+        
+        CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
+    });
+    
+    return result;
+}
+
+bool SOSAccountIsAccountIdentity(SOSAccountRef account, SOSPeerInfoRef peer_info, CFErrorRef *error)
+{
+    __block bool matches = false;
+    CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
+        if (!matches) {
+            matches = CFEqual(peer_info, SOSFullPeerInfoGetPeerInfo((SOSFullPeerInfoRef) value));
+        }
+    });
+    
+    return matches;
+}
+
+bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
+{
+    __block bool result = true;
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        if (!SOSAccountSyncWithAllPeersInCircle(account, circle, error))
+            result = false;
+    });
+    
+    return result;
+}
+
+bool SOSAccountSyncWithAllPeersInCircle(SOSAccountRef account, SOSCircleRef circle,
+                                CFErrorRef *error)
+{
+    SOSPeerInfoRef my_peer = SOSAccountGetMyPeerInCircle(account, circle, error);
+    if (!my_peer)
+        return false;
+    
+    __block bool didSync = false;
+    __block bool result = true;
+    
+    if (SOSCircleHasPeer(circle, my_peer, NULL)) {
+        SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+            if (!CFEqual(SOSPeerInfoGetPeerID(my_peer), SOSPeerInfoGetPeerID(peer)))
+            {
+                bool local_didSync = false;
+                if (!SOSAccountSyncWithPeer(account, circle, peer, &local_didSync, error))
+                    result = false;
+                if (!didSync && local_didSync)
+                {
+                    didSync = true;
+                }
+            }
+        });
+        
+        if (didSync)
+        {
+            SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
+        }
+    }
+    
+    return result;
+}
+
+bool SOSAccountSyncWithPeer(SOSAccountRef account, SOSCircleRef circle,
+                            SOSPeerInfoRef thisPeer, bool* didSendData, CFErrorRef* error)
+{
+    CFStringRef peer_id = SOSPeerInfoGetPeerID(thisPeer);
+    CFStringRef peer_write_key = SOSMessageKeyCreateWithAccountAndPeer(account, circle, peer_id);
+    SOSFullPeerInfoRef myRef = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+    
+    __block bool sentData = false;
+
+
+    SOSPeerSendBlock writeToKVSKey = ^bool (CFDataRef data, CFErrorRef* error) {
+        secnotice("account", "writing data of size %ld:", data?CFDataGetLength(data):0);
+        sentData = (NULL != data);
+        CFDictionaryRef writeToDo = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peer_write_key, data, NULL);
+        bool written = account->update_block(writeToDo, error);
+        if (account->processed_message_block)
+            account->processed_message_block(circle, NULL, data);
+        CFRelease(writeToDo);
+        return written;
+    };
+    
+    if (NULL != didSendData)
+    {
+        *didSendData = sentData;
+    }
+    
+    bool result = SOSCircleSyncWithPeer(myRef, circle, account->factory, writeToKVSKey, peer_id, error);
+    CFReleaseNull(peer_write_key);
+    return result;
+}
+
+static bool SOSAccountIsActivePeerInCircleNamed(SOSAccountRef account, CFStringRef circle_name, CFStringRef peerid, CFErrorRef* error) {
+    SOSCircleRef circle = SOSAccountFindCircle(account, circle_name, error);
+    if(!circle) return false;
+    return SOSCircleHasActivePeerWithID(circle, peerid, error);
+}
+
+static bool SOSAccountIsMyPeerActiveInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
+    SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), NULL);
+    if(!fpi) return false;
+    return SOSCircleHasActivePeer(circle, SOSFullPeerInfoGetPeerInfo(fpi), error);
+}
+
+bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
+                                SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
+{
+    if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
+    
+    CFMutableDictionaryRef keysToWrite = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+    SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef circlePeer) {
+        CFStringRef from_key = SOSMessageKeyCreateWithCircleAndPeerInfos(circle, cleanupPeer, circlePeer);
+        CFStringRef to_key = SOSMessageKeyCreateWithCircleAndPeerInfos(circle, circlePeer, cleanupPeer);
+
+        CFDictionaryAddValue(keysToWrite, from_key, kCFNull);
+        CFDictionaryAddValue(keysToWrite, to_key, kCFNull);
+
+        CFReleaseNull(from_key);
+        CFReleaseNull(to_key);
+    });
+    
+    if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) {
+        CFStringRef resignationKey = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(cleanupPeer));
+        CFDictionarySetValue(keysToWrite, resignationKey, kCFNull);
+        CFDictionaryRemoveValue(account->retired_peers, resignationKey);
+        CFReleaseNull(resignationKey);
+    }
+
+    bool success = account->update_block(keysToWrite, error);
+
+    CFReleaseNull(keysToWrite);
+
+    return success;
+}
+
+bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
+    CFMutableDictionaryRef keysToWrite = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+    
+    CFDictionaryRef copyToIterate = CFDictionaryCreateCopy(kCFAllocatorDefault, account->retired_peers);
+
+    CFDictionaryForEach(copyToIterate, ^(const void* resignationKey, const void* value) {
+        CFStringRef circle_name = NULL;
+        CFStringRef retiree_peerid = NULL;
+        SOSPeerInfoRef pi = NULL;
+        SOSKVSKeyType keytype = SOSKVSKeyGetKeyTypeAndParse(resignationKey, &circle_name, &retiree_peerid, NULL);
+        require_quiet(keytype == kRetirementKey && circle_name && retiree_peerid && isData(value), forget);
+        pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
+        require_quiet(pi && CFEqualSafe(retiree_peerid, SOSPeerInfoGetPeerID(pi)), forget);
+
+        require_quiet(!SOSAccountIsActivePeerInCircleNamed(account, circle_name, retiree_peerid, NULL), keep);
+        require_quiet(SOSPeerInfoRetireRetirementTicket(seconds, pi), keep);
+        
+        // Happy day, it's time and it's a ticket we should eradicate from KVS.
+        CFDictionarySetValue(keysToWrite, resignationKey, kCFNull);
+
+    forget:
+        CFDictionaryRemoveValue(account->retired_peers, resignationKey);
+    keep:
+        CFReleaseSafe(pi);
+        CFReleaseSafe(circle_name);
+        CFReleaseSafe(retiree_peerid);
+    });
+    CFReleaseNull(copyToIterate);
+
+    bool success = true;
+    if(CFDictionaryGetCount(keysToWrite)) {
+        success = account->update_block(keysToWrite, error);
+    }
+    CFReleaseNull(keysToWrite);
+        
+    return success;
+}
+
+bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
+    SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
+        CFStringRef key = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(peer));
+        if(key && !CFDictionaryGetValueIfPresent(account->retired_peers, key, NULL)) {
+            CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
+            if(value) {
+                CFDictionarySetValue(account->retired_peers, key, value);
+                SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
+            }
+            CFReleaseSafe(value);
+        }
+        CFReleaseSafe(key);
+    });
+    return true;
+}
+
+SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
+    CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
+    SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
+    if(!new_circle) return NULL;
+    
+    CFDictionaryForEach(account->retired_peers, ^(const void* resignationKey, const void* value) {
+        CFStringRef circle_name = NULL;
+        CFStringRef retiree_peerid = NULL;
+        
+        SOSKVSKeyType keytype = SOSKVSKeyGetKeyTypeAndParse(resignationKey, &circle_name, &retiree_peerid, NULL);
+        if(keytype == kRetirementKey && CFEqualSafe(circle_name, circle_to_mod) && SOSCircleHasPeerWithID(new_circle, retiree_peerid, NULL)) {
+            if(isData(value)) {
+                SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
+                SOSCircleUpdatePeerInfo(new_circle, pi);
+                CFReleaseSafe(pi);
+            }
+        }
+        CFReleaseSafe(circle_name);
+        CFReleaseSafe(retiree_peerid);
+    });
+    
+    if(SOSCircleCountPeers(new_circle) == 0) {
+        SOSCircleResetToEmpty(new_circle, NULL);
+    }
+    
+    return new_circle;
+}
+
+
+//
+// Circle Finding
+//
+SOSCircleRef SOSAccountFindCompatibleCircle(SOSAccountRef a, CFStringRef name)
+{
+    CFTypeRef entry = CFDictionaryGetValue(a->circles, name);
+    
+    if (CFGetTypeID(entry) == SOSCircleGetTypeID())
+        return (SOSCircleRef) entry;
+    
+    return NULL;
+}
+
+SOSCircleRef SOSAccountFindCircle(SOSAccountRef a, CFStringRef name, CFErrorRef *error)
+{
+    CFTypeRef entry = CFDictionaryGetValue(a->circles, name);
+    
+    require_action_quiet(!isNull(entry), fail,
+                         SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
+
+    require_action_quiet(entry, fail,
+                         SOSCreateError(kSOSErrorNoCircle, CFSTR("No circle found"), NULL, error));
+
+    
+    return (SOSCircleRef) entry;
+
+fail:
+    return NULL;
+}
+
+SOSCircleRef SOSAccountEnsureCircle(SOSAccountRef a, CFStringRef name, CFErrorRef *error)
+{
+    CFErrorRef localError = NULL;
+
+    SOSCircleRef circle = SOSAccountFindCircle(a, name, &localError);
+    
+    require_action_quiet(circle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
+                         if (error) { *error = localError; localError = NULL; });
+    
+
+    if (NULL == circle) {
+        circle = SOSCircleCreate(NULL, name, NULL);
+        if (circle){
+            CFDictionaryAddValue(a->circles, name, circle);
+            CFRelease(circle);
+            circle = SOSAccountFindCircle(a, name, &localError);
+        }
+        SOSUpdateKeyInterest(a, false, NULL);
+    }
+
+fail:
+    CFReleaseNull(localError);
+    return circle;
+}
+
+
+void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock)
+{
+    CFArrayAppendValue(a->change_blocks, changeBlock);
+}
+
+void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock)
+{
+    CFArrayRemoveAllValue(a->change_blocks, changeBlock);
+}
+
+static void DifferenceAndCall(CFArrayRef old_members, CFArrayRef new_members, void (^updatedCircle)(CFArrayRef additions, CFArrayRef removals))
+{
+    CFMutableArrayRef additions = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, new_members);
+    CFMutableArrayRef removals = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, old_members);
+    
+
+    CFArrayForEach(old_members, ^(const void * value) {
+        CFArrayRemoveAllValue(additions, value);
+    });
+    
+    CFArrayForEach(new_members, ^(const void * value) {
+        CFArrayRemoveAllValue(removals, value);
+    });
+
+    updatedCircle(additions, removals);
+    
+    CFReleaseSafe(additions);
+    CFReleaseSafe(removals);
+}
+
+static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle)
+{
+    CFMutableArrayRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault);
+    CFMutableArrayRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault);
+    
+    CFMutableArrayRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault);
+    CFMutableArrayRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault);
+    
+    DifferenceAndCall(old_members, new_members, ^(CFArrayRef added_members, CFArrayRef removed_members) {
+        DifferenceAndCall(old_applicants, new_applicants, ^(CFArrayRef added_applicants, CFArrayRef removed_applicants) {
+            CFArrayForEach(account->change_blocks, ^(const void * notificationBlock) {
+                ((SOSAccountCircleMembershipChangeBlock) notificationBlock)(newCircle, added_members, removed_members, added_applicants, removed_applicants);
+            });
+        });
+    });
+    
+    CFReleaseNull(old_applicants);
+    CFReleaseNull(new_applicants);
+
+    CFReleaseNull(old_members);
+    CFReleaseNull(new_members);
+}
+
+void SOSAccountForEachCircle(SOSAccountRef account, void (^process)(SOSCircleRef circle))
+{
+    CFDictionaryForEach(account->circles, ^(const void* key, const void* value) {
+        assert(value);
+        process((SOSCircleRef)value);
+    });
+}
+
+static void AppendCircleKeyName(CFMutableArrayRef array, CFStringRef name) {
+    CFStringRef circle_key = SOSCircleKeyCreateWithName(name, NULL);
+    CFArrayAppendValue(array, circle_key);
+    CFReleaseNull(circle_key);
+}
+
+static inline void AppendCircleInterests(CFMutableArrayRef circle_keys, CFMutableArrayRef retiree_keys, CFMutableArrayRef message_keys, SOSCircleRef circle, SOSFullPeerInfoRef me) {
+    CFStringRef my_peer_id = NULL;
+    
+    if (me) {
+        SOSPeerInfoRef my_peer = me ? SOSFullPeerInfoGetPeerInfo(me) : NULL;
+        my_peer_id = SOSPeerInfoGetPeerID(my_peer);
+    }
+    
+    if (circle_keys) {
+        CFStringRef circleName = SOSCircleGetName(circle);
+        AppendCircleKeyName(circle_keys, circleName);
+    }
+
+    SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+        if (!CFEqualSafe(my_peer_id, SOSPeerInfoGetPeerID(peer))) {
+            CFStringRef peer_name = SOSPeerInfoGetPeerID(peer);
+            if (retiree_keys) {
+                CFStringRef retirementKey = SOSRetirementKeyCreateWithCircleAndPeer(circle, peer_name);
+                CFArrayAppendValue(retiree_keys, retirementKey);
+                CFReleaseNull(retirementKey);
+            }
+            
+            if (my_peer_id && message_keys) {
+                CFStringRef messageKey = SOSMessageKeyCreateWithCircleAndPeerNames(circle, peer_name, my_peer_id);
+                CFArrayAppendValue(message_keys, messageKey);
+                CFRelease(messageKey);
+            }
+        }
+    });
+}
+
+static void SOSAccountCopyKeyInterests(SOSAccountRef account,
+                                       CFMutableArrayRef alwaysKeys,
+                                       CFMutableArrayRef afterFirstUnlockKeys,
+                                       CFMutableArrayRef whenUnlockedKeys)
+{
+    CFArrayAppendValue(afterFirstUnlockKeys, kSOSKVSKeyParametersKey);
+    
+    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+        AppendCircleKeyName(afterFirstUnlockKeys, name);
+    }, ^(SOSCircleRef circle) {
+        AppendCircleInterests(afterFirstUnlockKeys, afterFirstUnlockKeys, whenUnlockedKeys, circle, NULL);
+    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+        bool inCircle = SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(full_peer), NULL);
+
+        AppendCircleInterests(afterFirstUnlockKeys, afterFirstUnlockKeys, inCircle ? whenUnlockedKeys : NULL, circle, full_peer);
+    });
+}
+
+static bool SOSUpdateKeyInterest(SOSAccountRef account, bool getNewKeysOnly, CFErrorRef *error)
+{
+    if (account->update_interest_block) {
+
+        CFMutableArrayRef alwaysKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+        CFMutableArrayRef afterFirstUnlockKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+        CFMutableArrayRef whenUnlockedKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+        SOSAccountCopyKeyInterests(account, alwaysKeys, afterFirstUnlockKeys, whenUnlockedKeys);
+
+        account->update_interest_block(getNewKeysOnly, alwaysKeys, afterFirstUnlockKeys, whenUnlockedKeys);
+    
+        CFReleaseNull(alwaysKeys);
+        CFReleaseNull(afterFirstUnlockKeys);
+        CFReleaseNull(whenUnlockedKeys);
+    }
+    
+    return true;
+}
+
+static bool SOSAccountSendPendingChanges(SOSAccountRef account, CFErrorRef *error) {
+    CFErrorRef changeError = NULL;
+    
+    if (CFDictionaryGetCount(account->pending_changes) == 0)
+        return true;
+    
+    bool success = account->update_block(account->pending_changes, &changeError);
+    if (success) {
+        CFDictionaryRemoveAllValues(account->pending_changes);
+    } else {
+        SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL,
+                                 CFSTR("Send changes block failed [%@]"), account->pending_changes);
+    }
+    
+    return success;
+}
+
+static bool SOSAccountAddCircleToPending(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error)
+{
+    bool success = false;
+    CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
+    
+    if (circle_data) {
+        CFStringRef circle_key = SOSCircleKeyCreateWithCircle(circle, NULL);
+
+        CFDictionarySetValue(account->pending_changes, circle_key, circle_data);
+        success = true;
+        
+        CFReleaseNull(circle_data);
+        CFReleaseNull(circle_key);
+    }
+    
+    return success;
+}
+
+
+static void SOSAccountRecordRetiredPeerInCircleNamed(SOSAccountRef account, CFStringRef circleName, SOSPeerInfoRef retiree)
+{
+    // Replace Peer with RetiredPeer, if were a peer.
+    SOSAccountModifyCircle(account, circleName, NULL, ^(SOSCircleRef circle) {
+        if (SOSCircleUpdatePeerInfo(circle, retiree)) {
+            CFErrorRef cleanupError = NULL;
+            SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retiree, &cleanupError);
+            secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
+            CFReleaseSafe(cleanupError);
+        }
+    });
+}
+
+static bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
+    SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
+    if(!fpi) return false;
+       CFErrorRef localError = NULL;
+    SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
+       CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(pi));
+       SOSPeerInfoRef retire_peer = NULL;
+       CFDataRef retire_value = NULL;
+    bool retval = false;
+    bool writeCircle = false;
+    
+    // Create a Retirement Ticket and store it in the retired_peers of the account.
+       retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
+    require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError));
+       retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError);
+    require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError));
+
+    // See if we need to repost the circle we could either be an applicant or a peer already in the circle
+    if(SOSCircleHasApplicant(circle, pi, NULL)) {
+           // Remove our application if we have one.
+           SOSCircleWithdrawRequest(circle, pi, NULL);
+        writeCircle = true;
+    } else if (SOSCircleHasPeer(circle, pi, NULL)) {
+        if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
+            CFErrorRef cleanupError = NULL;
+            SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError);
+            secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
+            CFReleaseSafe(cleanupError);
+        }
+        writeCircle = true;
+    }
+    
+    // Store the retirement record locally.
+    CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
+
+    // Write pending change to KVS
+    CFDictionarySetValue(account->pending_changes, retire_key, retire_value);
+    
+    // Kill peer key but don't return error if we can't.
+    if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError))
+        secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError);
+
+    if (writeCircle) {
+        SOSAccountAddCircleToPending(account, circle, NULL);
+    }
+    retval = true;
+
+errout:
+    CFReleaseNull(localError);
+    CFReleaseNull(retire_peer);
+    CFReleaseNull(retire_key);
+    CFReleaseNull(retire_value);
+    return retval;
+}
+
+/*
+    NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
+    local value that has been overwritten by a distant value. If there is no
+    conflict between the local and the distant values when doing the initial
+    sync (e.g. if the cloud has no data stored or the client has not stored
+    any data yet), you'll never see that notification.
+
+    NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
+    with server but initial round trip with server does not imply
+    NSUbiquitousKeyValueStoreInitialSyncChange.
+ */
+
+//
+// MARK: Handle Circle Updates
+//
+
+
+static bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, bool initialSync, CFErrorRef *error)
+{
+    bool success = true;
+
+    secnotice("signing", "start: %@", prospective_circle);
+    if (!account->user_public || !account->user_public_trusted) {
+        SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
+        return false;
+    }
+    
+    if (!prospective_circle) {
+        secerror("##### Can't update to a NULL circle ######");
+        return false; // Can't update one we don't have.
+    }
+    
+    CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
+    SOSCircleRef oldCircle = SOSAccountFindCompatibleCircle(account, newCircleName);
+    SOSFullPeerInfoRef me_full = SOSAccountGetMyFullPeerInCircle(account, oldCircle, NULL);
+    SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
+    
+    if (initialSync)
+        secerror("##### Processing initial sync. Old (local) circle: %@, New (cloud) circle: %@", oldCircle, prospective_circle);
+
+    if (!oldCircle)
+        return false; // Can't update one we don't have.
+    
+    SOSAccountScanForRetired(account, prospective_circle, error);
+    SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
+    if(!newCircle) return false;
+    
+    SOSCircleUpdatePeerInfo(newCircle, me);
+    
+    typedef enum {
+        accept,
+        countersign,
+        leave,
+        revert,
+        ignore
+    } circle_action_t;
+    
+    circle_action_t circle_action = ignore;
+    enum DepartureReason leave_reason = kSOSNeverLeftCircle;
+    
+    SecKeyRef old_circle_key = NULL;
+    if(SOSCircleVerify(oldCircle, account->user_public, NULL)) old_circle_key = account->user_public;
+    else if(account->previous_public && SOSCircleVerify(oldCircle, account->previous_public, NULL)) old_circle_key = account->previous_public;
+    bool userTrustedOldCircle = (old_circle_key != NULL);
+    
+    SOSConcordanceStatus concstat =
+        SOSCircleConcordanceTrust(oldCircle, newCircle,
+                                  old_circle_key, account->user_public,
+                                  me, error);
+    
+    CFStringRef concStr = NULL;
+    switch(concstat) {
+        case kSOSConcordanceTrusted:
+            circle_action = countersign;
+            concStr = CFSTR("Trusted");
+            break;
+        case kSOSConcordanceGenOld:
+            circle_action = userTrustedOldCircle ? revert : ignore;
+            concStr = CFSTR("Generation Old");
+            break;
+        case kSOSConcordanceBadUserSig:
+        case kSOSConcordanceBadPeerSig:
+            circle_action = userTrustedOldCircle ? revert : accept;
+            concStr = CFSTR("Bad Signature");
+            break;
+        case kSOSConcordanceNoUserSig:
+            circle_action = userTrustedOldCircle ? revert : accept;
+            concStr = CFSTR("No User Signature");
+            break;
+        case kSOSConcordanceNoPeerSig:
+            circle_action = accept; // We might like this one eventually but don't countersign.
+            concStr = CFSTR("No trusted peer signature");
+            secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle);
+            break;
+        case kSOSConcordanceNoPeer:
+            circle_action = leave;
+            leave_reason = kSOSLeftUntrustedCircle;
+            concStr = CFSTR("No trusted peer left");
+            break;
+        case kSOSConcordanceNoUserKey:
+            secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
+            abort();
+            break;
+        default:
+            secerror("##### Bad Error Return from ConcordanceTrust");
+            abort();
+            break;
+    }
+
+    secnotice("signing", "Decided on action %d based on concordance state %d and %s circle.", circle_action, concstat, userTrustedOldCircle ? "trusted" : "untrusted");
+
+    SOSCircleRef circleToPush = NULL;
+
+    if (circle_action == leave) {
+        circle_action = ignore;
+
+        if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
+            if (sosAccountLeaveCircle(account, newCircle, error)) {
+                account->departure_code = leave_reason;
+                circleToPush = newCircle;
+                circle_action = accept;
+                me = NULL;
+                me_full = NULL;
+            }
+        }
+        else {
+            // We are not in this circle, but we need to update account with it, since we got it from cloud
+            secnotice("updatecircle", "We are not in this circle, but we need to update account with it");
+            circle_action = accept;
+        }
+    }
+
+    if (circle_action == countersign) {
+        if (me && SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
+            CFErrorRef signing_error = NULL;
+
+            if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
+                circleToPush = newCircle;
+                secnotice("signing", "Concurred with: %@", newCircle);
+            } else {
+                secerror("Failed to concurrence sign, error: %@  Old: %@ New: %@", signing_error, oldCircle, newCircle);
+            }
+            CFReleaseSafe(signing_error);
+        }
+        circle_action = accept;
+    }
+
+    if (circle_action == accept) {
+        if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
+            //  Don't destroy evidence of other code determining reason for leaving.
+            if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked;
+        }
+
+        if (me
+            && SOSCircleHasActivePeer(oldCircle, me, NULL)
+            && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
+            && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
+            secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
+            SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
+            me = NULL;
+            me_full = NULL;
+        }
+        
+        if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
+            SOSPeerInfoRef  reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
+            if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account->user_public, NULL)) {
+                secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
+                SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
+                me = NULL;
+                me_full = NULL;
+            } else {
+                SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL);
+                writeUpdate = true;
+            }
+        }
+        
+        CFRetain(oldCircle); // About to replace the oldCircle
+        CFDictionarySetValue(account->circles, newCircleName, newCircle);
+        SOSAccountSetPreviousPublic(account);
+        
+        secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle);
+        
+        if (me_full && account->user_public_trusted
+            && SOSCircleHasApplicant(oldCircle, me, NULL)
+            && SOSCircleCountPeers(newCircle) > 0
+            && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
+            // We weren't rejected (above would have set me to NULL.
+            // We were applying and we weren't accepted.
+            // Our application is declared lost, let us reapply.
+            
+            if (SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL))
+                writeUpdate = true;
+        }
+        
+        if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
+            SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL);
+        }
+
+        SOSAccountNotifyOfChange(account, oldCircle, newCircle);
+
+        CFReleaseNull(oldCircle);
+
+        if (writeUpdate)
+            circleToPush = newCircle;
+
+        success = SOSUpdateKeyInterest(account, true, error);
+    }
+
+    if (circle_action == revert) {
+        secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newCircle, oldCircle);
+        
+        circleToPush = oldCircle;
+    }
+
+
+    if (circleToPush != NULL) {
+        success = (success
+                   && SOSAccountAddCircleToPending(account, circleToPush, error)
+                   && SOSAccountSendPendingChanges(account, error));
+    }
+
+    CFReleaseSafe(newCircle);
+
+    return success;
+}
+
+static bool SOSAccountUpdateCircleFromRemote(SOSAccountRef account, SOSCircleRef newCircle, bool initialSync, CFErrorRef *error)
+{
+    return SOSAccountHandleUpdateCircle(account, newCircle, false, initialSync, error);
+}
+
+bool SOSAccountUpdateCircle(SOSAccountRef account, SOSCircleRef newCircle, CFErrorRef *error)
+{
+    return SOSAccountHandleUpdateCircle(account, newCircle, true, false, error);
+}
+
+bool SOSAccountModifyCircle(SOSAccountRef account,
+                            CFStringRef circleName,
+                            CFErrorRef* error,
+                            void (^action)(SOSCircleRef circle))
+{
+    bool success = false;
+    
+    SOSCircleRef circle = NULL;
+    SOSCircleRef accountCircle = SOSAccountFindCircle(account, circleName, error);
+    require_quiet(accountCircle, fail);
+
+    circle = SOSCircleCopyCircle(kCFAllocatorDefault, accountCircle, error);
+    require_quiet(circle, fail);
+
+    action(circle);
+    success = SOSAccountUpdateCircle(account, circle, error);
+    
+fail:
+    CFReleaseSafe(circle);
+    return success;
+}
+
+static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) {
+    if (value && !isData(value) && !isNull(value)) {
+        CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(value));
+        SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL,
+                                 CFSTR("Expected data or NULL got %@"), description);
+        CFReleaseSafe(description);
+        return NULL;
+    }
+
+    SOSCircleRef circle = NULL;
+    if (!value || isNull(value)) {
+        circle = SOSCircleCreate(kCFAllocatorDefault, circleName, error);
+    } else {
+        circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error);
+        if (circle) {
+            CFStringRef name = SOSCircleGetName(circle);
+            if (!CFEqualSafe(name, circleName)) {
+                SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL,
+                                     CFSTR("Expected circle named %@, got %@"), circleName, name);
+                CFReleaseNull(circle);
+            }
+        }
+    }
+    return circle;
+}
+
+static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle)
+{
+    if (SOSCircleCountPeers(circle) == 0)
+        return kSOSCCCircleAbsent;
+
+    return kSOSCCNotInCircle;
+}
+
+static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer)
+{
+    if (SOSCircleCountPeers(circle) == 0)
+        return kSOSCCCircleAbsent;
+
+    if (SOSCircleHasPeer(circle, this_peer, NULL))
+        return kSOSCCInCircle;
+
+    if (SOSCircleHasApplicant(circle, this_peer, NULL))
+        return kSOSCCRequestPending;
+
+    return kSOSCCNotInCircle;
+}
+
+static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status)
+{
+    switch (additional_circle_status) {
+        case kSOSCCInCircle:
+            return accumulated_status;
+        case kSOSCCRequestPending:
+            return (accumulated_status == kSOSCCInCircle) ?
+            kSOSCCRequestPending :
+            accumulated_status;
+        case kSOSCCNotInCircle:
+            return (accumulated_status == kSOSCCInCircle ||
+                    accumulated_status == kSOSCCRequestPending) ?
+            kSOSCCNotInCircle :
+            accumulated_status;
+        case kSOSCCCircleAbsent:
+            return (accumulated_status == kSOSCCInCircle ||
+                    accumulated_status == kSOSCCRequestPending ||
+                    accumulated_status == kSOSCCNotInCircle) ?
+            kSOSCCCircleAbsent :
+            accumulated_status;
+        default:
+            return additional_circle_status;
+    };
+
+}
+
+SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error)
+{
+    if (!SOSAccountHasPublicKey(account, error)) {
+        return kSOSCCError;
+    }
+
+    __block bool set_once = false;
+    __block SOSCCStatus status = kSOSCCInCircle;
+
+    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+        set_once = true;
+        status = kSOSCCError;
+        SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
+    }, ^(SOSCircleRef circle) {
+        set_once = true;
+        status = UnionStatus(status, SOSCCCircleStatus(circle));
+    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+        set_once = true;
+        SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
+        status = UnionStatus(status, circle_status);
+    });
+
+    if (!set_once)
+        status = kSOSCCCircleAbsent;
+
+    return status;
+}
+
+static SOSPeerInfoRef GenerateNewCloudIdentityPeerInfo(CFErrorRef *error) {
+    SecKeyRef cloud_key = GeneratePermanentFullECKeyForCloudIdentity(256, kicloud_identity_name, error);
+    SOSPeerInfoRef cloud_peer = NULL;
+    CFDictionaryRef query = NULL;
+    CFDictionaryRef change = NULL;
+    CFStringRef new_name = NULL;
+
+    CFDictionaryRef gestalt = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                                           kPIUserDefinedDeviceName, CFSTR("iCloud"),
+                                                           NULL);
+    require_action_quiet(gestalt, fail, SecError(errSecAllocate, error, CFSTR("Can't allocate gestalt")));
+
+    cloud_peer = SOSPeerInfoCreateCloudIdentity(kCFAllocatorDefault, gestalt, cloud_key, error);
+
+    require(cloud_peer, fail);
+
+    query = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                         kSecClass,             kSecClassKey,
+                                         kSecAttrSynchronizable,kCFBooleanTrue,
+                                         kSecUseTombstones,     kCFBooleanTrue,
+                                         kSecValueRef,          cloud_key,
+                                         NULL);
+
+    new_name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
+                                        CFSTR("Cloud Identity - '%@'"), SOSPeerInfoGetPeerID(cloud_peer));
+    
+    change = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+                                          kSecAttrLabel,        new_name,
+                                          NULL);
+    
+    SecError(SecItemUpdate(query, change), error, CFSTR("Couldn't update name"));
+
+fail:
+    CFReleaseNull(new_name);
+    CFReleaseNull(query);
+    CFReleaseNull(change);
+    CFReleaseNull(gestalt);
+    CFReleaseNull(cloud_key);
+
+    return cloud_peer;
+}
+
+static SOSFullPeerInfoRef CopyCloudKeychainIdentity(SOSPeerInfoRef cloudPeer, CFErrorRef *error) {    
+    return SOSFullPeerInfoCreateCloudIdentity(NULL, cloudPeer, error);
+}
+
+static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
+    SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+    if (!myCirclePeer)
+        return false;
+
+    SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+        bool result = false;
+        SOSFullPeerInfoRef cloud_identity = NULL;
+        CFErrorRef localError = NULL;
+
+        require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out);
+
+        {
+            SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
+            require_quiet(cloud_peer, err_out);
+            cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
+            require_quiet(cloud_identity, err_out);
+        }
+
+        account->departure_code = kSOSNeverLeftCircle;
+        require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
+        require_quiet(SOSCircleAcceptRequest(circle, user_key, myCirclePeer, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
+        result = true;
+        SOSAccountPublishCloudParameters(account, NULL);
+
+    err_out:
+        if (result == false)
+            secerror("error resetting circle (%@) to offering: %@", circle, localError);
+        if (localError && error && *error == NULL) {
+            *error = localError;
+            localError = NULL;
+        }
+        CFReleaseNull(localError);
+        CFReleaseNull(cloud_identity);
+    });
+
+    return true;
+}
+
+static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key,
+                                     SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) {
+    __block bool result = false;
+    __block SOSFullPeerInfoRef cloud_full_peer = NULL;
+
+    SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+    require_action_quiet(myCirclePeer, fail,
+                         SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle));
+    if (use_cloud_peer) {
+        cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle);
+    }
+
+    if (SOSCircleCountPeers(circle) == 0) {
+        result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+    } else {
+        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+            result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
+            account->departure_code = kSOSNeverLeftCircle;
+            if(result && cloud_full_peer) {
+                CFErrorRef localError = NULL;
+                CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
+                require_quiet(cloudid, finish);
+                require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
+                require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
+            finish:
+                if (localError){
+                    secerror("Failed to join with cloud identity: %@", localError);
+                    CFReleaseNull(localError);
+                }
+            }
+        });
+    }
+    
+fail:
+    CFReleaseNull(cloud_full_peer);
+    return result;
+}
+                           
+static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
+    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+    if (!user_key)
+        return false;
+
+    __block bool success = true;
+
+    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
+        success = false;
+        SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
+    }, ^(SOSCircleRef circle) {                                 // No Peer
+        success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
+    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {   // Have Peer
+        SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer);
+        if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present;
+        if(SOSCircleHasApplicant(circle, myPeer, NULL))  goto already_applied;
+        if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) {
+            SOSCircleRemoveRejectedPeer(circle, myPeer, NULL);
+        }
+        
+        secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle));
+        CFErrorRef localError = NULL;
+        if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) {
+            secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError);
+            CFReleaseNull(localError);
+        }
+    already_applied:
+        success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
+        return;
+    already_present:
+        success = true;
+        return;
+    });
+
+    if(success) account->departure_code = kSOSNeverLeftCircle;
+
+    return success;
+}
+
+bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
+    return SOSAccountJoinCircles_internal(account, false, error);
+}
+
+
+bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
+    return SOSAccountJoinCircles_internal(account, true, error);
+}
+
+
+bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
+{
+    __block bool result = true;
+    
+    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+               result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
+               });
+    });
+
+    account->departure_code = kSOSWithdrewMembership;
+
+    return SOSAccountSendPendingChanges(account, error) && result;
+}
+
+bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error)
+{
+    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+    dispatch_group_t group = dispatch_group_create();
+    __block bool result = false;
+    
+    secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
+    // Add a task to the group
+    dispatch_group_async(group, queue, ^{
+        SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+            SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+                result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
+            });
+        });
+        
+        account->departure_code = kSOSWithdrewMembership;
+        if(result) result = SOSAccountSendPendingChanges(account, error);
+    });
+    dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
+
+    dispatch_group_wait(group, milestone);
+    dispatch_release(group);
+    return result;
+}
+
+
+static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
+                                              void (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
+    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+        SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer);
+        CFErrorRef peer_error = NULL;
+        if (SOSCircleHasPeer(circle, me, &peer_error)) {
+            CFArrayForEach(peer_infos, ^(const void *value) {
+                SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
+                if (SOSCircleHasApplicant(circle, peer, NULL)) {
+                    SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
+                        action(circle, full_peer, peer);
+                    });
+                }
+            });
+        }
+        if (peer_error)
+            secerror("Got error in SOSCircleHasPeer: %@", peer_error);
+        CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
+    });
+}
+
+bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
+    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+    if (!user_key)
+        return false;
+
+    __block bool success = true;
+       __block int64_t num_peers = 0;
+
+    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
+        if (!SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error))
+            success = false;
+               else
+                       num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
+    });
+       
+    return success;
+}
+
+bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
+    __block bool success = true;
+       __block int64_t num_peers = 0;
+
+    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
+        if (!SOSCircleRejectRequest(circle, myCirclePeer, peer, error))
+            success = false;
+               else
+                       num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
+    });
+
+    return success;
+}
+
+bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
+    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+    if (!user_key)
+        return false;
+
+    __block bool result = true;
+
+    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+        SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
+        if (circle)
+            CFDictionaryAddValue(account->circles, name, circle);
+        
+        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+    }, ^(SOSCircleRef circle) {
+        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+    });
+
+    return result;
+}
+
+bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
+    if (!SOSAccountHasPublicKey(account, error))
+        return false;
+
+    __block bool result = true;
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+            if (!SOSCircleResetToEmpty(circle, error))
+            {
+                secerror("error: %@", *error);
+                result = false;
+            }
+            account->departure_code = kSOSWithdrewMembership;
+        });
+    });
+
+    return result;
+}
+
+CFArrayRef SOSAccountCopyApplicants(SOSAccountRef account, CFErrorRef *error) {
+    if (!SOSAccountHasPublicKey(account, error))
+        return NULL;
+    CFMutableArrayRef applicants = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSCircleForEachApplicant(circle, ^(SOSPeerInfoRef peer) {
+            CFArrayAppendValue(applicants, peer);
+        });
+    });
+
+    return applicants;
+}
+
+CFArrayRef SOSAccountCopyPeers(SOSAccountRef account, CFErrorRef *error) {
+    if (!SOSAccountHasPublicKey(account, error))
+        return NULL;
+
+    CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+            CFArrayAppendValue(peers, peer);
+        });
+    });
+
+    return peers;
+}
+
+CFArrayRef SOSAccountCopyActivePeers(SOSAccountRef account, CFErrorRef *error) {
+    if (!SOSAccountHasPublicKey(account, error))
+        return NULL;
+    
+    CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
+            CFArrayAppendValue(peers, peer);
+        });
+    });
+    
+    return peers;
+}
+
+CFArrayRef SOSAccountCopyActiveValidPeers(SOSAccountRef account, CFErrorRef *error) {
+    if (!SOSAccountHasPublicKey(account, error))
+        return NULL;
+    
+    CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        SOSCircleForEachActiveValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
+            CFArrayAppendValue(peers, peer);
+        });
+    });
+    
+    return peers;
+}
+
+
+CFArrayRef SOSAccountCopyConcurringPeers(SOSAccountRef account, CFErrorRef *error)
+{
+    if (!SOSAccountHasPublicKey(account, error))
+        return NULL;
+
+    CFMutableArrayRef concurringPeers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+        CFMutableArrayRef circleConcurring = SOSCircleCopyConcurringPeers(circle, NULL);
+        CFArrayAppendArray(concurringPeers, circleConcurring, CFRangeMake(0, CFArrayGetCount(circleConcurring)));
+        CFReleaseSafe(circleConcurring);
+    });
+
+    return concurringPeers;
+}
+
+CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error)
+{
+    return CFSTR("We're compatible, go away");
+}
+
+enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error)
+{
+    return account->departure_code;
+}
+
+//
+// TODO: Handle '|' and "¬" in other strings.
+//
+const CFStringRef kSOSKVSKeyParametersKey = CFSTR(">KeyParameters");
+const CFStringRef kSOSKVSInitialSyncKey = CFSTR("^InitialSync");
+const CFStringRef kSOSKVSAccountChangedKey = CFSTR("^AccountChanged");
+
+const CFStringRef sWarningPrefix = CFSTR("!");
+const CFStringRef sAncientCirclePrefix = CFSTR("@");
+const CFStringRef sCirclePrefix = CFSTR("o");
+const CFStringRef sRetirementPrefix = CFSTR("-");
+const CFStringRef sCircleSeparator = CFSTR("|");
+const CFStringRef sFromToSeparator = CFSTR(":");
+
+static CFStringRef stringEndingIn(CFMutableStringRef in, CFStringRef token) {
+    if(token == NULL) return CFStringCreateCopy(NULL, in);
+    CFRange tokenAt = CFStringFind(in, token, 0);
+    if(tokenAt.location == kCFNotFound) return NULL;
+    CFStringRef retval = CFStringCreateWithSubstring(NULL, in, CFRangeMake(0, tokenAt.location));
+    CFStringDelete(in, CFRangeMake(0, tokenAt.location+1));
+    return retval;
+}
+
+SOSKVSKeyType SOSKVSKeyGetKeyTypeAndParse(CFStringRef key, CFStringRef *circle, CFStringRef *from, CFStringRef *to)
+{
+    SOSKVSKeyType retval = kUnknownKey;
+    
+    if(CFStringHasPrefix(key, sCirclePrefix)) retval = kCircleKey;
+    else if(CFStringHasPrefix(key, sRetirementPrefix)) retval = kRetirementKey;
+    else if(CFStringHasPrefix(key, kSOSKVSKeyParametersKey)) retval = kParametersKey;
+    else if(CFStringHasPrefix(key, kSOSKVSInitialSyncKey)) retval = kInitialSyncKey;
+    else if(CFStringHasPrefix(key, kSOSKVSAccountChangedKey)) retval = kAccountChangedKey;
+    else retval = kMessageKey;
+    
+    switch(retval) {
+        case kCircleKey:
+            if (circle) {
+                CFRange fromRange = CFRangeMake(1, CFStringGetLength(key)-1);
+                *circle = CFStringCreateWithSubstring(NULL, key, fromRange);
+            }
+            break;
+        case kMessageKey: {
+                CFStringRef mCircle = NULL;
+                CFStringRef mFrom = NULL;
+                CFStringRef mTo = NULL;
+                CFMutableStringRef keycopy = CFStringCreateMutableCopy(NULL, 128, key);
+
+                if( ((mCircle = stringEndingIn(keycopy, sCircleSeparator)) != NULL) &&
+                    ((mFrom = stringEndingIn(keycopy, sFromToSeparator)) != NULL) &&
+                    (CFStringGetLength(mFrom) > 0)  ) {
+                        mTo = stringEndingIn(keycopy, NULL);                
+                        if (circle) *circle = CFStringCreateCopy(NULL, mCircle);
+                        if (from) *from = CFStringCreateCopy(NULL, mFrom);
+                        if (to && mTo) *to = CFStringCreateCopy(NULL, mTo);
+                } else {
+                    retval = kUnknownKey;
+                }
+                CFReleaseNull(mCircle);
+                CFReleaseNull(mFrom);
+                CFReleaseNull(mTo);
+                CFReleaseNull(keycopy);
+            }
+            break;
+        case kRetirementKey: {
+                CFStringRef mCircle = NULL;
+                CFStringRef mPeer = NULL;
+                CFMutableStringRef keycopy = CFStringCreateMutableCopy(NULL, 128, key);
+                CFStringDelete(keycopy, CFRangeMake(0, 1));
+                if( ((mCircle = stringEndingIn(keycopy, sCircleSeparator)) != NULL) &&
+                    ((mPeer = stringEndingIn(keycopy, NULL)) != NULL)) {
+                    if (circle) *circle = CFStringCreateCopy(NULL, mCircle);
+                    if (from) *from = CFStringCreateCopy(NULL, mPeer);
+                } else {
+                    retval = kUnknownKey;
+                }
+                // TODO - Update our circle
+                CFReleaseNull(mCircle);
+                CFReleaseNull(mPeer);
+                CFReleaseNull(keycopy);
+            }
+            break;
+        case kAccountChangedKey:
+        case kParametersKey:
+        case kInitialSyncKey:
+        case kUnknownKey:
+            break;
+    }
+    
+    return retval;
+}
+
+
+SOSKVSKeyType SOSKVSKeyGetKeyType(CFStringRef key)
+{
+    return SOSKVSKeyGetKeyTypeAndParse(key, NULL, NULL, NULL);
+}
+
+CFStringRef SOSCircleKeyCreateWithCircle(SOSCircleRef circle, CFErrorRef *error)
+{
+    return SOSCircleKeyCreateWithName(SOSCircleGetName(circle), error);
+}
+
+
+CFStringRef SOSCircleKeyCreateWithName(CFStringRef circleName, CFErrorRef *error)
+{
+    return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"), sCirclePrefix, circleName);
+}
+
+CFStringRef SOSCircleKeyCopyCircleName(CFStringRef key, CFErrorRef *error)
+{
+    CFStringRef circleName = NULL;
+    
+    if (kCircleKey != SOSKVSKeyGetKeyTypeAndParse(key, &circleName, NULL, NULL)) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find circle name in key '%@'"), key);
+        
+        CFReleaseNull(circleName);
+    }
+    
+    return circleName;
+}
+
+CFStringRef SOSMessageKeyCopyCircleName(CFStringRef key, CFErrorRef *error)
+{
+    CFStringRef circleName = NULL;
+    
+    if (SOSKVSKeyGetKeyTypeAndParse(key, &circleName, NULL, NULL) != kMessageKey) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find circle name in key '%@'"), key);
+        
+        CFReleaseNull(circleName);
+    }        
+    return circleName;
+}
+
+CFStringRef SOSMessageKeyCopyFromPeerName(CFStringRef messageKey, CFErrorRef *error)
+{
+    CFStringRef fromPeer = NULL;
+    
+    if (SOSKVSKeyGetKeyTypeAndParse(messageKey, NULL, &fromPeer, NULL) != kMessageKey) {
+        SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find from peer in key '%@'"), messageKey);
+        
+        CFReleaseNull(fromPeer);
+    }
+    return fromPeer;
+}
+
+CFStringRef SOSMessageKeyCreateWithCircleAndPeerNames(SOSCircleRef circle, CFStringRef from_peer_name, CFStringRef to_peer_name)
+{
+    return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@%@%@%@"),
+                                    SOSCircleGetName(circle), sCircleSeparator, from_peer_name, sFromToSeparator, to_peer_name);
+}
+
+CFStringRef SOSMessageKeyCreateWithCircleAndPeerInfos(SOSCircleRef circle, SOSPeerInfoRef from_peer, SOSPeerInfoRef to_peer)
+{
+    return SOSMessageKeyCreateWithCircleAndPeerNames(circle, SOSPeerInfoGetPeerID(from_peer), SOSPeerInfoGetPeerID(to_peer));
+}
+
+CFStringRef SOSMessageKeyCreateWithAccountAndPeer(SOSAccountRef account, SOSCircleRef circle, CFStringRef peer_name) {
+    // TODO: Handle errors!
+    CFErrorRef error = NULL;
+
+    SOSFullPeerInfoRef me = SOSAccountGetMyFullPeerInCircle(account, circle, &error);
+    SOSPeerInfoRef my_pi = SOSFullPeerInfoGetPeerInfo(me);
+    CFStringRef result = SOSMessageKeyCreateWithCircleAndPeerNames(circle, SOSPeerInfoGetPeerID(my_pi), peer_name);
+    CFReleaseSafe(error);
+    return result;
+}
+
+CFStringRef SOSRetirementKeyCreateWithCircleAndPeer(SOSCircleRef circle, CFStringRef retirement_peer_name)
+{
+     return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@%@%@"),
+                                    sRetirementPrefix, SOSCircleGetName(circle), sCircleSeparator, retirement_peer_name);
+}
+
+
+static SOSPeerCoderStatus SOSAccountHandlePeerMessage(SOSAccountRef account,
+                                        CFStringRef circle_id,
+                                        CFStringRef peer_name,
+                                        CFDataRef message,
+                                        SOSAccountSendBlock send_block,
+                                        CFErrorRef *error)
+{
+    bool success = false;
+    CFStringRef peer_key = NULL;
+
+    SOSCircleRef circle = SOSAccountFindCircle(account, circle_id, error);
+    require_quiet(circle, fail);
+    SOSFullPeerInfoRef myFullPeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+    SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(myFullPeer);
+    require_action_quiet(SOSCircleHasPeer(circle, myPeer, NULL), fail, SOSCreateErrorWithFormat(kSOSErrorNotReady, NULL, error, NULL, CFSTR("Not in circle, can't handle message")));
+    
+    peer_key = SOSMessageKeyCreateWithAccountAndPeer(account, circle, peer_name);
+
+    SOSPeerSendBlock peer_send_block = ^bool (CFDataRef message, CFErrorRef *error) {
+        return send_block(circle, peer_key, message, error);
+    };
+
+    success = SOSCircleHandlePeerMessage(circle, myFullPeer, account->factory, peer_send_block, peer_name, message, error);
+
+fail:
+    CFReleaseNull(peer_key);
+    return success;
+}
+
+bool SOSAccountHandleUpdates(SOSAccountRef account,
+                             CFDictionaryRef updates,
+                             CFErrorRef *error) {
+    
+    if(CFDictionaryGetValue(updates, kSOSKVSAccountChangedKey) != NULL) {
+        SOSAccountSetToNew(account);
+    }
+
+    CFTypeRef parameters = CFDictionaryGetValue(updates, kSOSKVSKeyParametersKey);
+    if (isData(parameters)) {
+        SecKeyRef newKey = NULL;
+        CFDataRef newParameters = NULL;
+        const uint8_t *parse_end = der_decode_cloud_parameters(kCFAllocatorDefault, kSecECDSAAlgorithmID,
+                                                               &newKey, &newParameters, error,
+                                                               CFDataGetBytePtr(parameters), CFDataGetPastEndPtr(parameters));
+
+        if (parse_end == CFDataGetPastEndPtr(parameters)) {
+            if (CFEqualSafe(account->user_public, newKey)) {
+                secnotice("updates", "Got same public key sent our way. Ignoring.");
+            } else if (CFEqualSafe(account->previous_public, newKey)) {
+                secnotice("updates", "Got previous public key repeated. Ignoring.");
+            } else {
+                CFReleaseNull(account->user_public);
+                SOSAccountPurgePrivateCredential(account);
+                CFReleaseNull(account->user_key_parameters);
+
+                account->user_public_trusted = false;
+
+                account->user_public = newKey;
+                newKey = NULL;
+
+                account->user_key_parameters = newParameters;
+                newParameters = NULL;
+
+                secnotice("updates", "Got new parameters for public key: %@", account->user_public);
+                debugDumpUserParameters(CFSTR("params"), account->user_key_parameters);
+            }
+        }
+
+        CFReleaseNull(newKey);
+        CFReleaseNull(newParameters);
+    }
+
+    if (!account->user_public_trusted) {
+        if (!account->deferred_updates) {
+            account->deferred_updates = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+        }
+        
+        CFDictionaryForEach(updates, ^(const void *key, const void *value) {
+            if (!CFEqualSafe(key, kSOSKVSKeyParametersKey) && !CFEqualSafe(key, kSOSKVSAccountChangedKey))
+                CFDictionarySetValue(account->deferred_updates, key, value);
+        });
+        secnotice("updates", "No public peer key, deferring updates: %@", updates);
+        return true;
+    }
+
+    // Iterate though keys in updates.  Perform circle change update.
+    // Then instantiate circles and engines and peers for all peers that
+    // are receiving a message in updates.
+    __block bool is_initial_sync = CFDictionaryContainsKey(updates, kSOSKVSInitialSyncKey);
+
+    CFDictionaryForEach(updates, ^(const void *key, const void *value) {
+        CFStringRef circle_name = NULL;
+        CFErrorRef localError = NULL;
+        SOSCircleRef circle = NULL;
+        
+        if (SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, NULL, NULL) == kCircleKey) {
+            circle = SOSAccountCreateCircleFrom(circle_name, value, &localError);
+            if (!circle) {
+                if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) {
+                    SOSAccountDestroyCirclePeerInfoNamed(account, circle_name, NULL);
+                    CFDictionarySetValue(account->circles, circle_name, kCFNull);
+                } else {
+                    SOSCreateErrorWithFormat(kSOSErrorNameMismatch, localError, error, NULL,
+                                         CFSTR("Bad key for message, no circle '%@'"), key);
+                    goto circle_done;
+                }
+            }
+
+            if (!SOSAccountUpdateCircleFromRemote(account, circle, is_initial_sync, &localError)) {
+                SOSCreateErrorWithFormat(kSOSErrorProcessingFailure, localError, error, NULL,
+                                         CFSTR("Error handling circle change '%@'"), key);
+                secnotice("update", "Error updating circle '%@': %@", key, circle);
+                goto circle_done;
+            }
+        }
+        circle_done:
+        CFReleaseSafe(circle_name);
+        CFReleaseNull(circle);
+        CFReleaseNull(localError);
+    });
+    
+    CFDictionaryForEach(updates, ^(const void *key, const void *value) {
+        CFErrorRef localError = NULL;
+        CFStringRef circle_name = NULL;
+        CFStringRef from_name = NULL;
+        CFStringRef to_name = NULL;
+        switch (SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, &from_name, &to_name)) {
+            case kParametersKey:
+            case kInitialSyncKey:
+            case kCircleKey:
+                break;
+            case kMessageKey:
+            {
+                SOSFullPeerInfoRef my_peer = NULL;
+
+                require_action_quiet(isData(value), message_error, SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, localError, error, NULL, CFSTR("Non-Data for message(%@) from '%@'"), value, key));
+                require_quiet(my_peer = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circle_name, &localError), message_error);
+                
+                CFStringRef my_id = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(my_peer));
+                require_quiet(SOSAccountIsActivePeerInCircleNamed(account, circle_name, my_id, &localError), skip);
+                require_quiet(CFEqual(my_id, to_name), skip);
+                require_quiet(!CFEqual(my_id, from_name), skip);
+
+                SOSAccountSendBlock cacheInDictionary = ^ bool (SOSCircleRef circle, CFStringRef key, CFDataRef new_message, CFErrorRef* error) {
+                    CFDictionarySetValue(account->pending_changes, key, new_message);
+                    
+                    if (account->processed_message_block) {
+                        account->processed_message_block(circle, value, new_message);
+                    }
+                    
+                    return true;
+                };
+                
+                if (SOSAccountHandlePeerMessage(account, circle_name, from_name, value, cacheInDictionary, &localError) == kSOSPeerCoderFailure) {
+                    SOSCreateErrorWithFormat(kSOSErrorNameMismatch, localError, error, NULL,
+                                             CFSTR("Error handling peer message from '%@'"), key);
+                    localError = NULL; // Released by SOSCreateErrorWithFormat
+                    goto message_error;
+                }
+
+            message_error:
+            skip:
+                break;
+            }
+            case kRetirementKey:
+                if(isData(value)) {                
+                    SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
+                    if(pi && CFEqual(from_name, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) {
+                        CFDictionarySetValue(account->retired_peers, key, value);
+                        SOSAccountRecordRetiredPeerInCircleNamed(account, circle_name, pi);
+                    }
+                    CFReleaseSafe(pi);
+                }
+                break;
+            
+            case kAccountChangedKey: // Handled at entry to function to make sure these are processed first.
+                break;
+                
+            case kUnknownKey:
+                secnotice("updates", "Unknown key '%@', ignoring", key);
+                break;
+            
+        }
+        
+        CFReleaseNull(circle_name);
+        CFReleaseNull(from_name);
+        CFReleaseNull(to_name);
+
+        if (error && *error)
+            secerror("Peer message processing error for: %@ -> %@ (%@)", key, value, *error);
+        if (localError)
+            secerror("Peer message local processing error for: %@ -> %@ (%@)", key, value, localError);
+
+        CFReleaseNull(localError);
+    });
+    
+    return SOSAccountSendPendingChanges(account, error);
+}
+
+void SOSAccountSetMessageProcessedBlock(SOSAccountRef account, SOSAccountMessageProcessedBlock processedBlock)
+{
+    CFRetainSafe(processedBlock);
+    CFReleaseNull(account->processed_message_block);
+    account->processed_message_block = processedBlock;
+}
+
+CFStringRef SOSInterestListCopyDescription(CFArrayRef interests)
+{
+    CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0);
+    CFStringAppendFormat(description, NULL, CFSTR("<Interest: "));
+    
+    CFArrayForEach(interests, ^(const void* string) {
+        if (isString(string))
+            CFStringAppendFormat(description, NULL, CFSTR(" '%@'"), string);
+    });
+    CFStringAppend(description, CFSTR(">"));
+
+    return description;
+}