diff --git a/services/identity/src/config.rs b/services/identity/src/config.rs
--- a/services/identity/src/config.rs
+++ b/services/identity/src/config.rs
@@ -1,11 +1,11 @@
 use curve25519_dalek::ristretto::RistrettoPoint;
 use once_cell::sync::Lazy;
 use opaque_ke::{errors::PakeError, keypair::KeyPair};
-use std::{env, fmt, fs, io, path::Path};
+use std::{env, fmt, fs, io, path};
 
 use crate::constants::{
   AUTH_TOKEN, LOCALSTACK_ENDPOINT, SECRETS_DIRECTORY, SECRETS_FILE_EXTENSION,
-  SECRETS_FILE_NAME,
+  SECRETS_FILE_NAME, SECRETS_SETUP_FILE,
 };
 
 pub static CONFIG: Lazy<Config> =
@@ -17,15 +17,18 @@
 
 #[derive(Clone)]
 pub struct Config {
+  // Opaque 1.2 server secrets
   pub server_keypair: KeyPair<RistrettoPoint>,
   // this is temporary, while the only authorized caller is ashoat's keyserver
   pub keyserver_auth_token: String,
   pub localstack_endpoint: Option<String>,
+  // Opaque 2.0 server secrets
+  pub server_setup: comm_opaque2::ServerSetup<comm_opaque2::Cipher>,
 }
 
 impl Config {
   fn load() -> Result<Self, Error> {
-    let mut path = env::current_dir()?;
+    let mut path = path::PathBuf::new();
     path.push(SECRETS_DIRECTORY);
     path.push(SECRETS_FILE_NAME);
     path.set_extension(SECRETS_FILE_EXTENSION);
@@ -34,10 +37,16 @@
       env::var(AUTH_TOKEN).unwrap_or_else(|_| String::from("test"));
     let localstack_endpoint = env::var(LOCALSTACK_ENDPOINT).ok();
 
+    let mut path = path::PathBuf::new();
+    path.push(SECRETS_DIRECTORY);
+    path.push(SECRETS_SETUP_FILE);
+    let server_setup = get_server_setup_from_file(&path)?;
+
     Ok(Self {
       server_keypair,
       keyserver_auth_token,
       localstack_endpoint,
+      server_setup,
     })
   }
 }
@@ -52,22 +61,29 @@
   }
 }
 
-#[derive(
-  Debug, derive_more::Display, derive_more::From, derive_more::Error,
-)]
+#[derive(Debug, derive_more::Display, derive_more::From)]
 pub enum Error {
   #[display(...)]
   Pake(PakeError),
   #[display(...)]
+  Opaque(comm_opaque2::ProtocolError),
+  #[display(...)]
   IO(io::Error),
   #[display(...)]
   Env(env::VarError),
 }
 
-fn get_keypair_from_file<P: AsRef<Path>>(
+fn get_keypair_from_file<P: AsRef<path::Path>>(
   path: P,
 ) -> Result<KeyPair<RistrettoPoint>, Error> {
   let bytes = fs::read(path)?;
   KeyPair::from_private_key_slice(&bytes)
     .map_err(|e| Error::Pake(PakeError::CryptoError(e)))
 }
+
+fn get_server_setup_from_file<P: AsRef<path::Path>>(
+  path: &P,
+) -> Result<comm_opaque2::ServerSetup<comm_opaque2::Cipher>, Error> {
+  let bytes = fs::read(path)?;
+  comm_opaque2::ServerSetup::deserialize(&bytes).map_err(Error::Opaque)
+}
diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs
--- a/services/identity/src/constants.rs
+++ b/services/identity/src/constants.rs
@@ -3,6 +3,7 @@
 pub const SECRETS_DIRECTORY: &str = "secrets";
 pub const SECRETS_FILE_NAME: &str = "secret_key";
 pub const SECRETS_FILE_EXTENSION: &str = "txt";
+pub const SECRETS_SETUP_FILE: &str = "server_setup.txt";
 
 // DynamoDB
 
diff --git a/services/identity/src/keygen.rs b/services/identity/src/keygen.rs
--- a/services/identity/src/keygen.rs
+++ b/services/identity/src/keygen.rs
@@ -1,24 +1,41 @@
-use crate::constants::{SECRETS_FILE_EXTENSION, SECRETS_FILE_NAME};
+use crate::constants::{
+  SECRETS_FILE_EXTENSION, SECRETS_FILE_NAME, SECRETS_SETUP_FILE,
+};
 use comm_opaque::Cipher;
 use opaque_ke::{ciphersuite::CipherSuite, rand::rngs::OsRng};
-use std::{env, fs, io};
+use std::{fs, io, path};
 
 pub fn generate_and_persist_keypair(dir: &str) -> Result<(), io::Error> {
+  let mut secrets_dir = path::PathBuf::new();
+  secrets_dir.push(dir);
+  if !secrets_dir.exists() {
+    println!("Creating secrets directory {:?}", secrets_dir);
+    fs::create_dir(&secrets_dir)?;
+  }
+
+  // Opaque_ke 1.2.0 setup
   let mut rng = OsRng;
   let server_kp = Cipher::generate_random_keypair(&mut rng);
-  let mut path = env::current_dir()?;
-  path.push(dir);
-  if !path.exists() {
-    println!("Creating secrets directory {:?}", path);
-    fs::create_dir(&path)?;
-  }
+  let mut path = secrets_dir.clone();
   path.push(SECRETS_FILE_NAME);
   path.set_extension(SECRETS_FILE_EXTENSION);
+  if !path.exists() {
+    println!("Writing secret key to {:?}", path);
+    fs::write(&path, server_kp.private().to_arr())?;
+  } else {
+    println!("{:?} already exists, skipping", path);
+  }
+
+  // Opaque 2.0 setup
+  let server_setup = comm_opaque2::server::generate_server_setup();
+  let mut path = secrets_dir.clone();
+  path.push(SECRETS_SETUP_FILE);
   if path.exists() {
-    println!("{:?} already exists", path);
-    return Ok(());
+    eprintln!("{:?} already exists, skipping", path);
+  } else {
+    println!("Writing setup file to {:?}", path);
+    fs::write(&path, server_setup.serialize())?;
   }
-  println!("Writing secret key to {:?}", path);
-  fs::write(&path, server_kp.private().to_arr())?;
+
   Ok(())
 }
diff --git a/shared/comm-opaque2/src/lib.rs b/shared/comm-opaque2/src/lib.rs
--- a/shared/comm-opaque2/src/lib.rs
+++ b/shared/comm-opaque2/src/lib.rs
@@ -5,6 +5,9 @@
 pub mod server;
 
 pub use crate::opaque::Cipher;
+pub use opaque_ke;
+pub use opaque_ke::errors::ProtocolError;
+pub use opaque_ke::ServerSetup;
 
 #[test]
 pub fn test_register_and_login() {
diff --git a/shared/comm-opaque2/src/server/mod.rs b/shared/comm-opaque2/src/server/mod.rs
--- a/shared/comm-opaque2/src/server/mod.rs
+++ b/shared/comm-opaque2/src/server/mod.rs
@@ -3,3 +3,12 @@
 
 pub use login::Login;
 pub use register::Registration;
+
+use opaque_ke::rand::rngs::OsRng;
+use opaque_ke::ServerSetup;
+
+use crate::Cipher;
+
+pub fn generate_server_setup() -> ServerSetup<Cipher> {
+  ServerSetup::<Cipher>::new(&mut OsRng)
+}