public byte[] processBlock(byte[] in, int inOff, int len) throws InvalidCipherTextException {
        if (param instanceof BGV11LeveledPublicKeyParameters) {
            // Encrypt under input level parameters
            BGV11LeveledPublicKeyParameters pk = ((BGV11LeveledPublicKeyParameters) param);
            BGV11LeveledParameters parameters = pk.getInputLevelParameters();

            // Load the message
            Element m = parameters.getRq().newElement();
            m.setFromBytes(in, inOff);

            // Choose the randomness
            Element r = parameters.getRtN().newRandomElement();

            // Encrypt
            Element c0 = m.add(ip(pk.getInputLevelB(), r));
            Element c1 = ip(pk.getInputLevelA(), r);

            // Move to bytes
            return new ElementObjectOutput().writeInt(pk.getInputLevel()).writeElements(c0, c1).toByteArray();
        } else if (param instanceof BGV11LeveledSecretKeyParameters) {
            // Dec
            BGV11LeveledSecretKeyParameters sk = ((BGV11LeveledSecretKeyParameters) param);

            // Load level
            ElementObjectInput reader = new ElementObjectInput(in, inOff, len);
            int level = reader.readInt();
            BGV11LeveledParameters parameters = sk.getParametersAt(level);
            Element s = sk.getSecretAt(level);

            // Load the ciphertext
            reader.setField(parameters.getRq());
            Element[] c = reader.readElements(2);

            this.decryptionLevel = level;

            // Decrypt
            return c[0].add(c[1].mul(s)).mod(parameters.getT()).toBytes();
        } else if (param instanceof BGV11LeveledAddParameters) {
            // Add
            BGV11LeveledPublicKeyParameters pk = ((BGV11LeveledAddParameters) param).getPk();

            ElementObjectInput reader = new ElementObjectInput(in, inOff, len);

            // Load left part
            int leftLevel = reader.readInt();
            BGV11LeveledParameters leftParams = pk.getParametersAt(leftLevel);
            reader.setField(leftParams.getRq());
            Element[] left = reader.readElements(2);

            // Load right
            int rightLevel = reader.readInt();
            BGV11LeveledParameters rightParams = pk.getParametersAt(rightLevel);
            reader.setField(rightParams.getRq());
            Element[] right = reader.readElements(2);

            // Check levels
            int currentLevel = leftLevel;
            if (leftLevel != rightLevel) {
                if (leftLevel < rightLevel) {
                    right = pk.refresh(rightLevel, leftLevel, right);
                } else {
                    left = pk.refresh(leftLevel, rightLevel, left);
                    currentLevel = rightLevel;
                }
            }
            int nextLevel = currentLevel - 1;

            // Sum
            left[0].add(right[0]);
            left[1].add(right[1]);

            // Move to bytes a refreshed ciphertext
            return new ElementObjectOutput().writeInt(nextLevel).writeElements(
                    pk.refresh(currentLevel, left)
//                    left
            ).toByteArray();
        } else if (param instanceof BGV11LeveledMulParameters) {
            // Mul
            BGV11LeveledPublicKeyParameters pk = ((BGV11LeveledMulParameters) param).getPk();

            ElementObjectInput reader = new ElementObjectInput(in, inOff, len);

            // Load left part
            int leftLevel = reader.readInt();
            BGV11LeveledParameters leftParams = pk.getParametersAt(leftLevel);
            reader.setField(leftParams.getRq());
            Element[] left = reader.readElements(2);

            // Load right
            int rightLevel = reader.readInt();
            BGV11LeveledParameters rightParams = pk.getParametersAt(rightLevel);
            reader.setField(rightParams.getRq());
            Element[] right = reader.readElements(2);

            // Check levels
            int currentLevel = leftLevel;
            if (leftLevel != rightLevel) {
                if (leftLevel < rightLevel) {
                    right = pk.refresh(rightLevel, leftLevel, right);
                } else {
                    left = pk.refresh(leftLevel, rightLevel, left);
                    currentLevel = rightLevel;
                }
            }
            int nextLevel = currentLevel - 1;

            // Mul
            Element[] product = new Element[3];
            product[0] = left[0].duplicate().mul(right[0]);
            product[1] = left[0].duplicate().mul(right[1]).add(left[1].duplicate().mul(right[0]));
            product[2] = left[1].duplicate().mul(right[1]);

            // Move to bytes a refreshed ciphertext
            return new ElementObjectOutput().writeInt(nextLevel).writeElements(
                    pk.refresh(currentLevel, product)
            ).toByteArray();
        }

        throw new IllegalStateException("Invalid parameters.");
    }