diff --git a/CHANGES.rst b/CHANGES.rst
index 7569466..4a1e878 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,29 @@
 Changes
 =======
 
+2.0.0 (unreleased)
+------------------
+
+- The transaction ``user`` and ``description`` attributes are now
+  defined to be text (unicode) as apposed to Python the ``str`` type.
+
+- Added the ``extended_info`` transaction attribute which contains
+  transaction meta data.  (The ``_extension`` attribute is retained as
+  an alias for backward compatibility.)
+
+  The transaction interface, ``ITransaction``, now requires
+  ``extended_info`` keys to be text (unicode) and values to be
+  JSON-serializable.
+
+- Removed setUser from ITransaction.  We'll keep the method
+  undefinately, but it's unseemly in ITransaction. :)
+
+The main purpose of these changes is to tighten up the text
+specification of user, description and extended_info keys, and to give
+us more flexibility in the future for serializing extended info.  It's
+possible that these changes will be breaking, so we're also increasing
+the major version number.
+
 1.7.0 (2016-11-08)
 ------------------
 
diff --git a/transaction/_transaction.py b/transaction/_transaction.py
index fd4122e..3aa2050 100644
--- a/transaction/_transaction.py
+++ b/transaction/_transaction.py
@@ -76,10 +76,10 @@ class Transaction(object):
     # savepoint to its index (see above).
     _savepoint2index = None
 
-    # Meta data.  ._extension is also metadata, but is initialized to an
+    # Meta data. extended_info is also metadata, but is initialized to an
     # emtpy dict in __init__.
-    user = ""
-    description = ""
+    _user = u""
+    _description = u""
 
     def __init__(self, synchronizers=None, manager=None):
         self.status = Status.ACTIVE
@@ -100,9 +100,9 @@ class Transaction(object):
         # manager as a key, because we can't guess whether the actual
         # resource managers will be safe to use as dict keys.
 
-        # The user, description, and _extension attributes are accessed
+        # The user, description, and extended_info attributes are accessed
         # directly by storages, leading underscore notwithstanding.
-        self._extension = {}
+        self.extended_info = {}
 
         self.log = _makeLogger()
         self.log.debug("new transaction")
@@ -118,6 +118,28 @@ class Transaction(object):
         # List of (hook, args, kws) tuples added by addAfterCommitHook().
         self._after_commit = []
 
+    @property
+    def _extension(self):
+        # for backward compatibility, since most clients used this
+        # absent any formal API.
+        return self.extended_info
+
+    @property
+    def user(self):
+        return self._user
+
+    @user.setter
+    def user(self, v):
+        self._user = v + u'' # + u'' to make sure it's unicode
+
+    @property
+    def description(self):
+        return self._description
+
+    @description.setter
+    def description(self, v):
+        self._description = v + u'' # + u'' to make sure it's unicode
+
     def isDoomed(self):
         """ See ITransaction.
         """
@@ -504,19 +526,19 @@ class Transaction(object):
         """
         text = text.strip()
         if self.description:
-            self.description += "\n" + text
+            self.description += u"\n" + text
         else:
             self.description = text
 
     def setUser(self, user_name, path="/"):
         """ See ITransaction.
         """
-        self.user = "%s %s" % (path, user_name)
+        self.user = u"%s %s" % (path, user_name)
 
     def setExtendedInfo(self, name, value):
         """ See ITransaction.
         """
-        self._extension[name] = value
+        self.extended_info[name + u''] = value # + u'' to make sure it's unicode
 
 
 # TODO: We need a better name for the adapters.
diff --git a/transaction/interfaces.py b/transaction/interfaces.py
index 52798fa..c7be269 100644
--- a/transaction/interfaces.py
+++ b/transaction/interfaces.py
@@ -105,7 +105,7 @@ class ITransaction(Interface):
         """A user name associated with the transaction.
 
         The format of the user name is defined by the application.  The value
-        is of Python type str.  Storages record the user value, as meta-data,
+        is text (unicode).  Storages record the user value, as meta-data,
         when a transaction commits.
 
         A storage may impose a limit on the size of the value; behavior is
@@ -116,7 +116,7 @@ class ITransaction(Interface):
     description = Attribute(
         """A textual description of the transaction.
 
-        The value is of Python type str.  Method note() is the intended
+        The value is text (unicode).  Method note() is the intended
         way to set the value.  Storages record the description, as meta-data,
         when a transaction commits.
 
@@ -125,6 +125,13 @@ class ITransaction(Interface):
         raise an exception, or truncate the value).
         """)
 
+    extended_info = Attribute(
+        """A dictionary containing application-defined metadata.
+
+        Keys must be text (unicode).  Values must be simple values
+        serializable with json or pickle (not instances).
+        """)
+
     def commit():
         """Finalize the transaction.
 
@@ -167,7 +174,7 @@ class ITransaction(Interface):
         """
 
     def note(text):
-        """Add text to the transaction description.
+        """Add text (unicode) to the transaction description.
 
         This modifies the `.description` attribute; see its docs for more
         detail.  First surrounding whitespace is stripped from `text`.  If
@@ -176,21 +183,17 @@ class ITransaction(Interface):
         appended to `.description`.
         """
 
-    def setUser(user_name, path="/"):
-        """Set the user name.
-
-        path should be provided if needed to further qualify the
-        identified user.  This is a convenience method used by Zope.
-        It sets the .user attribute to str(path) + " " + str(user_name).
-        This sets the `.user` attribute; see its docs for more detail.
-        """
-
     def setExtendedInfo(name, value):
         """Add extension data to the transaction.
 
-        name is the name of the extension property to set, of Python type
-        str; value must be picklable.  Multiple calls may be made to set
-        multiple extension properties, provided the names are distinct.
+        name
+          is the text (unicode) name of the extension property to set
+
+        value
+          must be picklable and json serializable (not an instance).
+
+        Multiple calls may be made to set multiple extension
+        properties, provided the names are distinct.
 
         Storages record the extension data, as meta-data, when a transaction
         commits.

diff --git a/transaction/tests/test__transaction.py b/transaction/tests/test__transaction.py
index 4e63bb3..8fe8c68 100644
--- a/transaction/tests/test__transaction.py
+++ b/transaction/tests/test__transaction.py
@@ -69,14 +69,15 @@ class TransactionTests(unittest.TestCase):
         self.assertTrue(isinstance(txn._synchronizers, WeakSet))
         self.assertEqual(len(txn._synchronizers), 0)
         self.assertTrue(txn._manager is None)
-        self.assertEqual(txn.user, "")
-        self.assertEqual(txn.description, "")
+        self.assertEqual(txn.user, u"")
+        self.assertEqual(txn.description, u"")
         self.assertTrue(txn._savepoint2index is None)
         self.assertEqual(txn._savepoint_index, 0)
         self.assertEqual(txn._resources, [])
         self.assertEqual(txn._adapters, {})
         self.assertEqual(txn._voted, {})
-        self.assertEqual(txn._extension, {})
+        self.assertEqual(txn.extended_info, {})
+        self.assertTrue(txn._extension is txn.extended_info) # legacy
         self.assertTrue(txn.log is logger)
         self.assertEqual(len(logger._log), 1)
         self.assertEqual(logger._log[0][0], 'debug')
@@ -983,33 +984,45 @@ class TransactionTests(unittest.TestCase):
         txn = self._makeOne()
         try:
             txn.note('This is a note.')
-            self.assertEqual(txn.description, 'This is a note.')
+            self.assertEqual(txn.description, u'This is a note.')
             txn.note('Another.')
-            self.assertEqual(txn.description, 'This is a note.\nAnother.')
+            self.assertEqual(txn.description, u'This is a note.\nAnother.')
         finally:
             txn.abort()
 
+    def test_description_nonascii_bytes(self):
+        txn = self._makeOne()
+        with self.assertRaises((UnicodeDecodeError, TypeError)):
+            txn.description = b'\xc2\x80'
+
     def test_setUser_default_path(self):
         txn = self._makeOne()
         txn.setUser('phreddy')
-        self.assertEqual(txn.user, '/ phreddy')
+        self.assertEqual(txn.user, u'/ phreddy')
 
     def test_setUser_explicit_path(self):
         txn = self._makeOne()
         txn.setUser('phreddy', '/bedrock')
-        self.assertEqual(txn.user, '/bedrock phreddy')
+        self.assertEqual(txn.user, u'/bedrock phreddy')
+
+    def test_user_nonascii_bytes(self):
+        txn = self._makeOne()
+        with self.assertRaises((UnicodeDecodeError, TypeError)):
+            txn.user = b'\xc2\x80'
 
     def test_setExtendedInfo_single(self):
         txn = self._makeOne()
         txn.setExtendedInfo('frob', 'qux')
-        self.assertEqual(txn._extension, {'frob': 'qux'})
+        self.assertEqual(txn.extended_info, {u'frob': 'qux'})
+        self.assertTrue(txn._extension is txn._extension) # legacy
 
     def test_setExtendedInfo_multiple(self):
         txn = self._makeOne()
         txn.setExtendedInfo('frob', 'qux')
         txn.setExtendedInfo('baz', 'spam')
         txn.setExtendedInfo('frob', 'quxxxx')
-        self.assertEqual(txn._extension, {'frob': 'quxxxx', 'baz': 'spam'})
+        self.assertEqual(txn._extension, {u'frob': 'quxxxx', u'baz': 'spam'})
+        self.assertTrue(txn._extension is txn._extension) # legacy
 
     def test_data(self):
         txn = self._makeOne()

Define transaction description and user to be unicode
See:

https://groups.google.com/forum/#!topic/python-transaction/Yn326XwCZ5E
transaction/tests/test__transaction.py::TransactionTests::test_ctor_defaults transaction/tests/test__transaction.py::TransactionTests::test_description_nonascii_bytes transaction/tests/test__transaction.py::TransactionTests::test_setExtendedInfo_single transaction/tests/test__transaction.py::TransactionTests::test_user_nonascii_bytes
transaction/tests/test__transaction.py::TransactionTests::test__commitResources_error_in_afterCompletion transaction/tests/test__transaction.py::TransactionTests::test__commitResources_error_in_commit transaction/tests/test__transaction.py::TransactionTests::test__commitResources_error_in_tpc_begin transaction/tests/test__transaction.py::TransactionTests::test__commitResources_error_in_tpc_finish transaction/tests/test__transaction.py::TransactionTests::test__commitResources_error_in_tpc_vote transaction/tests/test__transaction.py::TransactionTests::test__commitResources_normal transaction/tests/test__transaction.py::TransactionTests::test__invalidate_all_savepoints transaction/tests/test__transaction.py::TransactionTests::test__prior_operation_failed transaction/tests/test__transaction.py::TransactionTests::test__remove_and_invalidate_after_hit transaction/tests/test__transaction.py::TransactionTests::test__remove_and_invalidate_after_miss transaction/tests/test__transaction.py::TransactionTests::test__unjoin_hit transaction/tests/test__transaction.py::TransactionTests::test__unjoin_miss transaction/tests/test__transaction.py::TransactionTests::test_abort_clears_resources transaction/tests/test__transaction.py::TransactionTests::test_abort_error_w_afterCompleteHooks transaction/tests/test__transaction.py::TransactionTests::test_abort_error_w_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_abort_w_afterCommitHooks transaction/tests/test__transaction.py::TransactionTests::test_abort_w_beforeCommitHooks transaction/tests/test__transaction.py::TransactionTests::test_abort_w_savepoints transaction/tests/test__transaction.py::TransactionTests::test_abort_w_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_abort_wo_savepoints_wo_hooks_wo_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_addAfterCommitHook transaction/tests/test__transaction.py::TransactionTests::test_addAfterCommitHook_wo_kws transaction/tests/test__transaction.py::TransactionTests::test_addBeforeCommitHook transaction/tests/test__transaction.py::TransactionTests::test_addBeforeCommitHook_w_kws transaction/tests/test__transaction.py::TransactionTests::test_callAfterCommitHook_w_abort transaction/tests/test__transaction.py::TransactionTests::test_callAfterCommitHook_w_error transaction/tests/test__transaction.py::TransactionTests::test_commit_COMMITFAILED transaction/tests/test__transaction.py::TransactionTests::test_commit_DOOMED transaction/tests/test__transaction.py::TransactionTests::test_commit_clears_resources transaction/tests/test__transaction.py::TransactionTests::test_commit_error_w_afterCompleteHooks transaction/tests/test__transaction.py::TransactionTests::test_commit_error_w_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_commit_w_afterCommitHooks transaction/tests/test__transaction.py::TransactionTests::test_commit_w_beforeCommitHooks transaction/tests/test__transaction.py::TransactionTests::test_commit_w_savepoints transaction/tests/test__transaction.py::TransactionTests::test_commit_w_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_commit_wo_savepoints_wo_hooks_wo_synchronizers transaction/tests/test__transaction.py::TransactionTests::test_ctor_w_syncs transaction/tests/test__transaction.py::TransactionTests::test_data transaction/tests/test__transaction.py::TransactionTests::test_doom_active transaction/tests/test__transaction.py::TransactionTests::test_doom_already_doomed transaction/tests/test__transaction.py::TransactionTests::test_doom_invalid transaction/tests/test__transaction.py::TransactionTests::test_getAfterCommitHooks_empty transaction/tests/test__transaction.py::TransactionTests::test_getBeforeCommitHooks_empty transaction/tests/test__transaction.py::TransactionTests::test_isDoomed transaction/tests/test__transaction.py::TransactionTests::test_join_ACTIVE_w_preparing_w_sp2index transaction/tests/test__transaction.py::TransactionTests::test_join_COMMITFAILED transaction/tests/test__transaction.py::TransactionTests::test_join_COMMITTED transaction/tests/test__transaction.py::TransactionTests::test_join_COMMITTING transaction/tests/test__transaction.py::TransactionTests::test_join_DOOMED_non_preparing_wo_sp2index transaction/tests/test__transaction.py::TransactionTests::test_note transaction/tests/test__transaction.py::TransactionTests::test_register_w_jar transaction/tests/test__transaction.py::TransactionTests::test_register_w_jar_already_adapted transaction/tests/test__transaction.py::TransactionTests::test_register_wo_jar transaction/tests/test__transaction.py::TransactionTests::test_savepoint_COMMITFAILED transaction/tests/test__transaction.py::TransactionTests::test_savepoint_empty transaction/tests/test__transaction.py::TransactionTests::test_savepoint_non_optimistc_resource_wo_support transaction/tests/test__transaction.py::TransactionTests::test_setExtendedInfo_multiple transaction/tests/test__transaction.py::TransactionTests::test_setUser_default_path transaction/tests/test__transaction.py::TransactionTests::test_setUser_explicit_path transaction/tests/test__transaction.py::TransactionTests::test_verifyImplements_ITransaction transaction/tests/test__transaction.py::TransactionTests::test_verifyProvides_ITransaction transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test___repr__ transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_abort transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_abort_w_error transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_commit transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_ctor transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_sortKey transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_tpc_abort transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_tpc_begin transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_tpc_finish transaction/tests/test__transaction.py::MultiObjectResourceAdapterTests::test_tpc_vote transaction/tests/test__transaction.py::Test_rm_key::test_hit transaction/tests/test__transaction.py::Test_rm_key::test_miss transaction/tests/test__transaction.py::Test_object_hint::test_hit transaction/tests/test__transaction.py::Test_object_hint::test_miss transaction/tests/test__transaction.py::Test_oid_repr::test_as_nonstring transaction/tests/test__transaction.py::Test_oid_repr::test_as_string_all_Fs transaction/tests/test__transaction.py::Test_oid_repr::test_as_string_not_8_chars transaction/tests/test__transaction.py::Test_oid_repr::test_as_string_xxx transaction/tests/test__transaction.py::Test_oid_repr::test_as_string_z64 transaction/tests/test__transaction.py::DataManagerAdapterTests::test_abort transaction/tests/test__transaction.py::DataManagerAdapterTests::test_commit transaction/tests/test__transaction.py::DataManagerAdapterTests::test_ctor transaction/tests/test__transaction.py::DataManagerAdapterTests::test_sortKey transaction/tests/test__transaction.py::DataManagerAdapterTests::test_tpc_abort transaction/tests/test__transaction.py::DataManagerAdapterTests::test_tpc_begin transaction/tests/test__transaction.py::DataManagerAdapterTests::test_tpc_finish transaction/tests/test__transaction.py::DataManagerAdapterTests::test_tpc_vote transaction/tests/test__transaction.py::SavepointTests::test_ctor_w_savepoint_aware_resources transaction/tests/test__transaction.py::SavepointTests::test_ctor_w_savepoint_oblivious_resource_non_optimistic transaction/tests/test__transaction.py::SavepointTests::test_ctor_w_savepoint_oblivious_resource_optimistic transaction/tests/test__transaction.py::SavepointTests::test_rollback_w_sp_error transaction/tests/test__transaction.py::SavepointTests::test_rollback_w_txn_None transaction/tests/test__transaction.py::SavepointTests::test_valid_w_transacction transaction/tests/test__transaction.py::SavepointTests::test_valid_wo_transacction transaction/tests/test__transaction.py::AbortSavepointTests::test_ctor transaction/tests/test__transaction.py::AbortSavepointTests::test_rollback transaction/tests/test__transaction.py::NoRollbackSavepointTests::test_ctor transaction/tests/test__transaction.py::NoRollbackSavepointTests::test_rollback transaction/tests/test__transaction.py::MiscellaneousTests::test_BBB_join transaction/tests/test__transaction.py::MiscellaneousTests::test_bug239086 transaction/tests/test__transaction.py::test_suite