diff --git a/bin/load_gen b/bin/load_gen
index 3250fa4b347dd5b03f0dd4e0495489b0109ca17c..f7584e5b98c3d2cf344eec3732eb632c15606bb5 100755
--- a/bin/load_gen
+++ b/bin/load_gen
@@ -102,12 +102,10 @@ var activity = {
     // new user signup
     probability: (1.0 / (40 * 28 * .2))
   },
-/*
   "reset_pass": {
     // users forget their password once every 4 weeks
     probability: (1.0 / (40 * 28.0))
   },
-*/
   "add_email": {
     // users add a new email address once every 2 weeks
     probability: (1.0 / (40 * 14.0))
@@ -128,6 +126,10 @@ var activity = {
     // inclusion.  The strict probability is 100% - sum of above
     // probabilities.  We round to 31 / 40.
     probability: (31 / 40.0)
+  },
+  "change_pass": {
+    // users change their passwords once every two months
+    probability: (1.0 / (40 * 56))
   }
 };
 
diff --git a/lib/load_gen/activities/change_pass.js b/lib/load_gen/activities/change_pass.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc0f402387ca2789ddcd9e0fee5e6460bfd0f21c
--- /dev/null
+++ b/lib/load_gen/activities/change_pass.js
@@ -0,0 +1,87 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Lloyd Hilaiel <lloyd@hilaiel.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* this file is the "signin" activity, which simulates the process of a user
+ * with an existing browserid account and existing authentication material
+ * signin into a site. */
+
+const
+wcli = require("../../wsapi_client.js"),
+userdb = require("../user_db.js"),
+winston = require('winston'),
+crypto = require('../crypto'),
+common = require('../common');
+
+exports.startFunc = function(cfg, cb) {
+  var user = userdb.getExistingUser();
+
+  if (!user) {
+    winston.warn("can't achieve desired concurrency!  not enough users!");
+    return cb("not enough users");
+  }
+
+  // unlock the user when we're done with them
+  cb = (function() {
+    var _cb = cb;
+    return function(x) {
+      userdb.releaseUser(user);
+      _cb(x);
+    };
+  })();
+
+  // pick one of the user's emails that we'll use
+  var email = userdb.any(user.emails);
+
+  // pick one of the user's devices that we'll use
+  var context = userdb.any(user.ctxs);
+
+  var origin = userdb.any(user.sites);
+
+  // establish session context and authenticate if needed
+  common.auth(cfg, user, context, email, function(err) {
+    if (err) return cb(err);
+    wcli.post(cfg, '/wsapi/update_password', context, {
+      oldpass: user.password,
+      newpass: user.password
+    }, function (r) {
+      try {
+        cb(JSON.parse(r.body).success === true ? undefined : "password update failed");
+      } catch(e) {
+        cb("password update failed: " + e.toString());
+      }
+    });
+  });
+};
diff --git a/lib/load_gen/activities/reset_pass.js b/lib/load_gen/activities/reset_pass.js
index 8bb6ca856c4457d18255fcc99d8feea7f606611c..4f4d02a5869a1c14431fddafe8287aa6265cfda4 100644
--- a/lib/load_gen/activities/reset_pass.js
+++ b/lib/load_gen/activities/reset_pass.js
@@ -37,11 +37,72 @@
 /* this file is the "reset_pass" activity, which simulates the process of a
  * user resetting their password. */
 
+const
+wcli = require("../../wsapi_client.js"),
+userdb = require("../user_db.js"),
+winston = require('winston'),
+common = require('../common');
+
 exports.startFunc = function(cfg, cb) {
-  // At present, this process is exactly the same as signup, with the difference
-  // being that the email used is one that has already been verified.  we can
-  // reuse code here for now.
 
-  // XXX: write me
-  setTimeout(function() { cb(true); }, 10); 
+  var user = userdb.getExistingUser();
+
+  if (!user) {
+    winston.warn("can't achieve desired concurrency!  not enough users!");
+    return cb("not enough users");
+  }
+
+  // unlock the user when we're done with them
+  cb = (function() {
+    var _cb = cb;
+    return function(x) {
+      userdb.releaseUser(user);
+      _cb(x);
+    };
+  })();
+
+  // to "reset" a password, we'll break a single email of of the selected existing user
+  // into a new user.
+  user = userdb.splitUser(user);
+
+  // now everything is identical to the signup flow
+  // pick a device context at random
+  var context = userdb.any(user.ctxs);
+
+  // pick an email address to operate on (there should really be
+  // only one at this point
+  var email = userdb.any(user.emails);
+
+  var origin = userdb.any(user.sites);
+
+  // stage them
+  wcli.post(cfg, '/wsapi/stage_user', context, {
+    email: email,
+    site: userdb.any(user.sites)
+  }, function (r) {
+    if (r.code !== 200) return cb(false);
+    // now get the verification secret
+    wcli.get(cfg, '/wsapi/fake_verification', context, {
+      email: email
+    }, function (r) {
+      if (r.code !== 200) return cb(false);
+      // and simulate clickthrough
+      wcli.post(cfg, '/wsapi/complete_user_creation', context, {
+        token: r.body,
+        pass: user.password
+      }, function (r) {
+        r.body = JSON.parse(r.body);
+        if (r.code !== 200 || r.body.success !== true) {
+          return cb("failed to complete user creation");
+        }
+        // and now let's log in with this email address
+        common.authAndKey(cfg, user, context, email, function(err) {
+          if (err) return cb(err);
+          common.genAssertionAndVerify(cfg, user, context, email, origin, function(err) {
+            cb(err);
+          });
+        });
+      });
+    });
+  });
 };
diff --git a/lib/load_gen/activities/signup.js b/lib/load_gen/activities/signup.js
index 56e9adda7e2ad7dfa9e00fc54127adefe09a7422..cd62201a22edef67b4f3d05ff14e669465b915e9 100644
--- a/lib/load_gen/activities/signup.js
+++ b/lib/load_gen/activities/signup.js
@@ -88,6 +88,8 @@ exports.startFunc = function(cfg, cb) {
   // only one at this point
   var email = userdb.any(user.emails);
 
+  var origin = userdb.any(user.sites);
+
   // stage them
   wcli.post(cfg, '/wsapi/stage_user', context, {
     email: email,
@@ -108,10 +110,12 @@ exports.startFunc = function(cfg, cb) {
         if (r.code !== 200 || r.body.success !== true) {
           return cb("failed to complete user creation");
         }
-        // and now let's prepare this idenity in this context (get a keypair
-        // and certify it)
+        // and now let's log in with this email address
         common.authAndKey(cfg, user, context, email, function(err) {
-          cb(err);
+          if (err) return cb(err);
+          common.genAssertionAndVerify(cfg, user, context, email, origin, function(err) {
+            cb(err);
+          });
         });
       });
     });
diff --git a/lib/load_gen/user_db.js b/lib/load_gen/user_db.js
index 799e8a2435419e12ba4ceef7d8f45b853a6a9a90..46062907b32fab68e67cc6410a355914eedbf4d1 100644
--- a/lib/load_gen/user_db.js
+++ b/lib/load_gen/user_db.js
@@ -35,8 +35,7 @@
  * ***** END LICENSE BLOCK ***** */
 
 /* the "user database".  a little in-memory collection of users for the
- * purposes of performance testing. 
- */
+ * purposes of performance testing. */
 
 const
 secrets = require('../secrets.js'),
@@ -112,6 +111,18 @@ exports.getExistingUser = function() {
   }
 };
 
+exports.splitUser = function(user) {
+  if (!user.locked) throw "you can't split a user that's not in use!";
+  if (user.emails.length == 1) {
+    return user;
+  } else {
+    var newuser = exports.getNewUser();
+    newuser.emails[0] = user.emails.shift();
+    exports.releaseUser(user);
+    return newuser;
+  }
+};
+
 exports.releaseUser = function(user) {
   if (!user.locked) throw "you can't release a user that's not in use!";
   delete user.locked;