diff --git a/Makefile b/Makefile
index 2819161feb45d3dca63abb129a64bc8ec509912c..ffd284c8516f251dbe049a40a9c9b276bc942020 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-default: bsspeke
+default: demo
 
 monocypher.o: monocypher.c monocypher.h
 	cc -c monocypher.c
@@ -6,7 +6,10 @@ monocypher.o: monocypher.c monocypher.h
 bsspeke.o: bsspeke.c bsspeke.h monocypher.h
 	cc -c bsspeke.c
 
-bsspeke: bsspeke.o monocypher.o
-	cc -o bsspeke bsspeke.o monocypher.o
+demo.o: demo.c bsspeke.h
+	cc -c demo.c
+
+demo: demo.o bsspeke.o monocypher.o
+	cc -o demo demo.o bsspeke.o monocypher.o
 
 
diff --git a/bsspeke.c b/bsspeke.c
index 3d19f4c33e014c233bd0ff0cbea747582c95ce45..40aa2afd4edbf64bc349a3ac5a76c3a299418d8f 100644
--- a/bsspeke.c
+++ b/bsspeke.c
@@ -25,6 +25,34 @@
 #include "monocypher.h"
 #include "bsspeke.h"
 
+typedef enum {
+    DEBUG,
+    INFO,
+    WARN,
+    ERROR,
+    FATAL
+} debug_level_t;
+
+debug_level_t curr_level = DEBUG;
+
+void debug(debug_level_t level,
+           const char *msg
+) {
+    //if( level >= curr_level ) {
+        puts(msg);
+    //}
+}
+
+void print_point(const char *label,
+                 uint8_t point[32]
+) {
+    printf("%8s:\t[", label);
+    int i = 31;
+    for(i=31; i >= 0; i--)
+        printf("%02x", point[i]);
+    printf("]\n");
+}
+
 void
 bsspeke_client_init(bsspeke_client_ctx *ctx,
                     const char* client_id, const size_t client_id_len,
@@ -42,77 +70,226 @@ bsspeke_client_init(bsspeke_client_ctx *ctx,
 }
 
 void
-bsspeke_client_generate_message1(bsspeke_msg1_t *msg1,
-                                 bsspeke_client_ctx *client
+bsspeke_server_init(bsspeke_server_ctx *ctx,
+                    const char* server_id, const size_t server_id_len
+) {
+    ctx->server_id = (uint8_t *)server_id;
+    ctx->server_id_len = server_id_len;
+}
+
+void
+bsspeke_client_generate_message1
+(
+    bsspeke_login_msg1_t *msg1,
+    bsspeke_client_ctx *client
 ) {
+    debug(DEBUG, "Hashing client's password");
     // 1. Hash the client's password, client_id, server_id to a point on the curve
     uint8_t scalar_hash[32];
     uint8_t curve_point[32];
     {
-        crypto_blake2b_ctx bctx;
+        crypto_blake2b_ctx hash_ctx;
         // Give us a 256 bit (32 byte) hash; Don't use a key
-        crypto_blake2b_general_init(&bctx, 32, NULL, 0);
+        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         // Add the client id, server id, and the password to the hash
-        crypto_blake2b_update(&bctx,
+        crypto_blake2b_update(&hash_ctx,
                               (const uint8_t *)(client->password),
                               client->password_len);
-        crypto_blake2b_update(&bctx,
+        crypto_blake2b_update(&hash_ctx,
                               (const uint8_t *)(client->client_id),
                               client->client_id_len);
-        crypto_blake2b_update(&bctx,
+        crypto_blake2b_update(&hash_ctx,
                               (const uint8_t *)(client->server_id),
                               client->server_id_len);
         // Write the digest value into `scalar_hash`
-        crypto_blake2b_final(&bctx, scalar_hash);
+        crypto_blake2b_final(&hash_ctx, scalar_hash);
     }
+    debug(DEBUG, "Mapping password hash onto the curve");
     // Now use Elligator to map our scalar hash to a point on the curve
     crypto_hidden_to_curve(curve_point, scalar_hash);
 
+    print_point("H(pass)", curve_point);
+
     // 2. Generate random r
     //    * Actually generate 1/r first, and clamp() it
     //      That way we know it will always lead us back to a point on the curve
     //    * Then use the inverse of 1/r as `r`
     //  FIXME: On second thought, monocypher seems to handle all of this complexity for us.  Let's see what happens if we just do things the straightforward way for now...
+    debug(DEBUG, "Generating random blind `r`");
     arc4random_buf(client->r, 32);
+    print_point("r", client->r);
+    debug(DEBUG, "Clamping r");
     crypto_x25519_clamp(client->r);
+    print_point("r", client->r);
+
+    debug(DEBUG, "Multiplying curve point by r");
     // 3. Multiply our curve point by r
     crypto_x25519_scalarmult(msg1->blind, client->r, curve_point, 256);
+    print_point("blind", msg1->blind);
+    debug(DEBUG, "Done");
     return;
 }
 
 void
-bsspeke_server_init(bsspeke_server_ctx *ctx,
-                    const char* server_id
+bsspeke_client_setup_generate_message1
+(
+    bsspeke_setup_msg1_t *msg1,
+    bsspeke_client_ctx *client
+) {
+    bsspeke_client_generate_message1(msg1, client);
+}
+
+void
+bsspeke_server_setup_generate_message2
+    (
+        bsspeke_setup_msg2_t *msg2,
+        const bsspeke_setup_msg1_t *msg1,
+        uint8_t *salt, const size_t salt_len,
+        bsspeke_server_ctx *server_ctx
+    )
+{
+    // We're setting up a new account
+    // So we have to create a new random salt for the user
+    debug(DEBUG, "Generating new salt");
+    arc4random_buf(salt, salt_len);
+    print_point("salt", salt);
+
+    // Hash the salt
+    debug(DEBUG, "Hashing the salt");
+    uint8_t H_salt[32];
+    crypto_blake2b_general(H_salt, 32, NULL, 0, salt, salt_len);
+    // Use clamp() to ensure we stay on the curve in the multiply below
+    crypto_x25519_clamp(H_salt);
+    print_point("H_salt", H_salt);
+    // Multiply H(salt) by msg1->blind, save into msg2->blind_salt
+    debug(DEBUG, "Multiplying H_salt by the user's blind");
+    crypto_x25519_scalarmult(msg2->blind_salt, H_salt, msg1->blind, 256);
+    print_point("blndsalt", msg2->blind_salt);
+}
+
+int
+bsspeke_client_setup_generate_message3
+    (
+        bsspeke_setup_msg3_t *msg3,
+        const bsspeke_setup_msg2_t *msg2,
+        uint32_t phf_blocks, uint32_t phf_iterations,
+        bsspeke_client_ctx *client
+    )
+{
+    // Sanity checks first, before we do any work
+    if( phf_blocks < BSSPEKE_MIN_PHF_BLOCKS ) {
+        return -1;
+    }
+    if( phf_iterations < BSSPEKE_MIN_PHF_ITERATIONS ) {
+        return -1;
+    }
+
+    uint8_t oblivious_salt[32];
+    // Multiply the blinded salt by 1/r to get the oblivious salt
+    // Here we rely on Monocypher to do the heavy lifting for us
+    debug(DEBUG, "Removing the blind from the oblivious salt");
+    crypto_x25519_inverse(oblivious_salt, client->r, msg2->blind_salt);
+    print_point("obv_salt", oblivious_salt);
+
+    // Hash the oblivious salt together with the id's to create the salt for the PHF
+    uint8_t password_hash[64];
+
+    debug(DEBUG, "Creating the salt for the PHF");
+    uint8_t phf_salt[32];
+    {
+        crypto_blake2b_ctx hash_ctx;
+        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
+        crypto_blake2b_update(&hash_ctx, oblivious_salt, 32);
+        crypto_blake2b_update(&hash_ctx,
+                              client->client_id,
+                              client->client_id_len);
+        crypto_blake2b_update(&hash_ctx,
+                              client->server_id,
+                              client->server_id_len);
+        crypto_blake2b_final(&hash_ctx, phf_salt);
+    }
+    print_point("phf_salt", phf_salt);
+
+    debug(DEBUG, "Running the PHF to generate p and v");
+    void *work_area;
+    if ((work_area = malloc(phf_blocks * 1024)) == NULL) {
+        return -1;
+    }
+    crypto_argon2i(password_hash, 64, work_area,
+                   phf_blocks, phf_iterations,
+                   client->password, client->password_len,
+                   phf_salt, 32);
+    free(work_area);
+
+    // p || v = pwKdf(password, BlindSalt, idC, idS, settings)
+    uint8_t *p = &(password_hash[0]);
+    uint8_t *v = &(password_hash[32]);
+    // FIXME Should we clamp() v before we multiply???
+    crypto_x25519_clamp(v);
+    print_point("p", p);
+    print_point("v", v);
+
+    // Hash p onto the curve to get this user's base point P
+    //uint8_t P[32];
+    debug(DEBUG, "Hashing p onto the curve to get P");
+    crypto_hidden_to_curve(msg3->P, p);
+    print_point("P", msg3->P);
+
+    // Generate our long-term public key V = v * P
+    debug(DEBUG, "V = v * P");
+    crypto_x25519_scalarmult(msg3->V, v, msg3->P, 256);
+    print_point("V", msg3->V);
+
+    return 0;
+}
+
+
+void
+bsspeke_client_login_generate_message1
+(
+    bsspeke_login_msg1_t *msg1,
+    bsspeke_client_ctx *client
 ) {
-    ctx->server_id = server_id;
+    bsspeke_client_generate_message1(msg1, client);
 }
 
 void
-bsspeke_server_generate_message2(bsspeke_msg2_t *msg2,
-                                 const bsspeke_msg1_t *msg1,
+bsspeke_server_login_generate_message2(bsspeke_login_msg2_t *msg2,
+                                 const bsspeke_login_msg1_t *msg1,
                                  const uint8_t *salt, const size_t salt_len,
                                  uint8_t P[32], uint8_t V[32],
                                  uint32_t phf_blocks,
                                  uint32_t phf_iterations,
-                                 bsspeke_server_ctx *server_ctx
+                                 bsspeke_server_ctx *server
 ) {
     // Hash the salt
+    debug(DEBUG, "Hashing the salt");
     uint8_t H_salt[32];
     crypto_blake2b_general(H_salt, 32, NULL, 0, salt, salt_len);
     // Use clamp() to ensure we stay on the curve in the multiply below
     crypto_x25519_clamp(H_salt);
+    print_point("H_salt", H_salt);
     // Multiply H(salt) by msg1->blind, save into msg2->blind_salt
+    debug(DEBUG, "Multiplying H_salt by the user's blind");
     crypto_x25519_scalarmult(msg2->blind_salt, H_salt, msg1->blind, 256);
+    print_point("blndsalt", msg2->blind_salt);
 
     // Generate random ephemeral private key b, save it in ctx->b
-    arc4random_buf(server_ctx->b, 32);
-    crypto_x25519_clamp(server_ctx->b);
+    debug(DEBUG, "Generating ephemeral private key b");
+    arc4random_buf(server->b, 32);
+    crypto_x25519_clamp(server->b);
+    print_point("b", server->b);
+
+    debug(DEBUG, "Using client's base point P");
+    print_point("P", P);
 
     // Compute public key B = b * P, save it in msg2->B
-    crypto_x25519_scalarmult(server_ctx->B, server_ctx->b, P, 256);
+    debug(DEBUG, "Computing ephemeral public key B = b * P");
+    crypto_x25519_scalarmult(server->B, server->b, P, 256);
+    print_point("B", server->B);
 
     // Copy the public key into the outgoing message as well
-    memcpy(msg2->B, server_ctx->B, 32);
+    memcpy(msg2->B, server->B, 32);
 
     // Copy the PHF params too
     msg2->phf_blocks = phf_blocks;
@@ -121,107 +298,134 @@ bsspeke_server_generate_message2(bsspeke_msg2_t *msg2,
     return;
 }
 
+
 int
-bsspeke_client_generate_message3(bsspeke_msg3_t *msg3,
-                                 const bsspeke_msg2_t *msg2,
-                                 bsspeke_client_ctx *client_ctx
+bsspeke_client_login_generate_message3(bsspeke_login_msg3_t *msg3,
+                                 const bsspeke_login_msg2_t *msg2,
+                                 bsspeke_client_ctx *client
 ) {
     // Sanity checks first, before we do any work
     if( msg2->phf_blocks < BSSPEKE_MIN_PHF_BLOCKS ) {
+        debug(ERROR, "phf_blocks is too low.  Aborting.");
         return -1;
     }
     if( msg2->phf_iterations < BSSPEKE_MIN_PHF_ITERATIONS ) {
+        debug(ERROR, "phf_iterations is too low.  Aborting.");
         return -1;
     }
 
     uint8_t oblivious_salt[32];
     // Multiply the blinded salt by 1/r to get the oblivious salt
     // Here we rely on Monocypher to do the heavy lifting for us
-    crypto_x25519_inverse(oblivious_salt, client_ctx->r, msg2->blind_salt);
+    debug(DEBUG, "Removing the blind from the oblivious salt");
+    crypto_x25519_inverse(oblivious_salt, client->r, msg2->blind_salt);
+    print_point("obv_salt", oblivious_salt);
 
     // Hash the oblivious salt together with the id's to create the salt for the PHF
     uint8_t password_hash[64];
 
+    debug(DEBUG, "Creating the salt for the PHF");
     uint8_t phf_salt[32];
     {
         crypto_blake2b_ctx hash_ctx;
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         crypto_blake2b_update(&hash_ctx, oblivious_salt, 32);
         crypto_blake2b_update(&hash_ctx,
-                              client_ctx->client_id,
-                              client_ctx->client_id_len);
+                              client->client_id,
+                              client->client_id_len);
         crypto_blake2b_update(&hash_ctx,
-                              client_ctx->server_id,
-                              client_ctx->server_id_len);
+                              client->server_id,
+                              client->server_id_len);
         crypto_blake2b_final(&hash_ctx, phf_salt);
     }
+    print_point("phf_salt", phf_salt);
 
+    debug(DEBUG, "Running the PHF to generate p and v");
     void *work_area;
     if ((work_area = malloc(msg2->phf_blocks * 1024)) == NULL) {
+        debug(ERROR, "Failed to malloc() memory for PHF work area");
         return -1;
     }
     crypto_argon2i(password_hash, 64, work_area,
                    msg2->phf_blocks, msg2->phf_iterations,
-                   client_ctx->password, client_ctx->password_len,
+                   client->password, client->password_len,
                    phf_salt, 32);
     free(work_area);
 
     // p || v = pwKdf(password, BlindSalt, idC, idS, settings)
     uint8_t *p = &(password_hash[0]);
     uint8_t *v = &(password_hash[32]);
+    // In the setup protocol, we clamped v before using it
+    // So we should do the same here
+    crypto_x25519_clamp(v);
+    print_point("p", p);
+    print_point("v", v);
 
     // Hash p onto the curve to get this user's base point P
     uint8_t P[32];
+    debug(DEBUG, "Hashing p onto the curve to get P");
     crypto_hidden_to_curve(P, p);
+    print_point("P", P);
 
     // Generate a random ephemeral private key a, store it in ctx->a
-    arc4random_buf(client_ctx->a, 32);
-    crypto_x25519_clamp(client_ctx->a);
+    debug(DEBUG, "Generating ephemeral private key a");
+    arc4random_buf(client->a, 32);
+    crypto_x25519_clamp(client->a);
+    print_point("a", client->a);
     // Generate the ephemeral public key A = a * P, store it in msg3->A
-    crypto_x25519_scalarmult(msg3->A, client_ctx->a, P, 256);
+    debug(DEBUG, "Generating ephemeral public key A = a * P");
+    crypto_x25519_scalarmult(msg3->A, client->a, P, 256);
+    print_point("A", msg3->A);
 
     // Compute the two Diffie-Hellman shared secrets 
+    debug(DEBUG, "Computing Diffie-Hellman shared secrets");
     // DH shared secret from a * B
     uint8_t a_B[32];
-    crypto_x25519(a_B, client_ctx->a, msg2->B);
+    crypto_x25519(a_B, client->a, msg2->B);
+    print_point("a * B", a_B);
     // DH shared secret from v * B
     uint8_t v_B[32];
     crypto_x25519(v_B, v, msg2->B);
+    print_point("v * B", v_B);
 
     // Hash everything we know so far to generate our key, save it in ctx->K_c
+    debug(DEBUG, "Hashing current state to get key K_c");
     {
         crypto_blake2b_ctx hash_ctx;
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         crypto_blake2b_update(&hash_ctx,
-                              client_ctx->client_id,
-                              client_ctx->client_id_len);
+                              client->client_id,
+                              client->client_id_len);
         crypto_blake2b_update(&hash_ctx,
-                              client_ctx->server_id,
-                              client_ctx->server_id_len);
+                              client->server_id,
+                              client->server_id_len);
         crypto_blake2b_update(&hash_ctx, msg3->A, 32);
         crypto_blake2b_update(&hash_ctx, msg2->B, 32);
         crypto_blake2b_update(&hash_ctx, a_B, 32);
         crypto_blake2b_update(&hash_ctx, v_B, 32);
-        crypto_blake2b_final(&hash_ctx, client_ctx->K_c);
+        crypto_blake2b_final(&hash_ctx, client->K_c);
     }
+    print_point("K_c", client->K_c);
 
     // Hash k and the client modifier to get our verifier, save it in msg3->client_verifier
+    debug(DEBUG, "Hashing K_c and modifier to get our verifier");
     {
         crypto_blake2b_ctx hash_ctx;
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
-        crypto_blake2b_update(&hash_ctx, client_ctx->K_c, 32);
+        crypto_blake2b_update(&hash_ctx, client->K_c, 32);
         crypto_blake2b_update(&hash_ctx,
-                              BSSPEKE_VERIFY_CLIENT_MODIFIER,
+                              (uint8_t *)BSSPEKE_VERIFY_CLIENT_MODIFIER,
                               BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
         crypto_blake2b_final(&hash_ctx, msg3->client_verifier);
     }
+    print_point("client_v", msg3->client_verifier);
 
     return 0;
 }
 
 int
-bsspeke_server_generate_message4(bsspeke_msg4_t *msg4,
-                                 const bsspeke_msg3_t *msg3,
+bsspeke_server_login_generate_message4(bsspeke_login_msg4_t *msg4,
+                                 const bsspeke_login_msg3_t *msg3,
                                  bsspeke_server_ctx *server_ctx
 ) {
     // Compute the two Diffie-Hellman shared secrets
@@ -257,7 +461,7 @@ bsspeke_server_generate_message4(bsspeke_msg4_t *msg4,
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         crypto_blake2b_update(&hash_ctx, server_ctx->K_s, 32);
         crypto_blake2b_update(&hash_ctx,
-                              BSSPEKE_VERIFY_CLIENT_MODIFIER,
+                              (uint8_t *)BSSPEKE_VERIFY_CLIENT_MODIFIER,
                               BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
         crypto_blake2b_final(&hash_ctx, my_client_verifier);
     }
@@ -273,7 +477,7 @@ bsspeke_server_generate_message4(bsspeke_msg4_t *msg4,
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         crypto_blake2b_update(&hash_ctx, server_ctx->K_s, 32);
         crypto_blake2b_update(&hash_ctx,
-                              BSSPEKE_VERIFY_SERVER_MODIFIER,
+                              (uint8_t *)BSSPEKE_VERIFY_SERVER_MODIFIER,
                               BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
         crypto_blake2b_final(&hash_ctx, msg4->server_verifier);
     }
@@ -283,7 +487,7 @@ bsspeke_server_generate_message4(bsspeke_msg4_t *msg4,
 }
 
 int
-bsspeke_client_verify_message4(const bsspeke_msg4_t *msg4,
+bsspeke_client_verify_message4(const bsspeke_login_msg4_t *msg4,
                                const bsspeke_client_ctx *client_ctx
 ) {
     // Compute our own version of the server's verifier hash
@@ -293,7 +497,7 @@ bsspeke_client_verify_message4(const bsspeke_msg4_t *msg4,
         crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
         crypto_blake2b_update(&hash_ctx, client_ctx->K_c, 32);
         crypto_blake2b_update(&hash_ctx,
-                              BSSPEKE_VERIFY_SERVER_MODIFIER,
+                              (uint8_t *)BSSPEKE_VERIFY_SERVER_MODIFIER,
                               BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
         crypto_blake2b_final(&hash_ctx, my_server_verifier);
     }
@@ -307,8 +511,3 @@ bsspeke_client_verify_message4(const bsspeke_msg4_t *msg4,
     return 0;
 }
 
-int main(int argc, char *argv[])
-{
-
-    return 0;
-}
diff --git a/bsspeke.h b/bsspeke.h
index 95f3a32f0c37331dbd638b9a6c121b90204c506e..8385b2afd21df55e4169a6efcd053ed1d1d343e5 100644
--- a/bsspeke.h
+++ b/bsspeke.h
@@ -51,42 +51,121 @@ typedef struct {
 } bsspeke_client_ctx;
 
 typedef struct {
-    uint8_t *server_id;
+    uint8_t *server_id;    // Server's identifier (eg domain name)
     size_t server_id_len;
-    uint8_t *client_id;
+
+    uint8_t *client_id;    // Client's identifier (eg Matrix user_id)
     size_t client_id_len;
 
-    // Stored ECDH parameters for the given user
-    //uint8_t P[32];         // Base point
+    uint8_t P[32];         // Base point for the user
     uint8_t V[32];         // User's long-term public key
-    // Ephemeral keypair
-    uint8_t b[32];
-    uint8_t B[32];
-    // User's ephemeral public key
-    uint8_t A[32];
-    // Session key
-    uint8_t K_s[32];
+
+    uint8_t b[32];         // Ephemeral private key
+    uint8_t B[32];         // Ephemeral public key
+    
+    uint8_t A[32];         // Client's ephemeral public key
+
+    uint8_t K_s[32];       // Session key
 } bsspeke_server_ctx;
 
 typedef struct {
     char *client_id;
-    uint8_t *blind;
+    uint8_t blind[32];
 } bsspeke_msg1_t;
 
+typedef bsspeke_msg1_t bsspeke_setup_msg1_t;
+
+typedef struct {
+    uint8_t blind_salt[32];
+} bsspeke_setup_msg2_t;
+
+typedef struct {
+    uint8_t P[32];
+    uint8_t V[32];
+    uint32_t phf_blocks;
+    uint32_t phf_iterations;
+} bsspeke_setup_msg3_t;
+
+
+typedef bsspeke_msg1_t bsspeke_login_msg1_t;
+
 typedef struct {
     uint8_t blind_salt[32];
     uint8_t B[32];
     uint32_t phf_blocks;
     uint32_t phf_iterations;
-} bsspeke_msg2_t;
+} bsspeke_login_msg2_t;
 
 typedef struct {
     uint8_t A[32];
     uint8_t client_verifier[32];
-} bsspeke_msg3_t;
+} bsspeke_login_msg3_t;
 
 typedef struct {
     uint8_t server_verifier[32];
-} bsspeke_msg4_t;
+} bsspeke_login_msg4_t;
+
+void
+bsspeke_client_init(bsspeke_client_ctx *ctx,
+                    const char* client_id, const size_t client_id_len,
+                    const char* server_id, const size_t server_id_len,
+                    const char* password, const size_t password_len);
+
+void
+bsspeke_server_init(bsspeke_server_ctx *ctx,
+                    const char* server_id, const size_t server_id_len);
+
+void
+bsspeke_client_setup_generate_message1
+(
+    bsspeke_setup_msg1_t *msg1,
+    bsspeke_client_ctx *client
+);
+
+
+void
+bsspeke_server_setup_generate_message2
+(
+    bsspeke_setup_msg2_t *msg2,
+    const bsspeke_setup_msg1_t *msg1,
+    uint8_t *salt, const size_t salt_len,
+    bsspeke_server_ctx *server_ctx
+);
+
+int
+bsspeke_client_setup_generate_message3
+(
+    bsspeke_setup_msg3_t *msg3,
+    const bsspeke_setup_msg2_t *msg2,
+    uint32_t phf_blocks, uint32_t phf_iterations,
+    bsspeke_client_ctx *client
+);
+
+void
+bsspeke_client_login_generate_message1(bsspeke_login_msg1_t *msg1,
+                                 bsspeke_client_ctx *client);
+
+void
+bsspeke_server_login_generate_message2(bsspeke_login_msg2_t *msg2,
+                                 const bsspeke_login_msg1_t *msg1,
+                                 const uint8_t *salt, const size_t salt_len,
+                                 uint8_t P[32], uint8_t V[32],
+                                 uint32_t phf_blocks,
+                                 uint32_t phf_iterations,
+                                 bsspeke_server_ctx *server_ctx);
+
+int
+bsspeke_client_login_generate_message3(bsspeke_login_msg3_t *msg3,
+                                 const bsspeke_login_msg2_t *msg2,
+                                 bsspeke_client_ctx *client_ctx);
+
+int
+bsspeke_server_login_generate_message4(bsspeke_login_msg4_t *msg4,
+                                 const bsspeke_login_msg3_t *msg3,
+                                 bsspeke_server_ctx *server_ctx);
+
+int
+bsspeke_client_verify_message4(const bsspeke_login_msg4_t *msg4,
+                               const bsspeke_client_ctx *client_ctx);
 
 #endif
diff --git a/demo.c b/demo.c
new file mode 100644
index 0000000000000000000000000000000000000000..c6d36f475a7c479dad1a547e990d3521f5eece9f
--- /dev/null
+++ b/demo.c
@@ -0,0 +1,135 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bsspeke.h"
+
+int demo_setup(uint8_t salt[32],
+               uint8_t P[32],
+               uint8_t V[32])
+{
+    bsspeke_client_ctx client;
+    bsspeke_server_ctx server;
+
+    puts("");
+    puts("Starting Setup");
+
+    const char *client_id = "@alice:example.com";
+    const char *server_id = "bob.example.com";
+    const char *password = "P@ssword1";
+
+    puts("");
+    puts("Setup: Initializing client");
+    bsspeke_client_init(&client,
+                        client_id, strlen(client_id),
+                        server_id, strlen(server_id),
+                        password, strlen(password));
+
+    puts("Setup: Initializing server");
+    bsspeke_server_init(&server,
+                        server_id, strlen(server_id));
+
+    bsspeke_setup_msg1_t msg1;
+    bsspeke_setup_msg2_t msg2;
+    bsspeke_setup_msg3_t msg3;
+
+    puts("");
+    puts("Setup: Client generating message 1");
+    bsspeke_client_setup_generate_message1(&msg1, &client);
+
+    puts("");
+    puts("Setup: Server generating message 2");
+    bsspeke_server_setup_generate_message2(&msg2, &msg1,
+                                           salt, 32,
+                                           &server);
+
+    puts("");
+    puts("Setup: Client generating message 3");
+    bsspeke_client_setup_generate_message3(&msg3, &msg2,
+                                           100000, 3,
+                                           &client);
+    
+    puts("");
+    puts("Setup: Saving parameters from setup");
+    memcpy(P, msg3.P, 32);
+    memcpy(V, msg3.V, 32);
+
+    puts("");
+    puts("Setup: Done");
+
+    return 0;
+}
+
+int demo_login(uint8_t salt[32],
+               uint8_t P[32],
+               uint8_t V[32])
+{
+    bsspeke_client_ctx client;
+    bsspeke_server_ctx server;
+
+    puts("");
+    puts("Starting Login");
+
+    const char *client_id = "@alice:example.com";
+    const char *server_id = "bob.example.com";
+    const char *password = "P@ssword1";
+
+    puts("");
+    puts("Login: Initializing client");
+    bsspeke_client_init(&client,
+                        client_id, strlen(client_id),
+                        server_id, strlen(server_id),
+                        password, strlen(password));
+
+    puts("Login: Initializing server");
+    bsspeke_server_init(&server,
+                        server_id, strlen(server_id));
+
+    bsspeke_login_msg1_t msg1;
+    bsspeke_login_msg2_t msg2;
+    bsspeke_login_msg3_t msg3;
+    bsspeke_login_msg4_t msg4;
+
+    puts("");
+    puts("Login: Client generating message 1");
+    bsspeke_client_login_generate_message1(&msg1, &client);
+
+    puts("");
+    puts("Login: Server generating message 2");
+    bsspeke_server_login_generate_message2(&msg2, &msg1,
+                                           salt, 32,
+                                           P, V,
+                                           100000, 3,
+                                           &server);
+
+    puts("");
+    puts("Login: Client generating message 3");
+    bsspeke_client_login_generate_message3(&msg3, &msg2, &client);
+
+
+    return 0;
+
+}
+
+int main(int argc, char *argv[])
+{
+    // Here we're pretending to be the server's long-term storage
+    // It needs to know the salt, V, and P for each registered user
+    uint8_t salt[32];
+    uint8_t V[32];
+    uint8_t P[32];
+
+    int rc = 0;
+
+    if( (rc = demo_setup(salt, P, V)) != 0 ) {
+        puts("Setup failed :(");
+        exit(-1);
+    }
+
+    if( (rc = demo_login(salt, P, V)) != 0 ) {
+        puts("Login failed :(");
+        exit(-1);
+    }
+
+    return 0;
+}
diff --git a/doc/Login.md b/doc/Login.md
new file mode 100644
index 0000000000000000000000000000000000000000..e07e0acf39d366ed5ba9fb1d503c16b624f29f08
--- /dev/null
+++ b/doc/Login.md
@@ -0,0 +1 @@
+# BS-SPEKE Login Protocol
diff --git a/doc/Setup.md b/doc/Setup.md
new file mode 100644
index 0000000000000000000000000000000000000000..b59e65a26234c358f3f0ae00caa696a658d57d8d
--- /dev/null
+++ b/doc/Setup.md
@@ -0,0 +1,58 @@
+# BS-SPEKE Setup Protocol
+
+### Message 1 (client -> server)
+This is the same as Message 1 in the login protocol.
+
+The client sends the user's ID and a blind for the OPRF.
+
+```
+client_id: string
+blind: base64-encoded curve point
+```
+
+### Message 2 (server -> client)
+This is similar to one half of the server's Message 2 in
+the login protocol.
+
+The server multiplies its hashed salt by the client's blind
+curve point to create the blind salt.
+
+```
+blind_salt: base64-encoded curve point
+```
+
+### Message 3 (client -> server)
+Here the client finishes computation of the OPRF and returns
+its public parameters to the server.
+
+```
+P: base64-encoded curve point
+V: base64-encoded curve point
+phf_params: {
+    name: string
+    blocks: integer
+    iterations: integer
+}
+```
+
+There's a valid question here:
+"How does the server know that these values come from the real
+client, and not from a MITM?"
+
+The easy answer is that it's Trust On First Use (TOFU).
+
+A true MITM in the network would not be able to defeat the TLS
+session that protects this connection for an application like Matrix.
+
+A malicious CDN could create arbitrary accounts, but they could
+do that already with plaintext username and password.
+Moreover, this reduces the server's vulnerability to the CDN;
+now the adversary has to own the CDN at the moment when the password
+is first set up.
+Whereas before, owning the CDN at the time of any login was 
+sufficient to learn the user's password.
+And here, the CDN (and the server) still NEVER learn the 
+password.
+The most that the malicious CDN can do is to set their own fake
+password for the victim user.
+