diff --git a/.gitignore b/.gitignore
index 7b35202b..90e9c209 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 resources/openpgpjs.pem
 build/
 .DS_Store
+node_modules
+test/integration/lib
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..f5632394
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+before_install:
+  - npm install -g grunt-cli
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 00000000..1909a371
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,52 @@
+module.exports = function(grunt) {
+    'use strict';
+
+    // Project configuration.
+    grunt.initConfig({
+        connect: {
+            dev: {
+                options: {
+                    port: 8680,
+                    base: '.',
+                    keepalive: true
+                }
+            },
+            test: {
+                options: {
+                    port: 8681,
+                    base: '.'
+                }
+            }
+        },
+
+        mocha: {
+            all: {
+                options: {
+                    urls: ['http://localhost:<%= connect.test.options.port %>/test/integration/index.html'],
+                    run: false,
+                    reporter: 'Spec'
+                }
+            }
+        },
+
+        copy: {
+            npm: {
+                expand: true,
+                flatten: true,
+                cwd: 'node_modules/',
+                src: ['requirejs/require.js', 'mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'sinon/pkg/sinon.js'],
+                dest: 'test/integration/lib/'
+            }
+        }
+    });
+
+    // Load the plugin(s)
+    grunt.loadNpmTasks('grunt-contrib-copy');
+    grunt.loadNpmTasks('grunt-contrib-connect');
+    grunt.loadNpmTasks('grunt-mocha');
+
+    // Test/Dev tasks
+    grunt.registerTask('dev', ['connect:dev']);
+    grunt.registerTask('test', ['copy', 'connect:test', 'mocha']);
+
+};
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..38b4abf2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+    "name": "openpgpjs",
+    "version": "0.1.0-dev",
+    "engines": {
+        "node": ">=0.8"
+    },
+    "scripts": {
+        "pretest": "make minify",
+        "test": "grunt test",
+        "start": "grunt dev"
+    },
+    "dependencies": {},
+    "devDependencies": {
+        "grunt": "0.4.1",
+        "mocha": "1.13.0",
+        "phantomjs": "1.9.1-9",
+        "requirejs": "2.1.8",
+        "chai": "1.7.2",
+        "sinon": "1.7.3",
+        "phantomjs": "1.9.1-9",
+        "grunt-contrib-connect": "0.5.0",
+        "grunt-contrib-copy": "0.4.1",
+        "grunt-mocha": "0.4.1"
+    }
+}
\ No newline at end of file
diff --git a/test/integration/index.html b/test/integration/index.html
new file mode 100644
index 00000000..ca87d20d
--- /dev/null
+++ b/test/integration/index.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>JavaScript Unit Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+
+  </head>
+  <body>
+    <div id="mocha"></div>
+    
+    <script src="lib/chai.js"></script>
+    <script src="lib/sinon.js"></script>
+    <script src="lib/mocha.js"></script>
+
+    <script data-main="main.js" src="lib/require.js"></script>
+    
+  </body>
+</html>
diff --git a/test/integration/main.js b/test/integration/main.js
new file mode 100644
index 00000000..f0703f35
--- /dev/null
+++ b/test/integration/main.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// config require.js
+require.config({
+    baseUrl: './',
+    paths: {
+        openpgp: '../../resources/openpgp.min'
+    },
+    shim: {
+        openpgp: {
+            exports: 'window'
+        }
+    }
+});
+
+// start mocha tests
+mocha.setup('bdd');
+require(
+    [
+        'pgp-test'
+    ], function() {
+        // require modules loaded -> run tests
+        mocha.run();
+    }
+);
\ No newline at end of file
diff --git a/test/integration/pgp-test.js b/test/integration/pgp-test.js
new file mode 100644
index 00000000..1b115f2e
--- /dev/null
+++ b/test/integration/pgp-test.js
@@ -0,0 +1,159 @@
+define(function(require) {
+    'use strict';
+
+    var PGP = require('pgp'),
+        expect = chai.expect;
+
+    describe('PGP Crypto Api unit tests', function() {
+        var pgp,
+            user = 'test@t-online.de',
+            passphrase = 'asdf',
+            keySize = 512,
+            keyId = 'F6F60E9B42CDFF4C',
+            pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+                'Version: OpenPGP.js v.1.20131011\n' +
+                'Comment: http://openpgpjs.org\n' +
+                '\n' +
+                'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\n' +
+                'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\n' +
+                'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\n' +
+                'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\n' +
+                'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\n' +
+                '=6XMW\n' +
+                '-----END PGP PUBLIC KEY BLOCK-----',
+            privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' +
+                'Version: OpenPGP.js v.1.20131011\n' +
+                'Comment: http://openpgpjs.org\n' +
+                '\n' +
+                'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\n' +
+                'eUVRr2yvPLp1d3FP9bTmHTbFyOqUqf2bY8r+72wVABEBAAH+AwMIhNB4ivtv\n' +
+                'Y2xg6VeMcjjHxZayESHACV+nQx5Tx6ev6xzIF1Qh72fNPDppLhFSFOuTTMsU\n' +
+                'kTN4c+BVYt29spH+cA1jcDAxQ2ULrNAXo+hheOqhpedTs8aCbcLFkJAS16hk\n' +
+                'YSk4OnJgp/z24rVju1SHRSFbgundPzmNgXeX9e8IkviGhhQ11Wc5YwVkx03t\n' +
+                'Z3MdDMF0jyhopbPIoBdyJB0dhvBh98w3JmwpYh9wjUA9MBHD1tvHpRmSZ3BM\n' +
+                'UCmATn2ZLWBRWiYqFbgDnL1GM80pV2hpdGVvdXQgVXNlciA8d2hpdGVvdXQu\n' +
+                'dGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhMvQkQ9vYOm0LN/0wAAAW4\n' +
+                'Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXqIiN602mWrkd8jcEzLsW5\n' +
+                'IUNzVPLhrFIuKyBDTpLnC07Loce1\n' +
+                '=ULta\n' +
+                '-----END PGP PRIVATE KEY BLOCK-----';
+
+        beforeEach(function() {
+            pgp = new PGP();
+        });
+
+        afterEach(function() {});
+
+        describe('Generate key pair', function() {
+            it('should fail', function(done) {
+                pgp.generateKeys({
+                    emailAddress: 'test@t-onlinede',
+                    keySize: keySize,
+                    passphrase: passphrase
+                }, function(err, keys) {
+                    expect(err).to.exist;
+                    expect(keys).to.not.exist;
+                    done();
+                });
+            });
+            it('should fail', function(done) {
+                pgp.generateKeys({
+                    emailAddress: 'testt-online.de',
+                    keySize: keySize,
+                    passphrase: passphrase
+                }, function(err, keys) {
+                    expect(err).to.exist;
+                    expect(keys).to.not.exist;
+                    done();
+                });
+            });
+            it('should work', function(done) {
+                pgp.generateKeys({
+                    emailAddress: user,
+                    keySize: keySize,
+                    passphrase: passphrase
+                }, function(err, keys) {
+                    expect(err).to.not.exist;
+                    expect(keys.keyId).to.exist;
+                    expect(keys.privateKeyArmored).to.exist;
+                    expect(keys.publicKeyArmored).to.exist;
+                    done();
+                });
+            });
+        });
+
+        describe('Import/Export key pair', function() {
+            it('should fail', function(done) {
+                pgp.importKeys({
+                    passphrase: 'asd',
+                    privateKeyArmored: privkey,
+                    publicKeyArmored: pubkey
+                }, function(err) {
+                    expect(err).to.exist;
+
+                    pgp.exportKeys(function(err, keys) {
+                        expect(err).to.exist;
+                        expect(keys).to.not.exist;
+                        done();
+                    });
+                });
+            });
+            it('should work', function(done) {
+                pgp.importKeys({
+                    passphrase: passphrase,
+                    privateKeyArmored: privkey,
+                    publicKeyArmored: pubkey
+                }, function(err) {
+                    expect(err).to.not.exist;
+
+                    pgp.exportKeys(function(err, keys) {
+                        expect(err).to.not.exist;
+                        expect(keys.keyId).to.equal(keyId);
+                        expect(keys.privateKeyArmored).to.equal(privkey);
+                        expect(keys.publicKeyArmored).to.equal(pubkey);
+                        done();
+                    });
+                });
+            });
+        });
+
+        describe('Encryption', function() {
+            var message = 'Hello, World!',
+                ciphertext;
+
+            beforeEach(function(done) {
+                pgp.importKeys({
+                    passphrase: passphrase,
+                    privateKeyArmored: privkey,
+                    publicKeyArmored: pubkey
+                }, function(err) {
+                    expect(err).to.not.exist;
+                    done();
+                });
+            });
+
+            describe('Encrypt', function() {
+                it('should work', function(done) {
+                    pgp.encrypt(message, [pubkey], function(err, ct) {
+                        expect(err).to.not.exist;
+                        expect(ct).to.exist;
+                        ciphertext = ct;
+                        done();
+                    });
+                });
+            });
+
+            describe('Decrypt', function() {
+                it('should work', function(done) {
+                    pgp.decrypt(ciphertext, pubkey, function(err, pt) {
+                        expect(err).to.not.exist;
+                        expect(pt).to.equal(message);
+                        done();
+                    });
+                });
+            });
+
+        });
+
+    });
+});
\ No newline at end of file
diff --git a/test/integration/pgp.js b/test/integration/pgp.js
new file mode 100644
index 00000000..8640e189
--- /dev/null
+++ b/test/integration/pgp.js
@@ -0,0 +1,173 @@
+/**
+ * High level crypto api that handles all calls to OpenPGP.js
+ */
+define(function(require) {
+    'use strict';
+
+    var openpgp = require('openpgp').openpgp,
+        util = require('openpgp').util;
+
+    var PGP = function() {
+        openpgp.init();
+    };
+
+    /**
+     * Generate a key pair for the user
+     */
+    PGP.prototype.generateKeys = function(options, callback) {
+        var keys, userId;
+
+        if (!util.emailRegEx.test(options.emailAddress) || !options.keySize || typeof options.passphrase !== 'string') {
+            callback({
+                errMsg: 'Crypto init failed. Not all options set!'
+            });
+            return;
+        }
+
+        // generate keypair (keytype 1=RSA)
+        try {
+            userId = 'Whiteout User <' + options.emailAddress + '>';
+            keys = openpgp.generate_key_pair(1, options.keySize, userId, options.passphrase);
+        } catch (e) {
+            callback({
+                errMsg: 'Keygeneration failed!',
+                err: e
+            });
+            return;
+        }
+
+        callback(null, {
+            keyId: util.hexstrdump(keys.privateKey.getKeyId()).toUpperCase(),
+            privateKeyArmored: keys.privateKeyArmored,
+            publicKeyArmored: keys.publicKeyArmored
+        });
+    };
+
+    /**
+     * Import the user's key pair
+     */
+    PGP.prototype.importKeys = function(options, callback) {
+        var publicKey, privateKey;
+
+        // check passphrase
+        if (typeof options.passphrase !== 'string' || !options.privateKeyArmored || !options.publicKeyArmored) {
+            callback({
+                errMsg: 'Importing keys failed. Not all options set!'
+            });
+            return;
+        }
+
+        // clear any keypair already in the keychain
+        openpgp.keyring.init();
+        // unlock and import private key 
+        if (!openpgp.keyring.importPrivateKey(options.privateKeyArmored, options.passphrase)) {
+            openpgp.keyring.init();
+            callback({
+                errMsg: 'Incorrect passphrase!'
+            });
+            return;
+        }
+        // import public key
+        openpgp.keyring.importPublicKey(options.publicKeyArmored);
+
+        // check if keys have the same id
+        privateKey = openpgp.keyring.exportPrivateKey(0);
+        publicKey = openpgp.keyring.getPublicKeysForKeyId(privateKey.keyId)[0];
+        if (!privateKey || !privateKey.armored || !publicKey || !publicKey.armored || privateKey.keyId !== publicKey.keyId) {
+            // reset keyring
+            openpgp.keyring.init();
+            callback({
+                errMsg: 'Key IDs dont match!'
+            });
+            return;
+        }
+
+        callback();
+    };
+
+    /**
+     * Export the user's key pair
+     */
+    PGP.prototype.exportKeys = function(callback) {
+        var publicKey, privateKey;
+
+        privateKey = openpgp.keyring.exportPrivateKey(0);
+        if (privateKey && privateKey.keyId) {
+            publicKey = openpgp.keyring.getPublicKeysForKeyId(privateKey.keyId)[0];
+        }
+
+        if (!privateKey || !privateKey.keyId || !privateKey.armored || !publicKey || !publicKey.armored) {
+            callback({
+                errMsg: 'Could not export keys!'
+            });
+            return;
+        }
+
+        callback(null, {
+            keyId: util.hexstrdump(privateKey.keyId).toUpperCase(),
+            privateKeyArmored: privateKey.armored,
+            publicKeyArmored: publicKey.armored
+        });
+    };
+
+    /**
+     * Encrypt and sign a pgp message for a list of receivers
+     */
+    PGP.prototype.encrypt = function(plaintext, receiverKeys, callback) {
+        var ct, i,
+            privateKey = openpgp.keyring.exportPrivateKey(0).obj;
+
+        for (i = 0; i < receiverKeys.length; i++) {
+            receiverKeys[i] = openpgp.read_publicKey(receiverKeys[i])[0];
+        }
+
+        ct = openpgp.write_signed_and_encrypted_message(privateKey, receiverKeys, plaintext);
+
+        callback(null, ct);
+    };
+
+    /**
+     * Decrypt and verify a pgp message for a single sender
+     */
+    PGP.prototype.decrypt = function(ciphertext, senderKey, callback) {
+        var privateKey = openpgp.keyring.exportPrivateKey(0).obj;
+        senderKey = openpgp.read_publicKey(senderKey)[0];
+
+        var msg = openpgp.read_message(ciphertext)[0];
+        var keymat = null;
+        var sesskey = null;
+
+        // Find the private (sub)key for the session key of the message
+        for (var i = 0; i < msg.sessionKeys.length; i++) {
+            if (privateKey.privateKeyPacket.publicKey.getKeyId() === msg.sessionKeys[i].keyId.bytes) {
+                keymat = {
+                    key: privateKey,
+                    keymaterial: privateKey.privateKeyPacket
+                };
+                sesskey = msg.sessionKeys[i];
+                break;
+            }
+            for (var j = 0; j < privateKey.subKeys.length; j++) {
+                if (privateKey.subKeys[j].publicKey.getKeyId() === msg.sessionKeys[i].keyId.bytes) {
+                    keymat = {
+                        key: privateKey,
+                        keymaterial: privateKey.subKeys[j]
+                    };
+                    sesskey = msg.sessionKeys[i];
+                    break;
+                }
+            }
+        }
+        if (keymat !== null) {
+            var decrypted = msg.decryptAndVerifySignature(keymat, sesskey, senderKey);
+            callback(null, decrypted.text);
+
+        } else {
+            callback({
+                errMsg: 'No private key found!'
+            });
+        }
+    };
+
+    return PGP;
+});
\ No newline at end of file