diff --git a/Gopkg.lock b/Gopkg.lock
index 05214eb..6a76123 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -104,7 +104,7 @@
 
 [[projects]]
   branch = "master"
-  digest = "1:23e3d5f66ff9fd60dd264fbf26e71cab6b4a3bd2ef993a1645432d5c4c3b50fc"
+  digest = "1:4df631634d7dc4496e9c51436cc2d3ccd0d0d4734640f63108950fe68b348b7e"
   name = "golang.org/x/crypto"
   packages = [
     "blake2b",
@@ -118,6 +118,7 @@
     "poly1305",
     "salsa20",
     "salsa20/salsa",
+    "scrypt",
     "sha3",
   ]
   pruneopts = "UT"
@@ -164,6 +165,7 @@
     "golang.org/x/crypto/pbkdf2",
     "golang.org/x/crypto/poly1305",
     "golang.org/x/crypto/salsa20",
+    "golang.org/x/crypto/scrypt",
     "golang.org/x/crypto/sha3",
   ]
   solver-name = "gps-cdcl"
diff --git a/SPEC.md b/SPEC.md
index d747e3a..98bdd36 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -1,5 +1,7 @@
 # Jess Specification
 
+This document takes a closer look at the inner workings of Jess. It does not, however, define the formats and some other aspects. First, some basics will be covered. Then, the wire protocol is given a closer look.
+
 ## Basics
 
 The basic building blocks of jess are:
diff --git a/core-wire_test.go b/core-wire_test.go
index 115cf5e..698a84b 100644
--- a/core-wire_test.go
+++ b/core-wire_test.go
@@ -11,20 +11,19 @@ import (
 func TestWire(t *testing.T) {
 	wireReKeyAfterMsgs = 100
 
-	testWireCorrespondence(t, RecommendedNetwork, testData1)
-	testWireCorrespondence(t, RecommendedNetwork, testData2)
+	// current suites recommendation
+	testWireCorrespondence(t, getSuite(t, SuiteWire), testData1)
+	testWireCorrespondence(t, getSuite(t, SuiteWire), testData2)
 
-	testWireCorrespondence(t, []string{"ECDH-P224", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
-	testWireCorrespondence(t, []string{"ECDH-P256", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
-	testWireCorrespondence(t, []string{"ECDH-P384", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
-	testWireCorrespondence(t, []string{"ECDH-P521", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
-	testWireCorrespondence(t, []string{"RSA-OAEP(SHA2-256)", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
+	// older suites
+	// testWireCorrespondence(t, getSuite(t, SuiteWireV1), testData1)
+	// testWireCorrespondence(t, getSuite(t, SuiteWireV1), testData2)
 }
 
-func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
+func testWireCorrespondence(t *testing.T, suite *Suite, testData string) {
 	wtr := &wireTestRange{t: t}
-	wtr.init(toolIDs, testData)
-	fmt.Printf("\n\nsimulating %v\n", toolIDs)
+	wtr.init(suite, testData)
+	fmt.Printf("\n\nsimulating %v\n", suite.ID)
 	fmt.Println("two dots are one packet send+recv:")
 
 	fmt.Println("\nclient ->")
@@ -74,7 +73,7 @@ func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
 	duration := wtr.endTime.Sub(wtr.startTime)
 	t.Logf(
 		"%v tested: msgsize=%d, rekey every %d msgs, %d msgs, %d bytes, +%f%% overhead, %s, %s per msg, %f Mbit/s",
-		wtr.toolIDs,
+		wtr.suite.ID,
 		len(testData),
 		wireReKeyAfterMsgs,
 		wtr.msgsTransferred,
@@ -92,7 +91,7 @@ func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
 
 type wireTestRange struct {
 	t        *testing.T
-	toolIDs  []string
+	suite    *Suite
 	testData string
 
 	client *Session
@@ -108,12 +107,12 @@ type wireTestRange struct {
 	endTime          time.Time
 }
 
-func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInvalid bool) {
-	wtr.toolIDs = toolIDs
+func (wtr *wireTestRange) init(suite *Suite, testData string) (detectedInvalid bool) {
+	wtr.suite = suite
 
-	e, err := setupEnvelopeAndTrustStore(wtr.t, wtr.toolIDs)
+	e, err := setupEnvelopeAndTrustStore(wtr.t, wtr.suite)
 	if err != nil {
-		wtr.t.Fatalf("%v failed to setup envelope: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to setup envelope: %s", wtr.suite.ID, err)
 		return false
 	}
 	if e == nil {
@@ -122,7 +121,7 @@ func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInval
 
 	wtr.client, err = e.WireCorrespondence(testTrustStore)
 	if err != nil {
-		wtr.t.Fatalf("%v failed to init client session: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to init client session: %s", wtr.suite.ID, err)
 	}
 
 	// setup and reset
@@ -142,18 +141,18 @@ func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInval
 func (wtr *wireTestRange) clientSend() {
 	letter, err := wtr.client.Close([]byte(wtr.testData))
 	if err != nil {
-		wtr.t.Fatalf("%v failed to close: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to close: %s", wtr.suite.ID, err)
 	}
 
 	wireData, err := letter.ToWire()
 	if err != nil {
-		wtr.t.Fatalf("%v failed to serialize to wire: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to serialize to wire: %s", wtr.suite.ID, err)
 	}
 
 	select {
 	case wtr.clientToServer <- wireData:
 	default:
-		wtr.t.Fatalf("%v could not send to server", wtr.toolIDs)
+		wtr.t.Fatalf("%s could not send to server", wtr.suite.ID)
 	}
 
 	fmt.Print(".")
@@ -167,27 +166,27 @@ func (wtr *wireTestRange) serverRecv() {
 
 		letter, err := LetterFromWire(wireData)
 		if err != nil {
-			wtr.t.Fatalf("%v failed to parse initial wired letter: %s", wtr.toolIDs, err)
+			wtr.t.Fatalf("%s failed to parse initial wired letter: %s", wtr.suite.ID, err)
 		}
 
 		if wtr.server == nil {
 			wtr.server, err = letter.WireCorrespondence(testTrustStore)
 			if err != nil {
-				wtr.t.Fatalf("%v failed to init server session: %s", wtr.toolIDs, err)
+				wtr.t.Fatalf("%s failed to init server session: %s", wtr.suite.ID, err)
 			}
 		}
 
 		origData, err := wtr.server.Open(letter)
 		if err != nil {
-			wtr.t.Fatalf("%v failed to open: %s", wtr.toolIDs, err)
+			wtr.t.Fatalf("%s failed to open: %s", wtr.suite.ID, err)
 		}
 		wtr.bytesTransferred += len(origData)
 
 		if string(origData) != wtr.testData {
-			wtr.t.Fatalf("%v testdata mismatch", wtr.toolIDs)
+			wtr.t.Fatalf("%s testdata mismatch", wtr.suite.ID)
 		}
 	default:
-		wtr.t.Fatalf("%v could not recv from client", wtr.toolIDs)
+		wtr.t.Fatalf("%s could not recv from client", wtr.suite.ID)
 	}
 
 	fmt.Print(".")
@@ -196,18 +195,18 @@ func (wtr *wireTestRange) serverRecv() {
 func (wtr *wireTestRange) serverSend() {
 	letter, err := wtr.server.Close([]byte(wtr.testData))
 	if err != nil {
-		wtr.t.Fatalf("%v failed to close: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to close: %s", wtr.suite.ID, err)
 	}
 
 	wireData, err := letter.ToWire()
 	if err != nil {
-		wtr.t.Fatalf("%v failed to serialize to wire: %s", wtr.toolIDs, err)
+		wtr.t.Fatalf("%s failed to serialize to wire: %s", wtr.suite.ID, err)
 	}
 
 	select {
 	case wtr.serverToClient <- wireData:
 	default:
-		wtr.t.Fatalf("%v could not send to client", wtr.toolIDs)
+		wtr.t.Fatalf("%s could not send to client", wtr.suite.ID)
 	}
 
 	fmt.Print(".")
@@ -221,20 +220,20 @@ func (wtr *wireTestRange) clientRecv() {
 
 		letter, err := LetterFromWire(wireData)
 		if err != nil {
-			wtr.t.Fatalf("%v failed to parse initial wired letter: %s", wtr.toolIDs, err)
+			wtr.t.Fatalf("%s failed to parse initial wired letter: %s", wtr.suite.ID, err)
 		}
 
 		origData, err := wtr.client.Open(letter)
 		if err != nil {
-			wtr.t.Fatalf("%v failed to open: %s", wtr.toolIDs, err)
+			wtr.t.Fatalf("%s failed to open: %s", wtr.suite.ID, err)
 		}
 		wtr.bytesTransferred += len(origData)
 
 		if string(origData) != wtr.testData {
-			wtr.t.Fatalf("%v testdata mismatch", wtr.toolIDs)
+			wtr.t.Fatalf("%s testdata mismatch", wtr.suite.ID)
 		}
 	default:
-		wtr.t.Fatalf("%v could not recv from server", wtr.toolIDs)
+		wtr.t.Fatalf("%s could not recv from server", wtr.suite.ID)
 	}
 
 	fmt.Print(".")
diff --git a/core.go b/core.go
index 09f7420..2b4e33d 100644
--- a/core.go
+++ b/core.go
@@ -16,7 +16,7 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
 
 	if s.wire == nil || s.wire.msgNo == 0 {
 		letter.Version = 1
-		letter.Tools = s.envelope.Tools
+		letter.SuiteID = s.envelope.SuiteID
 	}
 
 	/////////////////
@@ -314,6 +314,15 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
 
 // Verify verifies signatures of the given letter.
 func (s *Session) Verify(letter *Letter) error {
+
+	// debugging:
+	/*
+		fmt.Printf("opening: %+v\n", letter)
+		for _, sig := range letter.Signatures {
+			fmt.Printf("sig: %+v\n", sig)
+		}
+	*/
+
 	var err error
 	if s.wire == nil && letter.Version != 1 {
 		return fmt.Errorf("unsupported letter version: %d", letter.Version)
@@ -323,44 +332,54 @@ func (s *Session) Verify(letter *Letter) error {
 	// verify
 	/////////
 
-	if len(s.signers) == 0 {
-		return errors.New("letter is not signed")
-	}
+	// TODO: signature verification is run before tool setup. Currently, this is ok, but might change in the future. This might break additional signing algorithms that actually need setup.
 
 	data := letter.Data
-	associatedSigningData := letter.compileAssociatedSigningData(nil)
 
-	// run managed signing hashers
-	if s.managedSigningHashers != nil {
-		err = s.feedManagedHashers(s.managedSigningHashers, data, associatedSigningData)
-		if err != nil {
-			return err
-		}
-
-		defer s.resetManagedHashers(s.managedSigningHashers)
+	// build associated data
+	var associatedData []byte
+	if len(s.integratedCiphers) > 0 || len(s.macs) > 0 {
+		associatedData = letter.compileAssociatedData()
 	}
 
-	// run signers
-	if len(s.envelope.Senders) != len(letter.Signatures) {
-		return errors.New("mismatch regarding available signatures and senders")
-	}
-	sigIndex := 0
+	// Signature
+	if len(s.signers) > 0 {
+		associatedSigningData := letter.compileAssociatedSigningData(associatedData)
 
-	for _, tool := range s.signers {
-		//nolint:scopelint // function is executed immediately within loop
-		err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
-
-			err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
+		// run managed signing hashers
+		if s.managedSigningHashers != nil {
+			err = s.feedManagedHashers(s.managedSigningHashers, data, associatedSigningData)
 			if err != nil {
-				return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
+				return err
 			}
 
-			sigIndex++
-			return nil
-		})
-		if err != nil {
-			return err
+			defer s.resetManagedHashers(s.managedSigningHashers)
 		}
+
+		// run signers
+		if len(s.envelope.Senders) != len(letter.Signatures) {
+			return errors.New("mismatch regarding available signatures and senders")
+		}
+		sigIndex := 0
+
+		for _, tool := range s.signers {
+			//nolint:scopelint // function is executed immediately within loop
+			err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
+
+				err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
+				if err != nil {
+					return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
+				}
+
+				sigIndex++
+				return nil
+			})
+			if err != nil {
+				return err
+			}
+		}
+	} else {
+		return errors.New("no signatures to verify")
 	}
 
 	return nil
diff --git a/core_test.go b/core_test.go
index 57f58a0..cef65b8 100644
--- a/core_test.go
+++ b/core_test.go
@@ -120,16 +120,8 @@ func init() {
 }
 
 func TestCoreBasic(t *testing.T) {
-	// toolsets to test
-	toolsets := [][]string{
-		RecommendedStorageKey,
-		RecommendedStoragePassword,
-		{"HKDF(SHA2-256)", "CHACHA20-POLY1305"},
-		{"PBKDF2-SHA2-256", "HKDF(SHA2-256)", "CHACHA20-POLY1305"},
-	}
-
-	for _, toolIDs := range toolsets {
-		testStorage(t, toolIDs)
+	for _, suite := range Suites() {
+		testStorage(t, suite)
 	}
 }
 
@@ -137,10 +129,12 @@ func TestCoreBasic(t *testing.T) {
 func TestCoreAllCombinations(t *testing.T) {
 	// This shall test all tools in all combinations and every tool should be tested when placed before and after every other tool.
 
-	// skip in short tests
-	if testing.Short() {
+	// skip in short tests and when not running comprehensive
+	if testing.Short() || !runComprehensiveTestsActive {
 		return
 	}
+	// run this test with
+	// go test -timeout 10m github.com/safing/jess -v -count=1 -ldflags "-X github.com/safing/jess.RunComprehensiveTests=true" -run ^TestCoreAllCombinations$
 
 	// add all tools
 	var all []string
@@ -193,7 +187,7 @@ func TestCoreAllCombinations(t *testing.T) {
 
 			// rotate to test before/after differences
 			for i := 0; i < len(testTools); i++ {
-				detectedInvalid := testStorage(t, testTools)
+				detectedInvalid := testStorage(t, &Suite{Tools: testTools})
 				combinationsTested++
 				if detectedInvalid {
 					combinationsDetectedInvalid++
@@ -207,7 +201,7 @@ func TestCoreAllCombinations(t *testing.T) {
 			}
 		} else {
 			// test this order only
-			detectedInvalid := testStorage(t, testTools)
+			detectedInvalid := testStorage(t, &Suite{Tools: testTools})
 			combinationsTested++
 			if detectedInvalid {
 				combinationsDetectedInvalid++
@@ -226,12 +220,12 @@ func TestCoreAllCombinations(t *testing.T) {
 	t.Logf("of these, %d were successfully detected as invalid", combinationsDetectedInvalid)
 }
 
-func testStorage(t *testing.T, toolIDs []string) (detectedInvalid bool) {
-	// t.Logf("testing storage with %v", toolIDs)
+func testStorage(t *testing.T, suite *Suite) (detectedInvalid bool) {
+	// t.Logf("testing storage with %s", suite.ID)
 
-	e, err := setupEnvelopeAndTrustStore(t, toolIDs)
+	e, err := setupEnvelopeAndTrustStore(t, suite)
 	if err != nil {
-		tErrorf(t, "%v failed: %s", toolIDs, err)
+		tErrorf(t, "%s failed: %s", suite.ID, err)
 		return false
 	}
 	if e == nil {
@@ -242,122 +236,72 @@ func testStorage(t *testing.T, toolIDs []string) (detectedInvalid bool) {
 
 	s, err := e.Correspondence(testTrustStore)
 	if err != nil {
-		tErrorf(t, "%v failed to init session (1): %s", toolIDs, err)
+		tErrorf(t, "%s failed to init session (1): %s", suite.ID, err)
 		return false
 	}
 
 	letter, err := s.Close([]byte(testData1))
 	if err != nil {
-		tErrorf(t, "%v failed to close (1): %s", toolIDs, err)
+		tErrorf(t, "%s failed to close (1): %s", suite.ID, err)
 		return false
 	}
 
 	msg, err := letter.ToJSON()
 	if err != nil {
-		tErrorf(t, "%v failed to json encode (1): %s", toolIDs, err)
+		tErrorf(t, "%s failed to json encode (1): %s", suite.ID, err)
 		return false
 	}
 
-	// test 2: open from session
+	// test 2: open
 
 	letter2, err := LetterFromJSON(msg)
 	if err != nil {
-		tErrorf(t, "%v failed to json decode (2): %s", toolIDs, err)
+		tErrorf(t, "%s failed to json decode (2): %s", suite.ID, err)
 		return false
 	}
 
-	origData2, err := s.Open(letter2)
+	origData2, err := letter2.Open(e.suite.Provides, testTrustStore)
 	if err != nil {
-		tErrorf(t, "%v failed to open (2): %s", toolIDs, err)
+		tErrorf(t, "%s failed to open (2): %s", suite.ID, err)
 		return false
 	}
 	if string(origData2) != testData1 {
-		tErrorf(t, "%v original data mismatch (2): %s", toolIDs, string(origData2))
+		tErrorf(t, "%s original data mismatch (2): %s", suite.ID, string(origData2))
 		return false
 	}
 
-	if len(letter2.Signatures) > 0 {
-		err = s.Verify(letter2)
-		if err != nil {
-			tErrorf(t, "%v failed to verify (2): %s", toolIDs, err)
-			return false
-		}
-	}
-
-	// extended tests
-	// only run for toolsets greater than 3 if we comprehensive testing is on
-	// for these tests, it is enough if every tool is tested once
-	if len(toolIDs) > 3 && RunComprehensiveTests != "true" {
-		return false
-	}
-
-	// test 2.1: open again to check if reset after opening works
+	// test 2.1: verify
 
 	letter21, err := LetterFromJSON(msg)
 	if err != nil {
-		tErrorf(t, "%v failed to json decode (2.1): %s", toolIDs, err)
+		tErrorf(t, "%s failed to json decode (2): %s", suite.ID, err)
 		return false
 	}
 
-	origData21, err := s.Open(letter21)
-	if err != nil {
-		tErrorf(t, "%v failed to open (2.1): %s", toolIDs, err)
-		return false
-	}
-	if string(origData21) != testData1 {
-		tErrorf(t, "%v original data mismatch (2.1): %s", toolIDs, string(origData21))
-		return false
-	}
-
-	// test 2.2: close and open again to check if reset after closing works
-
-	letter22, err := s.Close([]byte(testData1))
-	if err != nil {
-		tErrorf(t, "%v failed to close (2.2): %s", toolIDs, err)
-		return false
-	}
-
-	origData22, err := s.Open(letter22)
-	if err != nil {
-		tErrorf(t, "%v failed to open (2.2): %s", toolIDs, err)
-		return false
-	}
-	if string(origData22) != testData1 {
-		tErrorf(t, "%v original data mismatch (2.2): %s", toolIDs, string(origData22))
-		return false
-	}
-
-	// test 3: open from letter
-
-	// FIXME - other improvements broke these tests, pausing them
-	/*
-		letter3, err := LetterFromJSON(msg)
+	if len(letter21.Signatures) > 0 {
+		err = letter21.Verify(e.suite.Provides, testTrustStore)
 		if err != nil {
-			tErrorf(t, "%v failed to json decode (3): %s", toolIDs, err)
+			tErrorf(t, "%s failed to verify (2): %s", suite.ID, err)
 			return false
 		}
-
-		origData3, err := letter3.Open(nil, testTrustStore)
-		if err != nil {
-			tErrorf(t, "%v failed to open (3): %s", toolIDs, err)
-			return false
-		}
-		if string(origData3) != testData1 {
-			tErrorf(t, "%v original data mismatch (3): %s", toolIDs, string(origData3))
-			return false
-		}
-	*/
+	}
 
 	return false
 }
 
 //nolint:gocognit,gocyclo
-func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, error) {
+func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
+	// check if suite is registered
+	if suite.ID == "" {
+		// register as test suite
+		suite.ID = fmt.Sprintf("__unit_test_suite__" + strings.Join(suite.Tools, "__"))
+		registerSuite(suite)
+	}
 
 	// create envelope baseline
 	e := &Envelope{
-		Tools:        toolIDs,
-		requirements: newEmptyRequirements(),
+		SuiteID: suite.ID,
+		suite:   suite,
 	}
 
 	// check vars
@@ -366,7 +310,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
 	asyncKeyEstablishmentPresent := false
 
 	// process tools and setup envelope
-	for _, toolID := range e.Tools {
+	for _, toolID := range e.suite.Tools {
 
 		// remove hasher argument for now
 		if strings.Contains(toolID, "(") {
@@ -389,7 +333,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
 			e.Secrets = append(e.Secrets, pw)
 
 			// add a second one!
-			if len(toolIDs) <= 2 {
+			if len(suite.Tools) <= 2 {
 				pw1, err := getOrMakeSignet(t, nil, false, "test-pw-2")
 				if err != nil {
 					return nil, err
@@ -422,66 +366,69 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
 			passDerPresent = true
 			// add passderivation requirements later, as it is a bit special
 		case tools.PurposeKeyExchange:
-			e.requirements.Add(RecipientAuthentication)
+			e.suite.Provides.Add(RecipientAuthentication)
 		case tools.PurposeKeyEncapsulation:
-			e.requirements.Add(RecipientAuthentication)
+			e.suite.Provides.Add(RecipientAuthentication)
 		case tools.PurposeSigning:
-			e.requirements.Add(SenderAuthentication)
+			e.suite.Provides.Add(SenderAuthentication)
 		case tools.PurposeIntegratedCipher:
-			e.requirements.Add(Confidentiality)
-			e.requirements.Add(Integrity)
+			e.suite.Provides.Add(Confidentiality)
+			e.suite.Provides.Add(Integrity)
 		case tools.PurposeCipher:
-			e.requirements.Add(Confidentiality)
+			e.suite.Provides.Add(Confidentiality)
 		case tools.PurposeMAC:
-			e.requirements.Add(Integrity)
+			e.suite.Provides.Add(Integrity)
 		}
 	}
 
 	// if invalid: test if toolset is recognized as invalid
 
 	// no requirements -> only "meta" tools (kdf, pass derivation)
-	if e.requirements.Empty() {
+	if e.suite.Provides.Empty() {
 		return nil, testInvalidToolset(e, "there are only meta tools in toolset")
 	}
 
 	// recipient auth, but no confidentiality? nope.
-	if e.requirements.Has(RecipientAuthentication) &&
-		!e.requirements.Has(Confidentiality) {
+	if e.suite.Provides.Has(RecipientAuthentication) &&
+		!e.suite.Provides.Has(Confidentiality) {
 		return nil, testInvalidToolset(e, "authenticating the recipient without using confidentiality does not make sense")
 	}
 
 	// check if we are missing key derivation - this is only ok if we are merely signing
 	if !keyDerPresent &&
-		(len(e.requirements.all) != 1 ||
-			!e.requirements.Has(SenderAuthentication)) {
+		(len(e.suite.Provides.all) != 1 ||
+			!e.suite.Provides.Has(SenderAuthentication)) {
 		return nil, testInvalidToolset(e, "omitting a key derivation tool is only allowed when merely signing")
 	}
 
 	// check if we have key derivation, but not need it
 	if keyDerPresent &&
-		(!e.requirements.Has(Confidentiality) &&
-			!e.requirements.Has(Integrity)) {
+		(!e.suite.Provides.Has(Confidentiality) &&
+			!e.suite.Provides.Has(Integrity)) {
 		return nil, testInvalidToolset(e, "a key derivation tool was specified, albeit none is needed")
 	}
 
 	// add passderivation here, as to easier handle the other cases
 	if passDerPresent {
-		e.requirements.Add(SenderAuthentication)
-		e.requirements.Add(RecipientAuthentication)
+		e.suite.Provides.Add(SenderAuthentication)
+		e.suite.Provides.Add(RecipientAuthentication)
 
 		// need Confidentiality for this to make sense
-		if !e.requirements.Has(Confidentiality) {
+		if !e.suite.Provides.Has(Confidentiality) {
 			return nil, testInvalidToolset(e, "using a password without confidentiality does not make sense")
 		}
 	}
 
-	if e.requirements.Has(Confidentiality) &&
-		!e.requirements.Has(Integrity) {
+	if e.suite.Provides.Has(Confidentiality) &&
+		!e.suite.Provides.Has(Integrity) {
 		return nil, testInvalidToolset(e, "having confidentiality without integrity does not make sense")
 	}
 
 	// add static key if needed
 	if !asyncKeyEstablishmentPresent && !passDerPresent && keyDerPresent {
+		e.suite.Provides.Add(SenderAuthentication)
+		e.suite.Provides.Add(RecipientAuthentication)
+
 		key, err := getOrMakeSignet(t, nil, false, "test-key-1")
 		if err != nil {
 			return nil, err
@@ -489,7 +436,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
 		e.Secrets = append(e.Secrets, key)
 
 		// add a second one!
-		if len(toolIDs) <= 2 {
+		if len(suite.Tools) <= 2 {
 			key2, err := getOrMakeSignet(t, nil, false, "test-key-2")
 			if err != nil {
 				return nil, err
diff --git a/defaults.go b/defaults.go
index 752627b..baba152 100644
--- a/defaults.go
+++ b/defaults.go
@@ -3,10 +3,10 @@ package jess
 var (
 	// must be var in order decrease for testing for better speed
 
-	defaultSecurityLevel = 256
+	defaultSecurityLevel = 128
 	minimumSecurityLevel = 0
 
-	defaultSymmetricKeySize = 32
+	defaultSymmetricKeySize = 16
 	minimumSymmetricKeySize = 0
 )
 
diff --git a/envelope.go b/envelope.go
index 056d6c8..e568b39 100644
--- a/envelope.go
+++ b/envelope.go
@@ -9,7 +9,8 @@ import (
 type Envelope struct { //nolint:maligned // TODO
 	Version uint8
 	Name    string
-	Tools   []string
+	SuiteID string
+	suite   *Suite
 
 	// Secret keys and passwords
 	Secrets []*Signet
@@ -26,11 +27,10 @@ type Envelope struct { //nolint:maligned // TODO
 
 	// For users, envelopes describe how a letter is closed.
 	// Therefore Secrets and Senders always refer to private keys and Recipients to public keys in that context.
-	// These distictions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
+	// These distinctions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
 
-	MinimumSecurityLevel int
-	No                   string
-	requirements         *Requirements
+	// SecurityLevel is the security level of the envelope when it was created
+	SecurityLevel int
 
 	// flag to signify if envelope is used for opening
 	opening bool
@@ -39,10 +39,8 @@ type Envelope struct { //nolint:maligned // TODO
 // NewUnconfiguredEnvelope returns an unconfigured, but slightly initialized envelope.
 func NewUnconfiguredEnvelope() *Envelope {
 	e := &Envelope{
-		Version:      1,
-		requirements: NewRequirements(),
+		Version: 1,
 	}
-	e.SerializeRequirements()
 	return e
 }
 
@@ -52,14 +50,17 @@ func (e *Envelope) Correspondence(trustStore TrustStore) (*Session, error) {
 }
 
 func (e *Envelope) initCorrespondence(trustStore TrustStore, verifying bool) (*Session, error) {
-	err := e.LoadRequirements()
+	err := e.LoadSuite()
 	if err != nil {
 		return nil, err
 	}
 
+	//nolint:gocritic // TODO: see below
 	if verifying {
-		// prep sender signets only
-		err = e.prepSignets(e.Senders, e.opening, trustStore)
+		// TODO: prep sender signets only
+		// TODO: for this to work, newSession needs to only check verification related things
+		// err = e.prepSignets(e.Senders, e.opening, trustStore)
+		err = e.PrepareSignets(trustStore)
 	} else {
 		// prep all signets
 		err = e.PrepareSignets(trustStore)
@@ -92,82 +93,27 @@ func (e *Envelope) Check(trustStore TrustStore) error {
 	return err
 }
 
-// NoRecipientAuth removes the requirement to authenticate the recipient.
-func (e *Envelope) NoRecipientAuth() *Envelope {
-	if e.requirements == nil {
-		e.requirements = NewRequirements()
-	}
-
-	e.requirements.Remove(RecipientAuthentication)
-	return e
+// Suite returns the loaded suite.
+func (e *Envelope) Suite() *Suite {
+	return e.suite
 }
 
-// NoSenderAuth removes the requirement to authenticate the sender.
-func (e *Envelope) NoSenderAuth() *Envelope {
-	if e.requirements == nil {
-		e.requirements = NewRequirements()
-	}
-
-	e.requirements.Remove(SenderAuthentication)
-	e.SerializeRequirements()
-	return e
-}
-
-// NoConfidentiality removes the requirement to provide confidentiality.
-func (e *Envelope) NoConfidentiality() *Envelope {
-	if e.requirements == nil {
-		e.requirements = NewRequirements()
-	}
-
-	e.requirements.Remove(Confidentiality)
-	e.SerializeRequirements()
-	return e
-}
-
-// NoIntegrity removes the requirement to provide integrity.
-func (e *Envelope) NoIntegrity() *Envelope {
-	if e.requirements == nil {
-		e.requirements = NewRequirements()
-	}
-
-	e.requirements.Remove(Integrity)
-	e.SerializeRequirements()
-	return e
-}
-
-// Unsafe removes all requirements.
-func (e *Envelope) Unsafe() *Envelope {
-	e.requirements = &Requirements{}
-	e.SerializeRequirements()
-	return e
-}
-
-// Requirements returns the required requirements.
-func (e *Envelope) Requirements() *Requirements {
-	return e.requirements
-}
-
-// SetRequirements sets new requirements.
-func (e *Envelope) SetRequirements(requirements *Requirements) {
-	e.requirements = requirements
-}
-
-// LoadRequirements loads the required requirements from the struct's exposed negated "No" specification.
-func (e *Envelope) LoadRequirements() error {
-	if e.requirements == nil {
-		attrs, err := ParseRequirementsFromNoSpec(e.No)
-		if err != nil {
-			return nil
+// LoadSuite loads the suite specified in the envelope.
+func (e *Envelope) LoadSuite() error {
+	if e.suite == nil {
+		suite, ok := GetSuite(e.SuiteID)
+		if !ok {
+			return fmt.Errorf("suite %s does not exist", e.SuiteID)
 		}
-
-		e.requirements = attrs
+		e.suite = suite
 	}
 	return nil
 }
 
-// SerializeRequirements saves the requirement requirements in the struct's exposed negated "No" specification.
-func (e *Envelope) SerializeRequirements() {
-	e.No = e.requirements.SerializeToNoSpec()
+// ReloadSuite forces reloading the suite specified in the envelope.
+func (e *Envelope) ReloadSuite() error {
+	e.suite = nil
+	return e.LoadSuite()
 }
 
 // LoopSecrets loops over all secrets of the given scheme.
@@ -233,12 +179,16 @@ func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage Trust
 		// load from storage
 		if len(signet.Key) == 0 {
 			if signet.Scheme == SignetSchemePassword {
-				err := fillPassword(signet, !recipients, storage, e.MinimumSecurityLevel)
+				err := fillPassword(signet, !recipients, storage, e.suite.SecurityLevel)
 				if err != nil {
 					return fmt.Errorf(`failed to get password for "%s": %s`, signet.ID, err)
 				}
 				continue
 			}
+			// keys are _always_ signets
+			if signet.Scheme == SignetSchemeKey {
+				recipients = false
+			}
 
 			// signet is referrer
 			if len(signet.ID) == 0 {
diff --git a/letter-wire.go b/letter-wire.go
index 9540814..754cf93 100644
--- a/letter-wire.go
+++ b/letter-wire.go
@@ -15,9 +15,7 @@ import (
 	- 2: Sending Keys
 	- 4: Apply Keys
 - Version: varint (if Setup Msg)
-- Tools: (if Setup Msg)
-	- Amount: varint
-	- Names: byte blocks
+- SuiteID: byte block (if Setup Msg)
 - Keys:
 	- Amount: varint
 	- IDs/Values: byte blocks
@@ -58,13 +56,8 @@ func (letter *Letter) ToWire() (*container.Container, error) {
 		// Version: varint (if Setup Msg)
 		c.AppendNumber(uint64(letter.Version))
 
-		// Tools: (if Setup Msg)
-		//   - Amount: varint
-		// 	 - Names: byte blocks
-		c.AppendInt(len(letter.Tools))
-		for _, toolName := range letter.Tools {
-			c.AppendAsBlock([]byte(toolName))
-		}
+		// SuiteID: byte block (if Setup Msg)
+		c.AppendAsBlock([]byte(letter.SuiteID))
 	}
 
 	if len(letter.Keys) > 0 {
@@ -136,21 +129,12 @@ func LetterFromWire(c *container.Container) (*Letter, error) {
 		}
 		letter.Version = n
 
-		// Tools: (if Setup Msg)
-		//   - Amount: varint
-		// 	 - Names: byte blocks
-		n, err = c.GetNextN8()
+		// SuiteID: byte block (if Setup Msg)
+		suiteID, err := c.GetNextBlock()
 		if err != nil {
 			return nil, err
 		}
-		letter.Tools = make([]string, n)
-		for i := 0; i < len(letter.Tools); i++ {
-			toolName, err := c.GetNextBlock()
-			if err != nil {
-				return nil, err
-			}
-			letter.Tools[i] = string(toolName)
-		}
+		letter.SuiteID = string(suiteID)
 	}
 
 	if sendingKeys {
diff --git a/letter.go b/letter.go
index 11c29fd..d4a2e1a 100644
--- a/letter.go
+++ b/letter.go
@@ -17,8 +17,8 @@ import (
 
 // Letter is the data format for encrypted data at rest or in transit.
 type Letter struct { //nolint:maligned // TODO
-	Version uint8    // signed, MAC'd (may not exist when wired)
-	Tools   []string // signed, MAC'd (may not exist when wired)
+	Version uint8  // signed, MAC'd (may not exist when wired)
+	SuiteID string // signed, MAC'd (may not exist when wired)
 
 	Nonce []byte  // signed, MAC'd
 	Keys  []*Seal `json:",omitempty"` // signed, MAC'd
@@ -45,18 +45,34 @@ type Seal struct {
 }
 
 // Envelope returns an envelope built from the letter, configured for opening it.
-func (letter *Letter) Envelope() (*Envelope, error) {
+func (letter *Letter) Envelope(requirements *Requirements) (*Envelope, error) {
+	// basic checks
 	if letter.Version == 0 {
 		return nil, fmt.Errorf("letter does not specify version")
 	}
-	if len(letter.Tools) == 0 {
-		return nil, fmt.Errorf("letter does not specify any tools")
+	if len(letter.SuiteID) == 0 {
+		return nil, fmt.Errorf("letter does not specify a suite")
 	}
 
+	// create envelope
 	e := &Envelope{
-		Version:      letter.Version,
-		Tools:        letter.Tools,
-		requirements: newEmptyRequirements(),
+		Version: letter.Version,
+		SuiteID: letter.SuiteID,
+	}
+
+	// get and check suite
+	err := e.LoadSuite()
+	if err != nil {
+		return nil, err
+	}
+	// default to full requirements
+	if requirements == nil {
+		requirements = NewRequirements()
+	}
+	// check suite against requirements
+	err = e.suite.Provides.CheckComplianceTo(requirements)
+	if err != nil {
+		return nil, err
 	}
 
 	for _, seal := range letter.Keys {
@@ -91,13 +107,10 @@ func (letter *Letter) Envelope() (*Envelope, error) {
 
 // Open creates a session and opens the letter in one step.
 func (letter *Letter) Open(requirements *Requirements, trustStore TrustStore) ([]byte, error) {
-	e, err := letter.Envelope()
+	e, err := letter.Envelope(requirements)
 	if err != nil {
 		return nil, err
 	}
-	if requirements != nil {
-		e.requirements = requirements
-	}
 
 	s, err := e.Correspondence(trustStore)
 	if err != nil {
@@ -109,13 +122,10 @@ func (letter *Letter) Open(requirements *Requirements, trustStore TrustStore) ([
 
 // Verify creates a session and verifies the letter in one step.
 func (letter *Letter) Verify(requirements *Requirements, trustStore TrustStore) error {
-	e, err := letter.Envelope()
+	e, err := letter.Envelope(requirements)
 	if err != nil {
 		return err
 	}
-	if requirements != nil {
-		e.requirements = requirements
-	}
 
 	s, err := e.initCorrespondence(trustStore, true)
 	if err != nil {
@@ -127,7 +137,7 @@ func (letter *Letter) Verify(requirements *Requirements, trustStore TrustStore)
 
 // WireCorrespondence creates a wire session (communication over a network connection) from a letter.
 func (letter *Letter) WireCorrespondence(trustStore TrustStore) (*Session, error) {
-	e, err := letter.Envelope()
+	e, err := letter.Envelope(NewRequirements().Remove(SenderAuthentication))
 	if err != nil {
 		return nil, err
 	}
@@ -179,7 +189,7 @@ const (
 	// These IDs MUST NOT CHANGE
 
 	fieldIDLetterVersion uint64 = 1 // signed, MAC'd (may not exist when wired)
-	fieldIDLetterTools   uint64 = 2 // signed, MAC'd (may not exist when wired)
+	fieldIDLetterSuiteID uint64 = 2 // signed, MAC'd (may not exist when wired)
 	fieldIDLetterNonce   uint64 = 3 // signed, MAC'd
 	fieldIDLetterKeys    uint64 = 4 // signed, MAC'd
 	fieldIDLetterMac     uint64 = 5 // signed
@@ -199,12 +209,9 @@ func (letter *Letter) compileAssociatedData() []byte {
 		c.AppendNumber(fieldIDLetterVersion) // append field ID
 		c.AppendNumber(uint64(letter.Version))
 	}
-	if len(letter.Tools) > 0 {
-		c.AppendNumber(fieldIDLetterTools) // append field ID
-		c.AppendInt(len(letter.Tools))     // append number of tools
-		for _, toolID := range letter.Tools {
-			c.AppendAsBlock([]byte(toolID)) // append field content with length
-		}
+	if len(letter.SuiteID) > 0 {
+		c.AppendNumber(fieldIDLetterSuiteID)    // append field ID
+		c.AppendAsBlock([]byte(letter.SuiteID)) // append field content with length
 	}
 	if len(letter.Nonce) > 0 {
 		c.AppendNumber(fieldIDLetterNonce) // append field ID
diff --git a/letter_test.go b/letter_test.go
index 6d051c1..1887489 100644
--- a/letter_test.go
+++ b/letter_test.go
@@ -11,7 +11,7 @@ import (
 func TestSerialization(t *testing.T) {
 	subject := &Letter{
 		Version: 1,
-		Tools:   RecommendedNetwork,
+		SuiteID: SuiteComplete,
 		Keys: []*Seal{
 			{ID: "a"},
 			{ID: "b"},
@@ -25,7 +25,7 @@ func TestSerialization(t *testing.T) {
 	testSerialize(t, subject, true)
 
 	subject.Version = 0
-	subject.Tools = nil
+	subject.SuiteID = ""
 	testSerialize(t, subject, true)
 
 	subject.ApplyKeys = false
diff --git a/password_test.go b/password_test.go
index a418952..526ad81 100644
--- a/password_test.go
+++ b/password_test.go
@@ -2,6 +2,24 @@ package jess
 
 import "testing"
 
+func init() {
+	SetPasswordCallbacks(
+		func(signet *Signet, minSecurityLevel int) error {
+			return getTestPassword(signet)
+		},
+		getTestPassword,
+	)
+}
+
+func getTestPassword(signet *Signet) error {
+	pwSignet, err := testTrustStore.GetSignet(signet.ID, false)
+	if err != nil {
+		return err
+	}
+	signet.Key = pwSignet.Key
+	return nil
+}
+
 func TestCalculatePasswordSecurityLevel(t *testing.T) {
 	// basic weak
 	testPWSL(t, "asdf", -1)
@@ -13,62 +31,62 @@ func TestCalculatePasswordSecurityLevel(t *testing.T) {
 	testPWSL(t, "aaaaaaaaAAAAAAAA00000000********", -1)
 
 	// chars only
-	testPWSL(t, "AVWHBwmF", 58)
-	testPWSL(t, "AVWHBwmFGt", 70)
-	testPWSL(t, "AVWHBwmFGtLM", 81)
-	testPWSL(t, "AVWHBwmFGtLMGh", 93)
-	testPWSL(t, "AVWHBwmFGtLMGhYf", 104)
-	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdxs", 195)
+	testPWSL(t, "AVWHBwmF", 64)
+	testPWSL(t, "AVWHBwmFGt", 76)
+	testPWSL(t, "AVWHBwmFGtLM", 87)
+	testPWSL(t, "AVWHBwmFGtLMGh", 98)
+	testPWSL(t, "AVWHBwmFGtLMGhYf", 110)
+	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdxs", 201)
 
 	// with number
-	testPWSL(t, "AVWHBwm1", 60)
-	testPWSL(t, "AVWHBwmFG1", 72)
-	testPWSL(t, "AVWHBwmFGtL1", 84)
-	testPWSL(t, "AVWHBwmFGtLMG1", 96)
-	testPWSL(t, "AVWHBwmFGtLMGhY1", 108)
-	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdx1", 203)
+	testPWSL(t, "AVWHBwm1", 66)
+	testPWSL(t, "AVWHBwmFG1", 78)
+	testPWSL(t, "AVWHBwmFGtL1", 90)
+	testPWSL(t, "AVWHBwmFGtLMG1", 102)
+	testPWSL(t, "AVWHBwmFGtLMGhY1", 114)
+	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdx1", 209)
 
 	// with number and special
-	testPWSL(t, "AVWHBw1_", 61)
-	testPWSL(t, "AVWHBwmF1_", 73)
-	testPWSL(t, "AVWHBwmFGt1_", 86)
-	testPWSL(t, "AVWHBwmFGtLM1_", 98)
-	testPWSL(t, "AVWHBwmFGtLMGh1_", 110)
-	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1_", 207)
+	testPWSL(t, "AVWHBw1_", 67)
+	testPWSL(t, "AVWHBwmF1_", 79)
+	testPWSL(t, "AVWHBwmFGt1_", 91)
+	testPWSL(t, "AVWHBwmFGtLM1_", 103)
+	testPWSL(t, "AVWHBwmFGtLMGh1_", 116)
+	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1_", 213)
 
 	// with number and more special
-	testPWSL(t, "AVWHBw1*", 65)
-	testPWSL(t, "AVWHBwmF1*", 78)
-	testPWSL(t, "AVWHBwmFGt1*", 91)
-	testPWSL(t, "AVWHBwmFGtLM1*", 104)
-	testPWSL(t, "AVWHBwmFGtLMGh1*", 117)
-	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1*", 221)
+	testPWSL(t, "AVWHBw1*", 70)
+	testPWSL(t, "AVWHBwmF1*", 83)
+	testPWSL(t, "AVWHBwmFGt1*", 96)
+	testPWSL(t, "AVWHBwmFGtLM1*", 109)
+	testPWSL(t, "AVWHBwmFGtLMGh1*", 122)
+	testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1*", 226)
 
 	// created, strong
 
 	// "Schneier scheme"
 	// source: https://www.schneier.com/blog/archives/2014/03/choosing_secure_1.html
-	testPWSL(t, "WIw7,mstmsritt...", 116)
-	testPWSL(t, "Wow...doestcst", 94)
-	testPWSL(t, "Ltime@go-inag~faaa!", 135)
-	testPWSL(t, "uTVM,TPw55:utvm,tpwstillsecure", 210)
+	testPWSL(t, "WIw7,mstmsritt...", 122)
+	testPWSL(t, "Wow...doestcst", 100)
+	testPWSL(t, "Ltime@go-inag~faaa!", 140)
+	testPWSL(t, "uTVM,TPw55:utvm,tpwstillsecure", 216)
 
 	// generated, strong
-	testPWSL(t, "YebGPQuuoxQwyeJMvEWACTLexUUxVBFdHYqqUybBUNfBttCvWQxDdDCdYfgMPCQp", 378)
-	testPWSL(t, "dpPyXmXpbECn6LWuQDJaitTTJguGfRTqNUxWfoHnBKDHvRhjR2WiQ7iDcuRJNnEd", 394)
-	testPWSL(t, "WgEKCp8c8{bPrG{Zo(Ms97pKt3EsR9ycz4R=kMjPp^Uafqxsd2ZTFtkfvnoueKJz", 428)
-	testPWSL(t, "galena-fighter-festival", 127)
-	testPWSL(t, "impotent-drug-dropout-damage", 152)
-	testPWSL(t, "artless-newswire-rill-belgium-marplot", 196)
-	testPWSL(t, "forbade-momenta-spook-sure-devilish-wobbly", 221)
+	testPWSL(t, "YebGPQuuoxQwyeJMvEWACTLexUUxVBFdHYqqUybBUNfBttCvWQxDdDCdYfgMPCQp", 383)
+	testPWSL(t, "dpPyXmXpbECn6LWuQDJaitTTJguGfRTqNUxWfoHnBKDHvRhjR2WiQ7iDcuRJNnEd", 400)
+	testPWSL(t, "WgEKCp8c8{bPrG{Zo(Ms97pKt3EsR9ycz4R=kMjPp^Uafqxsd2ZTFtkfvnoueKJz", 434)
+	testPWSL(t, "galena-fighter-festival", 132)
+	testPWSL(t, "impotent-drug-dropout-damage", 157)
+	testPWSL(t, "artless-newswire-rill-belgium-marplot", 202)
+	testPWSL(t, "forbade-momenta-spook-sure-devilish-wobbly", 227)
 }
 
 func testPWSL(t *testing.T, password string, expectedSecurityLevel int) {
-	securityLevel := CalculatePasswordSecurityLevel(password, 20000)
+	securityLevel := CalculatePasswordSecurityLevel(password, 1<<20)
 
 	if securityLevel < expectedSecurityLevel {
-		t.Errorf("password %s (%di): %d - expected at least %d", password, 20000, securityLevel, expectedSecurityLevel)
+		t.Errorf("password %s (%di): %d - expected at least %d", password, 1<<20, securityLevel, expectedSecurityLevel)
 	} else {
-		t.Logf("password %s (%di): %d", password, 20000, securityLevel)
+		t.Logf("password %s (%di): %d", password, 1<<20, securityLevel)
 	}
 }
diff --git a/requirements.go b/requirements.go
index b102954..4533d7f 100644
--- a/requirements.go
+++ b/requirements.go
@@ -81,7 +81,7 @@ func (requirements *Requirements) CheckComplianceTo(requirement *Requirements) e
 		}
 	}
 	if missing != nil {
-		return fmt.Errorf("missing tools with security requirements: %s", missing.String())
+		return fmt.Errorf("missing security requirements: %s", missing.String())
 	}
 	return nil
 }
diff --git a/session.go b/session.go
index 5006029..3a0a44a 100644
--- a/session.go
+++ b/session.go
@@ -57,15 +57,34 @@ func (sh *managedHasher) Sum() ([]byte, error) {
 }
 
 func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
-	if len(e.Tools) == 0 {
-		return nil, errors.New("envelope is missing tools")
+	if e.suite == nil {
+		return nil, errors.New("suite not loaded")
 	}
 
+	// create session
 	s := &Session{
 		envelope:         e,
 		toolRequirements: newEmptyRequirements(),
 	}
 
+	// check envelope security level
+	if e.SecurityLevel > 0 {
+		err := s.checkSecurityLevel(e.SecurityLevel, func() string {
+			return fmt.Sprintf(`envelope "%s"`, e.Name)
+		})
+		if err != nil {
+			return nil, err
+		}
+	}
+	// check suite security level
+	err := s.checkSecurityLevel(e.suite.SecurityLevel, func() string {
+		return fmt.Sprintf(`suite "%s"`, e.suite.ID)
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// prepare variables
 	var (
 		keySourceAvailable    bool = false
 		totalSignetsSeen      int
@@ -74,13 +93,13 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
 	)
 
 	// tool init loop: start
-	for i, toolID := range s.envelope.Tools {
+	for i, toolID := range s.envelope.suite.Tools {
 
 		///////////////////////////////////////
 		// tool init loop: check for duplicates
 		///////////////////////////////////////
 
-		for j, dupeToolID := range s.envelope.Tools {
+		for j, dupeToolID := range s.envelope.suite.Tools {
 			if i != j && toolID == dupeToolID {
 				return nil, fmt.Errorf("cannot use tool %s twice, each tool may be only specified once", toolID)
 			}
@@ -311,7 +330,10 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
 	}
 
 	// key signets
-	err := e.LoopSecrets(SignetSchemeKey, func(signet *Signet) error {
+	err = e.LoopSecrets(SignetSchemeKey, func(signet *Signet) error {
+		s.toolRequirements.Add(SenderAuthentication)
+		s.toolRequirements.Add(RecipientAuthentication)
+
 		totalSignetsSeen++
 		keySourceAvailable = true
 		return s.calcAndCheckSecurityLevel(nil, signet)
@@ -350,7 +372,7 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
 	if s.toolRequirements.Empty() {
 		return nil, errors.New("envelope excludes all security requirements, no meaningful operation possible")
 	}
-	err = s.toolRequirements.CheckComplianceTo(s.envelope.requirements)
+	err = s.toolRequirements.CheckComplianceTo(s.envelope.suite.Provides)
 	if err != nil {
 		return nil, err
 	}
@@ -368,7 +390,7 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
 	}
 
 	// check if we are missing a kdf, but need one
-	if s.kdf == nil && len(s.signers) != len(s.envelope.Tools) {
+	if s.kdf == nil && len(s.signers) != len(s.envelope.suite.Tools) {
 		return nil, errors.New("missing a key derivation tool")
 	}
 
@@ -393,6 +415,15 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
 		return nil, fmt.Errorf("detected signet or recipient in envelope that is not used by any tool")
 	}
 
+	// check session security level
+	// while this should never result in an error (because every part was already checked separately) this is used as a precaution to catch errors in future code changes
+	err = s.checkSecurityLevel(s.SecurityLevel, func() string {
+		return "current session"
+	})
+	if err != nil {
+		return nil, err
+	}
+
 	return s, nil
 }
 
@@ -415,6 +446,9 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
 		// existence check is done when opening/closing
 		if len(signet.Key) > 0 {
 			switch logic.Info().Name {
+			case "SCRYPT-20":
+				// TODO: integrate this into the tool interface
+				calculatedSecurityLevel = CalculatePasswordSecurityLevel(string(signet.Key), 1<<20)
 			case "PBKDF2-SHA2-256":
 				// TODO: integrate this into the tool interface
 				calculatedSecurityLevel = CalculatePasswordSecurityLevel(string(signet.Key), 20000)
@@ -451,38 +485,18 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
 	}
 
 	if signet != nil {
-
 		// signet based security level checks
-		switch {
-		case minimumSecurityLevel > 0:
-			// check against minimumSecurityLevel
-			// minimumSecurityLevel overrides other checks
-			if calculatedSecurityLevel < minimumSecurityLevel {
-				return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the desired security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, minimumSecurityLevel)
-			}
-		case s.envelope.MinimumSecurityLevel > 0 && calculatedSecurityLevel < s.envelope.MinimumSecurityLevel:
-			// check against envelope's minimum security level
-			return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the envelope's minimum security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, s.envelope.MinimumSecurityLevel)
-		case s.SecurityLevel > 0 && calculatedSecurityLevel < s.SecurityLevel:
-			// check against toolset's security level
-			return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the toolset's security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, s.SecurityLevel)
-		}
-
+		err = s.checkSecurityLevel(calculatedSecurityLevel, func() string {
+			return fmt.Sprintf(`supplied %s signet "%s"`, signet.Scheme, signet.ID)
+		})
 	} else {
-
-		// tool based security level checks
-		switch {
-		case minimumSecurityLevel > 0:
-			// check against minimumSecurityLevel
-			// minimumSecurityLevel overrides other checks
-			if calculatedSecurityLevel < minimumSecurityLevel {
-				return fmt.Errorf(`tool %s with a security level of %d is weaker than the desired security level of %d`, logic.Info().Name, calculatedSecurityLevel, minimumSecurityLevel)
-			}
-		case s.envelope.MinimumSecurityLevel > 0 && calculatedSecurityLevel < s.envelope.MinimumSecurityLevel:
-			// check against envelope's minimum security level
-			return fmt.Errorf(`tool %s with a security level of %d is weaker than the envelope's minimum security level of %d`, logic.Info().Name, calculatedSecurityLevel, s.envelope.MinimumSecurityLevel)
-		}
-
+		// tool based securty level checks
+		err = s.checkSecurityLevel(calculatedSecurityLevel, func() string {
+			return "tool %s" + logic.Info().Name
+		})
+	}
+	if err != nil {
+		return err
 	}
 
 	// adapt security level of session
@@ -499,6 +513,42 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
 	return nil
 }
 
+func (s *Session) checkSecurityLevel(levelToCheck int, subject func() string) error {
+	switch {
+	case minimumSecurityLevel > 0:
+		// check against minimumSecurityLevel
+		// minimumSecurityLevel overrides other checks
+		if levelToCheck < minimumSecurityLevel {
+			return fmt.Errorf(
+				`%s with a security level of %d is weaker than the desired security level of %d`,
+				subject(),
+				levelToCheck,
+				minimumSecurityLevel,
+			)
+		}
+	case s.envelope.SecurityLevel > 0:
+		// check against envelope's minimum security level
+		if levelToCheck < s.envelope.SecurityLevel {
+			return fmt.Errorf(
+				`%s with a security level of %d is weaker than the envelope's minimum security level of %d`,
+				subject(),
+				levelToCheck,
+				s.envelope.SecurityLevel,
+			)
+		}
+	case levelToCheck < defaultSecurityLevel:
+		// check against default security level as fallback
+		return fmt.Errorf(
+			`%s with a security level of %d is weaker than the default minimum security level of %d`,
+			subject(),
+			levelToCheck,
+			defaultSecurityLevel,
+		)
+	}
+
+	return nil
+}
+
 // NonceSize returns the nonce size to use for new letters.
 func (s *Session) NonceSize() int {
 	size := s.maxSecurityLevel / 32
diff --git a/signet.go b/signet.go
index f6724cb..5c3e7d7 100644
--- a/signet.go
+++ b/signet.go
@@ -220,8 +220,8 @@ func (signet *Signet) StoreKey() error {
 
 // Verify verifies the signature of the signet.
 func (signet *Signet) Verify() error {
-	// FIXME
-	return errors.New("NIY")
+	// TODO
+	return errors.New("signet verification not yet implemented")
 }
 
 // Burn destroys all the key material and renders the Signet unusable. This is currently ineffective, see known issues in the project's README.
diff --git a/suite.go b/suite.go
new file mode 100644
index 0000000..232e95a
--- /dev/null
+++ b/suite.go
@@ -0,0 +1,17 @@
+package jess
+
+// Suite status options
+const (
+	SuiteStatusDeprecated  uint8 = 0
+	SuiteStatusPermitted   uint8 = 1
+	SuiteStatusRecommended uint8 = 2
+)
+
+// Suite describes a cipher suite - a set of algorithms and the attributes they provide.
+type Suite struct {
+	ID            string
+	Tools         []string
+	Provides      *Requirements
+	SecurityLevel int
+	Status        uint8
+}
diff --git a/suites.go b/suites.go
new file mode 100644
index 0000000..75b04d9
--- /dev/null
+++ b/suites.go
@@ -0,0 +1,100 @@
+package jess
+
+var (
+	// lists
+	suitesMap  = make(map[string]*Suite)
+	suitesList []*Suite
+
+	// suite definitions
+
+	// SuiteKeyV1 is a cipher suite for encryption with a key.
+	SuiteKeyV1 = registerSuite(&Suite{
+		ID:            "key_v1",
+		Tools:         []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuitePasswordV1 is a cipher suite for encryption with a password.
+	SuitePasswordV1 = registerSuite(&Suite{
+		ID:            "pw_v1",
+		Tools:         []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
+	SuiteRcptOnlyV1 = registerSuite(&Suite{
+		ID:            "rcpt_v1",
+		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteSignV1 is a cipher suite for signing (no encryption).
+	SuiteSignV1 = registerSuite(&Suite{
+		ID:            "sign_v1",
+		Tools:         []string{"Ed25519(BLAKE2b-256)"},
+		Provides:      newEmptyRequirements().Add(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
+	SuiteCompleteV1 = registerSuite(&Suite{
+		ID:            "v1",
+		Tools:         []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
+	SuiteWireV1 = registerSuite(&Suite{
+		ID:            "w1",
+		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+
+	// currently recommended suites
+
+	// SuiteKey is a a cipher suite for encryption with a key.
+	SuiteKey = SuiteKeyV1
+	// SuitePassword is a a cipher suite for encryption with a password.
+	SuitePassword = SuitePasswordV1
+	// SuiteRcptOnly is a a cipher suite for encrypting for someone, but without verifying the sender/source.
+	SuiteRcptOnly = SuiteRcptOnlyV1
+	// SuiteSign is a a cipher suite for signing (no encryption).
+	SuiteSign = SuiteSignV1
+	// SuiteComplete is a a cipher suite for both encrypting for someone and signing.
+	SuiteComplete = SuiteCompleteV1
+	// SuiteWire is a a cipher suite for network communication, including authentication of the server, but not the client.
+	SuiteWire = SuiteWireV1
+)
+
+func registerSuite(suite *Suite) (suiteID string) {
+	// add if not exists
+	_, ok := suitesMap[suite.ID]
+	if !ok {
+		suitesMap[suite.ID] = suite
+		suitesList = append(suitesList, suite)
+	}
+
+	return suite.ID
+}
+
+// GetSuite returns the suite with the given ID.
+func GetSuite(suiteID string) (suite *Suite, ok bool) {
+	suite, ok = suitesMap[suiteID]
+	return
+}
+
+// Suites returns all registered suites as a slice.
+func Suites() []*Suite {
+	return suitesList
+}
+
+// SuitesMap returns all registered suites as a map.
+func SuitesMap() map[string]*Suite {
+	return suitesMap
+}
diff --git a/suites_test.go b/suites_test.go
new file mode 100644
index 0000000..86a37c8
--- /dev/null
+++ b/suites_test.go
@@ -0,0 +1,474 @@
+package jess
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/safing/jess/hashtools"
+	"github.com/safing/jess/tools"
+)
+
+func getSuite(t *testing.T, suiteID string) (suite *Suite) {
+	suite, ok := GetSuite(suiteID)
+	if !ok {
+		t.Fatalf("suite %s does not exist", suiteID)
+		return nil
+	}
+	return suite
+}
+
+func TestSuites(t *testing.T) {
+	for _, suite := range Suites() {
+
+		err := suiteBullshitCheck(suite)
+		if err != nil {
+			t.Errorf("suite %s has incorrect property: %s", suite.ID, err)
+			continue
+		}
+
+		envelope, err := setupEnvelopeAndTrustStore(t, suite)
+		if err != nil {
+			t.Errorf("failed to setup test envelope for suite %s: %s", suite.ID, err)
+			continue
+		}
+		if envelope == nil {
+			t.Errorf("suite %s has an invalid toolset", suite.ID)
+			continue
+		}
+
+		session, err := envelope.Correspondence(testTrustStore)
+		if err != nil {
+			t.Errorf("failed to init session for suite %s: %s", suite.ID, err)
+			continue
+		}
+
+		letter, err := session.Close([]byte(testData1))
+		if err != nil {
+			tErrorf(t, "suite %s failed to close (1): %s", suite.ID, err)
+			continue
+		}
+
+		msg, err := letter.ToJSON()
+		if err != nil {
+			tErrorf(t, "suite %s failed to json encode (1): %s", suite.ID, err)
+			continue
+		}
+
+		// test 2: open
+
+		letter2, err := LetterFromJSON(msg)
+		if err != nil {
+			tErrorf(t, "suite %s failed to json decode (2): %s", suite.ID, err)
+			continue
+		}
+
+		origData2, err := letter2.Open(envelope.suite.Provides, testTrustStore)
+		if err != nil {
+			tErrorf(t, "suite %s failed to open (2): %s", suite.ID, err)
+			continue
+		}
+		if string(origData2) != testData1 {
+			tErrorf(t, "%v original data mismatch (2): %s", suite.ID, string(origData2))
+			continue
+		}
+
+		// test 2.1: verify
+
+		letter21, err := LetterFromJSON(msg)
+		if err != nil {
+			tErrorf(t, "suite %s failed to json decode (2): %s", suite.ID, err)
+			continue
+		}
+
+		if len(letter21.Signatures) > 0 {
+			err = letter21.Verify(envelope.suite.Provides, testTrustStore)
+			if err != nil {
+				tErrorf(t, "suite %s failed to verify (2): %s", suite.ID, err)
+				continue
+			}
+		}
+
+	}
+
+}
+
+func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
+	// pre checks
+	if suite.Provides == nil {
+		return errors.New("provides no requirement attributes")
+	}
+	if suite.SecurityLevel == 0 {
+		return errors.New("does not specify security level")
+	}
+
+	// create session struct for holding information
+	s := &Session{
+		envelope: &Envelope{
+			suite: suite,
+		},
+		toolRequirements: newEmptyRequirements(),
+	}
+
+	// check if we are assuming we have a key
+	assumeKey := strings.Contains(suite.ID, "key")
+	if assumeKey {
+		s.toolRequirements.Add(SenderAuthentication)
+		s.toolRequirements.Add(RecipientAuthentication)
+	}
+
+	// tool check loop: start
+	for i, toolID := range suite.Tools {
+
+		////////////////////////////////////////
+		// tool check loop: check for duplicates
+		////////////////////////////////////////
+
+		for j, dupeToolID := range suite.Tools {
+			if i != j && toolID == dupeToolID {
+				return fmt.Errorf("cannot use tool %s twice, each tool may be only specified once", toolID)
+			}
+		}
+
+		///////////////////////////////////////
+		// tool check loop: parse, prep and get
+		///////////////////////////////////////
+
+		var (
+			hashTool  *hashtools.HashTool
+			hashSumFn func() ([]byte, error)
+		)
+
+		// parse ID for args
+		var arg string
+		if strings.Contains(toolID, "(") {
+			splitted := strings.Split(toolID, "(")
+			toolID = splitted[0]
+			arg = strings.Trim(splitted[1], "()")
+		}
+
+		// get tool
+		tool, err := tools.Get(toolID)
+		if err != nil {
+			return fmt.Errorf("the specified tool %s could not be found", toolID)
+		}
+
+		// create logic instance and add to logic and state lists
+		logic := tool.Factory()
+		s.all = append(s.all, logic)
+		if tool.Info.HasOption(tools.OptionHasState) {
+			s.toolsWithState = append(s.toolsWithState, logic)
+		}
+
+		///////////////////////////////////////////////////////////////
+		// tool check loop: assign tools to queues and add requirements
+		///////////////////////////////////////////////////////////////
+
+		switch tool.Info.Purpose {
+		case tools.PurposeKeyDerivation:
+			if s.kdf != nil {
+				return fmt.Errorf("cannot use %s, you may only specify one key derivation tool and %s was already specified", tool.Info.Name, s.kdf.Info().Name)
+			}
+			s.kdf = logic
+
+		case tools.PurposePassDerivation:
+			if s.passDerivator != nil {
+				return fmt.Errorf("cannot use %s, you may only specify one password derivation tool and %s was already specified", tool.Info.Name, s.passDerivator.Info().Name)
+			}
+			s.passDerivator = logic
+			s.toolRequirements.Add(SenderAuthentication)
+			s.toolRequirements.Add(RecipientAuthentication)
+
+		case tools.PurposeKeyExchange:
+			s.keyExchangers = append(s.keyExchangers, logic)
+			s.toolRequirements.Add(RecipientAuthentication)
+
+		case tools.PurposeKeyEncapsulation:
+			s.keyEncapsulators = append(s.keyEncapsulators, logic)
+			s.toolRequirements.Add(RecipientAuthentication)
+
+		case tools.PurposeSigning:
+			s.signers = append(s.signers, logic)
+			s.toolRequirements.Add(SenderAuthentication)
+
+		case tools.PurposeIntegratedCipher:
+			s.integratedCiphers = append(s.integratedCiphers, logic)
+			s.toolRequirements.Add(Confidentiality)
+			s.toolRequirements.Add(Integrity)
+
+		case tools.PurposeCipher:
+			s.ciphers = append(s.ciphers, logic)
+			s.toolRequirements.Add(Confidentiality)
+
+		case tools.PurposeMAC:
+			s.macs = append(s.macs, logic)
+			s.toolRequirements.Add(Integrity)
+		}
+
+		////////////////////////////////////////////////
+		// tool check loop: process options, get hashers
+		////////////////////////////////////////////////
+
+		for _, option := range tool.Info.Options {
+			switch option {
+
+			case tools.OptionNeedsManagedHasher:
+				// get managed hasher list
+				var managedHashers map[string]*managedHasher
+				switch tool.Info.Purpose {
+				case tools.PurposeMAC:
+					if s.managedMACHashers == nil {
+						s.managedMACHashers = make(map[string]*managedHasher)
+					}
+					managedHashers = s.managedMACHashers
+				case tools.PurposeSigning:
+					if s.managedSigningHashers == nil {
+						s.managedSigningHashers = make(map[string]*managedHasher)
+					}
+					managedHashers = s.managedSigningHashers
+				default:
+					return fmt.Errorf("only MAC and Signing tools may use managed hashers")
+				}
+
+				// get or assign a new managed hasher
+				mngdHasher, ok := managedHashers[arg]
+				if !ok {
+					// get hashtool
+					ht, err := hashtools.Get(arg)
+					if err != nil {
+						return fmt.Errorf("the specified hashtool for %s(%s) could not be found", toolID, arg)
+					}
+
+					// save to managed hashers
+					mngdHasher = &managedHasher{
+						tool: ht,
+						hash: ht.New(),
+					}
+					managedHashers[arg] = mngdHasher
+				}
+
+				hashTool = mngdHasher.tool
+				hashSumFn = mngdHasher.Sum
+
+			case tools.OptionNeedsDedicatedHasher:
+				hashTool, err = hashtools.Get(arg)
+				if err != nil {
+					return fmt.Errorf("the specified hashtool for %s(%s) could not be found", toolID, arg)
+				}
+
+			}
+		}
+
+		///////////////////////////////////
+		// tool check loop: initialize tool
+		///////////////////////////////////
+
+		// init tool
+		logic.Init(
+			tool,
+			&Helper{
+				session: s,
+				info:    tool.Info,
+			},
+			hashTool,
+			hashSumFn,
+		)
+
+		//////////////////////////////////////////////////
+		// tool check loop: calc and check security levels
+		//////////////////////////////////////////////////
+
+		err = s.calcAndCheckSecurityLevel(logic, nil)
+		if err != nil {
+			return err
+		}
+
+	} // tool check loop: end
+
+	///////////////
+	// final checks
+	///////////////
+
+	// check requirements requirements
+	if s.toolRequirements.Empty() {
+		return errors.New("suite does not provide any security attributes")
+	}
+
+	// check if we have recipient auth without confidentiality
+	if s.toolRequirements.Has(RecipientAuthentication) &&
+		!s.toolRequirements.Has(Confidentiality) {
+		return errors.New("having recipient authentication without confidentiality does not make sense")
+	}
+
+	// check if we have confidentiality without integrity
+	if s.toolRequirements.Has(Confidentiality) &&
+		!s.toolRequirements.Has(Integrity) {
+		return errors.New("having confidentiality without integrity does not make sense")
+	}
+
+	// check if we are missing a kdf, but need one
+	if s.kdf == nil && len(s.signers) != len(s.envelope.suite.Tools) {
+		return errors.New("missing a key derivation tool")
+	}
+
+	// check if have a kdf, even if we don't need one
+	if len(s.integratedCiphers) == 0 &&
+		len(s.ciphers) == 0 &&
+		len(s.macs) == 0 &&
+		s.kdf != nil {
+		return errors.New("key derivation tool specified, but not needed")
+	}
+
+	/////////////////////////////////////////
+	// check if values match suite definition
+	/////////////////////////////////////////
+
+	// check if security level matches
+	if s.SecurityLevel != suite.SecurityLevel {
+		return fmt.Errorf("suite has incorrect security level: %d (expected %d)", suite.SecurityLevel, s.SecurityLevel)
+	}
+
+	// check if requirements match
+	if s.toolRequirements.SerializeToNoSpec() != suite.Provides.SerializeToNoSpec() {
+		return fmt.Errorf(
+			"suite has incorrect attributes: no %s (expected no %s)",
+			suite.Provides.SerializeToNoSpec(),
+			s.toolRequirements.SerializeToNoSpec(),
+		)
+	}
+
+	///////////////////////////////////////////////////////////
+	// check if computeSuiteAttributes returns the same results
+	///////////////////////////////////////////////////////////
+
+	computedSuite := computeSuiteAttributes(suite.Tools, assumeKey)
+	if computedSuite == nil {
+		return errors.New("internal error: could not compute suite attributes")
+	}
+	if suite.SecurityLevel != computedSuite.SecurityLevel {
+		return fmt.Errorf("internal error: computeSuiteAttributes error: security level: suite=%d computed=%d", suite.SecurityLevel, computedSuite.SecurityLevel)
+	}
+	if suite.Provides.SerializeToNoSpec() != computedSuite.Provides.SerializeToNoSpec() {
+		return fmt.Errorf(
+			"internal error: computeSuiteAttributes error: attributes: suite=no %s compute=no %s)",
+			suite.Provides.SerializeToNoSpec(),
+			computedSuite.Provides.SerializeToNoSpec(),
+		)
+	}
+
+	return nil
+}
+
+func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
+	new := &Suite{
+		Provides:      newEmptyRequirements(),
+		SecurityLevel: 0,
+	}
+
+	// if we have a key
+	if assumeKey {
+		new.Provides.Add(SenderAuthentication)
+		new.Provides.Add(RecipientAuthentication)
+	}
+
+	// check all security levels and collect attributes
+	for _, toolID := range toolIDs {
+
+		///////////////////////////////////////
+		// tool check loop: parse, prep and get
+		///////////////////////////////////////
+
+		var hashTool *hashtools.HashTool
+
+		// parse ID for args
+		var arg string
+		if strings.Contains(toolID, "(") {
+			splitted := strings.Split(toolID, "(")
+			toolID = splitted[0]
+			arg = strings.Trim(splitted[1], "()")
+		}
+
+		// get tool
+		tool, err := tools.Get(toolID)
+		if err != nil {
+			return nil
+		}
+
+		// create logic instance and add to logic and state lists
+		logic := tool.Factory()
+
+		//////////////////////////////////////
+		// tool check loop: collect attributes
+		//////////////////////////////////////
+
+		switch tool.Info.Purpose {
+		case tools.PurposePassDerivation:
+			new.Provides.Add(SenderAuthentication)
+			new.Provides.Add(RecipientAuthentication)
+
+		case tools.PurposeKeyExchange:
+			new.Provides.Add(RecipientAuthentication)
+
+		case tools.PurposeKeyEncapsulation:
+			new.Provides.Add(RecipientAuthentication)
+
+		case tools.PurposeSigning:
+			new.Provides.Add(SenderAuthentication)
+
+		case tools.PurposeIntegratedCipher:
+			new.Provides.Add(Confidentiality)
+			new.Provides.Add(Integrity)
+
+		case tools.PurposeCipher:
+			new.Provides.Add(Confidentiality)
+
+		case tools.PurposeMAC:
+			new.Provides.Add(Integrity)
+		}
+
+		////////////////////////////////////////////////
+		// tool check loop: process options, get hashers
+		////////////////////////////////////////////////
+
+		for _, option := range tool.Info.Options {
+			switch option {
+			case tools.OptionNeedsManagedHasher,
+				tools.OptionNeedsDedicatedHasher:
+				hashTool, err = hashtools.Get(arg)
+				if err != nil {
+					return nil
+				}
+			}
+		}
+
+		///////////////////////////////////
+		// tool check loop: initialize tool
+		///////////////////////////////////
+
+		// init tool
+		logic.Init(
+			tool,
+			&Helper{
+				info: tool.Info,
+			},
+			hashTool,
+			nil,
+		)
+
+		//////////////////////////////////////////
+		// tool check loop: compute security level
+		//////////////////////////////////////////
+
+		toolSecurityLevel, err := logic.SecurityLevel(nil)
+		if err != nil {
+			return nil
+		}
+		if new.SecurityLevel == 0 || toolSecurityLevel < new.SecurityLevel {
+			new.SecurityLevel = toolSecurityLevel
+		}
+
+	}
+
+	return new
+}
diff --git a/tools/gostdlib/rsa-oaep.go b/tools/gostdlib/rsa-oaep.go
index 976a6de..b15756c 100644
--- a/tools/gostdlib/rsa-oaep.go
+++ b/tools/gostdlib/rsa-oaep.go
@@ -35,6 +35,9 @@ func (oaep *RsaOAEP) EncapsulateKey(key []byte, signet tools.SignetInt) ([]byte,
 	if !ok {
 		return nil, tools.ErrInvalidKey
 	}
+	if rsaPubKey == nil {
+		return nil, tools.ErrInvalidKey
+	}
 
 	// check key length: The message must be no longer than the length of the public modulus minus twice the hash length, minus a further 2.
 	maxMsgSize := rsaPubKey.Size() - (2 * oaep.HashTool().DigestSize) - 2
diff --git a/tools_test.go b/tools_test.go
index 041bbcc..b3eccac 100644
--- a/tools_test.go
+++ b/tools_test.go
@@ -1,7 +1,12 @@
 package jess
 
 import (
+	"bytes"
+	"encoding/json"
 	"testing"
+	"time"
+
+	"github.com/safing/jess/hashtools"
 
 	"github.com/safing/jess/tools"
 
@@ -32,7 +37,39 @@ func TestConformity(t *testing.T) {
 
 }
 
+func TestPasswordHashingSpeed(t *testing.T) {
+	// skip in short tests and when not running comprehensive
+	if testing.Short() || !runComprehensiveTestsActive {
+		return
+	}
+	// run this test with
+	// go test -timeout 10m github.com/safing/jess -v -count=1 -ldflags "-X github.com/safing/jess.RunComprehensiveTests=true" -run ^TestPasswordHashingSpeed$
+
+	for _, tool := range tools.AsList() {
+		if tool.Info.Purpose == tools.PurposePassDerivation {
+			password := []byte(testPassword1)
+			salt, err := RandomBytes(4)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			start := time.Now()
+			_, err = tool.StaticLogic.DeriveKeyFromPassword(password, salt)
+			if err != nil {
+				t.Fatal(err)
+			}
+			t.Logf("%s took %s to derive key from password", tool.Info.Name, time.Since(start))
+		}
+	}
+}
+
+//nolint:gocognit,gocyclo
 func TestSignetHandling(t *testing.T) {
+	hashTool, err := hashtools.Get("SHA2-256")
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	for _, tool := range tools.AsList() {
 		switch tool.Info.Purpose {
 		case tools.PurposeKeyExchange,
@@ -88,6 +125,128 @@ func TestSignetHandling(t *testing.T) {
 				t.Fatalf("failed to load %s recipient: %s", tool.Info.Name, err)
 			}
 
+			// store signet
+			signetJSON, err := json.Marshal(signet)
+			if err != nil {
+				t.Fatalf("failed to serialize %s signet: %s", tool.Info.Name, err)
+			}
+
+			// load signet
+			loadedSignet := &Signet{}
+			err = json.Unmarshal(signetJSON, loadedSignet)
+			if err != nil {
+				t.Fatalf("failed to parse serialized %s signet: %s", tool.Info.Name, err)
+			}
+			err = loadedSignet.LoadKey()
+			if err != nil {
+				t.Fatalf("failed to load key of %s signet: %s", tool.Info.Name, err)
+			}
+
+			// store rcpt
+			rcptJSON, err := json.Marshal(rcpt)
+			if err != nil {
+				t.Fatalf("failed to serialize %s rcpt: %s", tool.Info.Name, err)
+			}
+
+			// load rcpt
+			loadedRcpt := &Signet{}
+			err = json.Unmarshal(rcptJSON, loadedRcpt)
+			if err != nil {
+				t.Fatalf("failed to parse serialized %s rcpt: %s", tool.Info.Name, err)
+			}
+			err = loadedRcpt.LoadKey()
+			if err != nil {
+				t.Fatalf("failed to load key of %s rcpt: %s", tool.Info.Name, err)
+			}
+
+			// load tool
+			err = loadedSignet.loadTool()
+			if err != nil {
+				t.Fatalf("failed to load tool of %s: %s", tool.Info.Name, err)
+			}
+
+			// init tool with hashtool
+			toolLogic := tool.Factory()
+			hasher := managedHasher{
+				tool: hashTool,
+				hash: hashTool.New(),
+			}
+			toolLogic.Init(
+				tool,
+				&Helper{info: tool.Info},
+				hashTool,
+				hasher.Sum,
+			)
+
+			// do an operation
+			switch loadedSignet.tool.Info.Purpose {
+			case tools.PurposeKeyExchange:
+				// create and generate signet
+				peerSignet := NewSignetBase(tool)
+				err := peerSignet.GenerateKey()
+				if err != nil {
+					t.Fatalf("failed to generate key with %s peer: %s", tool.Info.Name, err)
+				}
+
+				// transform to recipient
+				peerRcpt, err := peerSignet.AsRecipient()
+				if err != nil {
+					t.Fatalf("failed to get %s peer as recipient: %s", tool.Info.Name, err)
+				}
+
+				sharedSecret1, err := toolLogic.MakeSharedKey(loadedSignet, peerRcpt)
+				if err != nil {
+					t.Fatalf("failed to make shared secret 1 with %s: %s", tool.Info.Name, err)
+				}
+
+				sharedSecret2, err := toolLogic.MakeSharedKey(peerSignet, loadedRcpt)
+				if err != nil {
+					t.Fatalf("failed to make shared secret 2 with %s: %s", tool.Info.Name, err)
+				}
+
+				if !bytes.Equal(sharedSecret1, sharedSecret2) {
+					t.Fatalf("shared secrets made with %s do not match, got:\ns1: %v\ns2: %v", tool.Info.Name, sharedSecret1, sharedSecret2)
+				}
+			case tools.PurposeKeyEncapsulation:
+				origKey, err := RandomBytes(16)
+				if err != nil {
+					t.Fatalf("failed to generate test key: %s", err)
+				}
+
+				wrappedKey, err := toolLogic.EncapsulateKey(origKey, loadedRcpt)
+				if err != nil {
+					t.Fatalf("failed to encapsulate key with %s: %s", tool.Info.Name, err)
+				}
+
+				unwrappedKey, err := toolLogic.UnwrapKey(wrappedKey, loadedSignet)
+				if err != nil {
+					t.Fatalf("failed to unwrap key with %s: %s", tool.Info.Name, err)
+				}
+
+				if !bytes.Equal(origKey, unwrappedKey) {
+					t.Fatalf("original and unwrapped key with %s do not match, got:\norig: %v\nunwrapped: %v", tool.Info.Name, origKey, unwrappedKey)
+				}
+
+			case tools.PurposeSigning:
+				testData, err := RandomBytes(16)
+				if err != nil {
+					t.Fatalf("failed to generate test data: %s", err)
+				}
+				_, err = hasher.hash.Write(testData)
+				if err != nil {
+					t.Fatalf("failed to write to hash: %s", err)
+				}
+
+				signature, err := toolLogic.Sign(testData, nil, loadedSignet)
+				if err != nil {
+					t.Fatalf("failed to sign with %s: %s", tool.Info.Name, err)
+				}
+
+				err = toolLogic.Verify(testData, nil, signature, loadedRcpt)
+				if err != nil {
+					t.Fatalf("failed to verify with %s: %s", tool.Info.Name, err)
+				}
+			}
 		}
 	}
 
diff --git a/truststores/io.go b/truststores/io.go
index 282853f..ff420c3 100644
--- a/truststores/io.go
+++ b/truststores/io.go
@@ -65,9 +65,6 @@ func WriteEnvelopeToFile(envelope *jess.Envelope, filename string) error {
 		return errInvalidEnvelopeNameChars
 	}
 
-	// serialize requirements
-	envelope.SerializeRequirements()
-
 	// serialize
 	data, err := dsd.DumpIndent(envelope, dsd.JSON, "\t")
 	if err != nil {
@@ -100,8 +97,8 @@ func LoadEnvelopeFromFile(filename string) (*jess.Envelope, error) {
 		return nil, err
 	}
 
-	// parse requirements from "No"
-	err = envelope.LoadRequirements()
+	// load suite using SuiteID
+	err = envelope.LoadSuite()
 	if err != nil {
 		return nil, err
 	}