├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── apis ├── crypto │ └── index.js ├── dns │ └── index.js ├── fs │ ├── abstract_directory.js │ ├── abstract_file.js │ ├── abstract_filesystem_node.js │ ├── builtin_file.js │ ├── index.js │ ├── virtual_directory.js │ └── virtual_file.js ├── localstorage │ └── index.js ├── net │ ├── index.js │ └── proxied_socket.js ├── secrets │ └── index.js └── time │ └── index.js ├── index.js ├── lib ├── api_module.js ├── compiler.js ├── config.js ├── contractrunner.js ├── crypto.js ├── engine.js ├── filehash.js ├── filemanager.js └── system_error.js ├── package.json ├── runtime_library ├── localstorage.js └── secrets.js └── test ├── lib.compiler.js ├── lib.crypto.js ├── lib.engine.js ├── lib.filehash.js └── test_contract └── contract.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .codius/ 3 | coverage/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ripple Labs Inc. 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contracts Engine 2 | The engine to run contracts 3 | 4 | ## Playing with the engine 5 | 6 | + `npm install` 7 | + `npm test` runs the existing tests 8 | + Use the [`codius-cli`](https://github.com/codius/codius-cli) to run test contracts 9 | 10 | ## Structure of the engine 11 | 12 | The engine is the part of Codius responsible for running sandboxed code and providing APIs for that code to communicate with. 13 | 14 | For the sandbox we are currently using [`codius/node-sandbox`](https://github.com/codius/node-sandbox), which utilizes Native Client. 15 | 16 | The [`Engine`](lib/engine.js) is the main component used to run contract code and the [`ContractRunner`](lib/contractrunner.js) is responsible for handling messages coming from the sandbox and passing valid ones to the corresponding APIs. 17 | 18 | Contract code uses the `postMessage` command to communicate with the outside. The format is defined below. Most contracts will not use the `postMessage` command directly but will instead use modules or [`runtime_library`](runtime_library/) components, which are loaded into the sandbox by default. 19 | 20 | ## Questions? 21 | 22 | Any questions? Join the live chat! [![Gitter chat](https://badges.gitter.im/codius/codius-chat.png)](https://gitter.im/codius/codius-chat) 23 | 24 | ## APIs and Message Formats 25 | 26 | ### IPC Messaging Format 27 | 28 | #### Contract -> Sandbox 29 | 30 | API call with callback 31 | ```js 32 | { 33 | "type": "api", 34 | "api": "http", 35 | "method": "get", 36 | "data": "http://some.url", 37 | "callback": 4 38 | } 39 | ``` 40 | 41 | #### Sandbox -> Contract 42 | 43 | API callback 44 | ```js 45 | { 46 | "type": "callback", 47 | "callback": 4, 48 | "error": null, 49 | "result": "some stringified result" 50 | } 51 | ``` 52 | 53 | ### Contract-specific Secrets and Keypairs 54 | 55 | #### Secret Derivation 56 | 57 | The engine must be started with a `MASTER_SECRET`. 58 | 59 | This secret is used to derive multiple other secrets, which are used to provide contracts with unique private values and public/private key pairs. Derived secrets are the HMAC of the "parent" secret and the name of the "child" secret 60 | 61 | + `CONTRACT_SECRET_GENERATOR` - used to generate 512-bit private values 62 | + `CONTRACT_KEYPAIR_GENERATOR_ec_secp256k1` - used to generate `secp256k1` key pairs (e.g. `CONTRACT_KEYPAIR_13550350a8681c84c861aac2e5b440161c2b33a3e4f302ac680ca5b686de48de`) 63 | + other `CONTRACT_KEYPAIR_GENERATOR_{other signature schemes}` (e.g. `ec_ed25519`) 64 | + `MASTER_KEYPAIR_ec_secp256k1` - used to sign contracts' public keys 65 | + other `MASTER_KEYPAIR_{other signature schemes}` (e.g. `ec_ed25519`) 66 | 67 | 68 | #### API 69 | 70 | ```js 71 | var secrets = require('secrets'); 72 | 73 | // Get a secret that is deterministically generated and unique for the contract 74 | secrets.getSecret(function(error, secret){ 75 | // error: null 76 | // secret: "c88097bb32531bd14fc0c4e8afbdb8aa22d4d6eefcbe980d8c52bd6381c6c60ca746b330ce93decf5061a011ed71afde8b4ed4fbbf1531d010788e8bb79c8b6d" 77 | }); 78 | 79 | // Get a secp256k1 key pair and the engine's signature on the public key 80 | // Note that the signature is in DER format 81 | secrets.getKeypair('ec_secp256k1', function(error, keypair){ 82 | // error: null 83 | // keypair: { 84 | // public: '0417b9f5b3ba8d550f19fdfb5233818cd27d19aaea029b667f547f5918c307ed3b1ee32e285f9152d61c2a85b275f1b27d955c2b59a313900c4006377afa538370', 85 | // private: '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d', 86 | // signature: '304402206f1c9e05bc2ad120e0bb58ff368035359d40597ef034509a7dc66a79d4648bea022015b417401d194cf2917e853a7565cfbce32ee90c5c8f34f54075ee2f87519d88' 87 | // } 88 | }); 89 | -------------------------------------------------------------------------------- /apis/crypto/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('crypto', function (runner){ 22 | return new CryptoApi(); 23 | }); 24 | }; 25 | 26 | 27 | var crypto = require('crypto'); 28 | var util = require('util'); 29 | 30 | var ApiModule = require('../../lib/api_module').ApiModule; 31 | 32 | function CryptoApi() { 33 | ApiModule.call(this); 34 | 35 | var self = this; 36 | } 37 | 38 | util.inherits(CryptoApi, ApiModule); 39 | 40 | CryptoApi.methods = [ 41 | 'randomBytes' 42 | ]; 43 | 44 | CryptoApi.prototype.randomBytes = function (size, callback) { 45 | crypto.randomBytes(size, function (error, bytes) { 46 | if (error) { 47 | callback(error); 48 | } else { 49 | callback(null, bytes.toString('hex')); 50 | } 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /apis/dns/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('dns', function (runner){ 22 | return new DnsApi(); 23 | }); 24 | }; 25 | 26 | 27 | var dns = require('dns'); 28 | var util = require('util'); 29 | 30 | var ApiModule = require('../../lib/api_module').ApiModule; 31 | 32 | function DnsApi() { 33 | ApiModule.call(this); 34 | 35 | var self = this; 36 | } 37 | 38 | util.inherits(DnsApi, ApiModule); 39 | 40 | DnsApi.methods = [ 41 | 'lookup' 42 | ]; 43 | 44 | DnsApi.prototype.lookup = function (hostname, family, callback) { 45 | if (typeof family === 'function') { 46 | callback = family; 47 | family = 0; 48 | } 49 | 50 | dns.lookup(hostname, family, callback); 51 | }; 52 | -------------------------------------------------------------------------------- /apis/fs/abstract_directory.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | 22 | var AbstractFilesystemNode = require('./abstract_filesystem_node').AbstractFilesystemNode; 23 | 24 | var AbstractDirectory = function () { 25 | AbstractFilesystemNode.apply(this); 26 | }; 27 | 28 | util.inherits(AbstractDirectory, AbstractFilesystemNode); 29 | 30 | // We don't really care about any of the stat properties of the virtual 31 | // directories, so we just return something realistic. 32 | // TODO Could be more realistic. 33 | AbstractDirectory.STAT_FOR_DIRECTORIES = { 34 | dev: 2049, 35 | mode: 16893, 36 | nlink: 5, 37 | uid: 1000, 38 | gid: 1000, 39 | rdev: 0, 40 | blksize: 4096, 41 | ino: 6695080, 42 | size: 4096, 43 | blocks: 8, 44 | atime: 'Tue Oct 07 2014 11:02:22 GMT-0700 (PDT)', 45 | mtime: 'Tue Oct 07 2014 10:54:18 GMT-0700 (PDT)', 46 | ctime: 'Tue Oct 07 2014 10:54:18 GMT-0700 (PDT)' 47 | }; 48 | 49 | AbstractDirectory.prototype.isDirectory = function () { 50 | return true; 51 | }; 52 | 53 | AbstractDirectory.prototype.stat = 54 | AbstractDirectory.prototype.lstat = function (callback) { 55 | callback(null, AbstractDirectory.STAT_FOR_DIRECTORIES); 56 | }; 57 | 58 | exports.AbstractDirectory = AbstractDirectory; 59 | -------------------------------------------------------------------------------- /apis/fs/abstract_file.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | var fs = require('fs'); 22 | 23 | var AbstractFilesystemNode = require('./abstract_filesystem_node').AbstractFilesystemNode; 24 | 25 | var AbstractFile = function () { 26 | AbstractFilesystemNode.apply(this); 27 | }; 28 | 29 | util.inherits(AbstractFile, AbstractFilesystemNode); 30 | 31 | AbstractFile.prototype.stat = function (callback) { 32 | fs.stat(this.getRealPath(), callback); 33 | }; 34 | 35 | AbstractFile.prototype.lstat = function (callback) { 36 | fs.lstat(this.getRealPath(), callback); 37 | }; 38 | 39 | exports.AbstractFile = AbstractFile; 40 | -------------------------------------------------------------------------------- /apis/fs/abstract_filesystem_node.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var AbstractFilesystemNode = function () { 21 | 22 | }; 23 | 24 | AbstractFilesystemNode.prototype.isDirectory = function () { 25 | return false; 26 | }; 27 | 28 | exports.AbstractFilesystemNode = AbstractFilesystemNode; 29 | -------------------------------------------------------------------------------- /apis/fs/builtin_file.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | var path = require('path'); 22 | 23 | var AbstractFile = require('./abstract_file').AbstractFile; 24 | 25 | var BuiltinFile = function (path) { 26 | AbstractFile.apply(this); 27 | 28 | this._path = path; 29 | }; 30 | 31 | util.inherits(BuiltinFile, AbstractFile); 32 | 33 | BuiltinFile.prototype.getRealPath = function () { 34 | return this._path; 35 | }; 36 | 37 | exports.BuiltinFile = BuiltinFile; 38 | -------------------------------------------------------------------------------- /apis/fs/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('fs', function (runner){ 22 | var manifest = runner.getManifest(); 23 | var manifestHash = runner.getManifestHash(); 24 | return new FileSystemReadOnly({ 25 | runner: runner, 26 | filesystemPath: config.contractsFilesystemPath, 27 | runtimeLibraryPath: config.runtimeLibraryPath, 28 | apis: config.apis, 29 | manifest: manifest, 30 | manifestHash: manifestHash 31 | }); 32 | }); 33 | }; 34 | 35 | 36 | var fs = require('fs'); 37 | var path_module = require('path'); 38 | var util = require('util'); 39 | var _ = require('lodash'); 40 | 41 | var ApiModule = require('../../lib/api_module').ApiModule; 42 | var SystemError = require('../../lib/system_error').SystemError; 43 | 44 | var VirtualDirectory = require('./virtual_directory').VirtualDirectory; 45 | var VirtualFile = require('./virtual_file').VirtualFile; 46 | var BuiltinFile = require('./builtin_file').BuiltinFile; 47 | 48 | function FileSystemReadOnly(opts) { 49 | ApiModule.call(this); 50 | 51 | var self = this; 52 | 53 | self._runner = opts.runner; 54 | self._manifest = opts.manifest; 55 | self._manifest_hash = opts.manifestHash; 56 | self._sandbox_filesystem_path = opts.filesystemPath; 57 | self._sandbox_runtime_library_path = opts.runtimeLibraryPath; 58 | self._sandbox_apis = opts.apis; 59 | 60 | self._availableApis = _.intersection(self._manifest.apis, self._sandbox_apis); 61 | 62 | self._openedFds = []; 63 | } 64 | 65 | util.inherits(FileSystemReadOnly, ApiModule); 66 | 67 | FileSystemReadOnly.MANIFEST_PATH = '/codius-manifest.json'; 68 | FileSystemReadOnly.SUBMODULE_PREFIX = '/node_modules'; 69 | FileSystemReadOnly.HASH_REGEX = /^[0-9a-fA-F]{64}$/; 70 | FileSystemReadOnly.GLOBAL_MODULE_PREFIX = '/usr/lib/node'; 71 | FileSystemReadOnly.GLOBAL_MODULE_EXTENSION = '.js'; 72 | 73 | FileSystemReadOnly.methods = [ 74 | 'stat', 75 | 'lstat', 76 | 'fstat', 77 | 'open', 78 | 'close', 79 | 'read', 80 | 'readdir' 81 | ]; 82 | 83 | FileSystemReadOnly.prototype.stat = function(path, callback) { 84 | var self = this; 85 | 86 | var file = this._translateFilenameToPath(path); 87 | if (file) { 88 | file.stat(callback); 89 | } else { 90 | callback(SystemError.create(path, 'ENOENT', 'stat')); 91 | } 92 | }; 93 | 94 | FileSystemReadOnly.prototype.lstat = function(path, callback) { 95 | var self = this; 96 | 97 | var file = this._translateFilenameToPath(path); 98 | if (file) { 99 | file.lstat(callback); 100 | } else { 101 | callback(SystemError.create(path, 'ENOENT', 'lstat')); 102 | } 103 | }; 104 | 105 | FileSystemReadOnly.prototype.fstat = function(fd, callback) { 106 | var self = this; 107 | 108 | if (!self._openedFds[fd]) { 109 | callback(SystemError.create(path, 'EBADF', 'fstat')); 110 | return; 111 | } 112 | 113 | fs.fstat(self._openedFds[fd], callback); 114 | }; 115 | 116 | FileSystemReadOnly.prototype.readdir = function(path, callback) { 117 | var self = this; 118 | 119 | var file = this._translateFilenameToPath(path); 120 | if (file && file.isDirectory()) { 121 | file.readdir(callback); 122 | } else if (file) { 123 | callback(SystemError.create(path, 'ENOTDIR', 'readdir')); 124 | } else { 125 | callback(SystemError.create(path, 'ENOENT', 'readdir')); 126 | } 127 | }; 128 | 129 | /** 130 | * Open file. Creates a file descriptor. 131 | * 132 | * Note that we enforce a read-only policy here. 133 | * 134 | * @param {String} path Stringified object with `path` and `options` fields 135 | * @param {String} flags Ignored, we always use 'r' 136 | * @param {Number} mode Ignored and no effect since we never allow file creation 137 | * @param {Function} callback 138 | * 139 | * @callback 140 | * @param {Error} error 141 | * @param {Number} fd File descriptor 142 | */ 143 | FileSystemReadOnly.prototype.open = function(path, flags, mode, callback) { 144 | var self = this; 145 | 146 | var file = this._translateFilenameToPath(path); 147 | if (file && !file.isDirectory()) { 148 | fs.open(file.getRealPath(), 'r', function (error, fd) { 149 | var virtualFd = self._runner.getNextFreeFileDescriptor(); 150 | if (!error) { 151 | self._openedFds[virtualFd] = fd; 152 | } 153 | callback(error, virtualFd); 154 | }); 155 | } else if (file) { 156 | callback(SystemError.create(path, 'EISDIR', 'open')); 157 | } else { 158 | callback(SystemError.create(path, 'ENOENT', 'open')); 159 | } 160 | }; 161 | 162 | FileSystemReadOnly.prototype.close = function(fd, callback) { 163 | var self = this; 164 | 165 | if (!self._openedFds[fd]) { 166 | callback(SystemError.create(path, 'EBADF', 'close')); 167 | return; 168 | } 169 | 170 | // It's safer to always discard the fd mapping very agressively. If the 171 | // sandboxed code can somehow maintain the mapping while the outside fd 172 | // is freed up and possibly reused, it could gain access to files it's 173 | // not supposed to be able to access. 174 | var real_fd = self._openedFds[fd]; 175 | delete self._openedFds[fd]; 176 | 177 | fs.close(real_fd, function (err) { 178 | callback(err); 179 | }); 180 | }; 181 | 182 | FileSystemReadOnly.prototype.read = function(fd, size, position, encoding, callback) { 183 | var self = this; 184 | 185 | if (!self._openedFds[fd]) { 186 | callback(SystemError.create(path, 'EBADF', 'read')); 187 | return; 188 | } 189 | 190 | // TODO Should not be using legacy API 191 | fs.read(self._openedFds[fd], size, position, encoding, callback); 192 | }; 193 | 194 | FileSystemReadOnly.prototype._translateFilenameToPath = function (path, manifest, manifestHash) { 195 | var self = this; 196 | 197 | if (!manifest) { 198 | manifest = self._manifest; 199 | } 200 | 201 | if (!manifestHash) { 202 | manifestHash = self._manifest_hash; 203 | } 204 | 205 | // Case: Global runtime modules ('/usr/lib/node/*.js') 206 | if (path.slice(0, FileSystemReadOnly.GLOBAL_MODULE_PREFIX.length) === FileSystemReadOnly.GLOBAL_MODULE_PREFIX && 207 | path.slice(path.length - FileSystemReadOnly.GLOBAL_MODULE_EXTENSION.length) === FileSystemReadOnly.GLOBAL_MODULE_EXTENSION) { 208 | 209 | var moduleNameStartPos = FileSystemReadOnly.GLOBAL_MODULE_PREFIX.length + 1; 210 | var moduleNameEndPos = path.length - FileSystemReadOnly.GLOBAL_MODULE_EXTENSION.length; 211 | var requestedModule = path.slice(moduleNameStartPos, moduleNameEndPos); 212 | 213 | if (self._availableApis.indexOf(requestedModule) !== -1) { 214 | return new BuiltinFile(self._sandbox_runtime_library_path + requestedModule + FileSystemReadOnly.GLOBAL_MODULE_EXTENSION); 215 | } else { 216 | return false; 217 | } 218 | 219 | // Case: Simulate the directories /usr /usr/lib and /usr/lib/node 220 | } else if (path === '/usr') { 221 | return new VirtualDirectory(['lib']); 222 | 223 | } else if (path === '/usr/lib') { 224 | return new VirtualDirectory(['node']); 225 | 226 | } else if (path === '/usr/lib/node') { 227 | return new VirtualDirectory(self._availableApis.map(function (basename) { 228 | return basename + FileSystemReadOnly.GLOBAL_MODULE_EXTENSION; 229 | })); 230 | 231 | // Case: Virtual file system (any other file) 232 | } else { 233 | return self._translateFilenameToHash(path, manifest, manifestHash); 234 | } 235 | }; 236 | 237 | FileSystemReadOnly.prototype._translateFilenameToHash = function (path, manifest, manifestHash) { 238 | var self = this; 239 | 240 | if (!manifest) { 241 | manifest = self._manifest; 242 | } 243 | 244 | if (!manifestHash) { 245 | manifestHash = self._manifest_hash; 246 | } 247 | 248 | if (typeof path !== "string") { 249 | throw new Error('Path must be a string.'); 250 | } 251 | 252 | path = path_module.normalize(path); 253 | 254 | // Force path to be absolute 255 | // TODO To allow relative paths, we would need to keep track of the current 256 | // working directory, which requires implementing chdir calls. 257 | if (path.length < 1 || path[0] !== '/') { 258 | path = '/' + path; 259 | } 260 | 261 | // Case: the file requested is the manifest 262 | if (path === FileSystemReadOnly.MANIFEST_PATH) { 263 | 264 | // Ensure the manifestHash is actually a hash 265 | if (!FileSystemReadOnly.HASH_REGEX.test(manifestHash)) { 266 | throw new Error('Security error: Invalid manifest hash'); 267 | } 268 | 269 | return new VirtualFile(manifestHash, self._sandbox_filesystem_path); 270 | 271 | // Case: Special directory: module root 272 | } else if (path === '/' || path === '/.') { 273 | return new VirtualDirectory(Object.keys(manifest.files)); 274 | 275 | // Case: Special directory: node_modules folder 276 | } else if (path === '/node_modules') { 277 | return new VirtualDirectory(Object.keys(manifest.modules)); 278 | 279 | // Case: the file is from a submodule (node_modules) 280 | } else if (path.substr(0, FileSystemReadOnly.SUBMODULE_PREFIX.length) === FileSystemReadOnly.SUBMODULE_PREFIX) { 281 | // TODO What about escaped slashes? (Not allowed on unix it seems, but still a concern?) 282 | var moduleName = path.substr(FileSystemReadOnly.SUBMODULE_PREFIX.length+1).split('/')[0]; 283 | if (!manifest.modules.hasOwnProperty(moduleName)) { 284 | return false; 285 | } 286 | 287 | // Load module manifest 288 | var moduleManifestHash = manifest.modules[moduleName]; 289 | var moduleManifest; 290 | try { 291 | var firstDir = moduleManifestHash.slice(0, 2); 292 | var secondDir = moduleManifestHash.slice(2, 4); 293 | moduleManifest = fs.readFileSync(path_module.join(self._sandbox_filesystem_path, firstDir, secondDir, moduleManifestHash), { encoding: 'utf8' }); 294 | moduleManifest = JSON.parse(moduleManifest); 295 | } catch(error) { 296 | throw new Error('Cannot load manifest for module: "' + String(moduleName) + '". ' + error); 297 | } 298 | 299 | // Get the remainder of the path and recurse to the submodule's manifest 300 | var restOfPath = path.substr(FileSystemReadOnly.SUBMODULE_PREFIX.length + 1 + 301 | moduleName.length); 302 | 303 | return self._translateFilenameToHash(restOfPath, moduleManifest, moduleManifestHash); 304 | 305 | // Case: the file is another file in the contract 306 | } else if (manifest.files.hasOwnProperty(path.split('/', 2)[1]) !== -1) { 307 | var context = manifest.files; 308 | var segments = path.split('/').slice(1); 309 | 310 | // Walk the path and descend into the manifest.files hierarchy 311 | for (var i = 0, l = segments.length; i < l; i++) { 312 | if (context.hasOwnProperty(segments[i])) { 313 | context = context[segments[i]]; 314 | } else { 315 | return false; 316 | } 317 | } 318 | 319 | // Is it a directory? 320 | if (typeof context === 'object') { 321 | return new VirtualDirectory(Object.keys(context)); 322 | } else { 323 | // Ensure the file hash is actually a hash 324 | if (!FileSystemReadOnly.HASH_REGEX.test(context)) { 325 | throw new Error('Security error: Invalid manifest hash'); 326 | } 327 | 328 | // Constructor takes the hash of the file as an argument. 329 | return new VirtualFile(context, self._sandbox_filesystem_path); 330 | } 331 | } else { 332 | return false; 333 | } 334 | }; 335 | 336 | // Helper function that handles errors sync or async 337 | function _handleError(error, callback) { 338 | if (typeof callback === 'function') { 339 | callback(error); 340 | return; 341 | } else { 342 | throw error; 343 | } 344 | } 345 | 346 | -------------------------------------------------------------------------------- /apis/fs/virtual_directory.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | 22 | var AbstractDirectory = require('./abstract_directory').AbstractDirectory; 23 | 24 | var VirtualDirectory = function (subNodes) { 25 | AbstractDirectory.apply(this); 26 | 27 | if (!Array.isArray(subNodes)) { 28 | throw new Error('Subnodes must be an array!'); 29 | } 30 | 31 | this._subNodes = subNodes; 32 | }; 33 | 34 | util.inherits(VirtualDirectory, AbstractDirectory); 35 | 36 | VirtualDirectory.prototype.readdir = function (callback) { 37 | callback(null, this._subNodes); 38 | }; 39 | 40 | exports.VirtualDirectory = VirtualDirectory; 41 | -------------------------------------------------------------------------------- /apis/fs/virtual_file.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | var path = require('path'); 22 | 23 | var AbstractFile = require('./abstract_file').AbstractFile; 24 | 25 | var VirtualFile = function (hash, contractFilesystemPath) { 26 | AbstractFile.apply(this); 27 | 28 | this._hash = hash; 29 | this._contractFilesystemPath = contractFilesystemPath; 30 | }; 31 | 32 | util.inherits(VirtualFile, AbstractFile); 33 | 34 | VirtualFile.prototype.getRealPath = function () { 35 | var self = this; 36 | 37 | var firstDir = self._hash.slice(0, 2); 38 | var secondDir = self._hash.slice(2, 4); 39 | 40 | return path.join(this._contractFilesystemPath, firstDir, secondDir, this._hash); 41 | }; 42 | 43 | exports.VirtualFile = VirtualFile; 44 | -------------------------------------------------------------------------------- /apis/localstorage/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('localstorage', function (runner, storage){ 22 | var manifestHash = runner.getManifestHash(); 23 | var instanceId = runner.getInstanceId(); 24 | return new LocalStorage({ 25 | contractId: manifestHash + instanceId, 26 | storage: storage 27 | }); 28 | }); 29 | }; 30 | 31 | var util = require('util'); 32 | 33 | var ApiModule = require('../../lib/api_module').ApiModule; 34 | 35 | function LocalStorage(opts) { 36 | var self = this; 37 | 38 | ApiModule.call(this); 39 | 40 | self._contractId = opts.contractId; 41 | self._storage = opts.storage || LocalStorage.defaultStorage(); 42 | } 43 | util.inherits(LocalStorage, ApiModule); 44 | 45 | LocalStorage.methods = [ 46 | 'getItem', 47 | 'setItem', 48 | 'removeItem', 49 | 'clear', 50 | 'key' 51 | ]; 52 | 53 | /** 54 | * Default storage option simply uses an in-memory object 55 | */ 56 | LocalStorage.defaultStorage = function(){ 57 | 'use strict'; 58 | 59 | var storageObject = {}; 60 | 61 | function getItem(contractId, key, callback) { 62 | if (typeof storageObject[contractId] !== 'object') { 63 | storageObject[contractId] = {}; 64 | } 65 | callback(null, storageObject[contractId][key]); 66 | } 67 | 68 | function setItem(contractId, key, value, callback) { 69 | if (typeof storageObject[contractId] !== 'object') { 70 | storageObject[contractId] = {}; 71 | } 72 | storageObject[contractId][key] = value; 73 | callback(); 74 | } 75 | 76 | function removeItem(contractId, key, callback) { 77 | if (typeof storageObject[contractId] !== 'object') { 78 | storageObject[contractId] = {}; 79 | } 80 | delete storageObject[contractId][key]; 81 | callback(); 82 | } 83 | 84 | function clear(contractId, callback) { 85 | storageObject[contractId] = {}; 86 | callback(); 87 | } 88 | 89 | function key(contractId, index, callback) { 90 | if (typeof storageObject[contractId] !== 'object') { 91 | storageObject[contractId] = {}; 92 | } 93 | callback(null, Object.keys(storageObject[contractId])[index]); 94 | } 95 | 96 | var methods = { 97 | getItem: getItem, 98 | setItem: setItem, 99 | removeItem: removeItem, 100 | clear: clear, 101 | key: key 102 | }; 103 | return methods; 104 | }; 105 | 106 | /** 107 | * Takes a function that will be used to 108 | * provide contracts with persistent storage. 109 | * 110 | * Note that each function should expect the 111 | * contractId as the first parameter. 112 | * 113 | * @param {Function} contractStorage.getItem 114 | * @param {Function} contractStorage.setItem 115 | * @param {Function} contractStorage.removeItem 116 | * @param {Function} contractStorage.clear 117 | * @param {Function} contractStorage.key 118 | */ 119 | LocalStorage.prototype.setStorage = function(storage) { 120 | var self = this; 121 | 122 | self._storage = storage; 123 | }; 124 | 125 | 126 | LocalStorage.prototype.getItem = function(key, callback) { 127 | var self = this; 128 | 129 | if (typeof self._storage.getItem !== 'function') { 130 | callback(new Error('LocalStorage module supplied does not support getItem()')); 131 | return; 132 | } 133 | 134 | self._storage.getItem.call(self._storage, self._contractId, key, callback); 135 | }; 136 | 137 | LocalStorage.prototype.setItem = function(key, value, callback) { 138 | var self = this; 139 | 140 | if (typeof self._storage.setItem !== 'function') { 141 | callback(new Error('LocalStorage module supplied does not support setItem()')); 142 | return; 143 | } 144 | 145 | self._storage.setItem.call(self._storage, self._contractId, key, value, callback); 146 | }; 147 | 148 | LocalStorage.prototype.removeItem = function(key, callback) { 149 | var self = this; 150 | 151 | if (typeof self._storage.removeItem !== 'function') { 152 | callback(new Error('LocalStorage module supplied does not support removeItem()')); 153 | return; 154 | } 155 | 156 | self._storage.removeItem.call(self._storage, self._contractId, key, callback); 157 | }; 158 | 159 | LocalStorage.prototype.clear = function(callback) { 160 | var self = this; 161 | 162 | if (typeof self._storage.clear !== 'function') { 163 | callback(new Error('LocalStorage module supplied does not support clear()')); 164 | return; 165 | } 166 | 167 | self._storage.clear.call(self._storage, self._contractId, callback); 168 | }; 169 | 170 | LocalStorage.prototype.key = function(index, callback) { 171 | var self = this; 172 | 173 | if (typeof self._storage.key !== 'function') { 174 | callback(new Error('LocalStorage module supplied does not support key()')); 175 | return; 176 | } 177 | 178 | self._storage.key.call(self._storage, self._contractId, index, callback); 179 | }; 180 | -------------------------------------------------------------------------------- /apis/net/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('net', function (runner){ 22 | return new NetworkApi(runner); 23 | }); 24 | }; 25 | 26 | 27 | var net = require('net'); 28 | var util = require('util'); 29 | 30 | var ApiModule = require('../../lib/api_module').ApiModule; 31 | var ProxiedSocket = require('./proxied_socket').ProxiedSocket; 32 | 33 | function NetworkApi(runner) { 34 | ApiModule.call(this); 35 | 36 | var self = this; 37 | 38 | this._runner = runner; 39 | this._connections = []; 40 | this._ports = []; 41 | } 42 | 43 | util.inherits(NetworkApi, ApiModule); 44 | 45 | NetworkApi.methods = [ 46 | 'socket', 47 | 'connect', 48 | 'read', 49 | 'write', 50 | 'bind', 51 | 'accept', 52 | 'close', 53 | 'getRemoteFamily', 54 | 'getRemotePort', 55 | 'getRemoteAddress' 56 | ]; 57 | 58 | NetworkApi.prototype.socket = function (domain, type, protocol, callback) { 59 | sock = new ProxiedSocket(domain, type, protocol); 60 | var connectionId = this._runner.getNextFreeFileDescriptor(); 61 | this._connections[connectionId] = sock; 62 | callback(null, connectionId); 63 | }; 64 | 65 | NetworkApi.prototype.connect = function (connectionId, family, address, port, callback) { 66 | var sock = this._connections[connectionId]; 67 | if (sock) { 68 | sock.connect(family, address, port, callback); 69 | } else { 70 | throw new Error('Invalid connection ID'); 71 | } 72 | }; 73 | 74 | NetworkApi.prototype.read = function (connectionId, maxBytes, callback) { 75 | var sock = this._connections[connectionId]; 76 | if (sock) { 77 | sock.read(maxBytes, callback); 78 | } else { 79 | throw new Error('Invalid connection ID'); 80 | } 81 | }; 82 | 83 | NetworkApi.prototype.bind = function (connectionId, family, address, port, callback) { 84 | var sock = this._connections[connectionId]; 85 | if (sock) { 86 | sock.bind(this, family, address, port, callback); 87 | } else { 88 | throw new Error('Invalid connection ID'); 89 | } 90 | }; 91 | 92 | NetworkApi.prototype.accept = function (connectionId, callback) { 93 | var sock = this._connections[connectionId]; 94 | if (sock) { 95 | sock.accept(this._connections, this._runner, callback); 96 | } else { 97 | throw new Error('Invalid connection ID'); 98 | } 99 | }; 100 | 101 | NetworkApi.prototype.close = function (connectionId, callback) { 102 | var sock = this._connections[connectionId]; 103 | if (sock) { 104 | sock.close(callback); 105 | this._connections[connectionId] = void(0); 106 | } else { 107 | throw new Error('Invalid connection ID'); 108 | } 109 | }; 110 | 111 | NetworkApi.prototype.write = function (connectionId, data, dataFormat, callback) { 112 | var sock = this._connections[connectionId]; 113 | if (sock) { 114 | var buffer; 115 | if (dataFormat === 'hex') { 116 | buffer = new Buffer(data, 'hex'); 117 | } else { 118 | throw new Error('Invalid data format for socket write.'); 119 | } 120 | sock.write(buffer, callback); 121 | } else { 122 | throw new Error('Invalid connection ID'); 123 | } 124 | }; 125 | 126 | NetworkApi.prototype.getRemoteFamily = function (connectionId, callback) { 127 | var sock = this._connections[connectionId]; 128 | if (sock) { 129 | sock.getRemoteFamily(callback); 130 | } else { 131 | throw new Error('Invalid connection ID'); 132 | } 133 | }; 134 | 135 | NetworkApi.prototype.getRemotePort = function (connectionId, callback) { 136 | var sock = this._connections[connectionId]; 137 | if (sock) { 138 | sock.getRemotePort(callback); 139 | } else { 140 | throw new Error('Invalid connection ID'); 141 | } 142 | }; 143 | 144 | NetworkApi.prototype.getRemoteAddress = function (connectionId, callback) { 145 | var sock = this._connections[connectionId]; 146 | if (sock) { 147 | sock.getRemoteAddress(callback); 148 | } else { 149 | throw new Error('Invalid connection ID'); 150 | } 151 | }; 152 | 153 | /** 154 | * Return a listener function for a certain port. 155 | * 156 | * This allows the outside to simulate connections to the sandbox. 157 | * 158 | * This method will return a function that takes one parameter, a stream that 159 | * will be piped into the virtual socket. 160 | */ 161 | NetworkApi.prototype.getPortListener = function (port) { 162 | return this._ports[port]; 163 | }; 164 | 165 | /** 166 | * Register a new virtual listener. 167 | * 168 | * This should only be called by ProxiedSocket. 169 | */ 170 | NetworkApi.prototype.addPortListener = function (port, listener) { 171 | this._ports[port] = listener; 172 | this._runner.notifyAboutPortListener(port, listener); 173 | }; 174 | -------------------------------------------------------------------------------- /apis/net/proxied_socket.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var net = require('net'); 21 | var SystemError = require('../../lib/system_error').SystemError; 22 | 23 | var ProxiedSocket = function (domain, type, protocol) { 24 | if (ProxiedSocket.SUPPORTED_FAMILIES.indexOf(domain) === -1) { 25 | throw new Error("Unsupported socket domain: "+domain); 26 | } 27 | 28 | if (type !== ProxiedSocket.SOCK_STREAM) { 29 | throw new Error("Unsupported socket type: "+type); 30 | } 31 | 32 | if (protocol !== 0) { 33 | throw new Error("Unsupported protocol: "+protocol); 34 | } 35 | 36 | this._socket = null; 37 | this._buffer = []; 38 | this._waiting_incoming_connections = []; 39 | this._eof = false; 40 | } 41 | 42 | ProxiedSocket.AF_INET = 2; 43 | ProxiedSocket.AF_INET6 = 10; 44 | 45 | ProxiedSocket.SOCK_STREAM = 1; 46 | 47 | ProxiedSocket.SUPPORTED_FAMILIES = [ 48 | ProxiedSocket.AF_INET, 49 | ProxiedSocket.AF_INET6 50 | ]; 51 | 52 | ProxiedSocket.prototype.connect = function (family, address, port, callback) { 53 | var self = this; 54 | 55 | if (ProxiedSocket.SUPPORTED_FAMILIES.indexOf(family) === -1) { 56 | throw new Error("Unsupported socket domain: "+family); 57 | } 58 | 59 | var addressArray = [ 60 | address & 0xff, 61 | address >> 8 & 0xff, 62 | address >> 16 & 0xff, 63 | address >> 24 & 0xff 64 | ]; 65 | 66 | // Convert endianness 67 | port = (port >> 8 & 0xff) + (port << 8 & 0xffff); 68 | self._socket = net.createConnection({ 69 | port: port, 70 | host: addressArray.join('.') 71 | }); 72 | self._socket.once('connect', function (e) { 73 | console.log('ProxiedSocket connected to ' + addressArray.join('.') + ':' + port); 74 | callback(null, 0); 75 | }); 76 | 77 | self._socket.on('data', function(data) { 78 | self._buffer.push(data); 79 | }); 80 | 81 | self._socket.on('end', function () { 82 | self._eof = true; 83 | }); 84 | 85 | self._socket.on('error', function(error){ 86 | console.log('socket error: ', error); 87 | }); 88 | }; 89 | 90 | ProxiedSocket.prototype.bind = function (netApi, family, address, port, callback) { 91 | var self = this; 92 | 93 | // TODO-CODIUS: Actually honor the family choice 94 | 95 | if (ProxiedSocket.SUPPORTED_FAMILIES.indexOf(family) === -1) { 96 | throw new Error("Unsupported socket domain: "+family); 97 | } 98 | 99 | if (netApi.getPortListener(port)) { 100 | // Port is already bound 101 | callback(SystemError.create(address+':'+port, 'EADDRINUSE', 'bind')); 102 | } else { 103 | console.log('contract listening on port '+port); 104 | netApi.addPortListener(port, function(stream) { 105 | // We have a connection - a socket object will be assigned to the connection with accept() 106 | self._waiting_incoming_connections.push(stream); 107 | 108 | // console.log('Fake socket server connected to: ' + stream.remoteAddress +':'+ stream.remotePort); 109 | }); 110 | 111 | callback(null, 0); 112 | } 113 | }; 114 | 115 | ProxiedSocket.prototype.accept = function(connections, runner, callback) { 116 | var self = this; 117 | 118 | if (self._waiting_incoming_connections.length > 0) { 119 | var stream = self._waiting_incoming_connections.shift(); 120 | var peer_sock = new ProxiedSocket(ProxiedSocket.AF_INET, ProxiedSocket.SOCK_STREAM, 0); 121 | peer_sock._socket = stream; 122 | peer_sock._socket.on('data', function(data) { 123 | peer_sock._buffer.push(data); 124 | }); 125 | peer_sock._socket.on('end', function () { 126 | peer_sock._eof = true; 127 | }); 128 | var connectionId = runner.getNextFreeFileDescriptor(); 129 | connections[connectionId] = peer_sock; 130 | callback(null, connectionId); 131 | } else { 132 | // EAGAIN 133 | callback(null, -11); 134 | } 135 | }; 136 | 137 | ProxiedSocket.prototype.read = function (maxBytes, callback) { 138 | var self = this; 139 | 140 | if (!self._buffer.length && this._eof) { 141 | // UV_EOF (end of file) 142 | callback(null, -4095); 143 | return; 144 | } else if (!self._buffer.length) { 145 | // EAGAIN (no data, try again later) 146 | callback(null, -11); 147 | return; 148 | } 149 | 150 | var buffer = self._buffer.shift(); 151 | if (buffer.length > maxBytes) { 152 | self._buffer.unshift(buffer.slice(maxBytes)); 153 | buffer = buffer.slice(0, maxBytes); 154 | } 155 | 156 | callback(null, buffer.toString('hex')); 157 | }; 158 | 159 | ProxiedSocket.prototype.write = function (stringToWrite, callback) { 160 | var self = this; 161 | 162 | self._socket.write(stringToWrite); 163 | callback(null); 164 | } 165 | 166 | ProxiedSocket.prototype.close = function (callback) { 167 | var self = this; 168 | 169 | self._socket.destroy(); 170 | callback(null); 171 | } 172 | 173 | ProxiedSocket.prototype.getRemoteFamily = function (callback) { 174 | var self = this; 175 | 176 | var family = self._socket._getpeername().family; 177 | if (family === 'IPv4') { 178 | callback(null, ProxiedSocket.AF_INET); 179 | } else if (family === 'IPv6') { 180 | callback(null, ProxiedSocket.AF_INET6); 181 | } else { 182 | throw new Error("Unsupported socket family: " + family); 183 | } 184 | } 185 | 186 | ProxiedSocket.prototype.getRemotePort = function (callback) { 187 | var self = this; 188 | 189 | callback(null, self._socket._getpeername().port); 190 | } 191 | 192 | ProxiedSocket.prototype.getRemoteAddress = function (callback) { 193 | var self = this; 194 | 195 | callback(null, self._socket._getpeername().address); 196 | } 197 | 198 | exports.ProxiedSocket = ProxiedSocket; 199 | -------------------------------------------------------------------------------- /apis/secrets/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var crypto = require('../../lib/crypto'); 21 | var util = require('util'); 22 | var ApiModule = require('../../lib/api_module').ApiModule; 23 | 24 | exports.init = function(engine, config, secrets) { 25 | engine.registerAPI('secrets', function(runner){ 26 | var manifest = runner.getManifest(); 27 | var instance_id = runner.getInstanceId(); 28 | return new SecretGenerator(manifest, instance_id, secrets); 29 | }); 30 | }; 31 | 32 | /** 33 | * Class used to deterministically generate unique contract secrets 34 | */ 35 | function SecretGenerator(manifest, instance_id, secrets) { 36 | ApiModule.call(this); 37 | 38 | var self = this; 39 | 40 | self._manifest = manifest; 41 | self._secrets = secrets; 42 | self._instance_id = instance_id; 43 | } 44 | 45 | util.inherits(SecretGenerator, ApiModule); 46 | 47 | SecretGenerator.methods = [ 48 | 'getSecret', 49 | 'getKeypair' 50 | ]; 51 | 52 | /** 53 | * Get a deterministic 512-bit secret that is unique to the contract. 54 | * 55 | * @param {String} manifest.manifest_hash 56 | * @param {Function} callback 57 | * 58 | * @callback 59 | * @param {Error} error 60 | * @param {String} secret Hex-encoded 512-bit secret 61 | * 62 | */ 63 | SecretGenerator.prototype.getSecret = function(data, callback) { 64 | var self = this; 65 | 66 | var manifest_hash = self._manifest.manifest_hash; 67 | if (!manifest_hash) { 68 | throw new Error('SecretGenerator.getSecret not given manifest_hash, meaning it cannot derive unique contract secrets'); 69 | } 70 | 71 | var secret; 72 | try { 73 | secret = crypto.deriveSecret(self._secrets.CONTRACT_SECRET_GENERATOR, 'CONTRACT_SECRET_' + manifest_hash + self._instance_id, 'sha512'); 74 | } catch (error) { 75 | callback(new Error('Error deriving contract secret: ' + error)); 76 | return; 77 | } 78 | 79 | callback(null, secret); 80 | }; 81 | 82 | /** 83 | * Get a deterministic keypair that is unique to the contract. 84 | * 85 | * @param {Function} callback 86 | * 87 | * @callback 88 | * @param {Error} error 89 | * @param {Object} keypair Object with "public" and "private" fields that are hex-encoded strings 90 | * 91 | */ 92 | SecretGenerator.prototype.getKeypair = function(data, callback) { 93 | var self = this; 94 | 95 | var manifest_hash = self._manifest.manifest_hash; 96 | if (!manifest_hash) { 97 | throw new Error('SecretGenerator.getKeypair not given manifest_hash, meaning it cannot derive unique contract keypairs'); 98 | } 99 | 100 | if (data === 'ec_secp256k1') { 101 | 102 | var keypair; 103 | try { 104 | 105 | keypair = crypto.deriveKeypair(self._secrets.CONTRACT_KEYPAIR_GENERATOR_ec_secp256k1, 'CONTRACT_KEYPAIR_ec_secp256k1_' + manifest_hash + self._instance_id, 'ec_secp256k1'); 106 | keypair.signature = crypto.sign(self._secrets.MASTER_KEYPAIR_ec_secp256k1.private, keypair.public); 107 | 108 | } catch (error) { 109 | callback(new Error('Error deriving contract keypair: ' + error)); 110 | return; 111 | } 112 | 113 | } else { 114 | callback(new Error('Signature scheme not supported' + String(data))); 115 | return; 116 | } 117 | 118 | callback(null, keypair); 119 | 120 | }; 121 | -------------------------------------------------------------------------------- /apis/time/index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.init = function (engine, config) { 21 | engine.registerAPI('time', function (runner){ 22 | return new TimeApi(); 23 | }); 24 | }; 25 | 26 | var util = require('util'); 27 | 28 | var ApiModule = require('../../lib/api_module').ApiModule; 29 | 30 | function TimeApi() { 31 | ApiModule.call(this); 32 | 33 | var self = this; 34 | } 35 | 36 | util.inherits(TimeApi, ApiModule); 37 | 38 | TimeApi.methods = [ 39 | 'localtime' 40 | ]; 41 | 42 | function getDayOfYear (date) { 43 | var start = new Date(date.getFullYear(), 0, 1); 44 | var diff = date - start; 45 | var oneDay = 1000 * 60 * 60 * 24; 46 | return Math.floor(diff / oneDay); 47 | }; 48 | 49 | function stdTimezoneOffset (date) { 50 | var jan = new Date(date.getFullYear(), 0, 1); 51 | var jul = new Date(date.getFullYear(), 6, 1); 52 | return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); 53 | }; 54 | 55 | function isDST (date) { 56 | return date.getTimezoneOffset() < stdTimezoneOffset(date); 57 | }; 58 | 59 | TimeApi.prototype.localtime = function (callback) { 60 | var d = new Date(); 61 | var localtime = {}; 62 | localtime.tm_sec = d.getSeconds(); 63 | localtime.tm_min = d.getMinutes(); 64 | localtime.tm_hour = d.getHours(); 65 | localtime.tm_mday = d.getDate(); 66 | localtime.tm_mon = d.getMonth(); 67 | localtime.tm_year = d.getYear(); 68 | localtime.tm_wday = d.getDay(); 69 | localtime.tm_yday = getDayOfYear(d); 70 | localtime.tm_isdst = isDST(d) ? 1 : 0; 71 | localtime.tm_gmtoff = -d.getTimezoneOffset() * 60; 72 | localtime.tm_zone = String(String(d).split("(")[1]).split(")")[0]; 73 | 74 | callback(null, localtime); 75 | }; 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | exports.Compiler = require('./lib/compiler').Compiler; 21 | exports.Config = require('./lib/config').Config; 22 | exports.Engine = require('./lib/engine').Engine; 23 | exports.FileManager = require('./lib/filemanager').FileManager; 24 | exports.FileHash = require('./lib/filehash').FileHash; 25 | -------------------------------------------------------------------------------- /lib/api_module.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var ApiModule = function () { 21 | 22 | }; 23 | 24 | ApiModule.prototype.getMethod = function(name) { 25 | var self = this; 26 | 27 | if (self.constructor.methods.indexOf(name) !== -1) { 28 | return self[name].bind(self); 29 | } 30 | }; 31 | 32 | exports.ApiModule = ApiModule; 33 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var util = require('util'); 21 | var events = require('events'); 22 | var fs = require('fs'); 23 | var path = require('path'); 24 | var _ = require('lodash'); 25 | var ignore = require('ignore'); 26 | 27 | var FileHash = require('./filehash').FileHash; 28 | 29 | /** 30 | * Contracts compiler. 31 | * 32 | * This class will parse contract manifests and files and emit events for 33 | * processing them. 34 | */ 35 | var Compiler = function (config) { 36 | events.EventEmitter.call(this); 37 | 38 | this.config = config; 39 | this.ignoreFiles = config.ignoreFiles || ['.codiusignore']; 40 | 41 | this._filesystem = Compiler.RealFilesystem; 42 | }; 43 | 44 | util.inherits(Compiler, events.EventEmitter); 45 | 46 | /** 47 | * Compile the given contract or module, creating the manifest, 48 | * populating it with the `name`, `main`, `files`, `modules`, and `apis` fields, 49 | * and writing all of the files to the filesystem. 50 | * 51 | * @param {String} contractPath Absolute path of the contract or module to compile 52 | * @param {Object} manifest Optionally pass in a full or partially filled out manifest 53 | * @param {Array} [null] parent_ignores A list of inherited rules for filtering files 54 | * 55 | * @returns {String} contract_hash The hash of the contract's manifest 56 | */ 57 | Compiler.prototype.compileModule = function (contractPath, manifest, parent_ignores) { 58 | var _this = this; 59 | 60 | contractPath = contractPath || ''; 61 | 62 | // Read existing manifest or create a new one 63 | if (!manifest) { 64 | var manifest_path = path.join(contractPath, _this.config.manifestFilename); 65 | if (_this._filesystem.exists(manifest_path)) { 66 | try { 67 | manifest = _this._filesystem.readFile(manifest_path, { encoding: 'utf8' }); 68 | manifest = JSON.parse(manifest); 69 | } catch(error) { 70 | throw new Error('Error parsing manifest: ' + error); 71 | } 72 | } else { 73 | manifest = {}; 74 | } 75 | } 76 | 77 | // Ensure there is a manifest name property 78 | if (!manifest.name) { 79 | var manifest_path_array = contractPath.replace(/\/$/, '').split(path.sep); 80 | manifest.name = manifest_path_array[manifest_path_array.length - 1]; 81 | } 82 | 83 | // Normalize (or if needed, generate) manifest properties 84 | _this._ensureMainProperty(manifest, contractPath); 85 | var ignore_rules=[]; 86 | if (!parent_ignores) parent_ignores = []; 87 | _this._ensureFilesProperty(manifest, contractPath, parent_ignores, ignore_rules); 88 | _this._ensureModulesProperty(manifest, contractPath); 89 | _this._ensureApisProperty(manifest); 90 | _this._ensureEnvProperty(manifest, contractPath); 91 | 92 | // Now we emit events for each file in the manifest 93 | _this._emitFileEvents(manifest.files, contractPath); 94 | 95 | // Next we need to recurse to process modules as well 96 | var modulesPath = path.join(contractPath, 'node_modules'); 97 | if (Array.isArray(manifest.modules)) { 98 | var modulesMap = {}; 99 | manifest.modules.forEach(function(moduleName){ 100 | // TODO: node_modules/.bin should be included in the files, not the modules 101 | if (moduleName === '.bin') { 102 | return; 103 | } 104 | var modulePath = path.join(modulesPath, moduleName); 105 | var moduleHash = _this.compileModule(modulePath, null, ignore_rules); 106 | modulesMap[moduleName] = moduleHash; 107 | }); 108 | manifest.modules = modulesMap; 109 | } else if ("object" === typeof manifest.modules) { 110 | _.each(manifest.modules, function (expectedModuleHash, moduleName) { 111 | // TODO: node_modules/.bin should be included in the files, not the modules 112 | if (moduleName === '.bin') { 113 | return; 114 | } 115 | var modulePath = path.join(modulesPath, moduleName); 116 | var moduleHash = _this.compileModule(modulePath, null, ignore_rules); 117 | if (moduleHash !== expectedModuleHash) { 118 | throw new Error('Error parsing manifest: Incorrect hash for module: ' + moduleName); 119 | } 120 | }); 121 | } 122 | 123 | // Write manifest and return its hash 124 | var prettyManifest = JSON.stringify(manifest, null, ' '); 125 | var manifestBuffer = new Buffer(prettyManifest, 'utf-8'); 126 | var manifestHash = FileHash.hash(manifestBuffer); 127 | 128 | _this.emit('file', { 129 | data: manifestBuffer, 130 | hash: manifestHash, 131 | name: path.join(contractPath, 'codius-manifest.json'), 132 | isManifest: true 133 | }); 134 | 135 | return manifestHash; 136 | }; 137 | 138 | Compiler.prototype._ensureMainProperty = function (manifest, contractPath) { 139 | var _this = this; 140 | 141 | // Set main file 142 | if (!manifest.main) { 143 | // If there is only one file in the directory that must be the main file 144 | var files_in_dir = _this._filesystem.readdir(contractPath); 145 | if (files_in_dir.length === 1) { 146 | manifest.main = files_in_dir[0]; 147 | } else { 148 | 149 | // If there is more than one file in the directory then possible main files 150 | // include all of the standard ones, plus "{module name}.js" 151 | var main_possibilities = _this.config.defaultMainFilenames; 152 | main_possibilities.push(manifest.name + '.js'); 153 | 154 | // If package.json is found also add the main file from there 155 | if (_this._filesystem.exists(path.resolve(contractPath, 'package.json'))) { 156 | try { 157 | var package_json = _this._filesystem.readFile(path.resolve(contractPath, 'package.json')); 158 | var package_main = JSON.parse(package_json).main; 159 | if (typeof package_main === 'string') { 160 | main_possibilities.push(package_main); 161 | main_possibilities.push(package_main+'.js'); 162 | main_possibilities.push(package_main+'.json'); 163 | main_possibilities.push(package_main+'/index.js'); 164 | main_possibilities.push(package_main+'/index.json'); 165 | 166 | // NOTE Node.js also allows .node, but since we don't allow native 167 | // modules, we cannot support that yet. 168 | } 169 | } catch (e) {} 170 | } 171 | 172 | for (var m = 0; m < main_possibilities.length; m++) { 173 | var main_path = path.join(contractPath, main_possibilities[m]); 174 | 175 | if (_this._filesystem.exists(main_path)) { 176 | manifest.main = main_possibilities[m]; 177 | break; 178 | } 179 | } 180 | } 181 | } 182 | 183 | if (!manifest.main) { 184 | throw new Error('Contract has no main file in '+contractPath); 185 | } 186 | }; 187 | 188 | Compiler.prototype._ensureFilesProperty = function (manifest, contractPath, parent_ignores, ignore_rules) { 189 | var _this = this; 190 | 191 | if (!manifest.files) { 192 | // Go through all files and subdirectory files 193 | // excluding the node_modules 194 | manifest.files = _this._findAllFiles(contractPath, [_this.config.manifestFilename, 'node_modules', '.git', _this.config.configFilename], [], parent_ignores, ignore_rules); 195 | manifest.files = manifest.files.map(function (path) { 196 | return path.substr(contractPath.length + 1); 197 | }); 198 | } else if (typeof manifest.files !== 'object') { 199 | throw new Error('Invalid manifest, `files` must be an array or object'); 200 | } 201 | 202 | // Split subpaths into subobjects 203 | manifest.files = _this._expandFilesProperty(manifest.files, contractPath); 204 | 205 | // TODO Strip off initial slashes 206 | }; 207 | 208 | Compiler.prototype._expandFilesProperty = function (files, filepath) { 209 | var self = this; 210 | 211 | // The files field (or any subdirectory) can be provided as an array, in 212 | // which case we still need to go in and calculate the hashes. 213 | if (Array.isArray(files)) { 214 | var fileHashMap = {}; 215 | files.forEach(function (filename) { 216 | if (typeof filename !== 'string') { 217 | throw new Error('Invalid manifest `files` property, arrays may only contain strings, not "'+ 218 | (typeof filename) + '"'); 219 | } 220 | 221 | var file = self._filesystem.readFile(path.join(filepath, filename)); 222 | var hash = FileHash.hash(file); 223 | fileHashMap[filename] = hash; 224 | }); 225 | 226 | return self._expandFilesProperty(fileHashMap, filepath); 227 | } else if (files && typeof files === 'object') { 228 | var outputFiles = {}; 229 | _.forOwn(files, function (value, key) { 230 | 231 | if (typeof key !== 'string') { 232 | throw new Error('Invalid manifest `files` property, non-string key encountered in object.'); 233 | } 234 | 235 | if (value && typeof value !== 'string') { 236 | value = self._expandFilesProperty(value, path.join(filepath, key)); 237 | } 238 | 239 | // Expand path string into nested objects 240 | // {'foo/bar/baz': value} => {foo: {bar: {baz: value}}} 241 | var nestedValue = value; 242 | key.split('/').reverse().forEach(function (pathComponent) { 243 | var nextLayer = {}; 244 | nextLayer[pathComponent] = nestedValue; 245 | nestedValue = nextLayer; 246 | }); 247 | 248 | // Merge the resulting objects together into one object. 249 | _.merge(outputFiles, nestedValue); 250 | }); 251 | 252 | return outputFiles; 253 | } else { 254 | throw new Error('Invalid manifest `files` property, every directory definition must be an object or an array.'); 255 | } 256 | }; 257 | 258 | Compiler.prototype._ensureModulesProperty = function (manifest, contractPath) { 259 | var _this = this; 260 | 261 | if (!manifest.modules) { 262 | // Call this function on each of the modules found in the node_modules dir 263 | var modules_path = path.join(contractPath, 'node_modules'); 264 | if (_this._filesystem.exists(modules_path) && _this._filesystem.stat(modules_path).isDirectory()) { 265 | manifest.modules = _this._filesystem.readdir(modules_path); 266 | } else { 267 | manifest.modules = []; 268 | } 269 | } 270 | }; 271 | 272 | Compiler.prototype._ensureApisProperty = function (manifest, contractPath) { 273 | var _this = this; 274 | 275 | if (!manifest.apis) { 276 | manifest.apis = []; 277 | } 278 | 279 | // Make sure at least the default manifest apis are requested 280 | manifest.apis = _.union(manifest.apis, _this.config.defaultManifestApis); 281 | }; 282 | 283 | Compiler.prototype._emitFileEvents = function (files, basepath) { 284 | var self = this; 285 | 286 | _.forIn(files, function (hash, filename) { 287 | // This is actually a subdirectory, so let's recurse 288 | if (typeof hash === 'object') { 289 | self._emitFileEvents(hash, path.join(basepath, filename)); 290 | 291 | // This is a file 292 | } else if (typeof hash === 'string') { 293 | var file = self._filesystem.readFile(path.join(basepath, filename)); 294 | self.emit('file', { 295 | data: file, 296 | hash: hash, 297 | name: path.join(basepath, filename) 298 | }); 299 | } else { 300 | throw new Error('Invalid type.'); 301 | } 302 | }); 303 | }; 304 | 305 | /** 306 | * Load env property from codius-config.json if it exists 307 | */ 308 | Compiler.prototype._ensureEnvProperty = function (manifest, contractPath) { 309 | var _this = this; 310 | 311 | // Do not overwrite env if it is explicitly declared 312 | if (manifest.env) { 313 | return; 314 | } 315 | 316 | var configPath = path.join(contractPath, _this.config.configFilename); 317 | var config; 318 | if (_this._filesystem.exists(configPath)) { 319 | var configFile = _this._filesystem.readFile(configPath, 'utf8'); 320 | try { 321 | config = JSON.parse(configFile); 322 | } catch (err) { 323 | throw new Error('Invalid JSON: ' + configPath); 324 | return; 325 | } 326 | 327 | if (config.env) { 328 | manifest.env = config.env; 329 | } 330 | } 331 | 332 | if (!manifest.env) { 333 | manifest.env = {}; 334 | } 335 | }; 336 | 337 | /** 338 | * Recursively search through the given dir to find 339 | * the full paths of all of the files. 340 | * 341 | * Can be overridden using setFindAllFiles. 342 | * 343 | * @param {String} dir 344 | * @param {Array} [null] exclude_list A list of filenames or directory names to skip 345 | * @param {Array} [null] parent_ignores A list of inherited rules for filtering files 346 | * @param {Array} [null] ignore_rules A to be determined list of rules for filtering files 347 | * 348 | * @returns {Array} files Array of full paths for all files in dir 349 | */ 350 | Compiler.prototype._findAllFiles = function (dir, exclude_list, files, parent_ignores, ignore_rules) { 351 | var _this = this; 352 | if (!files) files = []; 353 | if (!ignore_rules) ignore_rules = []; 354 | 355 | var ignoreFilter = _this._createIgnoreFilter(dir, ignore_rules, parent_ignores); 356 | 357 | var dir_contents = _this._filesystem.readdir(dir); 358 | 359 | dir_contents.forEach(function(filename){ 360 | if (exclude_list && exclude_list.indexOf(filename) !== -1) { 361 | return; 362 | } 363 | var file_path = path.join(dir, filename); 364 | var file_stats = _this._filesystem.stat(file_path); 365 | if (file_stats.isFile()) { 366 | files.push(file_path); 367 | } else if (file_stats.isDirectory()) { 368 | _this._findAllFiles(file_path, [], files, ignore_rules); 369 | } 370 | }); 371 | 372 | files = files.filter(ignoreFilter); 373 | return files; 374 | }; 375 | 376 | Compiler.prototype._createIgnoreFilter = function(dir, ignore_rules, parent_ignores) { 377 | var _this = this; 378 | 379 | if (parent_ignores && parent_ignores.length > 0) { 380 | Array.prototype.push.apply(ignore_rules, parent_ignores); 381 | } 382 | 383 | if (_this.ignoreFiles) { 384 | _this.ignoreFiles.forEach(function(ignoreFile){ 385 | var ignoreFilePath = path.join(dir, ignoreFile); 386 | if (_this._filesystem.exists(ignoreFilePath)) { 387 | var rules = _this._filesystem.readFile(ignoreFilePath, 'utf8').split('\n'); 388 | Array.prototype.push.apply (ignore_rules, rules); 389 | } 390 | }); 391 | } 392 | 393 | if (ignore_rules.length===0) { 394 | return function(path) { 395 | return true; 396 | }; 397 | } 398 | return ignore({ 399 | ignore: ignore_rules 400 | }).createFilter(); 401 | }; 402 | 403 | Compiler.prototype.setFilesystem = function (filesystem) { 404 | this._filesystem = filesystem; 405 | }; 406 | 407 | Compiler.RealFilesystem = { 408 | readFile: fs.readFileSync.bind(fs), 409 | stat: fs.statSync.bind(fs), 410 | readdir: fs.readdirSync.bind(fs), 411 | exists: fs.existsSync.bind(fs) 412 | }; 413 | 414 | exports.Compiler = Compiler; 415 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var path = require('path'); 21 | var extend = require('extend'); 22 | 23 | function Config(opts) { 24 | extend(this, Config.defaults); 25 | extend(this, opts); 26 | } 27 | 28 | function getUserHome() { 29 | return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 30 | } 31 | 32 | Config.defaults = { 33 | /** 34 | * Path where the virtual contracts filesystem lives on the physical disk. 35 | */ 36 | contractsFilesystemPath: path.resolve(getUserHome() || (__dirname+'/..'), '.codius/contract_filesystem/')+path.sep, 37 | 38 | /** 39 | * Path to API modules. 40 | */ 41 | apisPath: path.resolve(__dirname, '../apis/')+path.sep, 42 | 43 | /** 44 | * Path to runtime library modules. 45 | */ 46 | runtimeLibraryPath: path.resolve(__dirname, '../runtime_library/')+path.sep, 47 | 48 | /** 49 | * Api modules that the engine should load and have available. 50 | */ 51 | apis: [ 52 | 'fs', 53 | 'secrets', 54 | 'dns', 55 | 'net', 56 | 'crypto', 57 | 'localstorage', 58 | 'time' 59 | ], 60 | 61 | /** 62 | * Apis to be added to automatically generated manifests. 63 | */ 64 | defaultManifestApis: [ 65 | 'fs', 66 | 'secrets', 67 | 'dns', 68 | 'net', 69 | 'crypto', 70 | 'localstorage', 71 | 'time' 72 | ], 73 | 74 | /** 75 | * Name for the manifest file. 76 | * 77 | * The manifest file specifies the basic properties of the contract. 78 | */ 79 | manifestFilename: 'codius-manifest.json', 80 | 81 | /** 82 | * Name for the config file. 83 | */ 84 | configFilename: 'codius-config.json', 85 | 86 | /** 87 | * Default filenames for primary contract script. 88 | * 89 | * When generating an implicit manifest, the contract engine will look for 90 | * these filenames as the entry point. 91 | */ 92 | defaultMainFilenames: [ 93 | 'contract.js', 94 | 'main.js', 95 | 'index.js' 96 | ], 97 | 98 | /** 99 | * Filenames that specify filepath patterns to ignore 100 | */ 101 | ignoreFiles: ['.codiusignore'], 102 | 103 | /** 104 | * The port that contracts should listen to inside the sandbox 105 | */ 106 | virtual_port: 8000, 107 | 108 | /** 109 | * Where to log to. 110 | * 111 | * Should be an object with methods such as "info", "warn", "error". 112 | */ 113 | logger: console, 114 | 115 | /** 116 | * Stream where the sandbox stdout should go. 117 | */ 118 | outputStream: process.stdout, 119 | 120 | /** 121 | * Enable debugging with GDB 122 | */ 123 | enableGdb: false, 124 | 125 | /** 126 | * Enable valgrind for debugging 127 | */ 128 | enableValgrind: false, 129 | 130 | /** 131 | * Use Native Client sandbox 132 | */ 133 | disableNacl: true 134 | 135 | }; 136 | 137 | exports.Config = Config; 138 | -------------------------------------------------------------------------------- /lib/contractrunner.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var fs = require('fs'); 21 | var crypto = require('crypto'); 22 | var events = require('events'); 23 | var util = require('util'); 24 | var Sandbox = require('codius-node-sandbox'); 25 | 26 | /** 27 | * Class to run contracts. 28 | * 29 | * @param {Object} data.manifest 30 | * @param {Object} data.apis 31 | * @param {String} data.manifest_hash 32 | */ 33 | function ContractRunner(config, data) { 34 | events.EventEmitter.call(this); 35 | 36 | var self = this; 37 | 38 | self.config = config; 39 | 40 | if (typeof data !== 'object' || 41 | typeof data.manifest !== 'object') { 42 | 43 | throw new Error('ApiHandler must be instantiated with the manifest'); 44 | } 45 | 46 | self._manifest = data.manifest; 47 | self._apis = {}; 48 | self._manifest_hash = data.manifest_hash; 49 | self._instance_id = data.instance_id; 50 | self._additional_libs = data.additional_libs || ''; 51 | self._env = data.env; 52 | self._nextFreeFileDescriptor = 4; 53 | 54 | // Add the manifest_hash to the manifest object so it will 55 | // be passed to the API modules when they are called 56 | self._manifest.manifest_hash = data.manifest_hash; 57 | 58 | // Setup sandbox instance 59 | self._sandbox = new Sandbox({ 60 | enableGdb: self.config.enableGdb, 61 | enableValgrind: self.config.enableValgrind, 62 | disableNacl: self.config.disableNacl 63 | }); 64 | 65 | self._sandbox.on('exit', self.handleExit.bind(self)); 66 | } 67 | 68 | util.inherits(ContractRunner, events.EventEmitter); 69 | 70 | /** 71 | * Expose a set of APIs to the running contract. 72 | * 73 | * Calling this method will overwrite any previously exposed APIs. 74 | */ 75 | ContractRunner.prototype.setApis = function(apis) { 76 | this._apis = apis; 77 | }; 78 | 79 | /** 80 | * Call the specified method, in the specified module, 81 | * with the contract manifest, the given data, and the callback 82 | * 83 | * @param {String} parameters.api 84 | * @param {String} parameters.method 85 | * @param {Object} parameters.data 86 | * @param {Function} callback 87 | */ 88 | ContractRunner.prototype.callApi = function(parameters, callback) { 89 | var self = this; 90 | 91 | if (!self._apis.hasOwnProperty(parameters.api)) { 92 | callback(new Error('Unknown or undeclared API Module: ' + parameters.api)); 93 | return; 94 | } 95 | var api = self._apis[parameters.api]; 96 | 97 | if (parameters.method.indexOf('_') === 0) { 98 | callback(new Error('Cannot access private method: ' + parameters.method)); 99 | return; 100 | } 101 | 102 | var method = api.getMethod(parameters.method); 103 | if (typeof method !== 'function') { 104 | callback(new Error('Unknown API Method: ' + parameters.api + '.' + parameters.method)); 105 | return; 106 | } 107 | 108 | var args = parameters.data; 109 | args.push(callback); 110 | method.apply(api, args); 111 | }; 112 | 113 | /** 114 | * Function to handle messages sent from within the sandbox. 115 | * 116 | * @param {String} message.api 117 | * @param {String} message.method 118 | * @param {Object} [[]] message.data 119 | * @param {Function} callback 120 | */ 121 | ContractRunner.prototype.handleMessage = function(message, callback) { 122 | var self = this; 123 | 124 | if (typeof message !== 'object' || !message.api || !message.method) { 125 | callback(new Error('Invalid message: must pass an object with the fields `api`, `method`, and optionally `data`.')); 126 | return; 127 | } 128 | 129 | var parameters = { 130 | api: message.api, 131 | method: message.method, 132 | data: message.data 133 | }; 134 | 135 | self.callApi(parameters, callback); 136 | 137 | }; 138 | 139 | ContractRunner.prototype.run = function() { 140 | var self = this; 141 | 142 | var message_handler = self.handleMessage.bind(self); 143 | self._sandbox.setApi(message_handler); 144 | 145 | self._sandbox.passthroughStdio(); 146 | 147 | self._sandbox.run(self._manifest.main, { env: self._env }); 148 | }; 149 | 150 | ContractRunner.prototype.getManifest = function () { 151 | return this._manifest; 152 | }; 153 | 154 | ContractRunner.prototype.getManifestHash = function () { 155 | return this._manifest_hash; 156 | }; 157 | 158 | ContractRunner.prototype.getInstanceId = function() { 159 | return this._instance_id || ''; 160 | }; 161 | 162 | ContractRunner.prototype.getPortListener = function (port) { 163 | if (!this._apis.net) { 164 | throw new Error('Tried to simulate a listener for a contract that does not support networking.'); 165 | } 166 | 167 | return this._apis.net.getPortListener(port); 168 | }; 169 | 170 | ContractRunner.prototype.notifyAboutPortListener = function (port, listener) { 171 | this.emit('portListener', { 172 | port: port, 173 | listener: listener 174 | }); 175 | }; 176 | 177 | ContractRunner.prototype.getNextFreeFileDescriptor = function () { 178 | return this._nextFreeFileDescriptor++; 179 | }; 180 | 181 | ContractRunner.prototype.handleExit = function (code, signal) { 182 | var self = this; 183 | 184 | self.emit('exit', code, signal); 185 | }; 186 | 187 | ContractRunner.prototype.kill = function(message) { 188 | var self = this; 189 | 190 | self._sandbox.kill(message); 191 | }; 192 | 193 | exports.ContractRunner = ContractRunner; 194 | -------------------------------------------------------------------------------- /lib/crypto.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var crypto = require('crypto'); 21 | var bitcoinjs = require('bitcoinjs-lib'); 22 | var BigInteger = require('bigi'); 23 | var ecurve = require('ecurve'); 24 | 25 | exports.getRandomMasterSecret = getRandomMasterSecret; 26 | exports.deriveSecret = deriveSecret; 27 | exports.deriveKeypair = deriveKeypair; 28 | exports.sign = sign; 29 | exports.verify = verify; 30 | 31 | function getRandomMasterSecret() { 32 | return crypto.randomBytes(32).toString('hex') 33 | } 34 | 35 | /** 36 | * Derive a "child" secret from a "parent" one using HMAC. 37 | * 38 | * @param {String} parent_secret 39 | * @param {String} child_name 40 | * @param {String} ['sha512'] hash_algorithm 41 | * 42 | * @returns {String} Note that the number of bits depends on the hash algorithm used 43 | */ 44 | function deriveSecret(parent_secret, child_name, hash_algorithm) { 45 | if (!hash_algorithm) { 46 | hash_algorithm = 'sha512'; 47 | } 48 | 49 | return crypto.createHmac(hash_algorithm, parent_secret).update(child_name).digest('hex'); 50 | } 51 | 52 | /** 53 | * Derive a public and private key from a "parent" secret. 54 | * 55 | * @param {String} parent_secret 56 | * @param {String} child_name 57 | * @param {String} ['secp256k1'] signature_scheme 58 | * 59 | * @returns {Object} Object with "public" and "private" fields 60 | */ 61 | function deriveKeypair(parent_secret, child_name, signature_scheme) { 62 | if (!signature_scheme) { 63 | signature_scheme = 'ec_secp256k1'; 64 | } 65 | 66 | var pair = {}; 67 | 68 | if (signature_scheme === 'ec_secp256k1') { 69 | 70 | pair.private = deriveSecret(parent_secret, child_name, 'sha256'); 71 | 72 | // If the private key is greater than the curve modulus we 73 | // use a counter to get a new random secret 74 | var modulus = new BigInteger(ecurve.getCurveByName('secp256k1').n, 16); 75 | var counter = 1; 76 | while (!pair.private || modulus.compareTo(pair.private) < 0) { 77 | pair.private = deriveSecret(parent_secret, child_name + '_' + counter, 'sha256'); 78 | counter += 1; 79 | } 80 | 81 | pair.public = new bitcoinjs.ECKey(new BigInteger(pair.private, 16), false).pub.toHex(); 82 | 83 | } else { 84 | 85 | throw new Error('Signature scheme: ' + signature_scheme + ' not currently supported'); 86 | 87 | } 88 | 89 | return pair; 90 | } 91 | 92 | /** 93 | * Sign the given data 94 | * 95 | * ** For now assumes ec_secp256k1 ** 96 | * 97 | * @param {String} private_key Hex-encoded private key 98 | * @param {String} data Hex-encoded data 99 | */ 100 | function sign(private_key, data) { 101 | var key = new bitcoinjs.ECKey(new BigInteger(private_key, 16), false); 102 | var hash = bitcoinjs.crypto.hash256(new Buffer(data, 'hex')); 103 | return key.sign(hash).toDER().toString('hex'); 104 | } 105 | 106 | /** 107 | * Verify a signature on the given data 108 | * 109 | * ** For now assumes ec_secp256k1 ** 110 | * 111 | * @param {String} public_key Hex-encoded public key 112 | * @param {String} data Hex-encoded data 113 | * @param {String} signature Hex-encoded signature 114 | */ 115 | function verify(public_key, data, signature) { 116 | var pubkey = bitcoinjs.ECPubKey.fromHex(public_key); 117 | var hash = bitcoinjs.crypto.hash256(new Buffer(data, 'hex')); 118 | var ecsignature = bitcoinjs.ECSignature.fromDER(new Buffer(signature, 'hex')); 119 | return pubkey.verify(hash, ecsignature); 120 | } 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /lib/engine.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var fs = require('fs'); 21 | var path = require('path'); 22 | var _ = require('lodash'); 23 | var extend = require('extend'); 24 | var crypto = require('./crypto'); 25 | var Runner = require('./contractrunner').ContractRunner; 26 | var Config = require('./config').Config; 27 | 28 | var MASTER_SECRET_REGEX = /^[0-9a-fA-F]{64}$/; 29 | var HASH_REGEX = /^[0-9a-fA-F]{64}$/; 30 | 31 | 32 | function Engine(config) { 33 | var self = this; 34 | 35 | if (!config) { 36 | config = new Config(); 37 | } 38 | 39 | self.config = config; 40 | 41 | // Generate the engine secrets used to generate 42 | // contract-specific secrets and keypairs, as well 43 | // as the host's keypairs to sign the contracts' public keys 44 | if (!MASTER_SECRET_REGEX.test(config.MASTER_SECRET)) { 45 | if (typeof config.MASTER_SECRET === 'undefined') { 46 | config.logger.warn('No MASTER_SECRET provided! Generating a random secret, please do not do this in production.'); 47 | } else { 48 | config.logger.warn('Invalid MASTER_SECRET format! Generating a random secret, please do not do this in production.'); 49 | } 50 | config.MASTER_SECRET = crypto.getRandomMasterSecret(); 51 | } 52 | self._secrets = self.generateSecrets(config.MASTER_SECRET); 53 | 54 | // All of the APIs the engine has access to 55 | self._apis = {}; 56 | 57 | // Register all of the desired APIs 58 | config.apis.forEach(function (apiName) { 59 | // The initialization function is what sets up all of the modules' hooks and modules 60 | 61 | if (apiName === 'secrets') { 62 | require(path.resolve(config.apisPath, apiName)).init(self, self.config, self._secrets); 63 | } else { 64 | require(path.resolve(config.apisPath, apiName)).init(self, self.config); 65 | } 66 | }); 67 | } 68 | 69 | /** 70 | * Register a new API module factory with the engine. 71 | * When a new contract is run each API module factory will 72 | * be called with a reference to the ContractRunner (which 73 | * includes the manifest and manifest hash, among other things). 74 | * The function registered here should return a new instace 75 | * of the API module's class, which should inherit from the 76 | * ApiModule abstract class. 77 | * 78 | * @param {String} name 79 | * @param {Function} moduleFactory 80 | */ 81 | Engine.prototype.registerAPI = function(name, moduleFactory) { 82 | var self = this; 83 | 84 | self._apis[name] = moduleFactory; 85 | }; 86 | 87 | /** 88 | * Takes a function that will be connected to the 89 | * LocalStorage API to provide contracts with persistent storage. 90 | * Each function should take the contract hash as the first 91 | * parameter. Otherwise, each function follows the browser 92 | * LocalStorage API. 93 | * 94 | * @param {Function} contractStorage.getItem 95 | * @param {Function} contractStorage.setItem 96 | * @param {Function} contractStorage.removeItem 97 | * @param {Function} contractStorage.clear 98 | * @param {Function} contractStorage.key 99 | */ 100 | Engine.prototype.setContractStorage = function(contractStorage) { 101 | var self = this; 102 | 103 | if (self._apis.localstorage) { 104 | self._apis.localstorage = _.partialRight(self._apis.localstorage, contractStorage); 105 | } 106 | }; 107 | 108 | /** 109 | * Run the contract specified by the given manifest hash. 110 | * 111 | * @param {String} manifest_hash 112 | * @param {Object} opts.env Environment variables to expose to sandboxed code 113 | * 114 | * @returns {ContractRunner} 115 | */ 116 | Engine.prototype.runContract = function(manifest_hash, opts) { 117 | var self = this; 118 | 119 | opts = opts || {}; 120 | 121 | var manifest = self._loadManifest(manifest_hash); 122 | 123 | // Load contract environment variables 124 | var contract_env; 125 | if (manifest.env && typeof manifest.env === 'object') { 126 | contract_env = manifest.env; 127 | } else { 128 | contract_env = {}; 129 | } 130 | 131 | if (opts.env) { 132 | contract_env = extend(contract_env, opts.env); 133 | } 134 | 135 | if (!contract_env.PORT && self.config.virtual_port) { 136 | contract_env.PORT = self.config.virtual_port; 137 | } 138 | 139 | // Create a new runner to run this contract 140 | var runner = new Runner(self.config, { 141 | manifest: manifest, 142 | manifest_hash: manifest_hash, 143 | additional_libs: self.config.additional_libs, 144 | env: contract_env, 145 | instance_id: opts.instance_id || '' 146 | }); 147 | 148 | // Setup available APIs 149 | // Each runner will get its own instances of all of the APIs 150 | var contract_apis = {}; 151 | if (typeof manifest.apis === 'object' && manifest.apis.length > 0) { 152 | manifest.apis.forEach(function(api_name){ 153 | if (self._apis.hasOwnProperty(api_name)) { 154 | var createApiHandlerInstance = self._apis[api_name]; 155 | contract_apis[api_name] = createApiHandlerInstance(runner); 156 | } 157 | }); 158 | } 159 | runner.setApis(contract_apis); 160 | 161 | runner.run(); 162 | 163 | return runner; 164 | }; 165 | 166 | Engine.prototype._loadManifest = function(manifestHash) { 167 | var self = this; 168 | 169 | // Load manifest file 170 | var firstDir = manifestHash.slice(0, 2); 171 | var secondDir = manifestHash.slice(2, 4); 172 | var manifest_path = path.join(self.config.contractsFilesystemPath, firstDir, secondDir, manifestHash); 173 | var manifest = fs.readFileSync(manifest_path, { encoding: 'utf8' }); 174 | try { 175 | manifest = JSON.parse(manifest); 176 | } catch(error) { 177 | throw new Error('Error parsing manifest: ' + error); 178 | } 179 | 180 | if (manifest.extend && HASH_REGEX.test(manifest.extend)) { 181 | manifest = extend(manifest, self._loadManifest(manifest.extend)); 182 | } 183 | 184 | return manifest; 185 | }; 186 | 187 | /** 188 | * Generate the host's secrets from the MASTER_SECRET 189 | * 190 | * @param {String} MASTER_SECRET 191 | * 192 | * @returns {Object} 193 | */ 194 | Engine.prototype.generateSecrets = function(MASTER_SECRET) { 195 | 196 | var secrets = {}; 197 | 198 | secrets.CONTRACT_SECRET_GENERATOR = crypto.deriveSecret(MASTER_SECRET, 'CONTRACT_SECRET_GENERATOR'); 199 | 200 | secrets.CONTRACT_KEYPAIR_GENERATOR_ec_secp256k1 = crypto.deriveSecret(MASTER_SECRET, 'CONTRACT_KEYPAIR_GENERATOR_ec_secp256k1', 'sha256'); 201 | 202 | secrets.MASTER_KEYPAIR_ec_secp256k1 = crypto.deriveKeypair(MASTER_SECRET, 'MASTER_KEYPAIR_ec_secp256k1', 'ec_secp256k1'); 203 | 204 | return secrets; 205 | 206 | }; 207 | 208 | /** 209 | * Get the engine's master public key 210 | * 211 | * @param {String} ['ec_secp256k1'] signature_scheme 212 | * 213 | * @returns {String} public_key 214 | */ 215 | Engine.prototype.getMasterPublicKey = function(signature_scheme) { 216 | var self = this; 217 | if (!signature_scheme) { 218 | signature_scheme = 'ec_secp256k1'; 219 | } 220 | 221 | if (signature_scheme === 'ec_secp256k1') { 222 | return self._secrets.MASTER_KEYPAIR_ec_secp256k1.public; 223 | } else { 224 | throw new Error('Signature scheme: ' + signature_scheme + ' not currently supported.'); 225 | } 226 | }; 227 | 228 | 229 | exports.Engine = Engine; 230 | -------------------------------------------------------------------------------- /lib/filehash.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var crypto = require('crypto'); 21 | 22 | var FileHash = {}; 23 | 24 | /** 25 | * Hash the given data and return 32 bytes of the hash. 26 | * 27 | * @param {String} data 28 | * @param {String} ['utf8'] encoding 29 | * 30 | * @returns {String} hash 32 bytes of the SHA512 hash 31 | */ 32 | FileHash.hash = function (data) { 33 | var hashing_function = crypto.createHash('sha512'); 34 | hashing_function.update(data); 35 | var hash_result = hashing_function.digest('hex'); 36 | return hash_result.slice(0, 64); 37 | }; 38 | 39 | exports.FileHash = FileHash; 40 | -------------------------------------------------------------------------------- /lib/filemanager.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var Promise = require('bluebird'); 21 | var fs = Promise.promisifyAll(require("fs")); 22 | var path = require('path'); 23 | 24 | var FileHash = require('./filehash').FileHash; 25 | 26 | var FileManager = function (config) { 27 | this.config = config; 28 | }; 29 | 30 | /** 31 | * Hash file data and store file in contracts filesystem. 32 | */ 33 | FileManager.prototype.storeFileData = function (data) { 34 | var hash = FileHash.hash(data); 35 | return this.storeFileWithHash(hash, data); 36 | }; 37 | 38 | /** 39 | * Write the given file to the filesystem using the given filename (should 40 | * be the hash if the file.) 41 | * 42 | * Files are stored such that the file "fdf9402e5a083a30430769dcf291018b1bed2a40a08f8e9915c5b14ea34668be" 43 | * would be found in the contract filesystem at fd/f9/fdf9402e5a083a30430769dcf291018b1bed2a40a08f8e9915c5b14ea34668be 44 | */ 45 | FileManager.prototype.storeFileWithHash = function (hash, file) { 46 | var self = this; 47 | 48 | var firstDir = hash.slice(0, 2); 49 | var secondDir = hash.slice(2, 4); 50 | 51 | var filePath = path.join(self.config.contractsFilesystemPath, firstDir, secondDir, hash); 52 | 53 | // Ensure that the contractsFilesystem dir exists 54 | return self._ensureDir(self.config.contractsFilesystemPath) 55 | .then(function(){ 56 | // Ensure that firstDir exists 57 | return self._ensureDir(path.join(self.config.contractsFilesystemPath, firstDir)); 58 | }) 59 | .then(function(){ 60 | // Ensure that secondDir exists 61 | return self._ensureDir(path.join(self.config.contractsFilesystemPath, firstDir, secondDir)); 62 | }) 63 | .then(function () { 64 | // Check if the file already exists 65 | return fs.statAsync(filePath); 66 | }) 67 | .catch(Promise.OperationalError, function (err) { 68 | // If file does not exist 69 | if (err.cause.code.indexOf('ENOENT') === 0) { 70 | return fs.writeFileAsync(filePath, file); 71 | } 72 | }).return(true); 73 | }; 74 | 75 | /** 76 | * Ensure a directory exists, otherwise create it. 77 | * 78 | * This will ensure a directory exists, if it doesn't it will create it 79 | * including any parent directories that are needed to create that path. 80 | */ 81 | FileManager.prototype._ensureDir = function (dir, mode, callback) { 82 | var self = this; 83 | 84 | return fs.statAsync(dir).catch(Promise.OperationalError, function (err) { 85 | if (err.cause.code === 'ENOENT') { 86 | var current = path.resolve(dir), parent = path.dirname(current); 87 | 88 | return self._ensureDir(parent, mode).then(function () { 89 | return fs.mkdirAsync(current, mode); 90 | }).catch(Promise.OperationalError, function (err) { 91 | if (err.cause.code.indexOf('EEXIST') === 0) { 92 | return; 93 | } else { 94 | throw err; 95 | } 96 | }); 97 | } 98 | }).return(true); 99 | } 100 | 101 | exports.FileManager = FileManager; 102 | -------------------------------------------------------------------------------- /lib/system_error.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var constants = require('constants'); 21 | 22 | var SystemError = {}; 23 | 24 | SystemError.create = function (path, code, methodName) { 25 | if ("number" !== typeof constants[code]) { 26 | console.error('Tried to create error with invalid error code "'+code+'"'); 27 | code = 'EFAULT'; 28 | } 29 | 30 | var error = new Error(code+', '+methodName+' \''+path+'\''); 31 | error.errno = constants[code]; 32 | error.code = code; 33 | error.path = path; 34 | return error; 35 | }; 36 | 37 | exports.SystemError = SystemError; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codius-engine", 3 | "version": "1.2.1", 4 | "description": "Codius smart oracle runtime engine for Node.js", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/codius/engine.git" 9 | }, 10 | "scripts": { 11 | "test": "./node_modules/.bin/istanbul test --preload-sources ./node_modules/.bin/_mocha -- --reporter spec test/*.js" 12 | }, 13 | "dependencies": { 14 | "bigi": "^1.3.0", 15 | "bitcoinjs-lib": "^1.1.3", 16 | "bluebird": "^2.3.5", 17 | "codius-node-sandbox": "git://github.com/codius/codius-node-sandbox#v1.2.0", 18 | "ecurve": "^1.0.0", 19 | "extend": "^2.0.0", 20 | "ignore": "^2.2.15", 21 | "lodash": "^2.4.1" 22 | }, 23 | "devDependencies": { 24 | "chai": "^1.9.1", 25 | "istanbul": "^0.2.13", 26 | "mocha": "^1.20.1", 27 | "sinon": "^1.10.2", 28 | "sinon-chai": "^2.5.0" 29 | }, 30 | "license": "ISC" 31 | } 32 | -------------------------------------------------------------------------------- /runtime_library/localstorage.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var codius = process.binding('async'); 21 | 22 | function LocalStorage() { 23 | 24 | } 25 | 26 | LocalStorage.prototype.getItem = function(key, callback) { 27 | codius.postMessage({ 28 | type: 'api', 29 | api: 'localstorage', 30 | method: 'getItem', 31 | data: [key] 32 | }, callback); 33 | }; 34 | 35 | 36 | LocalStorage.prototype.setItem = function(key, value, callback) { 37 | codius.postMessage({ 38 | type: 'api', 39 | api: 'localstorage', 40 | method: 'setItem', 41 | data: [key, value] 42 | }, callback); 43 | }; 44 | 45 | LocalStorage.prototype.removeItem = function(key, callback) { 46 | codius.postMessage({ 47 | type: 'api', 48 | api: 'localstorage', 49 | method: 'removeItem', 50 | data: [key] 51 | }, callback); 52 | }; 53 | 54 | LocalStorage.prototype.clear = function(callback) { 55 | codius.postMessage({ 56 | type: 'api', 57 | api: 'localstorage', 58 | method: 'clear', 59 | data: [ ] 60 | }, callback); 61 | }; 62 | 63 | LocalStorage.prototype.key = function(index, callback) { 64 | codius.postMessage({ 65 | type: 'api', 66 | api: 'localstorage', 67 | method: 'key', 68 | data: [index] 69 | }, callback); 70 | }; 71 | 72 | module.exports = new LocalStorage(); 73 | -------------------------------------------------------------------------------- /runtime_library/secrets.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | /* 3 | This file is part of Codius: https://github.com/codius 4 | Copyright (c) 2014 Ripple Labs Inc. 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | //============================================================================== 19 | 20 | var codius = process.binding('async'); 21 | 22 | function Secrets() { 23 | 24 | } 25 | 26 | /** 27 | * Get a 512-bit hex-encoded deterministic secret that is unique to the contract 28 | * 29 | * @param {Function} callback 30 | * 31 | * @callback 32 | * @param {Error} error 33 | * @param {String} secret 34 | */ 35 | Secrets.prototype.getSecret = function(callback) { 36 | codius.postMessage({ 37 | type: 'api', 38 | api: 'secrets', 39 | method: 'getSecret', 40 | data: [''] 41 | }, callback); 42 | }; 43 | 44 | /** 45 | * Get a keypair for the given signature scheme that is deterministic and unique 46 | * 47 | * @param {String} ['ec_secp256k1'] signature_scheme 48 | * @param {Function} callback 49 | * 50 | * @callback 51 | * @param {Error} error 52 | * @param {String} keypair.public 53 | * @param {String} keypair.private 54 | */ 55 | Secrets.prototype.getKeypair = function(signature_scheme, callback) { 56 | if (typeof signature_scheme === 'function') { 57 | callback = signature_scheme; 58 | signature_scheme = 'ec_secp256k1'; 59 | } 60 | 61 | codius.postMessage({ 62 | type: 'api', 63 | api: 'secrets', 64 | method: 'getKeypair', 65 | data: [signature_scheme] 66 | }, callback); 67 | }; 68 | 69 | module.exports = new Secrets; 70 | -------------------------------------------------------------------------------- /test/lib.compiler.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var sinonChai = require('sinon-chai'); 4 | chai.use(sinonChai); 5 | 6 | var path = require('path'); 7 | var Compiler = require(path.join(__dirname, '/../lib/compiler')).Compiler; 8 | var Config = require(path.join(__dirname, '/../lib/config')).Config; 9 | 10 | var config = new Config(); 11 | var compiler = new Compiler(config); 12 | 13 | describe('lib/compiler', function() { 14 | 15 | describe('getRandomMasterSecret', function(){ 16 | it('should compile a module into a manifest', function() { 17 | var manifestHash = compiler.compileModule(path.join(__dirname, '/test_contract')) 18 | expect(manifestHash).to.equal('f508c775790c378b6db6f1884817783e0e3a9ff64c2aa52d13737dca7eff7131'); 19 | }); 20 | }); 21 | 22 | describe('setFilesystem', function(){ 23 | it.skip('should accept a custom filesystem', function(done) { 24 | var fakeFilesystem = { 25 | // TODO: Redefine readFile, stat, readdir & exists 26 | } 27 | compiler.setFilesystem(fakeFilesystem); 28 | var manifestHash = compiler.compileModule(path.join(__dirname, '/test_contract')) 29 | expect(manifestHash).to.equal('f508c775790c378b6db6f1884817783e0e3a9ff64c2aa52d13737dca7eff7131'); 30 | }); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/lib.crypto.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var sinonChai = require('sinon-chai'); 4 | chai.use(sinonChai); 5 | 6 | var crypto = require('../lib/crypto'); 7 | var node_crypto = require('crypto'); 8 | var ecurve = require('ecurve'); 9 | 10 | var HEX_REGEX = /^[0-9a-fA-F]+$/; 11 | 12 | describe('lib/crypto', function(){ 13 | 14 | describe('getRandomMasterSecret', function(){ 15 | it('should produce 256-bit secrets', function(){ 16 | var secret = crypto.getRandomMasterSecret(); 17 | expect(HEX_REGEX.test(secret)).to.be.true; 18 | expect(secret).to.have.length(64); 19 | }); 20 | }); 21 | 22 | describe('deriveSecret', function(){ 23 | it('should accept the parent_secret as a buffer', function(){ 24 | var secret = crypto.deriveSecret(node_crypto.randomBytes(32), ''); 25 | expect(HEX_REGEX.test(secret)).to.be.true; 26 | }); 27 | 28 | it('should accept the parent_secret as a hex string', function(){ 29 | var secret = crypto.deriveSecret(node_crypto.randomBytes(32).toString('hex'), ''); 30 | expect(HEX_REGEX.test(secret)).to.be.true; 31 | }); 32 | 33 | it('should produce 256-bit secrets if sha256 is used', function(){ 34 | var secret = crypto.deriveSecret(node_crypto.randomBytes(32), '', 'sha256'); 35 | expect(HEX_REGEX.test(secret)).to.be.true; 36 | expect(secret).to.have.length(64); 37 | }); 38 | 39 | it('should produce 512-bit secrets if sha512 is used', function(){ 40 | var secret = crypto.deriveSecret(node_crypto.randomBytes(32), '', 'sha512'); 41 | expect(HEX_REGEX.test(secret)).to.be.true; 42 | expect(secret).to.have.length(128); 43 | }); 44 | 45 | it('should default to using sha512', function(){ 46 | var secret = crypto.deriveSecret(node_crypto.randomBytes(32), ''); 47 | expect(HEX_REGEX.test(secret)).to.be.true; 48 | expect(secret).to.have.length(128); 49 | }); 50 | 51 | }); 52 | 53 | describe('deriveKeypair', function(){ 54 | 55 | it('should return an object with "private" and "public fields"', function(){ 56 | var keypair = crypto.deriveKeypair(node_crypto.randomBytes(32), ''); 57 | expect(keypair).to.have.keys(['private', 'public']); 58 | }); 59 | 60 | // it('should default to secp256k1 if no signature scheme is specified', function(){ 61 | // // TODO 62 | // }); 63 | 64 | it('should throw an error if the signature scheme specified is not supported', function(){ 65 | expect(function(){ crypto.deriveKeypair(node_crypto.randomBytes(32), '', 'ed25519'); }).to.throw(/Signature scheme: \w+ not currently supported/); 66 | }); 67 | 68 | it('(secp256k1) should generate a valid secp256k1 keypair', function(){ 69 | var keypair = crypto.deriveKeypair(node_crypto.randomBytes(32), node_crypto.randomBytes(32)); 70 | var curve = ecurve.getCurveByName('secp256k1'); 71 | expect(curve.validate(ecurve.Point.decodeFrom(curve, new Buffer(keypair.public, 'hex')))).to.be.true; 72 | }); 73 | 74 | // it('(secp256k1) should generate a keypair that matches an alternate implementation', function(){ 75 | // // TODO 76 | // }); 77 | 78 | }); 79 | 80 | describe('sign', function(){ 81 | 82 | it('should take a hex-encoded private key and a hex-encoded string as data', function(){ 83 | var private_key = node_crypto.randomBytes(32).toString('hex'); 84 | var data = '04a762c77d95e9c03506261cd548980645b42ee22b4a7fff25bf260387c5b5b1efbfb5f656c97c02f84388d4a46df01a69dc4390f746838d03f556020ae5462214'; 85 | var signature = crypto.sign(private_key, data); 86 | expect(signature).to.match(HEX_REGEX); 87 | }); 88 | 89 | it('should produce a signature that the verify method verifies correctly', function(){ 90 | var private_key = '8dbf80d806b61d6283e8780c426139e6b1df05ef603189e3a58aadb5b4ac088f'; 91 | var public_key = '04f55ef1f7daa6ca8c2629fca4b710d69472720666f18039d910d79c5b9eb7fa8c0ab4ca3d932647669c27fff33fd3199b93cb68f0811948f818ec347aeab2d34d'; 92 | var data = '04a762c77d95e9c03506261cd548980645b42ee22b4a7fff25bf260387c5b5b1efbfb5f656c97c02f84388d4a46df01a69dc4390f746838d03f556020ae5462214'; 93 | expect(crypto.verify(public_key, data, crypto.sign(private_key, data))).to.be.true; 94 | }); 95 | 96 | // it('should produce a signature that matches an alternate implementation', function(){ 97 | // // TODO 98 | // }); 99 | 100 | }); 101 | 102 | describe('verify', function(){ 103 | 104 | it.skip('should verify a signature on the given data', function(){ 105 | // TODO 106 | }); 107 | 108 | }); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /test/lib.engine.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var sinonChai = require('sinon-chai'); 4 | var Promise = require('bluebird').Promise; 5 | chai.use(sinonChai); 6 | 7 | var path = require('path'); 8 | var Engine = require(path.join(__dirname, '/../lib/engine')).Engine; 9 | var Compiler = require(path.join(__dirname, '/../lib/compiler')).Compiler; 10 | var Config = require(path.join(__dirname, '/../lib/config')).Config; 11 | var FileManager = require(path.join(__dirname, '/../lib/filemanager')).FileManager; 12 | 13 | var config = new Config(); 14 | var engine = new Engine(config); 15 | var masterPublicKey; 16 | 17 | describe('lib/engine', function() { 18 | describe('registerAPI', function(){ 19 | it.skip('should register a new API module factory with the engine', function() { 20 | }); 21 | }); 22 | 23 | describe('setContractStorage', function(){ 24 | it.skip('should take a function that will be connected to the LocalStorage API', function() { 25 | }); 26 | }); 27 | 28 | describe('runContract', function(){ 29 | it('should run a contract', function() { 30 | var fileManager = new FileManager(config); 31 | var compiler = new Compiler(config); 32 | var currentDir = path.join(__dirname, '/test_contract'); 33 | var p = []; 34 | compiler.on('file', function(event) { 35 | if (event.name.indexOf(currentDir) !== 0) { 36 | throw new Error('File path does not have current directory prefix: ' + event.name); 37 | } 38 | p.push(fileManager.storeFileWithHash(event.hash, event.data)); 39 | }); 40 | var manifestHash = compiler.compileModule(path.join(__dirname, '/test_contract')); 41 | Promise.all(p).then(function() { 42 | var runner = engine.runContract(manifestHash); 43 | expect(runner._manifest_hash).to.equal(manifestHash) 44 | }); 45 | // TODO: more tests 46 | }); 47 | }); 48 | 49 | describe('generateSecrets', function(){ 50 | it('should generate the host\'s secrets from the MASTER_SECRET', function() { 51 | var secrets = engine.generateSecrets(config.MASTER_SECRET) 52 | expect(secrets).to.have.keys(['CONTRACT_SECRET_GENERATOR', 'CONTRACT_KEYPAIR_GENERATOR_ec_secp256k1', 'MASTER_KEYPAIR_ec_secp256k1']); 53 | expect(secrets.MASTER_KEYPAIR_ec_secp256k1).to.have.keys(['private', 'public']); 54 | masterPublicKey = secrets.MASTER_KEYPAIR_ec_secp256k1.public 55 | }); 56 | }); 57 | 58 | describe('getMasterPublicKey', function(){ 59 | it('should get the engine\'s master public key', function() { 60 | var publicKey = engine.getMasterPublicKey(); 61 | expect(publicKey).to.equal(masterPublicKey); 62 | }); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/lib.filehash.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var sinon = require('sinon'); 4 | var sinonChai = require('sinon-chai'); 5 | chai.use(sinonChai); 6 | 7 | var FileHash = require('../lib/filehash').FileHash; 8 | 9 | describe('lib/filehash', function(){ 10 | 11 | describe('hash', function(){ 12 | 13 | it('should produce 32 bytes of data\'s hash', function(){ 14 | var hash = FileHash.hash('my_test_data'); 15 | expect(hash).to.equal('a9ac07f0b4097240d2d33e832bd81ce6f417bdf29958db6e5f91c1631c68cfc9'); 16 | expect(hash).to.have.length(64); 17 | }); 18 | 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /test/test_contract/contract.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world'); --------------------------------------------------------------------------------