diff --git a/quantumlayer/.dockerignore b/quantumlayer/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..de153db3b796413119418ec4b9f82dad2e8cb939
--- /dev/null
+++ b/quantumlayer/.dockerignore
@@ -0,0 +1 @@
+artifacts
diff --git a/quantumlayer/.gitignore b/quantumlayer/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..de153db3b796413119418ec4b9f82dad2e8cb939
--- /dev/null
+++ b/quantumlayer/.gitignore
@@ -0,0 +1 @@
+artifacts
diff --git a/quantumlayer/Dockerfile b/quantumlayer/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d638068b767b6a143b3249323f64e2773736ef54
--- /dev/null
+++ b/quantumlayer/Dockerfile
@@ -0,0 +1,17 @@
+ARG GOLANG_VERSION=1.21
+ARG BUILDARGS
+
+FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm as builder
+ARG GITLAB_LOGIN
+ARG GITLAB_TOKEN
+WORKDIR /quantumlayer/
+COPY . .
+RUN echo "machine code.fbi.h-da.de login ${GITLAB_LOGIN} password ${GITLAB_TOKEN}" > ~/.netrc
+RUN --mount=type=cache,target=/root/go/pkg/mod \
+    --mount=type=cache,target=/root/.cache/go-build \
+    make build
+
+FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm
+WORKDIR /app/
+COPY --from=builder /quantumlayer/artifacts/quantumlayer ./quantumlayer
+ENTRYPOINT ["./quantumlayer"]
diff --git a/quantumlayer/Makefile b/quantumlayer/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..96932cd259b7332d370b66fbb98296f5d401f0ce
--- /dev/null
+++ b/quantumlayer/Makefile
@@ -0,0 +1,19 @@
+MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
+MAKEFILE_DIR  := $(dir $(MAKEFILE_PATH))
+GOPATH := $(~/go)
+
+GOCMD=go
+GOBUILD=$(GOCMD) build
+GOCLEAN=$(GOCMD) clean
+BUILD_ARTIFACTS_PATH=artifacts
+
+all: build
+
+pre:
+	mkdir -p $(BUILD_ARTIFACTS_PATH)
+
+build: pre
+	$(GOBUILD) -o $(BUILD_ARTIFACTS_PATH)/quantumlayer ./example/main.go
+
+container: build
+	docker buildx build --rm -t quantumlayer --load -f ./Dockerfile .
diff --git a/quantumlayer/example/main.go b/quantumlayer/example/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a3860d9d0024772f07d079ff3a5d5707107ff8ac
--- /dev/null
+++ b/quantumlayer/example/main.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"net"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"code.fbi.h-da.de/danet/quantumlayer"
+	pb "code.fbi.h-da.de/danet/quipsec/gen/go/quipsec"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+	"gopkg.in/yaml.v3"
+)
+
+type Config struct {
+	KMSAddr      string `yaml:"KMSAddr"`
+	UDPAddr      string `yaml:"UDPAddr"`
+	PeerUDPAddr  string `yaml:"PeerUDPAddr"`
+	GenerateKeys bool   `yaml:"GenerateKeys"`
+}
+
+func main() {
+	// TODO: flag validation
+	configPath := flag.String("config", "", "path to the config file")
+	flag.Parse()
+
+	// unmarshal config
+	config := &Config{}
+	file, err := os.ReadFile(*configPath)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+	if err := yaml.Unmarshal(file, config); err != nil {
+		logrus.Fatal(err)
+	}
+
+	newPeerConn, err := grpc.Dial(config.KMSAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		log.Fatal(err)
+		return
+	}
+
+	kmsClient := pb.NewKmsQkdmCommunicationServiceClient(newPeerConn)
+
+	peerUDPAddr, err := net.ResolveUDPAddr("udp", config.PeerUDPAddr)
+	if err != nil {
+		log.Fatal(err)
+		return
+	}
+
+	stopChan := make(chan os.Signal)
+	signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)
+
+	ql := quantumlayer.NewQuantumlayerEmuPRNG(kmsClient, os.Stdout, logrus.GetLevel(), false)
+	ql.Configure(config.GenerateKeys, config.UDPAddr)
+	ql.PowerOn()
+	ql.AddPeer(peerUDPAddr)
+
+	<-stopChan
+}
diff --git a/quantumlayer/quantumlayer-emu-prng.go b/quantumlayer/quantumlayer-emu-prng.go
new file mode 100644
index 0000000000000000000000000000000000000000..d70095adf735004b54a684d632d6f2844470e52f
--- /dev/null
+++ b/quantumlayer/quantumlayer-emu-prng.go
@@ -0,0 +1,374 @@
+package quantumlayer
+
+/*
+ *
+ * This packages "implements", actually it only emulates, the output of a
+ * quantum link, i.e., the transmitted random numbers from one quantum
+ * sender to a quantum receiver.
+ * This relies on crypto/rand to generate the random numbers that will be
+ * transmitted to the other end.
+ *
+ */
+import (
+	"context"
+	"crypto/rand"
+	"encoding/json"
+	"errors"
+	"io"
+	"math/big"
+	"net"
+	"strconv"
+	"sync"
+	"time"
+
+	pb "code.fbi.h-da.de/danet/quipsec/gen/go/quipsec"
+	"github.com/sirupsen/logrus"
+	logi "github.com/sirupsen/logrus"
+)
+
+type QuantumPayloadElement struct {
+	BulkKeyId     int64   `json:"bulk-key-id"`     // the unique ID of this bulk of keys
+	BulkKeyLength int     `json:"bulk-key-length"` // the length, counted in bytes, of bulkKey
+	BulkKey       *[]byte `json:"bulk-key"`        // the bulk key
+}
+
+type QuantumlayerEmuPRNG struct {
+	client           pb.KmsQkdmCommunicationServiceClient
+	configured       bool
+	poweron          bool // set to yes if operation, i.e., generating keys
+	generateKeys     bool // set to yes, if this qle should generate random number.
+	incomingRandNums chan QuantumPayloadElement
+	peerNumbers      *NumberStore
+	myNumbers        *NumberStore
+	localQLAddress   string
+	udpSrvConn       *net.UDPConn
+	qlPeer           string
+	qlPeerCancel     context.CancelFunc
+	qlLocalPort      *net.UDPAddr
+	qlPeerMutex      sync.Mutex
+}
+
+// We use our own logrus instance, as we would like to have different log levels for different parts.
+var log = logrus.New()
+
+func NewQuantumlayerEmuPRNG(client pb.KmsQkdmCommunicationServiceClient, logOutput io.Writer, logLevel logi.Level, logInJson bool) (newql *QuantumlayerEmuPRNG) {
+	/*
+	 * Setup logging
+	 */
+
+	//What level
+	log.SetLevel(logLevel)
+	// Where to send log out put
+	log.SetOutput(logOutput)
+	// and plain-text (standard) or json
+	if !logInJson {
+		log.SetFormatter(&logi.TextFormatter{})
+	} else {
+		log.SetFormatter(&logi.JSONFormatter{})
+	}
+	// print code function if level is set to Trace
+	if logLevel == logi.TraceLevel {
+		log.SetReportCaller(true)
+	} else {
+		log.SetReportCaller(false)
+	}
+
+	// Return PRNG Quantum Layer
+	return &QuantumlayerEmuPRNG{
+		client:           client,
+		configured:       false,
+		poweron:          false,
+		generateKeys:     false,
+		incomingRandNums: make(chan QuantumPayloadElement),
+		peerNumbers:      NewNumberStore(40000),
+		myNumbers:        NewNumberStore(40000),
+		qlPeer:           "",
+	}
+}
+
+// Configure the quantum emulation, but do not start if yet
+func (qlemuprng *QuantumlayerEmuPRNG) Configure(enableKeyGeneration bool, localQLAddress ...string) {
+
+	// Start receiving numberstores
+	go qlemuprng.peerNumbers.receiveNumbers(qlemuprng.incomingRandNums, qlemuprng.client)
+
+	// Determine if a local UDP address should be used or not
+	if len(localQLAddress) == 0 {
+		// No input
+		qlemuprng.localQLAddress = ":0"
+	} else {
+		qlemuprng.localQLAddress = localQLAddress[0]
+	}
+	qlemuprng.generateKeys = enableKeyGeneration
+	qlemuprng.configured = true
+}
+
+// Power on the quantum layer, i.e., open up the communication ports for the
+// other quantum module
+func (qlemuprng *QuantumlayerEmuPRNG) PowerOn() {
+	if !qlemuprng.configured {
+		// nothing do here move on
+		log.Errorf("QuantumlayerEmuPRNG: Sorry, the quantum layer is not configured for action. You've missed Configure()")
+		return
+	}
+	//qlemuprng.poweron = false
+	log.Infof("QuantumlayerEmuPRNG: is powering on...charging.")
+
+	if qlemuprng.generateKeys {
+		log.Infof("QuantumlayerEmuPRNG: will GENERATE random keys")
+	}
+
+	if qlemuprng.udpSrvConn == nil {
+		go func() {
+			// Get UDP server part going...
+			log.Debugf("QuantumlayerEmuPRNG: localQLAddress is %s", qlemuprng.localQLAddress)
+
+			// This reads random numbers from other Quantum end
+			udpSrvPort, err := net.ResolveUDPAddr("udp", qlemuprng.localQLAddress)
+			if err != nil {
+				log.Fatalf("QuantumlayerEmuPRNG: UDP failure: %s", err)
+				return
+			}
+
+			qlemuprng.udpSrvConn, err = net.ListenUDP("udp", udpSrvPort)
+			if err != nil {
+				log.Fatalf("QuantumlayerEmuPRNG: UDP failure: %s", err)
+				return
+			}
+			defer qlemuprng.udpSrvConn.Close()
+
+			// Retrieve local UDP address and store it for further actions.
+			qlemuprng.qlLocalPort = qlemuprng.udpSrvConn.LocalAddr().(*net.UDPAddr)
+
+			// TODO: This does not seem to be necessary if the gle is not generating rands
+			// serve UDP incoming
+			log.Infof("QuantumlayerEmuPRNG: started server, waiting for incoming rands on port %s \n", qlemuprng.udpSrvConn.LocalAddr().(*net.UDPAddr).String())
+			inBuffer := make([]byte, 1500)
+			for {
+				// Buffer for reading from "Quantum link"
+				n, addr, err := qlemuprng.udpSrvConn.ReadFromUDP(inBuffer)
+				if err != nil {
+					log.Errorf("QuantumlayerEmuPRNG: Could not read from UDP: %s", err)
+				} else {
+					log.Debugf("QuantumlayerEmuPRNG: read %d bytes from %s\n", n, addr)
+
+					// Check if sender of datagram is qlPeer
+					// Warning this is not checking the validity of the sender, i.e., spoofing is possible
+					if addr.String() == qlemuprng.qlPeer {
+						log.Debugf("QuantumlayerEmuPRNG: Peer %s listed", addr)
+						//dumb the received data into the channel and carry on
+						// TODO/XXX: no vetting for anything
+						// Unmarshall out of JSON
+						var inQBuffer QuantumPayloadElement
+						unmarshallErr := json.Unmarshal(inBuffer[0:n], &inQBuffer)
+						if unmarshallErr == nil {
+							qlemuprng.incomingRandNums <- inQBuffer
+						}
+					} else {
+						log.Infof("QuantumlayerEmuPRNG: Peer %s NOT listed", addr)
+					}
+				}
+			}
+		}()
+	}
+
+	// Wait for listening UDP socket in the above go-routine to get ready
+	for qlemuprng.udpSrvConn == nil {
+	}
+
+	// Ready, set, go!
+	qlemuprng.poweron = true
+
+	log.Infof("QuantumlayerEmuPRNG: is charged and powered on.")
+}
+
+// Power off the quantum layer, i.e., close the communication ports for the
+// other quantum module
+func (qlemuprng *QuantumlayerEmuPRNG) PowerOff() {
+	qlemuprng.poweron = false
+	log.Println("QuantumlayerEmuPRNG: is powered off...discharging.")
+}
+
+func (qlemuprng *QuantumlayerEmuPRNG) AddPeer(addr *net.UDPAddr) {
+	if !qlemuprng.poweron {
+		return
+	}
+	//TODO/XXX check the incoming addr
+
+	// Add  peer to the ....
+	qlemuprng.qlPeerMutex.Lock()
+	qlemuprng.qlPeer = addr.String()
+	qlemuprng.qlPeerMutex.Unlock()
+
+	// generate only keys if requested to do so.
+	if qlemuprng.generateKeys {
+		ctx, cancel := context.WithCancel(context.Background())
+		qlemuprng.qlPeerCancel = cancel
+
+		// Start the generation and shipping of random numbers
+		go func(ctx context.Context) {
+			for {
+				select {
+				case <-ctx.Done():
+					return
+				default:
+					if qlemuprng.poweron {
+						// retrieve a new back of random numbers
+						newNumberBatch := qlemuprng.GenerateRandomNumbers()
+						// TODO: Replace this by some generic encapsulation reader and not just JSON
+						//Get JSON for transmission ready
+						qpe := QuantumPayloadElement{time.Now().UnixNano(), len(newNumberBatch), &newNumberBatch}
+
+						// XXX/TODO: error must be handled
+						jsonPayload, err := json.Marshal(qpe)
+						if err != nil {
+							log.Errorf("QuantumlayerEmuPRNG: json.Marshal error %s", err)
+						}
+
+						_, _, err = qlemuprng.udpSrvConn.WriteMsgUDP(jsonPayload, nil, addr)
+						if err != nil {
+							log.Fatalf("QuantumlayerEmuPRNG: WriteMsgUDPAddrPort failed: %s", err)
+						}
+						qlemuprng.incomingRandNums <- qpe
+					}
+					// TODO: This sleep timer has to replaced by something for clever.
+					time.Sleep(5 * time.Second)
+				}
+			}
+		}(ctx)
+	}
+}
+
+func (qlemuprng *QuantumlayerEmuPRNG) RemovePeer() {
+	if !qlemuprng.poweron {
+		return
+	}
+
+	// Stop the generation and shipping of random numbers
+	qlemuprng.qlPeerCancel()
+
+	// delete peer
+	qlemuprng.qlPeerMutex.Lock()
+	qlemuprng.qlPeer = ""
+	qlemuprng.qlPeerMutex.Unlock()
+}
+
+func (qlemuprng *QuantumlayerEmuPRNG) GetLocalQLPort() (myAddr *net.UDPAddr) {
+	return qlemuprng.qlLocalPort
+}
+
+func (qlemuprng *QuantumlayerEmuPRNG) GenerateRandomNumbers() (randNums []byte) {
+	numRands, randError := rand.Int(rand.Reader, big.NewInt(1000))
+	if randError != nil {
+		log.Fatalf("QuantumlayerEmuPRNG: %s", randError)
+		return
+	}
+
+	b := make([]byte, numRands.Uint64())
+	_, randError = rand.Read(b)
+	if randError != nil {
+		log.Fatalf("QuantumlayerEmuPRNG: %s", randError)
+		return
+	}
+	return b
+}
+
+func (qlemuprng *QuantumlayerEmuPRNG) GetKeyBulkPeer() (QuantumLayerBulkKey, error) {
+	return qlemuprng.peerNumbers.GetBulk()
+}
+
+// GetStatus returns the current status of the QuantumLayerEmuPRNG. This
+// includes the information if the QLE is powered, aswell as if the QLE is
+// enabled for key generation.
+func (qlemuprng *QuantumlayerEmuPRNG) GetStatus() (poweredOn, enabled bool) {
+	return qlemuprng.poweron, qlemuprng.generateKeys
+}
+
+type NumberStore struct {
+	mu             sync.Mutex
+	maxBytes       int
+	storage        []byte
+	bulkKeyStorage []QuantumLayerBulkKey
+	topOfStorage   int
+}
+
+// Generates a new store with given maximum number of bytes
+func NewNumberStore(maxBytes int) (newNS *NumberStore) {
+	return &NumberStore{
+		maxBytes:     maxBytes,
+		storage:      make([]byte, maxBytes),
+		topOfStorage: 0,
+	}
+}
+
+func (store *NumberStore) GetBulk() (bulk QuantumLayerBulkKey, err error) {
+	store.mu.Lock()
+	defer store.mu.Unlock()
+
+	for nextID := range store.bulkKeyStorage {
+		next := store.bulkKeyStorage[nextID]
+		// Preprare to return
+		a := QuantumLayerBulkKey{
+			BulkKeyId:     next.BulkKeyId,
+			BulkKeyLength: next.BulkKeyLength,
+			BulkKey:       nil,
+		}
+
+		bulkKey := make([]byte, next.BulkKeyLength)
+		copy(bulkKey, *next.BulkKey)
+		a.BulkKey = &bulkKey
+
+		// Delete from key store
+		store.bulkKeyStorage = store.bulkKeyStorage[1:]
+		// and return
+		return a, nil
+	}
+	returnErr := errors.New("no bulk key to retrieve")
+	b := QuantumLayerBulkKey{}
+	return b, returnErr
+}
+
+func (store *NumberStore) GetBatch() (batch []byte) {
+	store.mu.Lock()
+	defer store.mu.Unlock()
+
+	if store.topOfStorage != 0 {
+		log.Debugf("QuantumlayerEmuPRNG: Have Storage in my belly")
+	}
+	// prepare to return full batch of numbers
+	batchReturn := make([]byte, store.topOfStorage)
+	copy(batchReturn, store.storage)
+	store.topOfStorage = 0
+
+	return batchReturn
+}
+
+func (store *NumberStore) receiveNumbers(incoming chan QuantumPayloadElement, client pb.KmsQkdmCommunicationServiceClient) {
+	for {
+		receivedNumbers := <-incoming
+		// add received to our buffer, if buffer still last
+		store.mu.Lock()
+		mem := QuantumLayerBulkKey{
+			BulkKeyId:     receivedNumbers.BulkKeyId,
+			BulkKeyLength: receivedNumbers.BulkKeyLength,
+			BulkKey:       receivedNumbers.BulkKey,
+		}
+		//store.bulkKeyStorage[receivedNumbers.BulkKeyId] = mem
+		store.bulkKeyStorage = append(store.bulkKeyStorage, mem)
+		store.mu.Unlock()
+
+		log.Info(mem.BulkKeyId)
+
+		_, err := client.PushKeys(context.Background(), &pb.PushKeysRequest{
+			Timestamp: time.Now().Unix(),
+			KeyBulk: &pb.KeyBulk{
+				KeyId:     strconv.FormatInt(receivedNumbers.BulkKeyId, 10),
+				KeyLength: uint64(receivedNumbers.BulkKeyLength),
+				Keys:      *receivedNumbers.BulkKey,
+			},
+		})
+		if err != nil {
+			logi.Error(err)
+		}
+	}
+}
diff --git a/quantumlayer/quantumlayer-emu-prng_test.go b/quantumlayer/quantumlayer-emu-prng_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..55854214246986afb98ba77fb37b00bf49ecbf93
--- /dev/null
+++ b/quantumlayer/quantumlayer-emu-prng_test.go
@@ -0,0 +1,62 @@
+package quantumlayer
+
+// Some tests
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"testing"
+	"time"
+
+	logrus "github.com/sirupsen/logrus"
+)
+
+func TestQuantumLayer(t *testing.T) {
+
+	// Generate UDPAddr for ql1 peer
+	udpQL2AddrString := fmt.Sprintf("127.0.0.1:%d", 5002)
+	udpQL2Addr, err := net.ResolveUDPAddr("udp", udpQL2AddrString)
+	if err != nil {
+		t.Fatalf("QuantumlayerEmuPRNG UDP failure: %s", err)
+		return
+	}
+
+	// Generate UDPAddr for ql2 peer
+	udpQL1AddrString := fmt.Sprintf("127.0.0.1:%d", 5001)
+	udpQL1Addr, err := net.ResolveUDPAddr("udp", udpQL1AddrString)
+	if err != nil {
+		t.Fatalf("QuantumlayerEmuPRNG UDP failure: %s", err)
+		return
+	}
+
+	ql1 := NewQuantumlayerEmuPRNG(nil, os.Stdout, logrus.DebugLevel, false)
+	ql1.Configure(true, udpQL1AddrString)
+	ql1.PowerOn() // this one generates keys
+	defer ql1.PowerOff()
+
+	ql2 := NewQuantumlayerEmuPRNG(nil, os.Stdout, logrus.DebugLevel, false)
+	ql2.Configure(false, udpQL2AddrString)
+	ql2.PowerOn() // this one does NOT generate keys
+	defer ql2.PowerOff()
+
+	ql1.AddPeer(udpQL2Addr)
+	ql2.AddPeer(udpQL1Addr)
+
+	// Wait for key gen to get up and running
+	time.Sleep(5 * time.Second)
+
+	for n := 0; n < 2; n++ {
+		resultQl1, err := ql1.GetKeyBulkPeer()
+		if err == nil {
+			t.Logf("run %d, *ql1* keyid %d \t keylen %d", n, resultQl1.BulkKeyId, resultQl1.BulkKeyLength)
+		} else {
+			t.Fatalf("Couldn't read local ql1 batch with error %s", err)
+		}
+
+		// TODO: Calculate checksum of BulkKey and double-check
+
+		time.Sleep(5 * time.Second)
+
+	}
+}
diff --git a/quantumlayer/quantumlayer.go b/quantumlayer/quantumlayer.go
new file mode 100644
index 0000000000000000000000000000000000000000..3399b6b956d942e4a71592819dad4a0f1b91af4b
--- /dev/null
+++ b/quantumlayer/quantumlayer.go
@@ -0,0 +1,31 @@
+// This package aims at emulating a quantum link and is extendable to different models
+// One can use most of the sourc code of emu-prng and reuse it.
+// To add a different quantum module one should only modify the GenerateRandomNumbers function
+
+package quantumlayer
+
+type QuantumLayerBulkKey struct {
+	BulkKeyId     int64 // the unique ID of this bulk of keys
+	BulkKeyLength int   // the length, counted in bytes, of bulkKey
+	// TODO: Pointer of slice should have a well thought reason;
+	// ask Martin if this is really necessary here
+	BulkKey *[]byte // the bulk key
+}
+
+type QuantumLayer interface {
+	Configure(...string)                          // configure the interface, e.g., used IP/Port config if emulated
+	PowerOn(enableKeyGeneration bool)             // switch on the quantum layer element
+	PowerOff()                                    // switch off the quantum layer element
+	GetStatus() (poweredOn bool)                  // returns true if quantum layer element is powered on
+	AddPeer()                                     // Adds a Quantum Layer Peer to the peer list
+	RemovePeer()                                  // Remmoves a Quantum Layer Peer to the peer list
+	GetLocalQLPort()                              // Returns the information about the local quantum layer IP and port
+	GetKeyBulkPeer() (QuantumLayerBulkKey, error) // retrieve the bulk key received from peer
+	GenerateRandomNumbers() []byte                // generate a number of random numbers
+}
+
+type NumberLayer interface {
+	GetBatch() []byte // allows to retrieve the current available batch of numbers
+	GetBulk() (QuantumLayerBulkKey, error)
+	receiveNumbers(chan []byte)
+}