diff --git a/go.mod b/go.mod
index 9f0fa8826937a5ffa41bf934cbb6ab8c398064c3..972eabec2f74a0a8d68db051612aeeac9cce1b6c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,11 +7,11 @@ require (
 	github.com/hack-pad/go-indexeddb v0.2.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	gitlab.com/elixxir/client v1.5.1-0.20221022003714-8980452f25e5
-	gitlab.com/elixxir/crypto v0.0.7-0.20221022003355-d8a6158b32a7
+	gitlab.com/elixxir/client v1.5.1-0.20221024210920-177a44943bef
+	gitlab.com/elixxir/crypto v0.0.7-0.20221024012326-cf941c375c1f
 	gitlab.com/elixxir/primitives v0.0.3-0.20221017172918-6176818d1aba
 	gitlab.com/xx_network/crypto v0.0.5-0.20221017172404-b384a8d8b171
-	gitlab.com/xx_network/primitives v0.0.4-0.20221017171439-42169a3e5c0d
+	gitlab.com/xx_network/primitives v0.0.4-0.20221020224054-cab1210c4841
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 )
 
@@ -23,26 +23,40 @@ require (
 	github.com/cloudflare/circl v1.2.0 // indirect
 	github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
 	github.com/elliotchance/orderedmap v1.4.0 // indirect
+	github.com/fsnotify/fsnotify v1.5.4 // indirect
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/improbable-eng/grpc-web v0.15.0 // indirect
+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.11.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.1.0 // indirect
+	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
+	github.com/pelletier/go-toml v1.9.5 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.2 // indirect
+	github.com/pkg/profile v1.6.0 // indirect
 	github.com/rs/cors v1.8.2 // indirect
 	github.com/sethvargo/go-diceware v0.3.0 // indirect
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
+	github.com/spf13/afero v1.9.2 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
+	github.com/spf13/cobra v1.5.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/spf13/viper v1.12.0 // indirect
+	github.com/subosito/gotenv v1.4.0 // indirect
 	github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
 	github.com/ttacon/libphonenumber v1.2.1 // indirect
 	github.com/tyler-smith/go-bip39 v1.1.0 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f // indirect
-	gitlab.com/elixxir/comms v0.0.4-0.20221017173926-4eaa6061dfaa // indirect
+	gitlab.com/elixxir/comms v0.0.4-0.20221024050701-bced94c1b026 // indirect
 	gitlab.com/elixxir/ekv v0.2.1 // indirect
 	gitlab.com/xx_network/comms v0.0.4-0.20221017172508-09e33697dc15 // indirect
 	gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 // indirect
@@ -54,5 +68,8 @@ require (
 	google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
 	google.golang.org/grpc v1.49.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/ini.v1 v1.66.6 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	nhooyr.io/websocket v1.8.7 // indirect
 )
diff --git a/go.sum b/go.sum
index 9ac20560b7d4e69c4029a5a9b2627379b5d6c9e0..a828bf7d433d006ba57b34dd6bdbb88e6eb018ce 100644
--- a/go.sum
+++ b/go.sum
@@ -165,6 +165,7 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
 github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -330,6 +331,7 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
@@ -347,6 +349,7 @@ github.com/improbable-eng/grpc-web v0.12.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6
 github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU=
 github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ=
 github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -402,6 +405,7 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U
 github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea h1:uyJ13zfy6l79CM3HnVhDalIyZ4RJAyVfDrbnfFeJoC4=
 github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea/go.mod h1:w4pGU9PkiX2hAWyF0yuHEHmYTQFAd6WHzp6+IY7JVjE=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
 github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -434,6 +438,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -479,8 +484,10 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
+github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
 github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
@@ -490,6 +497,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
 github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -556,14 +564,19 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
 github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
 github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
 github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -582,6 +595,7 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
+github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
 github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
@@ -616,15 +630,18 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
-gitlab.com/elixxir/client v1.5.1-0.20221022003714-8980452f25e5 h1:8SsC+GZYTZBDMn47OpSBc1kRVvFpx0IYZG6B8sL9BzU=
-gitlab.com/elixxir/client v1.5.1-0.20221022003714-8980452f25e5/go.mod h1:a5pLHWVKdwacbyL9yGlV/xP5xHbhC2ycqTbEsDyzjo4=
-gitlab.com/elixxir/comms v0.0.4-0.20221017173926-4eaa6061dfaa h1:/FEpu0N0rAyq74FkvO3uY8BcQoWLSbVPhj/s5QfscZw=
-gitlab.com/elixxir/comms v0.0.4-0.20221017173926-4eaa6061dfaa/go.mod h1:rW7xdbHntP2MoF3q+2+f+IR8OHol94MRyviotfR5rXg=
+gitlab.com/elixxir/client v1.5.1-0.20221024201221-688c766ef5c7 h1:dhWRgVnk7+9tqWSc3QySyzdV6IPMcrmpEU+3gb8ivS4=
+gitlab.com/elixxir/client v1.5.1-0.20221024201221-688c766ef5c7/go.mod h1:KVllKaLZKT9fLruj+0woZK5t8JKZNJTDJLsCNF+yIqQ=
+gitlab.com/elixxir/client v1.5.1-0.20221024202334-80c87bc2908c h1:deQEaPPJrjkmy6ij8RzxLFm3hfBLu3iZT/y0vmzoYno=
+gitlab.com/elixxir/client v1.5.1-0.20221024202334-80c87bc2908c/go.mod h1:KVllKaLZKT9fLruj+0woZK5t8JKZNJTDJLsCNF+yIqQ=
+gitlab.com/elixxir/client v1.5.1-0.20221024210920-177a44943bef h1:ZrL1sZfNMblXZRcUYv1QkSskT50fiYhbo3nEXwzuI+w=
+gitlab.com/elixxir/client v1.5.1-0.20221024210920-177a44943bef/go.mod h1:KVllKaLZKT9fLruj+0woZK5t8JKZNJTDJLsCNF+yIqQ=
+gitlab.com/elixxir/comms v0.0.4-0.20221024050701-bced94c1b026 h1:CdqvzyM91wN6u4MmGj0n+gKO/0tJabWPN3EQ4SFsZsg=
+gitlab.com/elixxir/comms v0.0.4-0.20221024050701-bced94c1b026/go.mod h1:NevrBdsi5wJvitUeMsid3xI1FrzzuzfxKy4Bapnhzao=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
-gitlab.com/elixxir/crypto v0.0.7-0.20221017173452-565da4101a3b/go.mod h1:1rftbwSVdy49LkBIkPr+w+P2mDOerYeBKoZuB3r0yqI=
-gitlab.com/elixxir/crypto v0.0.7-0.20221022003355-d8a6158b32a7 h1:+8DHBxZxJcmJSmcUFK4ZjjXgwV3wSo9O4+4NCaLdO4c=
-gitlab.com/elixxir/crypto v0.0.7-0.20221022003355-d8a6158b32a7/go.mod h1:P/S3pEPYl7fuHQ1m4mL2pIaCxAjYIXrJml/pnfofI+U=
+gitlab.com/elixxir/crypto v0.0.7-0.20221024012326-cf941c375c1f h1:ku5gWZnvgs8TPHfGIOfKO5QPnRJl5fsSAic5H1Y/QRg=
+gitlab.com/elixxir/crypto v0.0.7-0.20221024012326-cf941c375c1f/go.mod h1:P/S3pEPYl7fuHQ1m4mL2pIaCxAjYIXrJml/pnfofI+U=
 gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is=
 gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
@@ -643,8 +660,9 @@ gitlab.com/xx_network/crypto v0.0.5-0.20221017172404-b384a8d8b171/go.mod h1:sZ5b
 gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA=
 gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
 gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
-gitlab.com/xx_network/primitives v0.0.4-0.20221017171439-42169a3e5c0d h1:rOcGGx5SrhBkbD1P8JqbtqBBeuGhOSZxETytNGJcf/U=
 gitlab.com/xx_network/primitives v0.0.4-0.20221017171439-42169a3e5c0d/go.mod h1:AXVVFt7dDAeIUpOGPiStCcUIKsBXLWbmV/BgZ4T+tOo=
+gitlab.com/xx_network/primitives v0.0.4-0.20221020224054-cab1210c4841 h1:dhctONxd94XP4wLye22EuZvQoeFNXXlkkE47VQSlbGs=
+gitlab.com/xx_network/primitives v0.0.4-0.20221020224054-cab1210c4841/go.mod h1:AXVVFt7dDAeIUpOGPiStCcUIKsBXLWbmV/BgZ4T+tOo=
 gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 h1:eJZrXqHsMmmejEPWw8gNAt0I8CGAMNO/7C339Zco3TM=
 gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -1208,6 +1226,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
 gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go
index 410358d773fb486d31896dfe18fda0f8a29600b1..d9d2f7026d246f95a5e45def3863083805ae9434 100644
--- a/indexedDb/implementation.go
+++ b/indexedDb/implementation.go
@@ -39,6 +39,7 @@ const dbTimeout = time.Second
 // channel.
 type wasmModel struct {
 	db                *idb.Database
+	cipher            cryptoChannel.Cipher
 	receivedMessageCB MessageReceivedCallback
 	updateMux         sync.Mutex
 }
@@ -206,6 +207,16 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID,
 	timestamp time.Time, lease time.Duration, round rounds.Round,
 	mType channels.MessageType, status channels.SentStatus) uint64 {
 
+	// Handle encryption, if it is present
+	if w.cipher != nil {
+		cipherText, err := w.cipher.Encrypt([]byte(text))
+		if err != nil {
+			jww.ERROR.Printf("Failed to encrypt Message: %+v", err)
+			return 0
+		}
+		text = string(cipherText)
+	}
+
 	msgToInsert := buildMessage(
 		channelID.Marshal(), messageID.Bytes(), nil, nickname, text, pubKey,
 		codeset, timestamp, lease, round.ID, mType, status)
diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go
index 68a8dfeb6f04a8a1e1f9c141d9e07fa14f45019f..1de2f45703ac07ff1d1bebd7bdec959d006fe7ef 100644
--- a/indexedDb/implementation_test.go
+++ b/indexedDb/implementation_test.go
@@ -12,6 +12,8 @@ package indexedDb
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/hack-pad/go-indexeddb/idb"
+	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"gitlab.com/xx_network/primitives/netTime"
 	"os"
 	"strconv"
@@ -37,7 +39,7 @@ func dummyCallback(uint64, *id.ID, bool) {}
 func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 	testString := "test"
 	testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
-	eventModel, err := newWASMModel(testString, dummyCallback)
+	eventModel, err := newWASMModel(testString, nil, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -90,7 +92,8 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 
 // Smoke test wasmModel.JoinChannel/wasmModel.LeaveChannel happy paths.
 func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
-	eventModel, err := newWASMModel("test", dummyCallback)
+	storage.GetLocalStorage().Clear()
+	eventModel, err := newWASMModel("test", nil, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -129,7 +132,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
 // Test UUID gets returned when different messages are added.
 func Test_wasmModel_UUIDTest(t *testing.T) {
 	testString := "testHello"
-	eventModel, err := newWASMModel(testString, dummyCallback)
+	eventModel, err := newWASMModel(testString, nil, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -162,8 +165,9 @@ func Test_wasmModel_UUIDTest(t *testing.T) {
 
 // Tests if the same message ID being sent always returns the same UUID.
 func Test_wasmModel_DuplicateReceives(t *testing.T) {
+	storage.GetLocalStorage().Clear()
 	testString := "testHello"
-	eventModel, err := newWASMModel(testString, dummyCallback)
+	eventModel, err := newWASMModel(testString, nil, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -200,7 +204,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 	testString := "test_deleteMsgByChannel"
 	totalMessages := 10
 	expectedMessages := 5
-	eventModel, err := newWASMModel(testString, dummyCallback)
+	eventModel, err := newWASMModel(testString, nil, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -249,3 +253,55 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 		t.Errorf("Expected %d messages, got %d", expectedMessages, len(result))
 	}
 }
+
+// This test is designed to prove the behavior of unique indexes.
+// Inserts will not fail, they simply will not happen.
+func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
+	testString := "test_receiveHelper_UniqueIndex"
+	eventModel, err := newWASMModel(testString, nil, dummyCallback)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Ensure index is unique
+	txn, err := eventModel.db.Transaction(idb.TransactionReadOnly, messageStoreName)
+	if err != nil {
+		t.Fatal(err)
+	}
+	store, err := txn.ObjectStore(messageStoreName)
+	if err != nil {
+		t.Fatal(err)
+	}
+	idx, err := store.Index(messageStoreMessageIndex)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if isUnique, err := idx.Unique(); !isUnique {
+		t.Fatalf("Index is not unique!")
+	} else if err != nil {
+		t.Fatal(err)
+	}
+
+	// First message insert should succeed
+	testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
+	testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
+		testString, testString, []byte{8, 6, 7, 5}, 0, netTime.Now(),
+		time.Second, 0, 0, channels.Sent)
+	_, err = eventModel.receiveHelper(testMsg)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// The duplicate entry won't fail, it just silently shouldn't happen
+	_, err = eventModel.receiveHelper(testMsg)
+	if err != nil {
+		t.Fatalf("%+v", err)
+	}
+	results, err := eventModel.dump(messageStoreName)
+	if err != nil {
+		t.Fatalf("%+v", err)
+	}
+	if len(results) != 1 {
+		t.Fatalf("Expected only a single message, got %d", len(results))
+	}
+}
diff --git a/indexedDb/init.go b/indexedDb/init.go
index 651fb1e1c51a16202373065aa71e6a89e0d181e5..271300b4bafcec519a1b15ea05518fb0cfae5d6a 100644
--- a/indexedDb/init.go
+++ b/indexedDb/init.go
@@ -10,6 +10,8 @@
 package indexedDb
 
 import (
+	"github.com/pkg/errors"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"syscall/js"
 
@@ -38,25 +40,25 @@ type MessageReceivedCallback func(uuid uint64, channelID *id.ID, update bool)
 // NewWASMEventModelBuilder returns an EventModelBuilder which allows
 // the channel manager to define the path but the callback is the same
 // across the board.
-func NewWASMEventModelBuilder(
+func NewWASMEventModelBuilder(encryption cryptoChannel.Cipher,
 	cb MessageReceivedCallback) channels.EventModelBuilder {
 	fn := func(path string) (channels.EventModel, error) {
-		return NewWASMEventModel(path, cb)
+		return NewWASMEventModel(path, encryption, cb)
 	}
 	return fn
 }
 
 // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel.
 // The name should be a base64 encoding of the users public key.
-func NewWASMEventModel(path string, cb MessageReceivedCallback) (
-	channels.EventModel, error) {
+func NewWASMEventModel(path string, encryption cryptoChannel.Cipher,
+	cb MessageReceivedCallback) (channels.EventModel, error) {
 	databaseName := path + databaseSuffix
-	return newWASMModel(databaseName, cb)
+	return newWASMModel(databaseName, encryption, cb)
 }
 
 // newWASMModel creates the given [idb.Database] and returns a wasmModel.
-func newWASMModel(databaseName string, cb MessageReceivedCallback) (
-	*wasmModel, error) {
+func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
+	cb MessageReceivedCallback) (*wasmModel, error) {
 	// Attempt to open database object
 	ctx, cancel := newContext()
 	defer cancel()
@@ -88,8 +90,45 @@ func newWASMModel(databaseName string, cb MessageReceivedCallback) (
 
 	// Wait for database open to finish
 	db, err := openRequest.Await(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	// FIXME: The below is a hack that for some reason prevents moving on with
+	//        uninitialized database despite the previous call to Await.
+	//        It would be idea to find a different solution.
+	// Close and open again to ensure the state is finalized
+	err = db.Close()
+	if err != nil {
+		return nil, err
+	}
+	openRequest, err = idb.Global().Open(ctx, databaseName, currentVersion,
+		func(db *idb.Database, oldVersion, newVersion uint) error {
+			return nil
+		})
+	if err != nil {
+		return nil, err
+	}
+	// Wait for database open to finish
+	db, err = openRequest.Await(ctx)
+	if err != nil {
+		return nil, err
+	}
 
-	return &wasmModel{db: db, receivedMessageCB: cb}, err
+	encryptionStatus := encryption != nil
+	loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus(
+		databaseName, encryptionStatus)
+	if err != nil {
+		return nil, err
+	}
+
+	if encryptionStatus != loadedEncryptionStatus {
+		return nil, errors.New(
+			"Cannot load database with different encryption status.")
+	} else if !encryptionStatus {
+		jww.WARN.Printf("IndexedDb encryption disabled!")
+	}
+	return &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption}, err
 }
 
 // v1Upgrade performs the v0 -> v1 database upgrade.
diff --git a/main.go b/main.go
index b1507bb7e21fe27331c35616718b5ef2c96e06d5..7c5f570cf47b27cad75d3e65ba1465bd0590667e 100644
--- a/main.go
+++ b/main.go
@@ -73,13 +73,21 @@ func main() {
 		js.FuncOf(wasm.NewChannelsManagerWithIndexedDb))
 	js.Global().Set("LoadChannelsManagerWithIndexedDb",
 		js.FuncOf(wasm.LoadChannelsManagerWithIndexedDb))
+	js.Global().Set("LoadChannelsManagerWithIndexedDbUnsafe",
+		js.FuncOf(wasm.LoadChannelsManagerWithIndexedDbUnsafe))
+	js.Global().Set("NewChannelsManagerWithIndexedDbUnsafe",
+		js.FuncOf(wasm.NewChannelsManagerWithIndexedDbUnsafe))
 	js.Global().Set("GenerateChannel", js.FuncOf(wasm.GenerateChannel))
+	js.Global().Set("GetSavedChannelPrivateKeyUNSAFE",
+		js.FuncOf(wasm.GetSavedChannelPrivateKeyUNSAFE))
 	js.Global().Set("DecodePublicURL", js.FuncOf(wasm.DecodePublicURL))
 	js.Global().Set("DecodePrivateURL", js.FuncOf(wasm.DecodePrivateURL))
 	js.Global().Set("GetChannelJSON", js.FuncOf(wasm.GetChannelJSON))
 	js.Global().Set("GetChannelInfo", js.FuncOf(wasm.GetChannelInfo))
 	js.Global().Set("GetShareUrlType", js.FuncOf(wasm.GetShareUrlType))
 	js.Global().Set("IsNicknameValid", js.FuncOf(wasm.IsNicknameValid))
+	js.Global().Set("NewChannelsDatabaseCipher",
+		js.FuncOf(wasm.NewChannelsDatabaseCipher))
 
 	// wasm/cmix.go
 	js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix))
diff --git a/storage/indexedDbEncryptionTrack.go b/storage/indexedDbEncryptionTrack.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e52a2648d3ea076578e9697fa3e9b591edf043f
--- /dev/null
+++ b/storage/indexedDbEncryptionTrack.go
@@ -0,0 +1,37 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package storage
+
+import (
+	"github.com/pkg/errors"
+	"os"
+)
+
+// Key to store if the database is encrypted or not
+const databaseEncryptionToggleKey = "xxdkWasmDatabaseEncryptionToggle/"
+
+// StoreIndexedDbEncryptionStatus stores the encryption status if it has not
+// been previously saved. If it has, it returns its value.
+func StoreIndexedDbEncryptionStatus(
+	databaseName string, encryption bool) (bool, error) {
+	data, err := GetLocalStorage().GetItem(
+		databaseEncryptionToggleKey + databaseName)
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			GetLocalStorage().SetItem(
+				databaseEncryptionToggleKey+databaseName, []byte{1})
+			return encryption, nil
+		} else {
+			return false, err
+		}
+	}
+
+	return data[0] == 1, nil
+}
diff --git a/storage/version.go b/storage/version.go
index 263ad6a9c4c17762d84b6229f50b5c371fea2e65..e667145c9260470e6d367ddb75f952f17b714363 100644
--- a/storage/version.go
+++ b/storage/version.go
@@ -17,7 +17,7 @@ import (
 )
 
 // SEMVER is the current semantic version of xxDK WASM.
-const SEMVER = "0.0.0"
+const SEMVER = "0.1.0"
 
 // Storage keys.
 const (
diff --git a/wasm/channels.go b/wasm/channels.go
index ebb8a4119e2d7a12891f3392d430540dcd9b096f..dc68f919fef27270f4479c44db549182d1d942a1 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -94,7 +94,7 @@ func GenerateChannelIdentity(_ js.Value, args []js.Value) interface{} {
 	return utils.CopyBytesToJS(pi)
 }
 
-// identityMap stores identifies previously generated by ConstructIdentity.
+// identityMap stores identities previously generated by ConstructIdentity.
 var identityMap sync.Map
 
 // ConstructIdentity constructs a [channel.Identity] from a user's public key
@@ -325,21 +325,72 @@ func LoadChannelsManager(_ js.Value, args []js.Value) interface{} {
 //    returned as an int and the channelID as a Uint8Array. The row in the
 //    database that was updated can be found using the UUID. The channel ID is
 //    provided so that the recipient can filter if they want to the processes
+//    the update now or not. An "update" bool is present which tells you if the
+//    row is new or if it is an edited old row.
+//  - args[3] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//    object with [NewChannelsDatabaseCipher] and get its id with
+//    [ChannelDbCipher.GetID].
+//
+// Returns a promise:
+//  - Resolves to a Javascript representation of the [ChannelsManager] object.
+//  - Rejected with an error if loading indexedDb or the manager fails.
+//  - Throws a TypeError if the cipher ID does not correspond to a cipher.
+func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+	cmixID := args[0].Int()
+	privateIdentity := utils.CopyBytesToGo(args[1])
+	cipherID := args[3].Int()
+
+	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return newChannelsManagerWithIndexedDb(cmixID, privateIdentity, args[2], cipher)
+}
+
+// NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a
+// new private identity ([channel.PrivateIdentity]) and using indexedDb as a
+// backend to manage the event model. However, the data is written in plain text
+// and not encrypted. It is recommended that you do not use this in production.
+//
+// This is for creating a manager for an identity for the first time. For
+// generating a new one channel identity, use [GenerateChannelIdentity]. To
+// reload this channel manager, use [LoadChannelsManagerWithIndexedDbUnsafe],
+// passing in the storage tag retrieved by [ChannelsManager.GetStorageTag].
+//
+// This function initialises an indexedDb database.
+//
+// Parameters:
+//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
+//    using [Cmix.GetID].
+//  - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//    generated by [GenerateChannelIdentity] (Uint8Array).
+//  - args[2] - Function that takes in the same parameters as
+//    [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
+//    returned as an int and the channelID as a Uint8Array. The row in the
+//    database that was updated can be found using the UUID. The channel ID is
+//    provided so that the recipient can filter if they want to the processes
 //    the update now or not. An "update" bool is present which tells you if
 //	  the row is new or if it is an edited old row
 //
 // Returns a promise:
 //  - Resolves to a Javascript representation of the [ChannelsManager] object.
 //  - Rejected with an error if loading indexedDb or the manager fails.
-func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) interface{} {
 	cmixID := args[0].Int()
 	privateIdentity := utils.CopyBytesToGo(args[1])
 
+	return newChannelsManagerWithIndexedDb(cmixID, privateIdentity, args[2], nil)
+}
+
+func newChannelsManagerWithIndexedDb(cmixID int, privateIdentity []byte,
+	cb js.Value, cipher *bindings.ChannelDbCipher) interface{} {
+
 	fn := func(uuid uint64, channelID *id.ID, update bool) {
-		args[2].Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
+		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
 	}
 
-	model := indexedDb.NewWASMEventModelBuilder(fn)
+	model := indexedDb.NewWASMEventModelBuilder(cipher, fn)
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
@@ -372,21 +423,69 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
 //    returned as an int and the channelID as a Uint8Array. The row in the
 //    database that was updated can be found using the UUID. The channel ID is
 //    provided so that the recipient can filter if they want to the processes
+//    the update now or not. An "update" bool is present which tells you if the
+//    row is new or if it is an edited old row.
+//  - args[3] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//    object with [NewChannelsDatabaseCipher] and get its id with
+//    [ChannelDbCipher.GetID].
+//
+// Returns a promise:
+//  - Resolves to a Javascript representation of the [ChannelsManager] object.
+//  - Rejected with an error if loading indexedDb or the manager fails.
+//  - Throws a TypeError if the cipher ID does not correspond to a cipher.
+func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+	cmixID := args[0].Int()
+	storageTag := args[1].String()
+	cipherID := args[3].Int()
+
+	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return loadChannelsManagerWithIndexedDb(cmixID, storageTag, args[2], cipher)
+}
+
+// LoadChannelsManagerWithIndexedDbUnsafe loads an existing [ChannelsManager]
+// using an existing indexedDb database as a backend to manage the event model.
+// This should only be used to load unsafe channel managers created by
+// [NewChannelsManagerWithIndexedDbUnsafe].
+//
+// This is for loading a manager for an identity that has already been created.
+// The channel manager should have previously been created with
+// [NewChannelsManagerWithIndexedDb] and the storage is retrievable with
+// [ChannelsManager.GetStorageTag].
+//
+// Parameters:
+//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
+//    using [Cmix.GetID].
+//  - args[1] - The storage tag associated with the previously created channel
+//    manager and retrieved with [ChannelsManager.GetStorageTag] (string).
+//  - args[2] - Function that takes in the same parameters as
+//    [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
+//    returned as an int and the channelID as a Uint8Array. The row in the
+//    database that was updated can be found using the UUID. The channel ID is
+//    provided so that the recipient can filter if they want to the processes
 //    the update now or not. An "update" bool is present which tells you if
 //	  the row is new or if it is an edited old row
 //
 // Returns a promise:
 //  - Resolves to a Javascript representation of the [ChannelsManager] object.
 //  - Rejected with an error if loading indexedDb or the manager fails.
-func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) interface{} {
 	cmixID := args[0].Int()
 	storageTag := args[1].String()
 
+	return loadChannelsManagerWithIndexedDb(cmixID, storageTag, args[2], nil)
+}
+
+func loadChannelsManagerWithIndexedDb(cmixID int, storageTag string,
+	cb js.Value, cipher *bindings.ChannelDbCipher) interface{} {
 	fn := func(uuid uint64, channelID *id.ID, updated bool) {
-		args[2].Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
+		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
 	}
 
-	model := indexedDb.NewWASMEventModelBuilder(fn)
+	model := indexedDb.NewWASMEventModelBuilder(cipher, fn)
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
@@ -448,6 +547,29 @@ func GenerateChannel(_ js.Value, args []js.Value) interface{} {
 	return utils.CopyBytesToJS(gen)
 }
 
+// GetSavedChannelPrivateKeyUNSAFE loads the private key from storage for the
+// given channel ID.
+//
+// NOTE: This function is unsafe and only for debugging purposes only.
+//
+// Parameters:
+//  - args[0] - ID of [Cmix] object in tracker (int).
+//  - args[1] - The [id.ID] of the channel in base 64 encoding (string).
+//
+// Returns:
+//  - The PEM file of the private key (string).
+//  - Throws a TypeError if retrieving the [Cmix] object or the private key
+//    fails.
+func GetSavedChannelPrivateKeyUNSAFE(_ js.Value, args []js.Value) interface{} {
+	privKey, err := bindings.GetSavedChannelPrivateKeyUNSAFE(args[0].Int(), args[1].String())
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return privKey
+}
+
 // DecodePublicURL decodes the channel URL into a channel pretty print. This
 // function can only be used for public channel URLs. To get the privacy level
 // of a channel URL, use [GetShareUrlType].
@@ -517,7 +639,7 @@ func GetChannelJSON(_ js.Value, args []js.Value) interface{} {
 		return nil
 	}
 
-	return c
+	return utils.CopyBytesToJS(c)
 }
 
 // GetChannelInfo returns the info about a channel from its public description.
@@ -547,7 +669,7 @@ func GetChannelInfo(_ js.Value, args []js.Value) interface{} {
 //
 // Parameters:
 //  - args[0] - A portable channel string. Should be received from another user
-//    or generated via GenerateChannel (string).
+//    or generated via [GenerateChannel] (string).
 //
 // The pretty print will be of the format:
 //  <Speakeasy-v2:Test_Channel|description:Channel description.|level:Public|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=>
@@ -1274,3 +1396,108 @@ func (em *eventModel) UpdateSentStatus(
 	em.updateSentStatus(
 		uuid, utils.CopyBytesToJS(messageID), timestamp, roundID, status)
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Channel Cipher                                                             //
+////////////////////////////////////////////////////////////////////////////////
+
+// ChannelDbCipher wraps the [bindings.ChannelDbCipher] object so its methods
+// can be wrapped to be Javascript compatible.
+type ChannelDbCipher struct {
+	api *bindings.ChannelDbCipher
+}
+
+// newChannelDbCipherJS creates a new Javascript compatible object
+// (map[string]interface{}) that matches the [ChannelDbCipher] structure.
+func newChannelDbCipherJS(api *bindings.ChannelDbCipher) map[string]interface{} {
+	c := ChannelDbCipher{api}
+	channelDbCipherMap := map[string]interface{}{
+		"GetID":   js.FuncOf(c.GetID),
+		"Encrypt": js.FuncOf(c.Encrypt),
+		"Decrypt": js.FuncOf(c.Decrypt),
+	}
+
+	return channelDbCipherMap
+}
+
+// NewChannelsDatabaseCipher constructs a [ChannelDbCipher] object.
+//
+// Parameters:
+//  - args[0] - The tracked [Cmix] object ID (int).
+//  - args[1] - The password for storage. This should be the same password
+//    passed into [NewCmix] (Uint8Array).
+//  - args[2] - The maximum size of a payload to be encrypted. A payload passed
+//    into [ChannelDbCipher.Encrypt] that is larger than this value will result
+//    in an error (int).
+//
+// Returns:
+//   - JavaScript representation of the [ChannelDbCipher] object.
+//   - Throws a TypeError if creating the cipher fails.
+func NewChannelsDatabaseCipher(_ js.Value, args []js.Value) interface{} {
+	cmixId := args[0].Int()
+	password := utils.CopyBytesToGo(args[1])
+	plaintTextBlockSize := args[2].Int()
+
+	cipher, err := bindings.NewChannelsDatabaseCipher(
+		cmixId, password, plaintTextBlockSize)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return newChannelDbCipherJS(cipher)
+}
+
+// GetID returns the ID for this [bindings.ChannelDbCipher] in the
+// channelDbCipherTracker.
+//
+// Returns:
+//  - Tracker ID (int).
+func (c *ChannelDbCipher) GetID(js.Value, []js.Value) interface{} {
+	return c.api.GetID()
+}
+
+// Encrypt will encrypt the raw data. It will return a ciphertext. Padding is
+// done on the plaintext so all encrypted data looks uniform at rest.
+//
+// Parameters:
+//  - args[0] - The data to be encrypted (Uint8Array). This must be smaller than
+//    the block size passed into [NewChannelsDatabaseCipher]. If it is larger,
+//    this will return an error.
+//
+// Returns:
+//   - The ciphertext of the plaintext passed in (Uint8Array).
+//   - Throws a TypeError if it fails to encrypt the plaintext.
+func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) interface{} {
+
+	ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return utils.CopyBytesToJS(ciphertext)
+
+}
+
+// Decrypt will decrypt the passed in encrypted value. The plaintext will be
+// returned by this function. Any padding will be discarded within this
+// function.
+//
+// Parameters:
+//  - args[0] - the encrypted data returned by [ChannelDbCipher.Encrypt]
+//    (Uint8Array).
+//
+// Returns:
+//   - The plaintext of the ciphertext passed in (Uint8Array).
+//   - Throws a TypeError if it fails to encrypt the plaintext.
+func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) interface{} {
+	plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return utils.CopyBytesToJS(plaintext)
+
+}
diff --git a/wasm/channels_test.go b/wasm/channels_test.go
index 2e19cffdb62ef95ca5c3d2b70d726d619ba66797..127657af1507d6389bdf94e7fb920f7e1dddb2de 100644
--- a/wasm/channels_test.go
+++ b/wasm/channels_test.go
@@ -60,6 +60,47 @@ func Test_ChannelsManagerMethods(t *testing.T) {
 	}
 }
 
+// Tests that the map representing ChannelDbCipher returned by
+// newChannelDbCipherJS contains all of the methods on ChannelDbCipher.
+func Test_newChannelDbCipherJS(t *testing.T) {
+	cipherType := reflect.TypeOf(&ChannelDbCipher{})
+
+	cipher := newChannelDbCipherJS(&bindings.ChannelDbCipher{})
+	if len(cipher) != cipherType.NumMethod() {
+		t.Errorf("ChannelDbCipher JS object does not have all methods."+
+			"\nexpected: %d\nreceived: %d", cipherType.NumMethod(), len(cipher))
+	}
+
+	for i := 0; i < cipherType.NumMethod(); i++ {
+		method := cipherType.Method(i)
+
+		if _, exists := cipher[method.Name]; !exists {
+			t.Errorf("Method %s does not exist.", method.Name)
+		}
+	}
+}
+
+// Tests that ChannelDbCipher has all the methods that
+// [bindings.ChannelDbCipher] has.
+func Test_ChannelDbCipherMethods(t *testing.T) {
+	cipherType := reflect.TypeOf(&ChannelDbCipher{})
+	binCipherType := reflect.TypeOf(&bindings.ChannelDbCipher{})
+
+	if binCipherType.NumMethod() != cipherType.NumMethod() {
+		t.Errorf("WASM ChannelDbCipher object does not have all methods from "+
+			"bindings.\nexpected: %d\nreceived: %d",
+			binCipherType.NumMethod(), cipherType.NumMethod())
+	}
+
+	for i := 0; i < binCipherType.NumMethod(); i++ {
+		method := binCipherType.Method(i)
+
+		if _, exists := cipherType.MethodByName(method.Name); !exists {
+			t.Errorf("Method %s does not exist.", method.Name)
+		}
+	}
+}
+
 type jsIdentity struct {
 	pubKey  js.Value
 	codeset js.Value
diff --git a/wasm_test.go b/wasm_test.go
index 1b25d22270e75984c19ee41beeaf0e9a455df33e..38d1bdd30e9cd3efe0569afe50903b94948761af 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -42,6 +42,7 @@ func TestPublicFunctions(t *testing.T) {
 		"NewEventModel":                   {},
 		"NewChannelsManagerGoEventModel":  {},
 		"LoadChannelsManagerGoEventModel": {},
+		"GetChannelDbCipherTrackerFromID": {},
 
 		// Version functions were renamed to differentiate between WASM and
 		// client versions